[pushbullet] Add link and file push type support (#17472)

* [pushbullet] Add link and file push type support

Signed-off-by: jsetton <jeremy.setton@gmail.com>
Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
Jeremy 2024-09-28 16:58:23 -04:00 committed by Ciprian Pascu
parent 3b474220fb
commit 72eb7773d8
21 changed files with 1257 additions and 393 deletions

View File

@ -48,6 +48,11 @@ Two different actions available:
- `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.
@ -56,11 +61,25 @@ Once this action instance is retrieved, you can invoke the action method on it.
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

View File

@ -22,11 +22,12 @@ import org.openhab.core.thing.ThingTypeUID;
* 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");
@ -38,8 +39,16 @@ public class PushbulletBindingConstants {
public static final String TITLE = "title";
public static final String MESSAGE = "message";
// Binding logic constants
public static final String API_METHOD_PUSHES = "pushes";
// Thing properties
public static final String PROPERTY_EMAIL = "email";
public static final String PROPERTY_NAME = "name";
public static final int TIMEOUT = 30 * 1000; // 30 seconds
// Binding logic constants
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 MAX_UPLOAD_SIZE = 26214400;
}

View File

@ -19,37 +19,26 @@ import org.eclipse.jdt.annotation.Nullable;
* 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;
}
}

View File

@ -16,24 +16,36 @@ import static org.openhab.binding.pushbullet.internal.PushbulletBindingConstants
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);
@ -44,7 +56,7 @@ public class PushbulletHandlerFactory extends BaseThingHandlerFactory {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_BOT.equals(thingTypeUID)) {
return new PushbulletHandler(thing);
return new PushbulletHandler(thing, httpClient);
}
return null;

View File

@ -0,0 +1,203 @@
/**
* 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;
}
}

View File

@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
* 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")
@ -52,9 +53,9 @@ public class PushbulletActions implements ThingActions {
@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;
@ -63,18 +64,18 @@ public class PushbulletActions implements ThingActions {
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;
@ -83,11 +84,115 @@ public class PushbulletActions implements ThingActions {
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);
}
}

View File

@ -0,0 +1,33 @@
/**
* 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);
}
}

View File

@ -0,0 +1,29 @@
/**
* 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);
}
}

View File

@ -14,14 +14,14 @@ package org.openhab.binding.pushbullet.internal.handler;
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;
@ -29,49 +29,51 @@ import javax.mail.internet.InternetAddress;
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 final Gson gson = new GsonBuilder().create();
private static final Version VERSION = FrameworkUtil.getBundle(PushbulletHandler.class).getVersion();
private static final Pattern CHANNEL_PATTERN = Pattern.compile("^[a-zA-Z0-9_-]+$");
private @Nullable PushbulletConfiguration config;
private final Logger logger = LoggerFactory.getLogger(PushbulletHandler.class);
public PushbulletHandler(Thing thing) {
private final PushbulletHttpClient httpClient;
private int maxUploadSize;
public PushbulletHandler(Thing thing, HttpClient httpClient) {
super(thing);
this.httpClient = new PushbulletHttpClient(httpClient);
}
@Override
@ -86,13 +88,42 @@ public class PushbulletHandler extends BaseThingHandler {
@Override
public void initialize() {
logger.debug("Start initializing!");
config = getConfigAs(PushbulletConfiguration.class);
logger.debug("Starting {}", thing.getUID());
// Name and Token are both "required", so set the Thing immediately ONLINE.
updateStatus(ThingStatus.ONLINE);
PushbulletConfiguration config = getConfigAs(PushbulletConfiguration.class);
logger.debug("Finished initializing!");
if (config.getAccessToken().isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Undefined access token.");
return;
}
httpClient.setConfiguration(config);
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
@ -100,125 +131,227 @@ public class PushbulletHandler extends BaseThingHandler {
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);
Properties headers = prepareRequestHeaders(configuration);
String request = prepareMessageBody(recipient, title, message, type);
try (InputStream stream = new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8))) {
String pushAPI = configuration.getApiUrlBase() + "/" + API_METHOD_PUSHES;
String responseString = HttpUtil.executeUrl(HttpMethod.POST.asString(), pushAPI, headers, stream,
MimeTypes.Type.APPLICATION_JSON.asString(), TIMEOUT);
logger.debug("Got Response: {}", responseString);
PushResponse response = gson.fromJson(responseString, PushResponse.class);
logger.debug("Unpacked Response: {}", response);
if ((null != response) && (null == response.getPushError())) {
result = true;
}
} catch (IOException e) {
logger.warn("IO problems pushing note: {}", e.getMessage());
/**
* 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;
}
return result;
PushRequest request = newPushRequest(recipient, title, message, PushType.FILE);
request.setFileName(upload.getFileName());
request.setFileType(upload.getFileType());
request.setFileUrl(upload.getFileUrl());
return sendPush(request);
}
/**
* helper method to populate the request headers
* Helper method to send a push request
*
* @param configuration
* @return
* @param request the push request
* @return true if successful
*/
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());
private boolean sendPush(PushRequest request) {
logger.debug("Sending push notification for {}", thing.getUID());
logger.debug("Push Request: {}", request);
logger.debug("Headers: {}", headers);
return headers;
try {
httpClient.executeRequest(API_ENDPOINT_PUSHES, request, PushResponse.class);
return true;
} catch (PushbulletApiException e) {
return false;
}
}
/**
* helper method to create a message body from data to be transferred.
* Helper method to upload a file to use in push message
*
* @param recipient
* @param title
* @param message
* @param type
*
* @return the message as a String to be posted
* @param content the file content
* @param fileName the file name
* @return the upload response if successful, otherwise null
*/
private String prepareMessageBody(@Nullable String recipient, @Nullable String title, @Nullable String message,
String type) {
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("Content Data: {}", data);
int size = data.getBytes().length;
if (size > maxUploadSize) {
logger.warn("Content data size {} is greater than maximum upload size {}", size, maxUploadSize);
return null;
}
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 get the data for a given content
*
* @param content the file content
* @return the data raw type if available, otherwise null
*/
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;
}
}
/**
* 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 push request
*
* @param recipient the recipient
* @param title the title
* @param message the message
* @param type the push type
*
* @return the push request object
*/
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;

View File

@ -0,0 +1,42 @@
/**
* 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);
}
}

View File

@ -1,85 +0,0 @@
/**
* 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 + '\'' + '}';
}
}

View File

@ -1,74 +0,0 @@
/**
* 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 + '\'' + '}';
}
}

View File

@ -0,0 +1,128 @@
/**
* 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 + "'}";
}
}

View File

@ -12,200 +12,238 @@
*/
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("active")
private String active;
@SerializedName("iden")
private String iden;
private @Nullable String iden;
@SerializedName("active")
private @Nullable Boolean active;
@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;
public String getIden() {
@SerializedName("image_url")
private @Nullable String imageUrl;
@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() {
public @Nullable Instant getCreated() {
return created;
}
public @Nullable Instant getModified() {
return modified;
}
public @Nullable PushType getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Boolean getDismissed() {
public @Nullable Boolean getDismissed() {
return dismissed;
}
public void setDismissed(Boolean dismissed) {
this.dismissed = dismissed;
public @Nullable String getGuid() {
return guid;
}
public String getDirection() {
public @Nullable String getDirection() {
return direction;
}
public void setDirection(String direction) {
this.direction = 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 String getSenderName() {
public @Nullable String getSenderName() {
return senderName;
}
public void setSenderName(String senderName) {
this.senderName = senderName;
}
public String getReceiverIdentifier() {
public @Nullable String getReceiverIdentifier() {
return receiverIdentifier;
}
public void setReceiverIdentifier(String receiverIdentifier) {
this.receiverIdentifier = receiverIdentifier;
}
public String getReceiverEmail() {
public @Nullable String getReceiverEmail() {
return receiverEmail;
}
public void setReceiverEmail(String receiverEmail) {
this.receiverEmail = receiverEmail;
}
public String getReceiverEmailNormalized() {
public @Nullable String getReceiverEmailNormalized() {
return receiverEmailNormalized;
}
public void setReceiverEmailNormalized(String receiverEmailNormalized) {
this.receiverEmailNormalized = receiverEmailNormalized;
public @Nullable String getTargetDeviceIden() {
return targetDeviceIden;
}
public String getTitle() {
public @Nullable String getSourceDeviceIden() {
return sourceDeviceIden;
}
public @Nullable String getClientIden() {
return clientIden;
}
public @Nullable String getChannelIden() {
return channelIden;
}
public @Nullable List<String> getAwakeAppGuids() {
return awakeAppGuids;
}
public @Nullable String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
public @Nullable String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
public @Nullable String getUrl() {
return url;
}
public String getErrorCode() {
return errorCode;
public @Nullable String getFileName() {
return fileName;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
public @Nullable String getFileType() {
return fileType;
}
public PushError getPushError() {
return pushError;
public @Nullable String getFileUrl() {
return fileUrl;
}
public void setPushError(PushError pushError) {
this.pushError = pushError;
public @Nullable String getImageUrl() {
return imageUrl;
}
public @Nullable Integer getImageWidth() {
return imageWidth;
}
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 + "'}";
}
}

View File

@ -0,0 +1,32 @@
/**
* 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
}

View File

@ -0,0 +1,46 @@
/**
* 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 + "'}";
}
}

View File

@ -0,0 +1,61 @@
/**
* 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 + "'}";
}
}

View File

@ -0,0 +1,114 @@
/**
* 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 + "'}";
}
}

View File

@ -4,8 +4,8 @@
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>

View File

@ -2,9 +2,33 @@
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.
@ -12,6 +36,12 @@ actionSendPushbulletNoteInputTitleLabel = Title
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

View File

@ -22,8 +22,8 @@
</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">