- `sendPushbulletNote(String recipient, String messsage)`
- `sendPushbulletNote(String recipient, String title, String messsage)`
+- `sendPushbulletLink(String recipient, String url)`
+- `sendPushbulletLink(String recipient, String title, String messsage, String url)`
+- `sendPushbulletFile(String recipient, String content)`
+- `sendPushbulletFile(String recipient, String title, String messsage, String content)`
+- `sendPushbulletFile(String recipient, String title, String messsage, String content, String fileName)`
Since there is a separate rule action instance for each `bot` thing, this needs to be retrieved through `getActions(scope, thingUID)`.
The first parameter always has to be `pushbullet` and the second is the full Thing UID of the bot that should be used.
The recipient can either be an email address, a channel tag or `null`.
If it is not specified or properly formatted, the note will be broadcast to all of the user account's devices.
+The file content can be an image URL, a local file path or an Image item state.
+
+The file name is used in the upload link and how it appears in the push message for non-image content.
+If it is not specified, it is automatically determined from the image URL or file path.
+For Image item state content, it defaults to `image.jpg`.
+
+For the `sendPushbulletNote` action, parameter `message` is required.
+Likewise, for `sendPushbulletLink`, `url` and for `sendPushbulletFile`, `content` parameters are required.
+Any other parameters for these actions are optional and can be set to `null`.
+
Examples:
```java
val actions = getActions("pushbullet", "pushbullet:bot:r2d2")
-val result = actions.sendPushbulletNote("someone@example.com", "R2D2 talks here...", "This is the pushed note.")
+actions.sendPushbulletNote("someone@example.com", "Note Example", "This is the pushed note.")
+actions.sendPushbulletLink("someone@example.com", "Link Example", "This is the pushed link", "https://example.com")
+actions.sendPushbulletFile("someone@example.com", "File Example", "This is the pushed file", "https://example.com/image.png")
+actions.sendPushbulletFile("someone@example.com", null, null, "/path/to/somefile.pdf", "document.pdf")
+actions.sendPushbulletFile("someone@example.com", ImageItem.state.toFullString)
```
## Full Example
* used across the whole binding.
*
* @author Hakan Tandogan - Initial contribution
+ * @author Jeremy Setton - Add link and file push type support
*/
@NonNullByDefault
public class PushbulletBindingConstants {
- private static final String BINDING_ID = "pushbullet";
+ public static final String BINDING_ID = "pushbullet";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BOT = new ThingTypeUID(BINDING_ID, "bot");
public static final String TITLE = "title";
public static final String MESSAGE = "message";
+ // Thing properties
+ public static final String PROPERTY_EMAIL = "email";
+ public static final String PROPERTY_NAME = "name";
+
// Binding logic constants
- public static final String API_METHOD_PUSHES = "pushes";
+ public static final String API_ENDPOINT_PUSHES = "/pushes";
+ public static final String API_ENDPOINT_UPLOAD_REQUEST = "/upload-request";
+ public static final String API_ENDPOINT_USERS_ME = "/users/me";
+
+ public static final String IMAGE_FILE_NAME = "image.jpg";
- public static final int TIMEOUT = 30 * 1000; // 30 seconds
+ public static final int MAX_UPLOAD_SIZE = 26214400;
}
* The {@link PushbulletConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Hakan Tandogan - Initial contribution
+ * @author Jeremy Setton - Add link and file push type support
*/
@NonNullByDefault
public class PushbulletConfiguration {
private @Nullable String name;
- private String token = "invalid";
+ private String token = "";
- private String apiUrlBase = "invalid";
+ private String apiUrlBase = "https://api.pushbullet.com/v2";
public @Nullable String getName() {
return name;
}
- public void setName(String name) {
- this.name = name;
- }
-
- public String getToken() {
+ public String getAccessToken() {
return token;
}
- public void setToken(String token) {
- this.token = token;
- }
-
public String getApiUrlBase() {
return apiUrlBase;
}
-
- public void setApiUrlBase(String apiUrlBase) {
- this.apiUrlBase = apiUrlBase;
- }
}
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.pushbullet.internal.handler.PushbulletHandler;
+import org.openhab.core.io.net.http.HttpClientFactory;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
/**
* The {@link PushbulletHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Hakan Tandogan - Initial contribution
+ * @author Jeremy Setton - Add link and file push type support
*/
@NonNullByDefault
@Component(configurationPid = "binding.pushbullet", service = ThingHandlerFactory.class)
public class PushbulletHandlerFactory extends BaseThingHandlerFactory {
+ private final HttpClient httpClient;
+
+ @Activate
+ public PushbulletHandlerFactory(final @Reference HttpClientFactory httpClientFactory) {
+ this.httpClient = httpClientFactory.getCommonHttpClient();
+ }
+
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_BOT.equals(thingTypeUID)) {
- return new PushbulletHandler(thing);
+ return new PushbulletHandler(thing, httpClient);
}
return null;
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.pushbullet.internal;
+
+import java.time.Instant;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.HttpClient;
+import org.eclipse.jetty.client.api.ContentResponse;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.client.util.BytesContentProvider;
+import org.eclipse.jetty.client.util.MultiPartContentProvider;
+import org.eclipse.jetty.client.util.StringContentProvider;
+import org.eclipse.jetty.http.HttpFields;
+import org.eclipse.jetty.http.HttpMethod;
+import org.eclipse.jetty.http.HttpStatus;
+import org.eclipse.jetty.http.MimeTypes;
+import org.openhab.binding.pushbullet.internal.exception.PushbulletApiException;
+import org.openhab.binding.pushbullet.internal.exception.PushbulletAuthenticationException;
+import org.openhab.binding.pushbullet.internal.model.InstantDeserializer;
+import org.openhab.core.OpenHAB;
+import org.openhab.core.library.types.RawType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+
+/**
+ * The {@link PushbulletHttpClient} handles requests to Pushbullet API
+ *
+ * @author Jeremy Setton - Initial contribution
+ */
+@NonNullByDefault
+public class PushbulletHttpClient {
+ private static final String AGENT = "openHAB/" + OpenHAB.getVersion();
+
+ private static final int TIMEOUT = 30; // in seconds
+
+ private static final String HEADER_RATELIMIT_RESET = "X-Ratelimit-Reset";
+
+ private final Logger logger = LoggerFactory.getLogger(PushbulletHttpClient.class);
+
+ private final Gson gson = new GsonBuilder().registerTypeHierarchyAdapter(Instant.class, new InstantDeserializer())
+ .create();
+
+ private PushbulletConfiguration config = new PushbulletConfiguration();
+
+ private final HttpClient httpClient;
+
+ public PushbulletHttpClient(HttpClient httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ public void setConfiguration(PushbulletConfiguration config) {
+ this.config = config;
+ }
+
+ /**
+ * Executes an api request
+ *
+ * @param apiEndpoint the request api endpoint
+ * @param responseType the response type
+ * @return the unpacked response
+ * @throws PushbulletApiException
+ */
+ public <T> T executeRequest(String apiEndpoint, Class<T> responseType) throws PushbulletApiException {
+ return executeRequest(apiEndpoint, null, responseType);
+ }
+
+ /**
+ * Executes an api request
+ *
+ * @param apiEndpoint the request api endpoint
+ * @param body the request body object
+ * @param responseType the response type
+ * @return the unpacked response
+ * @throws PushbulletApiException
+ */
+ public <T> T executeRequest(String apiEndpoint, @Nullable Object body, Class<T> responseType)
+ throws PushbulletApiException {
+ String url = config.getApiUrlBase() + apiEndpoint;
+ String accessToken = config.getAccessToken();
+
+ Request request = newRequest(url).header("Access-Token", accessToken);
+
+ if (body != null) {
+ StringContentProvider content = new StringContentProvider(gson.toJson(body));
+ String contentType = MimeTypes.Type.APPLICATION_JSON.asString();
+
+ request.method(HttpMethod.POST).content(content, contentType);
+ }
+
+ String responseBody = sendRequest(request);
+
+ try {
+ T response = Objects.requireNonNull(gson.fromJson(responseBody, responseType));
+ logger.debug("Unpacked Response: {}", response);
+ return response;
+ } catch (JsonSyntaxException e) {
+ logger.debug("Failed to unpack response as '{}': {}", responseType.getSimpleName(), e.getMessage());
+ throw new PushbulletApiException(e);
+ }
+ }
+
+ /**
+ * Uploads a file
+ *
+ * @param url the upload url
+ * @param data the file data
+ * @throws PushbulletApiException
+ */
+ public void uploadFile(String url, RawType data) throws PushbulletApiException {
+ MultiPartContentProvider content = new MultiPartContentProvider();
+ content.addFieldPart("file", new BytesContentProvider(data.getMimeType(), data.getBytes()), null);
+
+ Request request = newRequest(url).method(HttpMethod.POST).content(content);
+
+ sendRequest(request);
+ }
+
+ /**
+ * Creates a new http request
+ *
+ * @param url the request url
+ * @return the new Request object with default parameters
+ */
+ private Request newRequest(String url) {
+ return httpClient.newRequest(url).agent(AGENT).timeout(TIMEOUT, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Sends a http request
+ *
+ * @param request the request to send
+ * @return the response body
+ * @throws PushbulletApiException
+ */
+ private String sendRequest(Request request) throws PushbulletApiException {
+ try {
+ logger.debug("Request {} {}", request.getMethod(), request.getURI());
+ logger.debug("Request Headers: {}", request.getHeaders());
+
+ ContentResponse response = request.send();
+
+ int statusCode = response.getStatus();
+ String statusReason = response.getReason();
+ String responseBody = response.getContentAsString();
+
+ logger.debug("Got HTTP {} Response: '{}'", statusCode, responseBody);
+
+ switch (statusCode) {
+ case HttpStatus.OK_200:
+ case HttpStatus.NO_CONTENT_204:
+ return responseBody;
+ case HttpStatus.UNAUTHORIZED_401:
+ case HttpStatus.FORBIDDEN_403:
+ throw new PushbulletAuthenticationException(statusReason);
+ case HttpStatus.TOO_MANY_REQUESTS_429:
+ logger.warn("Rate limited for making too many requests until {}",
+ getRateLimitResetTime(response.getHeaders()));
+ default:
+ throw new PushbulletApiException(statusReason);
+ }
+ } catch (InterruptedException | TimeoutException | ExecutionException e) {
+ logger.debug("Failed to send request: {}", e.getMessage());
+ throw new PushbulletApiException(e);
+ }
+ }
+
+ /**
+ * Returns the rate limit reset time included in response headers
+ *
+ * @param headers the response headers
+ * @return the rate limit reset time if found in headers, otherwise null
+ */
+ private @Nullable Instant getRateLimitResetTime(HttpFields headers) {
+ try {
+ long resetTime = headers.getLongField(HEADER_RATELIMIT_RESET);
+ if (resetTime != -1) {
+ return Instant.ofEpochSecond(resetTime);
+ }
+ } catch (NumberFormatException ignored) {
+ }
+ return null;
+ }
+}
* The {@link PushbulletActions} class defines rule actions for sending notifications
*
* @author Hakan Tandogan - Initial contribution
+ * @author Jeremy Setton - Add link and file push type support
*/
@Component(scope = ServiceScope.PROTOTYPE, service = PushbulletActions.class)
@ThingActionsScope(name = "pushbullet")
@RuleAction(label = "@text/actionSendPushbulletNoteLabel", description = "@text/actionSendPushbulletNoteDesc")
public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendPushbulletNote(
- @ActionInput(name = "recipient", label = "@text/actionSendPushbulletNoteInputRecipientLabel", description = "@text/actionSendPushbulletNoteInputRecipientDesc") @Nullable String recipient,
- @ActionInput(name = "title", label = "@text/actionSendPushbulletNoteInputTitleLabel", description = "@text/actionSendPushbulletNoteInputTitleDesc") @Nullable String title,
- @ActionInput(name = "message", label = "@text/actionSendPushbulletNoteInputMessageLabel", description = "@text/actionSendPushbulletNoteInputMessageDesc") @Nullable String message) {
+ @ActionInput(name = "recipient", label = "@text/actionSendPushbulletNoteInputRecipientLabel", description = "@text/actionSendPushbulletNoteInputRecipientDesc", type = "java.lang.String") @Nullable String recipient,
+ @ActionInput(name = "title", label = "@text/actionSendPushbulletNoteInputTitleLabel", description = "@text/actionSendPushbulletNoteInputTitleDesc", type = "java.lang.String") @Nullable String title,
+ @ActionInput(name = "message", label = "@text/actionSendPushbulletNoteInputMessageLabel", description = "@text/actionSendPushbulletNoteInputMessageDesc", type = "java.lang.String", required = true) String message) {
logger.trace("sendPushbulletNote '{}', '{}', '{}'", recipient, title, message);
PushbulletHandler localHandler = handler;
return false;
}
- return localHandler.sendPush(recipient, title, message, "note");
+ return localHandler.sendPushNote(recipient, title, message);
}
public static boolean sendPushbulletNote(ThingActions actions, @Nullable String recipient, @Nullable String title,
- @Nullable String message) {
+ String message) {
return ((PushbulletActions) actions).sendPushbulletNote(recipient, title, message);
}
@RuleAction(label = "@text/actionSendPushbulletNoteLabel", description = "@text/actionSendPushbulletNoteDesc")
public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendPushbulletNote(
- @ActionInput(name = "recipient", label = "@text/actionSendPushbulletNoteInputRecipientLabel", description = "@text/actionSendPushbulletNoteInputRecipientDesc") @Nullable String recipient,
- @ActionInput(name = "message", label = "@text/actionSendPushbulletNoteInputMessageLabel", description = "@text/actionSendPushbulletNoteInputMessageDesc") @Nullable String message) {
+ @ActionInput(name = "recipient", label = "@text/actionSendPushbulletNoteInputRecipientLabel", description = "@text/actionSendPushbulletNoteInputRecipientDesc", type = "java.lang.String") @Nullable String recipient,
+ @ActionInput(name = "message", label = "@text/actionSendPushbulletNoteInputMessageLabel", description = "@text/actionSendPushbulletNoteInputMessageDesc", type = "java.lang.String", required = true) String message) {
logger.trace("sendPushbulletNote '{}', '{}'", recipient, message);
PushbulletHandler localHandler = handler;
return false;
}
- return localHandler.sendPush(recipient, message, "note");
+ return localHandler.sendPushNote(recipient, null, message);
}
- public static boolean sendPushbulletNote(ThingActions actions, @Nullable String recipient,
- @Nullable String message) {
+ public static boolean sendPushbulletNote(ThingActions actions, @Nullable String recipient, String message) {
return ((PushbulletActions) actions).sendPushbulletNote(recipient, message);
}
+
+ @RuleAction(label = "@text/actionSendPushbulletLinkLabel", description = "@text/actionSendPushbulletLinkDesc")
+ public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendPushbulletLink(
+ @ActionInput(name = "recipient", label = "@text/actionSendPushbulletNoteInputRecipientLabel", description = "@text/actionSendPushbulletNoteInputRecipientDesc", type = "java.lang.String") @Nullable String recipient,
+ @ActionInput(name = "title", label = "@text/actionSendPushbulletNoteInputTitleLabel", description = "@text/actionSendPushbulletNoteInputTitleDesc", type = "java.lang.String") @Nullable String title,
+ @ActionInput(name = "message", label = "@text/actionSendPushbulletNoteInputMessageLabel", description = "@text/actionSendPushbulletNoteInputMessageDesc", type = "java.lang.String") @Nullable String message,
+ @ActionInput(name = "url", label = "@text/actionSendPushbulletLinkInputUrlLabel", description = "@text/actionSendPushbulletLinkInputUrlDesc", type = "java.lang.String", required = true) String url) {
+ logger.trace("sendPushbulletLink '{}', '{}', '{}', '{}'", recipient, title, message, url);
+
+ PushbulletHandler localHandler = handler;
+ if (localHandler == null) {
+ logger.warn("Pushbullet service Handler is null!");
+ return false;
+ }
+
+ return localHandler.sendPushLink(recipient, title, message, url);
+ }
+
+ public static boolean sendPushbulletLink(ThingActions actions, @Nullable String recipient, @Nullable String title,
+ @Nullable String message, String url) {
+ return ((PushbulletActions) actions).sendPushbulletLink(recipient, title, message, url);
+ }
+
+ @RuleAction(label = "@text/actionSendPushbulletLinkLabel", description = "@text/actionSendPushbulletLinkDesc")
+ public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendPushbulletLink(
+ @ActionInput(name = "recipient", label = "@text/actionSendPushbulletNoteInputRecipientLabel", description = "@text/actionSendPushbulletNoteInputRecipientDesc", type = "java.lang.String") @Nullable String recipient,
+ @ActionInput(name = "url", label = "@text/actionSendPushbulletLinkInputUrlLabel", description = "@text/actionSendPushbulletLinkInputUrlDesc", type = "java.lang.String", required = true) String url) {
+ logger.trace("sendPushbulletLink '{}', '{}'", recipient, url);
+
+ PushbulletHandler localHandler = handler;
+ if (localHandler == null) {
+ logger.warn("Pushbullet service Handler is null!");
+ return false;
+ }
+
+ return localHandler.sendPushLink(recipient, null, null, url);
+ }
+
+ public static boolean sendPushbulletLink(ThingActions actions, @Nullable String recipient, String url) {
+ return ((PushbulletActions) actions).sendPushbulletLink(recipient, url);
+ }
+
+ @RuleAction(label = "@text/actionSendPushbulletFileLabel", description = "@text/actionSendPushbulletFileDesc")
+ public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendPushbulletFile(
+ @ActionInput(name = "recipient", label = "@text/actionSendPushbulletNoteInputRecipientLabel", description = "@text/actionSendPushbulletNoteInputRecipientDesc", type = "java.lang.String") @Nullable String recipient,
+ @ActionInput(name = "title", label = "@text/actionSendPushbulletNoteInputTitleLabel", description = "@text/actionSendPushbulletNoteInputTitleDesc", type = "java.lang.String") @Nullable String title,
+ @ActionInput(name = "message", label = "@text/actionSendPushbulletNoteInputMessageLabel", description = "@text/actionSendPushbulletNoteInputMessageDesc", type = "java.lang.String") @Nullable String message,
+ @ActionInput(name = "content", label = "@text/actionSendPushbulletFileInputContent", description = "@text/actionSendPushbulletFileInputContentDesc", type = "java.lang.String", required = true) String content,
+ @ActionInput(name = "filename", label = "@text/actionSendPushbulletFileInputName", description = "@text/actionSendPushbulletFileInputNameDesc", type = "java.lang.String") @Nullable String fileName) {
+ logger.trace("sendPushbulletFile '{}', '{}', '{}', '{}', '{}'", recipient, title, message, content, fileName);
+
+ PushbulletHandler localHandler = handler;
+ if (localHandler == null) {
+ logger.warn("Pushbullet service Handler is null!");
+ return false;
+ }
+
+ return localHandler.sendPushFile(recipient, title, message, content, fileName);
+ }
+
+ public static boolean sendPushbulletFile(ThingActions actions, @Nullable String recipient, @Nullable String title,
+ @Nullable String message, String content, @Nullable String filename) {
+ return ((PushbulletActions) actions).sendPushbulletFile(recipient, title, message, content, filename);
+ }
+
+ @RuleAction(label = "@text/actionSendPushbulletFileLabel", description = "@text/actionSendPushbulletFileDesc")
+ public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendPushbulletFile(
+ @ActionInput(name = "recipient", label = "@text/actionSendPushbulletNoteInputRecipientLabel", description = "@text/actionSendPushbulletNoteInputRecipientDesc", type = "java.lang.String") @Nullable String recipient,
+ @ActionInput(name = "title", label = "@text/actionSendPushbulletNoteInputTitleLabel", description = "@text/actionSendPushbulletNoteInputTitleDesc", type = "java.lang.String") @Nullable String title,
+ @ActionInput(name = "message", label = "@text/actionSendPushbulletNoteInputMessageLabel", description = "@text/actionSendPushbulletNoteInputMessageDesc", type = "java.lang.String") @Nullable String message,
+ @ActionInput(name = "content", label = "@text/actionSendPushbulletFileInputContent", description = "@text/actionSendPushbulletFileInputContentDesc", type = "java.lang.String", required = true) String content) {
+ logger.trace("sendPushbulletFile '{}', '{}', '{}', '{}'", recipient, title, message, content);
+
+ PushbulletHandler localHandler = handler;
+ if (localHandler == null) {
+ logger.warn("Pushbullet service Handler is null!");
+ return false;
+ }
+
+ return localHandler.sendPushFile(recipient, title, message, content, null);
+ }
+
+ public static boolean sendPushbulletFile(ThingActions actions, @Nullable String recipient, @Nullable String title,
+ @Nullable String message, String content) {
+ return ((PushbulletActions) actions).sendPushbulletFile(recipient, title, message, content);
+ }
+
+ @RuleAction(label = "@text/actionSendPushbulletFileLabel", description = "@text/actionSendPushbulletFileDesc")
+ public @ActionOutput(name = "success", type = "java.lang.Boolean") Boolean sendPushbulletFile(
+ @ActionInput(name = "recipient", label = "@text/actionSendPushbulletNoteInputRecipientLabel", description = "@text/actionSendPushbulletNoteInputRecipientDesc", type = "java.lang.String") @Nullable String recipient,
+ @ActionInput(name = "content", label = "@text/actionSendPushbulletFileInputContent", description = "@text/actionSendPushbulletFileInputContentDesc", type = "java.lang.String", required = true) String content) {
+ logger.trace("sendPushbulletFile '{}', '{}'", recipient, content);
+
+ PushbulletHandler localHandler = handler;
+ if (localHandler == null) {
+ logger.warn("Pushbullet service Handler is null!");
+ return false;
+ }
+
+ return localHandler.sendPushFile(recipient, null, null, content, null);
+ }
+
+ public static boolean sendPushbulletFile(ThingActions actions, @Nullable String recipient, String content) {
+ return ((PushbulletActions) actions).sendPushbulletFile(recipient, content);
+ }
}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.pushbullet.internal.exception;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link PushbulletApiException} represents a Pushbullet API exception
+ *
+ * @author Jeremy Setton - Initial contribution
+ */
+@NonNullByDefault
+public class PushbulletApiException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public PushbulletApiException(String message) {
+ super(message);
+ }
+
+ public PushbulletApiException(Exception exception) {
+ super(exception);
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.pushbullet.internal.exception;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link PushbulletAuthenticationException} represents a Pushbullet authentication exception
+ *
+ * @author Jeremy Setton - Initial contribution
+ */
+@NonNullByDefault
+public class PushbulletAuthenticationException extends PushbulletApiException {
+ private static final long serialVersionUID = 1L;
+
+ public PushbulletAuthenticationException(String message) {
+ super(message);
+ }
+}
import static org.openhab.binding.pushbullet.internal.PushbulletBindingConstants.*;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.Collection;
-import java.util.Properties;
+import java.util.Objects;
import java.util.Set;
-import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.mail.internet.AddressException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
-import org.eclipse.jetty.http.HttpHeader;
-import org.eclipse.jetty.http.HttpMethod;
-import org.eclipse.jetty.http.MimeTypes;
+import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.pushbullet.internal.PushbulletConfiguration;
+import org.openhab.binding.pushbullet.internal.PushbulletHttpClient;
import org.openhab.binding.pushbullet.internal.action.PushbulletActions;
-import org.openhab.binding.pushbullet.internal.model.Push;
+import org.openhab.binding.pushbullet.internal.exception.PushbulletApiException;
+import org.openhab.binding.pushbullet.internal.exception.PushbulletAuthenticationException;
+import org.openhab.binding.pushbullet.internal.model.PushRequest;
import org.openhab.binding.pushbullet.internal.model.PushResponse;
+import org.openhab.binding.pushbullet.internal.model.PushType;
+import org.openhab.binding.pushbullet.internal.model.UploadRequest;
+import org.openhab.binding.pushbullet.internal.model.UploadResponse;
+import org.openhab.binding.pushbullet.internal.model.User;
import org.openhab.core.io.net.http.HttpUtil;
+import org.openhab.core.library.types.RawType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-
/**
* The {@link PushbulletHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Hakan Tandogan - Initial contribution
+ * @author Jeremy Setton - Add link and file push type support
*/
@NonNullByDefault
public class PushbulletHandler extends BaseThingHandler {
- private final Logger logger = LoggerFactory.getLogger(PushbulletHandler.class);
+ private static final Pattern CHANNEL_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]+$");
- private final Gson gson = new GsonBuilder().create();
+ private final Logger logger = LoggerFactory.getLogger(PushbulletHandler.class);
- private static final Version VERSION = FrameworkUtil.getBundle(PushbulletHandler.class).getVersion();
+ private final PushbulletHttpClient httpClient;
- private static final Pattern CHANNEL_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]+$");
+ private int maxUploadSize;
- private @Nullable PushbulletConfiguration config;
-
- public PushbulletHandler(Thing thing) {
+ public PushbulletHandler(Thing thing, HttpClient httpClient) {
super(thing);
+ this.httpClient = new PushbulletHttpClient(httpClient);
}
@Override
@Override
public void initialize() {
- logger.debug("Start initializing!");
- config = getConfigAs(PushbulletConfiguration.class);
+ logger.debug("Starting {}", thing.getUID());
+
+ PushbulletConfiguration config = getConfigAs(PushbulletConfiguration.class);
+
+ if (config.getAccessToken().isEmpty()) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Undefined access token.");
+ return;
+ }
- // Name and Token are both "required", so set the Thing immediately ONLINE.
- updateStatus(ThingStatus.ONLINE);
+ httpClient.setConfiguration(config);
- logger.debug("Finished initializing!");
+ scheduler.execute(() -> retrieveAccountInfo());
+
+ updateStatus(ThingStatus.UNKNOWN);
+ }
+
+ private void retrieveAccountInfo() {
+ try {
+ User user = httpClient.executeRequest(API_ENDPOINT_USERS_ME, User.class);
+
+ maxUploadSize = Objects.requireNonNullElse(user.getMaxUploadSize(), MAX_UPLOAD_SIZE);
+
+ logger.debug("Set maximum upload size for {} to {} bytes", thing.getUID(), maxUploadSize);
+
+ updateProperty(PROPERTY_NAME, user.getName());
+ updateProperty(PROPERTY_EMAIL, user.getEmail());
+
+ logger.debug("Updated properties for {} to {}", thing.getUID(), thing.getProperties());
+
+ updateStatus(ThingStatus.ONLINE);
+ } catch (PushbulletAuthenticationException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid access token.");
+ } catch (PushbulletApiException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "Unable to retrieve account info.");
+ }
}
@Override
return Set.of(PushbulletActions.class);
}
- public boolean sendPush(@Nullable String recipient, @Nullable String message, String type) {
- return sendPush(recipient, "", message, type);
+ /**
+ * Sends a push note
+ *
+ * @param recipient the recipient
+ * @param title the title
+ * @param message the message
+ * @return true if successful
+ */
+ public boolean sendPushNote(@Nullable String recipient, @Nullable String title, String message) {
+ PushRequest request = newPushRequest(recipient, title, message, PushType.NOTE);
+
+ return sendPush(request);
}
- public boolean sendPush(@Nullable String recipient, @Nullable String title, @Nullable String message, String type) {
- boolean result = false;
+ /**
+ * Sends a push link
+ *
+ * @param recipient the recipient
+ * @param title the title
+ * @param message the message
+ * @param url the message url
+ * @return true if successful
+ */
+ public boolean sendPushLink(@Nullable String recipient, @Nullable String title, @Nullable String message,
+ String url) {
+ PushRequest request = newPushRequest(recipient, title, message, PushType.LINK);
+ request.setUrl(url);
- logger.debug("sendPush is called for ");
- logger.debug("Thing {}", thing);
- logger.debug("Thing Label: '{}'", thing.getLabel());
+ return sendPush(request);
+ }
- PushbulletConfiguration configuration = getConfigAs(PushbulletConfiguration.class);
- logger.debug("CFG {}", configuration);
+ /**
+ * Sends a push file
+ *
+ * @param recipient the recipient
+ * @param title the title
+ * @param message the message
+ * @param content the file content
+ * @param fileName the file name
+ * @return true if successful
+ */
+ public boolean sendPushFile(@Nullable String recipient, @Nullable String title, @Nullable String message,
+ String content, @Nullable String fileName) {
+ UploadResponse upload = uploadFile(content, fileName);
+ if (upload == null) {
+ return false;
+ }
- Properties headers = prepareRequestHeaders(configuration);
+ PushRequest request = newPushRequest(recipient, title, message, PushType.FILE);
+ request.setFileName(upload.getFileName());
+ request.setFileType(upload.getFileType());
+ request.setFileUrl(upload.getFileUrl());
- String request = prepareMessageBody(recipient, title, message, type);
+ return sendPush(request);
+ }
- try (InputStream stream = new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8))) {
- String pushAPI = configuration.getApiUrlBase() + "/" + API_METHOD_PUSHES;
+ /**
+ * Helper method to send a push request
+ *
+ * @param request the push request
+ * @return true if successful
+ */
+ private boolean sendPush(PushRequest request) {
+ logger.debug("Sending push notification for {}", thing.getUID());
+ logger.debug("Push Request: {}", request);
- String responseString = HttpUtil.executeUrl(HttpMethod.POST.asString(), pushAPI, headers, stream,
- MimeTypes.Type.APPLICATION_JSON.asString(), TIMEOUT);
+ try {
+ httpClient.executeRequest(API_ENDPOINT_PUSHES, request, PushResponse.class);
+ return true;
+ } catch (PushbulletApiException e) {
+ return false;
+ }
+ }
- logger.debug("Got Response: {}", responseString);
- PushResponse response = gson.fromJson(responseString, PushResponse.class);
+ /**
+ * Helper method to upload a file to use in push message
+ *
+ * @param content the file content
+ * @param fileName the file name
+ * @return the upload response if successful, otherwise null
+ */
+ private @Nullable UploadResponse uploadFile(String content, @Nullable String fileName) {
+ RawType data = getContentData(content);
+ if (data == null) {
+ logger.warn("Failed to get content data from '{}'", content);
+ return null;
+ }
- logger.debug("Unpacked Response: {}", response);
+ logger.debug("Content Data: {}", data);
- if ((null != response) && (null == response.getPushError())) {
- result = true;
- }
- } catch (IOException e) {
- logger.warn("IO problems pushing note: {}", e.getMessage());
+ int size = data.getBytes().length;
+ if (size > maxUploadSize) {
+ logger.warn("Content data size {} is greater than maximum upload size {}", size, maxUploadSize);
+ return null;
}
- return result;
+ try {
+ UploadRequest request = new UploadRequest();
+ request.setFileName(fileName != null ? fileName : getContentFileName(content));
+ request.setFileType(data.getMimeType());
+
+ logger.debug("Upload Request: {}", request);
+
+ UploadResponse response = httpClient.executeRequest(API_ENDPOINT_UPLOAD_REQUEST, request,
+ UploadResponse.class);
+
+ String uploadUrl = response.getUploadUrl();
+ if (uploadUrl == null) {
+ throw new PushbulletApiException("Undefined upload url");
+ }
+
+ httpClient.uploadFile(uploadUrl, data);
+
+ return response;
+ } catch (PushbulletApiException e) {
+ return null;
+ }
}
/**
- * helper method to populate the request headers
+ * Helper method to get the data for a given content
*
- * @param configuration
- * @return
+ * @param content the file content
+ * @return the data raw type if available, otherwise null
*/
- private Properties prepareRequestHeaders(PushbulletConfiguration configuration) {
- Properties headers = new Properties();
- headers.put(HttpHeader.USER_AGENT, "openHAB / Pushbullet binding " + VERSION);
- headers.put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.APPLICATION_JSON.asString());
- headers.put("Access-Token", configuration.getToken());
-
- logger.debug("Headers: {}", headers);
+ private @Nullable RawType getContentData(String content) {
+ try {
+ if (content.startsWith("data:")) {
+ return RawType.valueOf(content);
+ } else if (content.startsWith("http")) {
+ return HttpUtil.downloadImage(content);
+ } else {
+ Path path = Path.of(content);
+ byte[] bytes = Files.readAllBytes(path);
+ String mimeType = Files.probeContentType(path);
+ return new RawType(bytes, mimeType);
+ }
+ } catch (IllegalArgumentException | IOException e) {
+ logger.debug("Failed to get content data: {}", e.getMessage());
+ return null;
+ }
+ }
- return headers;
+ /**
+ * Helper method to get the file name for a given content
+ *
+ * @param content the file content
+ * @return the file name if available, otherwise null
+ */
+ private @Nullable String getContentFileName(String content) {
+ if (content.startsWith("data:")) {
+ return IMAGE_FILE_NAME;
+ }
+ try {
+ Path fileName = Path.of(content.startsWith("http") ? new URL(content).getPath() : content).getFileName();
+ if (fileName != null) {
+ return fileName.toString();
+ }
+ } catch (MalformedURLException e) {
+ logger.debug("Malformed url content: {}", e.getMessage());
+ }
+ return null;
}
/**
- * helper method to create a message body from data to be transferred.
+ * Helper method to create a push request
*
- * @param recipient
- * @param title
- * @param message
- * @param type
+ * @param recipient the recipient
+ * @param title the title
+ * @param message the message
+ * @param type the push type
*
- * @return the message as a String to be posted
+ * @return the push request object
*/
- private String prepareMessageBody(@Nullable String recipient, @Nullable String title, @Nullable String message,
- String type) {
+ private PushRequest newPushRequest(@Nullable String recipient, @Nullable String title, @Nullable String message,
+ PushType type) {
logger.debug("Recipient is '{}'", recipient);
logger.debug("Title is '{}'", title);
logger.debug("Message is '{}'", message);
+ logger.debug("Type is '{}'", type);
- Push push = new Push();
- push.setTitle(title);
- push.setBody(message);
- push.setType(type);
+ PushRequest request = new PushRequest();
+ request.setTitle(title);
+ request.setBody(message);
+ request.setType(type);
if (recipient != null) {
if (isValidEmail(recipient)) {
logger.debug("Recipient is an email address");
- push.setEmail(recipient);
+ request.setEmail(recipient);
} else if (isValidChannel(recipient)) {
logger.debug("Recipient is a channel tag");
- push.setChannel(recipient);
+ request.setChannel(recipient);
} else {
logger.warn("Invalid recipient: {}", recipient);
logger.warn("Message will be broadcast to all user's devices.");
}
}
- logger.debug("Push: {}", push);
-
- String request = gson.toJson(push);
- logger.debug("Packed Request: {}", request);
-
return request;
}
/**
- * helper method checking if channel tag is valid.
+ * Helper method checking if channel tag is valid
*
- * @param channel
- * @return
+ * @param channel the channel tag
+ * @return true if matches pattern
*/
private static boolean isValidChannel(String channel) {
- Matcher m = CHANNEL_PATTERN.matcher(channel);
- return m.matches();
+ return CHANNEL_PATTERN.matcher(channel).matches();
}
/**
- * helper method checking if email address is valid.
+ * Helper method checking if email address is valid
*
- * @param email
- * @return
+ * @param email the email address
+ * @return true if parsed successfully
*/
private static boolean isValidEmail(String email) {
try {
- InternetAddress emailAddr = new InternetAddress(email);
- emailAddr.validate();
+ new InternetAddress(email, true);
return true;
} catch (AddressException e) {
return false;
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.pushbullet.internal.model;
+
+import java.lang.reflect.Type;
+import java.math.BigDecimal;
+import java.time.Instant;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+
+/**
+ * The {@link InstantDeserializer} deserializes a timestamp returned by the API.
+ *
+ * @author Jeremy Setton - Initial contribution
+ */
+@NonNullByDefault
+public class InstantDeserializer implements JsonDeserializer<Instant> {
+
+ @Override
+ public @Nullable Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
+ BigDecimal timestamp = json.getAsBigDecimal();
+ BigDecimal[] parts = timestamp.divideAndRemainder(BigDecimal.ONE);
+ long seconds = parts[0].longValueExact();
+ long nanos = parts[1].movePointRight(9).longValue();
+ return Instant.ofEpochSecond(seconds, nanos);
+ }
+}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.pushbullet.internal.model;
-
-import com.google.gson.annotations.SerializedName;
-
-/**
- * This class represents the push request sent to the API.
- *
- * @author Hakan Tandogan - Initial contribution
- * @author Hakan Tandogan - Migrated from openHAB 1 action with the same name
- */
-public class Push {
-
- @SerializedName("title")
- private String title;
-
- @SerializedName("body")
- private String body;
-
- @SerializedName("type")
- private String type;
-
- @SerializedName("email")
- private String email;
-
- @SerializedName("channel_tag")
- private String channelTag;
-
- public String getTitle() {
- return title;
- }
-
- public void setTitle(String title) {
- this.title = title;
- }
-
- public String getBody() {
- return body;
- }
-
- public void setBody(String body) {
- this.body = body;
- }
-
- public String getType() {
- return type;
- }
-
- public void setType(String type) {
- this.type = type;
- }
-
- public String getEmail() {
- return email;
- }
-
- public void setEmail(String email) {
- this.email = email;
- }
-
- public String getChannel() {
- return channelTag;
- }
-
- public void setChannel(String channelTag) {
- this.channelTag = channelTag;
- }
-
- @Override
- public String toString() {
- return "Push {" + "title='" + title + '\'' + ", body='" + body + '\'' + ", type='" + type + '\'' + ", email='"
- + email + '\'' + ", channelTag='" + channelTag + '\'' + '}';
- }
-}
+++ /dev/null
-/**
- * Copyright (c) 2010-2024 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.pushbullet.internal.model;
-
-import com.google.gson.annotations.SerializedName;
-
-/**
- * This class represents errors in the response fetched from the API.
- *
- * @author Hakan Tandogan - Initial contribution
- * @author Hakan Tandogan - Migrated from openHAB 1 action with the same name
- */
-public class PushError {
-
- @SerializedName("type")
- private String type;
-
- @SerializedName("message")
- private String message;
-
- @SerializedName("param")
- private String param;
-
- @SerializedName("cat")
- private String cat;
-
- public String getType() {
- return type;
- }
-
- public void setType(String type) {
- this.type = type;
- }
-
- public String getMessage() {
- return message;
- }
-
- public void setMessage(String message) {
- this.message = message;
- }
-
- public String getParam() {
- return param;
- }
-
- public void setParam(String param) {
- this.param = param;
- }
-
- public String getCat() {
- return cat;
- }
-
- public void setCat(String cat) {
- this.cat = cat;
- }
-
- @Override
- public String toString() {
- return "PushError {" + "type='" + type + '\'' + ", message='" + message + '\'' + ", param='" + param + '\''
- + ", cat='" + cat + '\'' + '}';
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.pushbullet.internal.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This class represents the push request sent to the API.
+ *
+ * @author Hakan Tandogan - Initial contribution
+ * @author Hakan Tandogan - Migrated from openHAB 1 action with the same name
+ * @author Jeremy Setton - Add link and file push type support
+ */
+@NonNullByDefault
+public class PushRequest {
+
+ @SerializedName("type")
+ private @Nullable PushType type;
+
+ @SerializedName("title")
+ private @Nullable String title;
+
+ @SerializedName("body")
+ private @Nullable String body;
+
+ @SerializedName("url")
+ private @Nullable String url;
+
+ @SerializedName("file_name")
+ private @Nullable String fileName;
+
+ @SerializedName("file_type")
+ private @Nullable String fileType;
+
+ @SerializedName("file_url")
+ private @Nullable String fileUrl;
+
+ @SerializedName("source_device_iden")
+ private @Nullable String sourceDeviceIden;
+
+ @SerializedName("device_iden")
+ private @Nullable String deviceIden;
+
+ @SerializedName("client_iden")
+ private @Nullable String clientIden;
+
+ @SerializedName("channel_tag")
+ private @Nullable String channelTag;
+
+ @SerializedName("email")
+ private @Nullable String email;
+
+ @SerializedName("guid")
+ private @Nullable String guid;
+
+ public void setTitle(@Nullable String title) {
+ this.title = title;
+ }
+
+ public void setType(@Nullable PushType type) {
+ this.type = type;
+ }
+
+ public void setBody(@Nullable String body) {
+ this.body = body;
+ }
+
+ public void setUrl(@Nullable String url) {
+ this.url = url;
+ }
+
+ public void setFileName(@Nullable String fileName) {
+ this.fileName = fileName;
+ }
+
+ public void setFileType(@Nullable String fileType) {
+ this.fileType = fileType;
+ }
+
+ public void setFileUrl(@Nullable String fileUrl) {
+ this.fileUrl = fileUrl;
+ }
+
+ public void setSourceDeviceIden(@Nullable String sourceDeviceIden) {
+ this.sourceDeviceIden = sourceDeviceIden;
+ }
+
+ public void setDeviceIden(@Nullable String deviceIden) {
+ this.deviceIden = deviceIden;
+ }
+
+ public void setClientIden(@Nullable String clientIden) {
+ this.clientIden = clientIden;
+ }
+
+ public void setChannel(@Nullable String channelTag) {
+ this.channelTag = channelTag;
+ }
+
+ public void setEmail(@Nullable String email) {
+ this.email = email;
+ }
+
+ public void setGuid(@Nullable String guid) {
+ this.guid = guid;
+ }
+
+ @Override
+ public String toString() {
+ return "Push {type='" + type + "', title='" + title + "', body='" + body + "', url='" + url + "', fileName='"
+ + fileName + "', fileType='" + fileType + "', fileUrl='" + fileUrl + "', sourceDeviceIden='"
+ + sourceDeviceIden + "', deviceIden='" + deviceIden + "', clientIden='" + clientIden + "', channelTag='"
+ + channelTag + "', email='" + email + "', guid='" + guid + "'}";
+ }
+}
*/
package org.openhab.binding.pushbullet.internal.model;
+import java.time.Instant;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
import com.google.gson.annotations.SerializedName;
/**
- * This class represents the answer to pushes provided by the API.
+ * This class represents the push response received from the API.
*
* @author Hakan Tandogan - Initial contribution
* @author Hakan Tandogan - Migrated from openHAB 1 action with the same name
+ * @author Jeremy Setton - Add link and file push type support
*/
+@NonNullByDefault
public class PushResponse {
+ @SerializedName("iden")
+ private @Nullable String iden;
+
@SerializedName("active")
- private String active;
+ private @Nullable Boolean active;
- @SerializedName("iden")
- private String iden;
+ @SerializedName("created")
+ private @Nullable Instant created;
+
+ @SerializedName("modified")
+ private @Nullable Instant modified;
@SerializedName("type")
- private String type;
+ private @Nullable PushType type;
@SerializedName("dismissed")
- private Boolean dismissed;
+ private @Nullable Boolean dismissed;
+
+ @SerializedName("guid")
+ private @Nullable String guid;
@SerializedName("direction")
- private String direction;
+ private @Nullable String direction;
@SerializedName("sender_iden")
- private String senderIdentifier;
+ private @Nullable String senderIdentifier;
@SerializedName("sender_email")
- private String senderEmail;
+ private @Nullable String senderEmail;
@SerializedName("sender_email_normalized")
- private String senderEmailNormalized;
+ private @Nullable String senderEmailNormalized;
@SerializedName("sender_name")
- private String senderName;
+ private @Nullable String senderName;
@SerializedName("receiver_iden")
- private String receiverIdentifier;
+ private @Nullable String receiverIdentifier;
@SerializedName("receiver_email")
- private String receiverEmail;
+ private @Nullable String receiverEmail;
@SerializedName("receiver_email_normalized")
- private String receiverEmailNormalized;
+ private @Nullable String receiverEmailNormalized;
+
+ @SerializedName("target_device_iden")
+ private @Nullable String targetDeviceIden;
+
+ @SerializedName("source_device_iden")
+ private @Nullable String sourceDeviceIden;
+
+ @SerializedName("client_iden")
+ private @Nullable String clientIden;
+
+ @SerializedName("channel_iden")
+ private @Nullable String channelIden;
+
+ @SerializedName("awake_app_guids")
+ private @Nullable List<String> awakeAppGuids;
@SerializedName("title")
- private String title;
+ private @Nullable String title;
@SerializedName("body")
- private String body;
+ private @Nullable String body;
- @SerializedName("error_code")
- private String errorCode;
+ @SerializedName("url")
+ private @Nullable String url;
- @SerializedName("error")
- private PushError pushError;
+ @SerializedName("file_name")
+ private @Nullable String fileName;
- public String getActive() {
- return active;
- }
+ @SerializedName("file_type")
+ private @Nullable String fileType;
- public void setActive(String active) {
- this.active = active;
- }
+ @SerializedName("file_url")
+ private @Nullable String fileUrl;
+
+ @SerializedName("image_url")
+ private @Nullable String imageUrl;
- public String getIden() {
+ @SerializedName("image_width")
+ private @Nullable Integer imageWidth;
+
+ @SerializedName("image_height")
+ private @Nullable Integer imageHeight;
+
+ public @Nullable String getIden() {
return iden;
}
- public void setIden(String iden) {
- this.iden = iden;
+ public @Nullable Boolean getActive() {
+ return active;
}
- public String getType() {
- return type;
+ public @Nullable Instant getCreated() {
+ return created;
}
- public void setType(String type) {
- this.type = type;
+ public @Nullable Instant getModified() {
+ return modified;
}
- public Boolean getDismissed() {
- return dismissed;
+ public @Nullable PushType getType() {
+ return type;
}
- public void setDismissed(Boolean dismissed) {
- this.dismissed = dismissed;
+ public @Nullable Boolean getDismissed() {
+ return dismissed;
}
- public String getDirection() {
- return direction;
+ public @Nullable String getGuid() {
+ return guid;
}
- public void setDirection(String direction) {
- this.direction = direction;
+ public @Nullable String getDirection() {
+ return direction;
}
- public String getSenderIdentifier() {
+ public @Nullable String getSenderIdentifier() {
return senderIdentifier;
}
- public void setSenderIdentifier(String senderIdentifier) {
- this.senderIdentifier = senderIdentifier;
- }
-
- public String getSenderEmail() {
+ public @Nullable String getSenderEmail() {
return senderEmail;
}
- public void setSenderEmail(String senderEmail) {
- this.senderEmail = senderEmail;
- }
-
- public String getSenderEmailNormalized() {
+ public @Nullable String getSenderEmailNormalized() {
return senderEmailNormalized;
}
- public void setSenderEmailNormalized(String senderEmailNormalized) {
- this.senderEmailNormalized = senderEmailNormalized;
+ public @Nullable String getSenderName() {
+ return senderName;
}
- public String getSenderName() {
- return senderName;
+ public @Nullable String getReceiverIdentifier() {
+ return receiverIdentifier;
}
- public void setSenderName(String senderName) {
- this.senderName = senderName;
+ public @Nullable String getReceiverEmail() {
+ return receiverEmail;
}
- public String getReceiverIdentifier() {
- return receiverIdentifier;
+ public @Nullable String getReceiverEmailNormalized() {
+ return receiverEmailNormalized;
}
- public void setReceiverIdentifier(String receiverIdentifier) {
- this.receiverIdentifier = receiverIdentifier;
+ public @Nullable String getTargetDeviceIden() {
+ return targetDeviceIden;
}
- public String getReceiverEmail() {
- return receiverEmail;
+ public @Nullable String getSourceDeviceIden() {
+ return sourceDeviceIden;
}
- public void setReceiverEmail(String receiverEmail) {
- this.receiverEmail = receiverEmail;
+ public @Nullable String getClientIden() {
+ return clientIden;
}
- public String getReceiverEmailNormalized() {
- return receiverEmailNormalized;
+ public @Nullable String getChannelIden() {
+ return channelIden;
}
- public void setReceiverEmailNormalized(String receiverEmailNormalized) {
- this.receiverEmailNormalized = receiverEmailNormalized;
+ public @Nullable List<String> getAwakeAppGuids() {
+ return awakeAppGuids;
}
- public String getTitle() {
+ public @Nullable String getTitle() {
return title;
}
- public void setTitle(String title) {
- this.title = title;
+ public @Nullable String getBody() {
+ return body;
}
- public String getBody() {
- return body;
+ public @Nullable String getUrl() {
+ return url;
+ }
+
+ public @Nullable String getFileName() {
+ return fileName;
}
- public void setBody(String body) {
- this.body = body;
+ public @Nullable String getFileType() {
+ return fileType;
}
- public String getErrorCode() {
- return errorCode;
+ public @Nullable String getFileUrl() {
+ return fileUrl;
}
- public void setErrorCode(String errorCode) {
- this.errorCode = errorCode;
+ public @Nullable String getImageUrl() {
+ return imageUrl;
}
- public PushError getPushError() {
- return pushError;
+ public @Nullable Integer getImageWidth() {
+ return imageWidth;
}
- public void setPushError(PushError pushError) {
- this.pushError = pushError;
+ public @Nullable Integer getImageHeight() {
+ return imageHeight;
}
@Override
public String toString() {
- return "PushResponse {" + "active='" + active + '\'' + ", iden='" + iden + '\'' + ", type='" + type + '\''
- + ", dismissed=" + dismissed + ", direction='" + direction + '\'' + ", senderIdentifier='"
- + senderIdentifier + '\'' + ", senderEmail='" + senderEmail + '\'' + ", senderEmailNormalized='"
- + senderEmailNormalized + '\'' + ", senderName='" + senderName + '\'' + ", receiverIdentifier='"
- + receiverIdentifier + '\'' + ", receiverEmail='" + receiverEmail + '\'' + ", receiverEmailNormalized='"
- + receiverEmailNormalized + '\'' + ", title='" + title + '\'' + ", body='" + body + '\''
- + ", errorCode='" + errorCode + '\'' + ", pushError=" + pushError + '}';
+ return "PushResponse {iden='" + iden + ", active='" + active + "', created='" + created + "', modified='"
+ + modified + "', type='" + type + "', dismissed='" + dismissed + "', guid='" + guid + "', direction='"
+ + direction + "', senderIdentifier='" + senderIdentifier + "', senderEmail='" + senderEmail
+ + "', senderEmailNormalized='" + senderEmailNormalized + "', senderName='" + senderName
+ + "', receiverIdentifier='" + receiverIdentifier + "', receiverEmail='" + receiverEmail
+ + "', receiverEmailNormalized='" + receiverEmailNormalized + "', targetDeviceIden='" + targetDeviceIden
+ + "', sourceDeviceIden='" + sourceDeviceIden + "', clientIden='" + clientIden + "', channelIden='"
+ + channelIden + "', awakeAppGuids='" + awakeAppGuids + "', title='" + title + "', body='" + body
+ + "', url='" + url + "', fileName='" + fileName + "', fileType='" + fileType + "', fileUrl='" + fileUrl
+ + "', imageUrl='" + imageUrl + "', imageWidth='" + imageWidth + "', imageHeight='" + imageHeight + "'}";
}
}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.pushbullet.internal.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This class represents the push type.
+ *
+ * @author Jeremy Setton - Initial contribution
+ */
+@NonNullByDefault
+public enum PushType {
+ @SerializedName("note")
+ NOTE,
+ @SerializedName("link")
+ LINK,
+ @SerializedName("file")
+ FILE
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.pushbullet.internal.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This class represents the upload request sent to the API.
+ *
+ * @author Jeremy Setton - Initial contribution
+ */
+@NonNullByDefault
+public class UploadRequest {
+
+ @SerializedName("file_name")
+ private @Nullable String fileName;
+
+ @SerializedName("file_type")
+ private @Nullable String fileType;
+
+ public void setFileName(@Nullable String fileName) {
+ this.fileName = fileName;
+ }
+
+ public void setFileType(@Nullable String fileType) {
+ this.fileType = fileType;
+ }
+
+ @Override
+ public String toString() {
+ return "UploadRequest {fileName='" + fileName + "', fileType='" + fileType + "'}";
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.pushbullet.internal.model;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This class represents the upload response received from the API.
+ *
+ * @author Jeremy Setton - Initial contribution
+ */
+@NonNullByDefault
+public class UploadResponse {
+
+ @SerializedName("file_name")
+ private @Nullable String fileName;
+
+ @SerializedName("file_type")
+ private @Nullable String fileType;
+
+ @SerializedName("file_url")
+ private @Nullable String fileUrl;
+
+ @SerializedName("upload_url")
+ private @Nullable String uploadUrl;
+
+ public @Nullable String getFileName() {
+ return fileName;
+ }
+
+ public @Nullable String getFileType() {
+ return fileType;
+ }
+
+ public @Nullable String getFileUrl() {
+ return fileUrl;
+ }
+
+ public @Nullable String getUploadUrl() {
+ return uploadUrl;
+ }
+
+ @Override
+ public String toString() {
+ return "UploadResponse {fileName='" + fileName + "', fileType='" + fileType + "', fileUrl='" + fileUrl
+ + "', uploadUrl='" + uploadUrl + "'}";
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.pushbullet.internal.model;
+
+import java.time.Instant;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ * This class represents the user object received from the API.
+ *
+ * @author Jeremy Setton - Initial contribution
+ */
+@NonNullByDefault
+public class User {
+
+ @SerializedName("iden")
+ private @Nullable String iden;
+
+ @SerializedName("active")
+ private @Nullable Boolean active;
+
+ @SerializedName("created")
+ private @Nullable Instant created;
+
+ @SerializedName("modified")
+ private @Nullable Instant modified;
+
+ @SerializedName("email")
+ private @Nullable String email;
+
+ @SerializedName("email_normalized")
+ private @Nullable String emailNormalized;
+
+ @SerializedName("name")
+ private @Nullable String name;
+
+ @SerializedName("image_url")
+ private @Nullable String imageUrl;
+
+ @SerializedName("max_upload_size")
+ private @Nullable Integer maxUploadSize;
+
+ @SerializedName("referred_count")
+ private @Nullable Integer referredCount;
+
+ @SerializedName("referrer_iden")
+ private @Nullable String referrerIden;
+
+ public @Nullable String getIden() {
+ return iden;
+ }
+
+ public @Nullable Boolean getActive() {
+ return active;
+ }
+
+ public @Nullable Instant getCreated() {
+ return created;
+ }
+
+ public @Nullable Instant getModified() {
+ return modified;
+ }
+
+ public @Nullable String getEmail() {
+ return email;
+ }
+
+ public @Nullable String getEmailNormalized() {
+ return emailNormalized;
+ }
+
+ public @Nullable String getName() {
+ return name;
+ }
+
+ public @Nullable String getImageUrl() {
+ return imageUrl;
+ }
+
+ public @Nullable Integer getMaxUploadSize() {
+ return maxUploadSize;
+ }
+
+ public @Nullable Integer getReferredCount() {
+ return referredCount;
+ }
+
+ public @Nullable String getReferrerIden() {
+ return referrerIden;
+ }
+
+ @Override
+ public String toString() {
+ return "User {iden='" + iden + ", active='" + active + "', created='" + created + "', modified='" + modified
+ + "', email='" + email + "', emailNormalized='" + emailNormalized + "', name='" + name + "', imageUrl='"
+ + imageUrl + "', maxUploadSize='" + maxUploadSize + "', referredCount='" + referredCount
+ + "', referrerIden='" + referrerIden + "'}";
+ }
+}
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
<type>binding</type>
- <name>@text/addon.pushbullet.name</name>
- <description>@text/addon.pushbullet.description</description>
+ <name>Pushbullet Binding</name>
+ <description>The Pushbullet binding allows you to send messages to other users of the Pushbullet service.</description>
<connection>cloud</connection>
</addon:addon>
addon.pushbullet.name = Pushbullet Binding
addon.pushbullet.description = The Pushbullet binding allows you to send messages to other users of the Pushbullet service.
+# thing types
+thing-type.pushbullet.bot.label = Pushbullet Bot
+thing-type.pushbullet.bot.description = Bot to send messages with.
+
+# thing types config
+thing-type.config.pushbullet.bot.name.label = Name
+thing-type.config.pushbullet.bot.name.description = Explicit Name of Bot, if wanted
+thing-type.config.pushbullet.bot.token.label = Access Token
+thing-type.config.pushbullet.bot.token.description = Access token obtained from the account settings page
+thing-type.config.pushbullet.bot.apiUrlBase.label = API Server
+thing-type.config.pushbullet.bot.apiUrlBase.description = The Pushbullet API Server to use, for local testing
+
+# channel types
+channel-type.pushbullet.recipient-channel.label = Recipient
+channel-type.pushbullet.recipient-channel.description = Mail address or Channel Name
+channel-type.pushbullet.title-channel.label = Title
+channel-type.pushbullet.title-channel.description = Title of the message
+channel-type.pushbullet.message-channel.label = Message
+channel-type.pushbullet.message-channel.description = The text that is to be sent
+
# action
actionSendPushbulletNoteLabel = publish a Pushbullet message
-actionSendPushbulletNoteDesc = Publishes a Title to the given Pushbullet Recipient.
+actionSendPushbulletNoteDesc = Publishes a note to the given Pushbullet Recipient.
+actionSendPushbulletLinkLabel = publish a Pushbullet message with link
+actionSendPushbulletLinkDesc = Publishes a link to the given Pushbullet Recipient.
+actionSendPushbulletFileLabel = publish a Pushbullet message with file
+actionSendPushbulletFileDesc = Publishes a file to the given Pushbullet Recipient.
actionSendPushbulletNoteInputRecipientLabel = Pushbullet Recipient
actionSendPushbulletNoteInputRecipientDesc = The Recipient to publish a Title to.
actionSendPushbulletNoteInputTitleDesc = The Title to publish
actionSendPushbulletNoteInputMessageLabel = Message
actionSendPushbulletNoteInputMessageDesc = The Message to publish
+actionSendPushbulletLinkInputUrlLabel = Link URL
+actionSendPushbulletLinkInputUrlDesc = The Link URL to publish
+actionSendPushbulletFileInputContent = File Content
+actionSendPushbulletFileInputContentDesc = The File Content to publish
+actionSendPushbulletFileInputName = File Name
+actionSendPushbulletFileInputNameDesc = The File Name to publish
# error texts
offline.conf-error-httpresponseexception = The pushbullet server reported an error, possibly an expired token. Check on web site
</parameter>
<parameter name="token" type="text" required="true">
- <label>Token</label>
- <description>Token as obtained from the server</description>
+ <label>Access Token</label>
+ <description>Access token obtained from the account settings page</description>
</parameter>
<parameter name="apiUrlBase" type="text" required="true">