diff --git a/src/main/java/de/svenkubiak/jpushover/JPushover.java b/src/main/java/de/svenkubiak/jpushover/JPushover.java index 57af088..54f130d 100644 --- a/src/main/java/de/svenkubiak/jpushover/JPushover.java +++ b/src/main/java/de/svenkubiak/jpushover/JPushover.java @@ -2,6 +2,7 @@ package de.svenkubiak.jpushover; import de.svenkubiak.jpushover.apis.Glance; import de.svenkubiak.jpushover.apis.Message; +import de.svenkubiak.jpushover.apis.OpenClient; /** * @@ -17,7 +18,7 @@ public class JPushover { * * @return Glance instance */ - public static Glance newGlance() { + public static Glance glanceAPI() { return new Glance(); } @@ -26,7 +27,16 @@ public class JPushover { * * @return Message instance */ - public static Message newMessage() { + public static Message messageAPI() { return new Message(); } + + /** + * Creates a new OpenClient instance for the Open Client API + * + * @return OpenClient instance + */ + public static OpenClient openClientAPI() { + return new OpenClient(); + } } \ No newline at end of file diff --git a/src/main/java/de/svenkubiak/jpushover/apis/OpenClient.java b/src/main/java/de/svenkubiak/jpushover/apis/OpenClient.java new file mode 100644 index 0000000..aa004ef --- /dev/null +++ b/src/main/java/de/svenkubiak/jpushover/apis/OpenClient.java @@ -0,0 +1,258 @@ +package de.svenkubiak.jpushover.apis; + +import java.io.IOException; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.net.http.WebSocket; +import java.net.http.WebSocket.Builder; +import java.time.Duration; +import java.util.Objects; + +import de.svenkubiak.jpushover.exceptions.JPushoverException; +import de.svenkubiak.jpushover.http.PushoverResponse; +import de.svenkubiak.jpushover.interfaces.MessageListener; +import de.svenkubiak.jpushover.listener.WebSocketListener; + +/** + * + * @author svenkubiak + * + */ +public class OpenClient { + private static final String LOGIN_URL = "https://api.pushover.net/1/users/login.json"; + private static final String DEVICE_URL = "https://api.pushover.net/1/devices.json"; + private static final String MESSAGES_URL = "https://api.pushover.net/1/messages.json"; + private static final String DELETE_URL = "https://api.pushover.net/1/devices/###DEVICE_ID###/update_highest_message.json"; + private static final String WEBSOCKET_URL = "wss://client.pushover.net/push"; + + /** + * Performs a Pushover login; required once for working with the Open Client API + * + * @param email Your Pushover email address + * @param password Your Pushover password + * @param twofa Your Your current Pushover two-factor code (if enabled) + * + * @return A PushoverResponse + * @throws JPushoverException if something went wrong with the HTTP request + */ + public PushoverResponse login(String email, String password, String twofa) throws JPushoverException { + Objects.requireNonNull(email, "email can not be null"); + Objects.requireNonNull(password, "password can not be null"); + + StringBuilder params = new StringBuilder() + .append("email") + .append("=") + .append(email) + .append("&") + .append("password") + .append("=") + .append(password); + + if (twofa != null) { + params + .append("&") + .append("twofa") + .append("=") + .append(twofa); + } + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(LOGIN_URL)) + .timeout(Duration.ofSeconds(5)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(params.toString())) + .build(); + + PushoverResponse pushoverResponse = PushoverResponse.create().isSuccessful(false); + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + pushoverResponse + .httpStatus(response.statusCode()) + .response(response.body()) + .isSuccessful((response.statusCode() == 200) ? true : false); + } catch (IOException | InterruptedException e) { + throw new JPushoverException("Login failed", e); + } + + return pushoverResponse; + } + + /** + * Performs a Pushover login; required once for working with the Open Client API + * + * @param email Your Pushover email address + * @param password Your Pushover password + * + * @return A PushoverResponse + * @throws JPushoverException if something went wrong with the HTTP request + */ + public PushoverResponse login(String email, String password) throws JPushoverException { + return login(email, password, null); + } + + /** + * Retrieves all available messages for the given deviceId + * + * @param secret Your Pushover secret retrieved after login + * @param deviceId The deviceId to get the messages + * + * @return A String containing raw Json with all available messages or null + * @throws JPushoverException if something went wrong with the HTTP request + */ + public String messages(String secret, String deviceId) throws JPushoverException { + Objects.requireNonNull(secret, "secret can not be null"); + Objects.requireNonNull(deviceId, "deviceId can not be null"); + + StringBuilder params = new StringBuilder() + .append("?secret") + .append("=") + .append(secret) + .append("&") + .append("deviceId") + .append("=") + .append(deviceId); + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(MESSAGES_URL + params.toString())) + .timeout(Duration.ofSeconds(5)) + .header("Content-Type", "application/json") + .build(); + + String messages = null; + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + messages = response.body(); + } catch (IOException | InterruptedException e) { + throw new JPushoverException("Failed to get messages", e); + } + + return messages; + } + + /** + * Deletes all messages after (and including) a given messagesId + * + * @param secret Your Pushover secret retrieved after login + * @param deviceId The deviceId to get the messages + * @param messageId The messagesId + * + * @return A PushoverResponse + * @throws JPushoverException if something went wrong with the HTTP request + */ + public PushoverResponse deleteMessages(String secret, String deviceId, String messageId) throws JPushoverException { + Objects.requireNonNull(deviceId, "secret can not be null"); + Objects.requireNonNull(secret, "deviceId can not be null"); + Objects.requireNonNull(messageId, "deviceId can not be null"); + + StringBuilder params = new StringBuilder() + .append("secret") + .append("=") + .append(secret) + .append("&") + .append("message") + .append("=") + .append(messageId); + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(DELETE_URL.replace("###DEVICE_ID###", deviceId))) + .timeout(Duration.ofSeconds(5)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(params.toString())) + .build(); + + PushoverResponse pushoverResponse = PushoverResponse.create().isSuccessful(false); + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + pushoverResponse + .httpStatus(response.statusCode()) + .response(response.body()) + .isSuccessful((response.statusCode() == 200) ? true : false); + + } catch (IOException | InterruptedException e) { + throw new JPushoverException("Failed to delete messages", e); + } + + return pushoverResponse; + } + + /** + * Establishes a WebSocket connect which listens to new messages + * + * @param secret Your Pushover secret retrieved after login + * @param deviceId The deviceId to get the messages + * @param messageListener Your instance of a MessagesListener + * + * @return True if the connection was established successful + */ + public boolean listen(String secret, String deviceId, MessageListener messageListener) { + Objects.requireNonNull(secret, "secret can not be null"); + Objects.requireNonNull(deviceId, "deviceId name can not be null"); + Objects.requireNonNull(messageListener, "messageListener can not be null"); + + HttpClient httpClient = HttpClient.newBuilder().build(); + Builder webSocketBuilder = httpClient.newWebSocketBuilder(); + WebSocket webSocket = webSocketBuilder.buildAsync(URI.create(WEBSOCKET_URL), new WebSocketListener(messageListener)).join(); + + StringBuilder params = new StringBuilder() + .append("login") + .append(":") + .append(deviceId) + .append(":") + .append(secret) + .append("\n"); + + webSocket.sendText(params.toString(), true); + + return !webSocket.isInputClosed(); + } + + /** + * Registers a new device + * + * @param secret Your Pushover secret retrieved after login + * @param deviceName The name of the device to register + * + * @return A PushoverResponse + * @throws JPushoverException if something went wrong with the HTTP request + */ + public PushoverResponse registerDevice(String secret, String deviceName) throws JPushoverException { + Objects.requireNonNull(secret, "secret can not be null"); + Objects.requireNonNull(deviceName, "device name can not be null"); + + StringBuilder params = new StringBuilder() + .append("secret") + .append("=") + .append(secret) + .append("&") + .append("name") + .append("=") + .append(deviceName) + .append("&os=O"); + + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(DEVICE_URL)) + .POST(HttpRequest.BodyPublishers.ofString(params.toString())) + .build(); + + PushoverResponse pushoverResponse = PushoverResponse.create().isSuccessful(false); + try { + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + if (response.statusCode() == 200) { + pushoverResponse + .httpStatus(response.statusCode()) + .response(response.body()) + .isSuccessful(true); + } + } catch (IOException | InterruptedException e) { + throw new JPushoverException("Failed to register new device", e); + } + + return pushoverResponse; + } +} \ No newline at end of file diff --git a/src/main/java/de/svenkubiak/jpushover/exceptions/JPushoverException.java b/src/main/java/de/svenkubiak/jpushover/exceptions/JPushoverException.java new file mode 100644 index 0000000..98e6c27 --- /dev/null +++ b/src/main/java/de/svenkubiak/jpushover/exceptions/JPushoverException.java @@ -0,0 +1,14 @@ +package de.svenkubiak.jpushover.exceptions; + +/** + * + * @author svenkubiak + * + */ +public class JPushoverException extends Exception{ + private static final long serialVersionUID = -5719174030861964503L; + + public JPushoverException(String message, Exception e) { + super(message, e); + } +} diff --git a/src/main/java/de/svenkubiak/jpushover/http/PushoverResponse.java b/src/main/java/de/svenkubiak/jpushover/http/PushoverResponse.java index 3382382..365b660 100644 --- a/src/main/java/de/svenkubiak/jpushover/http/PushoverResponse.java +++ b/src/main/java/de/svenkubiak/jpushover/http/PushoverResponse.java @@ -13,6 +13,10 @@ public class PushoverResponse { private int pushoverHttpStatus; private boolean pushoverSuccessful; + public static PushoverResponse create() { + return new PushoverResponse(); + } + public PushoverResponse response(String response) { this.pushoverResponse = response; return this; diff --git a/src/main/java/de/svenkubiak/jpushover/interfaces/MessageListener.java b/src/main/java/de/svenkubiak/jpushover/interfaces/MessageListener.java new file mode 100644 index 0000000..d55489b --- /dev/null +++ b/src/main/java/de/svenkubiak/jpushover/interfaces/MessageListener.java @@ -0,0 +1,18 @@ +package de.svenkubiak.jpushover.interfaces; + +/** + * + * @author svenkubiak + * + */ +public interface MessageListener { + /** + * Called when a new message/new messages is available + */ + void onMessage(); + + /** + * Called when the WebSocket ran into an error + */ + void onError(); +} \ No newline at end of file diff --git a/src/main/java/de/svenkubiak/jpushover/listener/WebSocketListener.java b/src/main/java/de/svenkubiak/jpushover/listener/WebSocketListener.java new file mode 100644 index 0000000..d2e634f --- /dev/null +++ b/src/main/java/de/svenkubiak/jpushover/listener/WebSocketListener.java @@ -0,0 +1,54 @@ +package de.svenkubiak.jpushover.listener; + +import java.net.http.WebSocket; +import java.net.http.WebSocket.Listener; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.concurrent.CompletionStage; + +import de.svenkubiak.jpushover.interfaces.MessageListener; + +/** + * + * @author svenkubiak + * + */ +public class WebSocketListener implements Listener { + private MessageListener messageListener; + + public WebSocketListener (MessageListener messageListener) { + Objects.requireNonNull(messageListener, "messageListener can not be null"); + this.messageListener = messageListener; + } + + @Override + public void onError(WebSocket webSocket, Throwable error) { + messageListener.onError(); + Listener.super.onError(webSocket, error); + } + + @Override + public CompletionStage onBinary(WebSocket webSocket, ByteBuffer data, boolean last) { + if (data != null) { + String frame = StandardCharsets.UTF_8.decode(data).toString(); + switch (frame) { + case "!": + messageListener.onMessage(); + break; + case "E": + messageListener.onError(); + break; + case "R": + messageListener.onError(); + break; + case "A": + messageListener.onError(); + break; + default: + } + } + + return Listener.super.onBinary(webSocket, data, last); + } +} \ No newline at end of file diff --git a/src/test/java/jpushover/apis/GlanceTests.java b/src/test/java/jpushover/apis/GlanceTests.java index 8610f54..1e4da1e 100644 --- a/src/test/java/jpushover/apis/GlanceTests.java +++ b/src/test/java/jpushover/apis/GlanceTests.java @@ -16,7 +16,7 @@ public class GlanceTests { @Test void testConstruct() { //given - Glance glance = JPushover.newGlance(); + Glance glance = JPushover.glanceAPI(); //then assertTrue(glance instanceof Glance); @@ -28,7 +28,7 @@ public class GlanceTests { String value = "myToken"; //when - Glance glance = JPushover.newGlance().withToken(value); + Glance glance = JPushover.glanceAPI().withToken(value); //then assertTrue(glance.getValue(Param.TOKEN.toString()).equals(value)); @@ -40,7 +40,7 @@ public class GlanceTests { String value = "myUser"; //when - Glance glance = JPushover.newGlance().withUser(value); + Glance glance = JPushover.glanceAPI().withUser(value); //then assertTrue(glance.getValue(Param.USER.toString()).equals(value)); @@ -52,7 +52,7 @@ public class GlanceTests { String value = "myDevice"; //when - Glance glance = JPushover.newGlance().withDevice(value); + Glance glance = JPushover.glanceAPI().withDevice(value); //then assertTrue(glance.getValue(Param.DEVICE.toString()).equals(value)); @@ -64,7 +64,7 @@ public class GlanceTests { String value = "myTitle"; //when - Glance glance = JPushover.newGlance().withTitle(value); + Glance glance = JPushover.glanceAPI().withTitle(value); //then assertTrue(glance.getValue(Param.TITLE.toString()).equals(value)); @@ -76,7 +76,7 @@ public class GlanceTests { String value = "myText"; //when - Glance glance = JPushover.newGlance().withText(value); + Glance glance = JPushover.glanceAPI().withText(value); //then assertTrue(glance.getValue(Param.TEXT.toString()).equals(value)); @@ -88,7 +88,7 @@ public class GlanceTests { String value = "mySubtext"; //when - Glance glance = JPushover.newGlance().withSubtext(value); + Glance glance = JPushover.glanceAPI().withSubtext(value); //then assertTrue(glance.getValue(Param.SUBTEXT.toString()).equals(value)); @@ -100,7 +100,7 @@ public class GlanceTests { int value = 23; //when - Glance glance = JPushover.newGlance().withCount(value); + Glance glance = JPushover.glanceAPI().withCount(value); //then assertTrue(glance.getValue(Param.COUNT.toString()).equals(String.valueOf(value))); @@ -112,7 +112,7 @@ public class GlanceTests { int value = 42; //when - Glance glance = JPushover.newGlance().withPercent(value); + Glance glance = JPushover.glanceAPI().withPercent(value); //then assertTrue(glance.getValue(Param.PERCENT.toString()).equals(String.valueOf(value))); @@ -125,7 +125,7 @@ public class GlanceTests { //when Exception exception = assertThrows(NullPointerException.class, () -> { - JPushover.newGlance().push(); + JPushover.glanceAPI().push(); }); String actualMessage = exception.getMessage(); @@ -140,7 +140,7 @@ public class GlanceTests { //when Exception exception = assertThrows(NullPointerException.class, () -> { - JPushover.newGlance().withToken("foo").push(); + JPushover.glanceAPI().withToken("foo").push(); }); String actualMessage = exception.getMessage(); diff --git a/src/test/java/jpushover/apis/MessageTests.java b/src/test/java/jpushover/apis/MessageTests.java index 4a63b05..c8a3fdb 100644 --- a/src/test/java/jpushover/apis/MessageTests.java +++ b/src/test/java/jpushover/apis/MessageTests.java @@ -18,7 +18,7 @@ public class MessageTests { @Test void testConstruct() { //given - Message message = JPushover.newMessage(); + Message message = JPushover.messageAPI(); //then assertTrue(message instanceof Message); @@ -27,7 +27,7 @@ public class MessageTests { @Test void testDefaults() { //given - Message message = JPushover.newMessage(); + Message message = JPushover.messageAPI(); //then assertTrue(message.getValue(Param.PRIORITY.toString()).equals(Priority.NORMAL.toString())); @@ -40,7 +40,7 @@ public class MessageTests { String value = "myToken"; //when - Message message = JPushover.newMessage().withToken(value); + Message message = JPushover.messageAPI().withToken(value); //then assertTrue(message.getValue(Param.TOKEN.toString()).equals(value)); @@ -52,7 +52,7 @@ public class MessageTests { String value = "myUser"; //when - Message message = JPushover.newMessage().withUser(value); + Message message = JPushover.messageAPI().withUser(value); //then assertTrue(message.getValue(Param.USER.toString()).equals(value)); @@ -64,7 +64,7 @@ public class MessageTests { int value = 3; //when - Message message = JPushover.newMessage().withRetry(value); + Message message = JPushover.messageAPI().withRetry(value); //then assertTrue(message.getValue(Param.RETRY.toString()).equals(String.valueOf(value))); @@ -76,7 +76,7 @@ public class MessageTests { int value = 5; //when - Message message = JPushover.newMessage().withExpire(value); + Message message = JPushover.messageAPI().withExpire(value); //then assertTrue(message.getValue(Param.EXPIRE.toString()).equals(String.valueOf(value))); @@ -88,7 +88,7 @@ public class MessageTests { String value = "myMessage"; //when - Message message = JPushover.newMessage().withMessage(value); + Message message = JPushover.messageAPI().withMessage(value); //then assertTrue(message.getValue(Param.MESSAGE.toString()).equals(String.valueOf(value))); @@ -100,7 +100,7 @@ public class MessageTests { String value = "myDevice"; //when - Message message = JPushover.newMessage().withDevice(value); + Message message = JPushover.messageAPI().withDevice(value); //then assertTrue(message.getValue(Param.DEVICE.toString()).equals(value)); @@ -112,7 +112,7 @@ public class MessageTests { String value = "myTitle"; //when - Message message = JPushover.newMessage().withTitle(value); + Message message = JPushover.messageAPI().withTitle(value); //then assertTrue(message.getValue(Param.TITLE.toString()).equals(value)); @@ -124,7 +124,7 @@ public class MessageTests { String value = "myUrl"; //when - Message message = JPushover.newMessage().withUrl(value); + Message message = JPushover.messageAPI().withUrl(value); //then assertTrue(message.getValue(Param.URL.toString()).equals(value)); @@ -136,7 +136,7 @@ public class MessageTests { String value = "myUrlTitle"; //when - Message message = JPushover.newMessage().withUrlTitle(value); + Message message = JPushover.messageAPI().withUrlTitle(value); //then assertTrue(message.getValue(Param.URL_TITLE.toString()).equals(value)); @@ -145,7 +145,7 @@ public class MessageTests { @Test void testEnableMonospace() { //when - Message message = JPushover.newMessage().enableMonospace(); + Message message = JPushover.messageAPI().enableMonospace(); //then assertTrue(message.getValue(Param.MONOSPACE.toString()).equals("1")); @@ -155,7 +155,7 @@ public class MessageTests { @Test void testEnableHtml() { //when - Message message = JPushover.newMessage().enableHtml(); + Message message = JPushover.messageAPI().enableHtml(); //then assertTrue(message.getValue(Param.MONOSPACE.toString()).equals("0")); @@ -168,7 +168,7 @@ public class MessageTests { int value = 555; //when - Message message = JPushover.newMessage().withTimestamp(value); + Message message = JPushover.messageAPI().withTimestamp(value); //then assertTrue(message.getValue(Param.TIMESTAMP.toString()).equals(String.valueOf(value))); @@ -180,7 +180,7 @@ public class MessageTests { Priority value = Priority.EMERGENCY; //when - Message message = JPushover.newMessage().withPriority(value); + Message message = JPushover.messageAPI().withPriority(value); //then assertTrue(message.getValue(Param.PRIORITY.toString()).equals(value.toString())); @@ -192,7 +192,7 @@ public class MessageTests { Sound value = Sound.BUGLE; //when - Message message = JPushover.newMessage().withSound(value); + Message message = JPushover.messageAPI().withSound(value); //then assertTrue(message.getValue(Param.SOUND.toString()).equals(value.toString())); @@ -204,7 +204,7 @@ public class MessageTests { String value = "myCallback"; //when - Message message = JPushover.newMessage().withCallback(value); + Message message = JPushover.messageAPI().withCallback(value); //then assertTrue(message.getValue(Param.CALLBACK.toString()).equals(value)); @@ -217,7 +217,7 @@ public class MessageTests { //when Exception exception = assertThrows(NullPointerException.class, () -> { - JPushover.newMessage().push(); + JPushover.messageAPI().push(); }); String actualMessage = exception.getMessage(); @@ -232,7 +232,7 @@ public class MessageTests { //when Exception exception = assertThrows(NullPointerException.class, () -> { - JPushover.newMessage().withToken("foo").push(); + JPushover.messageAPI().withToken("foo").push(); }); String actualMessage = exception.getMessage(); @@ -247,7 +247,7 @@ public class MessageTests { //when Exception exception = assertThrows(NullPointerException.class, () -> { - JPushover.newMessage().withToken("foo").withUser("bar").push(); + JPushover.messageAPI().withToken("foo").withUser("bar").push(); }); String actualMessage = exception.getMessage(); @@ -263,7 +263,7 @@ public class MessageTests { //when Exception exception = assertThrows(IllegalArgumentException.class, () -> { - JPushover.newMessage().withToken("foo").withUser("bar").withMessage(message).push(); + JPushover.messageAPI().withToken("foo").withUser("bar").withMessage(message).push(); }); String actualMessage = exception.getMessage(); @@ -279,7 +279,7 @@ public class MessageTests { //when Exception exception = assertThrows(IllegalArgumentException.class, () -> { - JPushover.newMessage().withToken("foo").withUser("bar").withMessage("foobar").withTitle(title).push(); + JPushover.messageAPI().withToken("foo").withUser("bar").withMessage("foobar").withTitle(title).push(); }); String actualMessage = exception.getMessage(); @@ -295,7 +295,7 @@ public class MessageTests { //when Exception exception = assertThrows(IllegalArgumentException.class, () -> { - JPushover.newMessage().withToken("foo").withUser("bar").withMessage("foobar").withUrl(url).push(); + JPushover.messageAPI().withToken("foo").withUser("bar").withMessage("foobar").withUrl(url).push(); }); String actualMessage = exception.getMessage(); @@ -311,7 +311,7 @@ public class MessageTests { //when Exception exception = assertThrows(IllegalArgumentException.class, () -> { - JPushover.newMessage().withToken("foo").withUser("bar").withMessage("foobar").withUrlTitle(urlTitle).push(); + JPushover.messageAPI().withToken("foo").withUser("bar").withMessage("foobar").withUrlTitle(urlTitle).push(); }); String actualMessage = exception.getMessage();