diff --git a/README.md b/README.md
index ac1d7d9..d35a955 100644
--- a/README.md
+++ b/README.md
@@ -3,47 +3,58 @@
JPushover
================
-Zero-dependency convenient class for sending messages to [Pushover][1] in Java.
+A zero-dependency convenient class for sending messages to [Pushover][1] in Java.
-Starting with version 3.x JPushover is build for and requires Java 11.
+Requires Java 11.
+
+Support [Messages API][3] and [Glances API][4].
Usage
------------------
1) Add the jpushover dependency to your pom.xml:
-```
-
- de.svenkubiak
- jpushover
- x.x.x
-
-```
+
+
+ de.svenkubiak
+ jpushover
+ x.x.x
+
+
+
2) Use the JPushover object with the required informations were you want
-```
-JPushover.create()
- .withToken("MyToken")
- .withUser("MyUser")
- .withMessage("MyMessage")
- .push();
-```
-You can additionally add all available options from the official [Pushover documentation][2]
+
+ JPushover.newMessage()
+ .withToken("MyToken")
+ .withUser("MyUser")
+ .withMessage("MyMessage")
+ .push();
+
+ JPushover.newGlance()
+ .withToken("MyToken")
+ .withUser("MyUser")
+ .withText("MyText")
+ .push();
+
+When using the Message API you can additionally add available options from the official [Pushover documentation][2]
You can also validate a user and token using the following method
- boolean valid = JPushover.create()
+ boolean valid = JPushover.newMessage()
.withToken("MyToken")
.withUser("MyUser")
.validate();
If you want more information and/or the response from the Pushover API, use the JPushoverResponse object.
- JPushoverResponse jPushoverResponse = JPushover.create()
+ JPushoverResponse jPushoverResponse = JPushover.newMessage()
.withToken("MyToken")
.withUser("MyUser")
.withMessage("MyMessage")
.push();
-The JPushoverResponse will return the raw HTTP status code, along with the raw JSON response and a convenient boolean if the request was successful or not.
+The JPushoverResponse will return the raw HTTP status code, along with the raw JSON response and a convenient boolean if the request was successful or not.
[1]: https://pushover.net
-[2]: https://pushover.net/api
\ No newline at end of file
+[2]: https://pushover.net/api
+[3]: https://pushover.net/api
+[4]: https://pushover.net/api/glances
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 71d9207..4c01d89 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,4 +1,6 @@
-
+
4.0.0
de.svenkubiak
jpushover
@@ -20,6 +22,8 @@
11
UTF-8
+ 1.3.1
+ 5.3.1
scm:git:git@github.com:svenkubiak/JPushover.git
@@ -59,7 +63,7 @@
org.apache.maven.plugins
maven-enforcer-plugin
- 3.0.0-M2
+ 3.0.0-M2
@@ -127,22 +131,22 @@
sonar-maven-plugin
3.5.0.1254
-
- org.owasp
- dependency-check-maven
- 3.3.2
-
- 12
- 4
-
-
-
-
- check
-
-
-
-
+
+ org.owasp
+ dependency-check-maven
+ 4.0.0
+
+ 12
+ 4
+
+
+
+
+ check
+
+
+
+
org.apache.maven.plugins
maven-release-plugin
@@ -170,7 +174,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 2.22.0
+ 3.0.0-M1
org.apache.maven.plugins
@@ -194,6 +198,120 @@
+
+
+ com.github.tomakehurst
+ wiremock
+ 2.19.0
+ test
+
+
+ org.slf4j
+ slf4j-api
+
+
+ org.apache.commons
+ commons-lang3
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ junit
+ junit
+
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.7
+ test
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.8.11
+ test
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+ 2.8.11
+ test
+
+
+ com.fasterxml.jackson.core
+ jackson-core
+ 2.8.11
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit.version}
+ test
+
+
+ org.junit.platform
+ junit-platform-launcher
+ ${junit.platform.version}
+ test
+
+
+ org.junit.platform
+ junit-platform-runner
+ ${junit.platform.version}
+ test
+
+
+ org.apache.logging.log4j
+ log4j-slf4j18-impl
+ 2.11.1
+ test
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
+ org.slf4j
+ slf4j-api
+ 1.8.0-beta2
+ test
+
+
ossrh
diff --git a/src/main/java/de/svenkubiak/jpushover/JPushover.java b/src/main/java/de/svenkubiak/jpushover/JPushover.java
index a15db6b..57af088 100644
--- a/src/main/java/de/svenkubiak/jpushover/JPushover.java
+++ b/src/main/java/de/svenkubiak/jpushover/JPushover.java
@@ -1,352 +1,32 @@
package de.svenkubiak.jpushover;
-import java.io.IOException;
-import java.net.InetSocketAddress;
-import java.net.ProxySelector;
-import java.net.URI;
-import java.net.http.HttpClient;
-import java.net.http.HttpRequest;
-import java.net.http.HttpResponse;
-import java.time.Duration;
-import java.util.Map;
-import java.util.NavigableMap;
-import java.util.Objects;
-import java.util.TreeMap;
-
-import de.svenkubiak.jpushover.enums.Constants;
-import de.svenkubiak.jpushover.enums.Priority;
-import de.svenkubiak.jpushover.enums.Sound;
+import de.svenkubiak.jpushover.apis.Glance;
+import de.svenkubiak.jpushover.apis.Message;
/**
*
- * Zero-dependency convenient class for sending messages to Pushover
+ * Zero-dependency convenient class for working with the Pushover API
+ * See https://pushover.net/api for API details
*
* @author svenkubiak
*
*/
public class JPushover {
- private static final int HTTP_OK = 200;
- private Priority pushoverPriority;
- private Sound pushoverSound;
- private String pushoverToken;
- private String pushoverUser;
- private String pushoverMessage;
- private String pushoverDevice;
- private String pushoverTitle;
- private String pushoverUrl;
- private String pushoverUrlTitle;
- private String pushoverTimestamp;
- private String pushoverRetry;
- private String pushoverExpire;
- private String pushoverCallback;
- private String proxyHost;
- private int proxyPort;
- private boolean pushoverHtml;
+ /**
+ * Creates a new Glance instance for the Glances API
+ *
+ * @return Glance instance
+ */
+ public static Glance newGlance() {
+ return new Glance();
+ }
- public JPushover() {
- this.withSound(Sound.PUSHOVER);
- this.withPriority(Priority.NORMAL);
- }
-
/**
- * Creates a new JPushover instance
- * @return JPushover instance
+ * Creates a new Message instance for the Messages API
+ *
+ * @return Message instance
*/
- public static JPushover create() {
- return new JPushover();
- }
-
- /**
- * Your application's API token
- * (required)
- *
- * @param token The pushover API token
- * @return JPushover instance
- */
- public final JPushover withToken(final String token) {
- this.pushoverToken = token;
- return this;
- }
-
- /**
- * The user/group key (not e-mail address) of your user (or you),
- * viewable when logged into the @see pushover dashboard
- * (required)
- *
- * @param user The username
- * @return JPushover instance
- */
- public final JPushover withUser(final String user) {
- this.pushoverUser = user;
- return this;
- }
-
- /**
- * Specifies how often (in seconds) the Pushover servers will send the same notification to the user.
- * Only required if priority is set to emergency.
- *
- * @param retry Number of seconds
- * @return JPushover instance
- */
- public final JPushover withRetry(final String retry) {
- this.pushoverRetry = retry;
- return this;
- }
-
- /**
- * Specifies how many seconds your notification will continue to be retried for (every retry seconds).
- * Only required if priority is set to emergency.
- *
- * @param expire Number of seconds
- * @return JPushover instance
- */
- public final JPushover withExpire(final String expire) {
- this.pushoverExpire = expire;
- return this;
- }
-
- /**
- * Your message
- * (required)
- *
- * @param message The message to sent
- * @return JPushover instance
- */
- public final JPushover withMessage(final String message) {
- this.pushoverMessage = message;
- return this;
- }
-
- /**
- * Your user's device name to send the message directly to that device,
- * rather than all of the user's devices
- * (optional)
- *
- * @param device The device name
- * @return JPushover instance
- */
- public final JPushover withDevice(final String device) {
- this.pushoverDevice = device;
- return this;
- }
-
- /**
- * Your message's title, otherwise your app's name is used
- * (optional)
- *
- * @param title The title
- * @return JPushover instance
- */
- public final JPushover withTitle(final String title) {
- this.pushoverTitle = title;
- return this;
- }
-
- /**
- * A supplementary URL to show with your message
- * (optional)
- *
- * @param url The url
- * @return JPushover instance
- */
- public final JPushover withUrl(final String url) {
- this.pushoverUrl = url;
- return this;
- }
-
- /**
- * Enables HTML in the pushover message
- * (optional)
- *
- * @return JPushover instance
- */
- public final JPushover enableHtml() {
- this.pushoverHtml = true;
- return this;
- }
-
- /**
- * A title for your supplementary URL, otherwise just the URL is shown
- *
- * @param urlTitle The url title
- * @return JPushover instance
- */
- public final JPushover withUrlTitle(final String urlTitle) {
- this.pushoverUrlTitle = urlTitle;
- return this;
- }
-
- /**
- * A Unix timestamp of your message's date and time to display to the user,
- * rather than the time your message is received by our API
- *
- * @param timestamp The Unix timestamp
- * @return JPushover instance
- */
- public final JPushover withTimestamp(final String timestamp) {
- this.pushoverTimestamp = timestamp;
- return this;
- }
-
- /**
- * Priority of the message based on the @see documentation
- * (optional)
- *
- * @param priority The priority enum
- * @return JPushover instance
- */
- public final JPushover withPriority(final Priority priority) {
- this.pushoverPriority = priority;
- return this;
- }
-
- /**
- * The name of one of the sounds supported by device clients to override
- * the user's default sound choice
- * (optional)
- *
- * @param sound THe sound enum
- * @return JPushover instance
- */
- public final JPushover withSound(final Sound sound) {
- this.pushoverSound = sound;
- return this;
- }
-
- /**
- * Callback parameter may be supplied with a publicly-accessible URL that the
- * pushover servers will send a request to when the user has acknowledged your
- * notification.
- * Only required if priority is set to emergency.
- *
- * @param callback The callback URL
- * @return JPushover instance
- */
- public final JPushover withCallback(final String callback) {
- this.pushoverCallback = callback;
- return this;
- }
-
- /**
- * Uses the given proxy for HTTP requests
- *
- * @param proxyHost The host that should be used for the Proxy
- * @param proxyPort The port that should be used for the Proxy
- * @return JPushover instance
- */
- public final JPushover withProxy(final String proxyHost, final int proxyPort) {
- this.proxyHost = proxyHost;
- this.proxyPort = proxyPort;
- return this;
- }
-
- /**
- * Sends a validation request to pushover ensuring that the token and user
- * is correct, that there is at least one active device on the account.
- *
- * Requires token parameter
- * Requires user parameter
- * Optional device parameter to check specific device
- *
- * @return true if token and user are valid and at least on device is on the account, false otherwise
- *
- * @throws IOException if validation fails
- * @throws InterruptedException if validation fails
- */
- public boolean validate() throws IOException, InterruptedException {
- Objects.requireNonNull(this.pushoverToken, "Token is required for validation");
- Objects.requireNonNull(this.pushoverUser, "User is required for validation");
-
- NavigableMap body = new TreeMap<>();
- body.put(Constants.TOKEN.toString(), this.pushoverToken);
- body.put(Constants.USER.toString(), this.pushoverUser);
-
- var httpResponse = getResponse(toJson(body), Constants.VALIDATION_URL.toString());
-
- var valid = false;
- if (httpResponse.statusCode() == HTTP_OK) {
- var response = httpResponse.body();
- if (response != null && response.contains("\"status\":1")) {
- valid = true;
- }
- }
-
- return valid;
- }
-
- /**
- * Sends a message to pushover
- *
- * @return JPushoverResponse instance
- *
- * @throws IOException if sending the message fails
- * @throws InterruptedException if sending the message fails
- */
- public final JPushoverResponse push() throws IOException, InterruptedException {
- Objects.requireNonNull(this.pushoverToken, "Token is required for a message");
- Objects.requireNonNull(this.pushoverUser, "User is required for a message");
- Objects.requireNonNull(this.pushoverMessage, "Message is required for a message");
-
- if (Priority.EMERGENCY.equals(this.pushoverPriority)) {
- Objects.requireNonNull(this.pushoverRetry, "Retry is required on priority emergency");
- Objects.requireNonNull(this.pushoverExpire, "Expire is required on priority emergency");
- }
-
- NavigableMap body = new TreeMap<>();
- body.put(Constants.TOKEN.toString(), this.pushoverToken);
- body.put(Constants.USER.toString(), this.pushoverUser);
- body.put(Constants.MESSAGE.toString(), this.pushoverMessage);
- body.put(Constants.DEVICE.toString(), this.pushoverDevice);
- body.put(Constants.TITLE.toString(), this.pushoverTitle);
- body.put(Constants.URL.toString(), this.pushoverUrl);
- body.put(Constants.RETRY.toString(), this.pushoverRetry);
- body.put(Constants.EXPIRE.toString(), this.pushoverExpire);
- body.put(Constants.CALLBACK.toString(), this.pushoverCallback);
- body.put(Constants.URLTITLE.toString(), this.pushoverUrlTitle);
- body.put(Constants.PRIORITY.toString(), this.pushoverPriority.toString());
- body.put(Constants.TIMESTAMP.toString(), this.pushoverTimestamp);
- body.put(Constants.SOUND.toString(), this.pushoverSound.toString());
- body.put(Constants.HTML.toString(), this.pushoverHtml ? "1" : "0");
-
- var httpResponse = getResponse(toJson(body), Constants.MESSAGES_URL.toString());
-
- var jPushoverResponse = new JPushoverResponse().isSuccessful(false);
- jPushoverResponse
- .httpStatus(httpResponse.statusCode())
- .response(httpResponse.body())
- .isSuccessful((httpResponse.statusCode() == HTTP_OK) ? true : false);
-
- return jPushoverResponse;
- }
-
- private HttpResponse getResponse(String body, String url) throws IOException, InterruptedException {
- var httpRequest = HttpRequest.newBuilder()
- .uri(URI.create(url))
- .timeout(Duration.ofSeconds(5))
- .header("Content-Type", "application/json")
- .POST(HttpRequest.BodyPublishers.ofString(body))
- .build();
-
- var httpClientBuilder = HttpClient.newBuilder();
-
- if (this.proxyHost != null && this.proxyPort > 0) {
- httpClientBuilder.proxy(ProxySelector.of(new InetSocketAddress(this.proxyHost, this.proxyPort)));
- }
-
- return httpClientBuilder.build().send(httpRequest, HttpResponse.BodyHandlers.ofString());
- }
-
- private String toJson(NavigableMap body) {
- StringBuilder buffer = new StringBuilder();
- buffer.append("{");
- for (Map.Entry entry : body.entrySet()) {
- buffer.append("\"").append(entry.getKey()).append("\"");
- buffer.append(":");
- buffer.append("\"").append(entry.getValue()).append("\"");
- buffer.append(",");
- }
- buffer.append("}");
-
- return buffer.toString().replace(",}", "}");
+ public static Message newMessage() {
+ return new Message();
}
}
\ No newline at end of file
diff --git a/src/main/java/de/svenkubiak/jpushover/apis/Glance.java b/src/main/java/de/svenkubiak/jpushover/apis/Glance.java
new file mode 100644
index 0000000..33c5d07
--- /dev/null
+++ b/src/main/java/de/svenkubiak/jpushover/apis/Glance.java
@@ -0,0 +1,142 @@
+package de.svenkubiak.jpushover.apis;
+
+import java.io.IOException;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.TreeMap;
+
+import de.svenkubiak.jpushover.enums.Param;
+import de.svenkubiak.jpushover.http.PushoverRequest;
+import de.svenkubiak.jpushover.http.PushoverResponse;
+import de.svenkubiak.jpushover.utils.Urls;
+import de.svenkubiak.jpushover.utils.Validate;
+
+/**
+ *
+ * @author svenkubiak
+ *
+ */
+public class Glance {
+ private static final String GLANCE_URL = Urls.getGlanceUrl();
+ private String token;
+ private String user;
+ private String device;
+ private String title;
+ private String text;
+ private String subtext;
+ private int count;
+ private int percent;
+ private String proxyHost;
+ private int proxyPort;
+
+ public Glance withToken(String token) {
+ Objects.requireNonNull(token, "token can not be null");
+
+ this.token = token;
+ return this;
+ }
+
+ public Glance withUser(String user) {
+ Objects.requireNonNull(user, "user can not be null");
+
+ this.user = user;
+ return this;
+ }
+
+ public Glance withDevice(String device) {
+ Objects.requireNonNull(device, "device can not be null");
+
+ this.device = device;
+ return this;
+ }
+
+ /**
+ * A description of the data being shown, such as "Widgets Sold"
+ *
+ * @param title the title to use
+ * @return Glance instance
+ */
+ public Glance withTitle(String title) {
+ Objects.requireNonNull(title, "title can not be null");
+ Validate.checkArgument(title.length() <= 100, "Title must not exceed a length of 100 characters");
+
+ this.title = title;
+ return this;
+ }
+
+ /**
+ * The main line of data, used on most screens
+ *
+ * @param text the text to use
+ * @return Glance instance
+ */
+ public Glance withText(String text) {
+ Objects.requireNonNull(text, "text can not be null");
+ Validate.checkArgument(text.length() <= 100, "Text must not exceed a length of 100 characters");
+
+ this.text = text;
+ return this;
+ }
+
+ /**
+ * A second line of data
+ *
+ * @param subtext the subtext to use
+ * @return Glance instance
+ */
+ public Glance withSubtext(String subtext) {
+ Objects.requireNonNull(subtext, "subtext can not be null");
+ Validate.checkArgument(subtext.length() <= 100, "subtext must not exceed a length of 100 characters");
+
+ this.subtext = subtext;
+ return this;
+ }
+
+ /**
+ * Shown on smaller screens; useful for simple counts
+ *
+ * @param count the count to use
+ * @return Glance instance
+ */
+ public Glance withCount(int count) {
+ this.count = count;
+ return this;
+ }
+
+ /**
+ * Shown on some screens as a progress bar/circle
+ *
+ * @param percent the percent to use
+ * @return GLance instance
+ */
+ public Glance withPercent(int percent) {
+ this.percent = percent;
+ return this;
+ }
+
+ /**
+ * Sends a glance to pushover
+ *
+ * @return PushoverResponse instance
+ *
+ * @throws IOException if sending the message fails
+ * @throws InterruptedException if sending the message fails
+ */
+ public PushoverResponse push() throws IOException, InterruptedException {
+ Objects.requireNonNull(this.token, "Token is required for a glance");
+ Objects.requireNonNull(this.user, "User is required for a glance");
+
+ NavigableMap body = new TreeMap<>();
+ body.put(Param.TOKEN.toString(), this.token);
+ body.put(Param.USER.toString(), this.user);
+ body.put(Param.DEVICE.toString(), this.device);
+ body.put(Param.TITLE.toString(), this.title);
+ body.put(Param.TEXT.toString(), this.text);
+ body.put(Param.SUBTEXT.toString(), this.subtext);
+ body.put(Param.COUNT.toString(), String.valueOf(this.count));
+ body.put(Param.PERCENT.toString(), String.valueOf(this.percent));
+
+
+ return new PushoverRequest().push(GLANCE_URL, body, this.proxyHost, this.proxyPort);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/svenkubiak/jpushover/apis/Message.java b/src/main/java/de/svenkubiak/jpushover/apis/Message.java
new file mode 100644
index 0000000..6014806
--- /dev/null
+++ b/src/main/java/de/svenkubiak/jpushover/apis/Message.java
@@ -0,0 +1,301 @@
+package de.svenkubiak.jpushover.apis;
+
+import java.io.IOException;
+import java.util.NavigableMap;
+import java.util.Objects;
+import java.util.TreeMap;
+
+import de.svenkubiak.jpushover.enums.Param;
+import de.svenkubiak.jpushover.enums.Priority;
+import de.svenkubiak.jpushover.enums.Sound;
+import de.svenkubiak.jpushover.http.PushoverRequest;
+import de.svenkubiak.jpushover.http.PushoverResponse;
+import de.svenkubiak.jpushover.utils.Urls;
+
+/**
+ *
+ * @author svenkubiak
+ *
+ */
+public class Message {
+ private static final String MESSAGE_URL = Urls.getMessageUrl();
+ private static final String VALIDATION_URL = Urls.getValidationUrl();
+ private Priority priority;
+ private Sound sound;
+ private String token;
+ private String user;
+ private String message;
+ private String device;
+ private String title;
+ private String url;
+ private String urlTitle;
+ private String timestamp;
+ private String retry;
+ private String expire;
+ private String callback;
+ private String proxyHost;
+ private int proxyPort;
+ private boolean html;
+
+ public Message() {
+ this.withSound(Sound.PUSHOVER);
+ this.withPriority(Priority.NORMAL);
+ }
+
+ /**
+ * Your application's API token
+ * (required)
+ *
+ * @param token The pushover API token
+ * @return Message instance
+ */
+ public final Message withToken(final String token) {
+ Objects.requireNonNull(token, "Token can not be null");
+
+ this.token = token;
+ return this;
+ }
+
+ /**
+ * The user/group key (not e-mail address) of your user (or you),
+ * viewable when logged into the @see pushover dashboard
+ * (required)
+ *
+ * @param user The username
+ * @return Message instance
+ */
+ public final Message withUser(final String user) {
+ this.user = user;
+ return this;
+ }
+
+ /**
+ * Specifies how often (in seconds) the Pushover servers will send the same notification to the user.
+ * Only required if priority is set to emergency.
+ *
+ * @param retry Number of seconds
+ * @return Message instance
+ */
+ public final Message withRetry(final String retry) {
+ this.retry = retry;
+ return this;
+ }
+
+ /**
+ * Specifies how many seconds your notification will continue to be retried for (every retry seconds).
+ * Only required if priority is set to emergency.
+ *
+ * @param expire Number of seconds
+ * @return Message instance
+ */
+ public final Message withExpire(final String expire) {
+ this.expire = expire;
+ return this;
+ }
+
+ /**
+ * Your message
+ * (required)
+ *
+ * @param message The message to sent
+ * @return Message instance
+ */
+ public final Message withMessage(final String message) {
+ this.message = message;
+ return this;
+ }
+
+ /**
+ * Your user's device name to send the message directly to that device,
+ * rather than all of the user's devices
+ * (optional)
+ *
+ * @param device The device name
+ * @return Message instance
+ */
+ public final Message withDevice(final String device) {
+ this.device = device;
+ return this;
+ }
+
+ /**
+ * Your message's title, otherwise your app's name is used
+ * (optional)
+ *
+ * @param title The title
+ * @return Message instance
+ */
+ public final Message withTitle(final String title) {
+ this.title = title;
+ return this;
+ }
+
+ /**
+ * A supplementary URL to show with your message
+ * (optional)
+ *
+ * @param url The url
+ * @return Message instance
+ */
+ public final Message withUrl(final String url) {
+ this.url = url;
+ return this;
+ }
+
+ /**
+ * Enables HTML in the pushover message
+ * (optional)
+ *
+ * @return Message instance
+ */
+ public final Message enableHtml() {
+ this.html = true;
+ return this;
+ }
+
+ /**
+ * A title for your supplementary URL, otherwise just the URL is shown
+ *
+ * @param urlTitle The url title
+ * @return Message instance
+ */
+ public final Message withUrlTitle(final String urlTitle) {
+ this.urlTitle = urlTitle;
+ return this;
+ }
+
+ /**
+ * A Unix timestamp of your message's date and time to display to the user,
+ * rather than the time your message is received by our API
+ *
+ * @param timestamp The Unix timestamp
+ * @return Message instance
+ */
+ public final Message withTimestamp(final String timestamp) {
+ this.timestamp = timestamp;
+ return this;
+ }
+
+ /**
+ * Priority of the message based on the @see documentation
+ * (optional)
+ *
+ * @param priority The priority enum
+ * @return Message instance
+ */
+ public final Message withPriority(final Priority priority) {
+ this.priority = priority;
+ return this;
+ }
+
+ /**
+ * The name of one of the sounds supported by device clients to override
+ * the user's default sound choice
+ * (optional)
+ *
+ * @param sound THe sound enum
+ * @return Message instance
+ */
+ public final Message withSound(final Sound sound) {
+ this.sound = sound;
+ return this;
+ }
+
+ /**
+ * Callback parameter may be supplied with a publicly-accessible URL that the
+ * pushover servers will send a request to when the user has acknowledged your
+ * notification.
+ * Only required if priority is set to emergency.
+ *
+ * @param callback The callback URL
+ * @return Message instance
+ */
+ public final Message withCallback(final String callback) {
+ this.callback = callback;
+ return this;
+ }
+
+ /**
+ * Uses the given proxy for HTTP requests
+ *
+ * @param proxyHost The host that should be used for the Proxy
+ * @param proxyPort The port that should be used for the Proxy
+ * @return Message instance
+ */
+ public final Message withProxy(final String proxyHost, final int proxyPort) {
+ this.proxyHost = proxyHost;
+ this.proxyPort = proxyPort;
+ return this;
+ }
+
+ /**
+ * Sends a validation request to pushover ensuring that the token and user
+ * is correct, that there is at least one active device on the account.
+ *
+ * Requires token parameter
+ * Requires user parameter
+ * Optional device parameter to check specific device
+ *
+ * @return true if token and user are valid and at least on device is on the account, false otherwise
+ *
+ * @throws IOException if validation fails
+ * @throws InterruptedException if validation fails
+ */
+ public boolean validate() throws IOException, InterruptedException {
+ Objects.requireNonNull(this.token, "Token is required for validation");
+ Objects.requireNonNull(this.user, "User is required for validation");
+
+ NavigableMap body = new TreeMap<>();
+ body.put(Param.TOKEN.toString(), this.token);
+ body.put(Param.USER.toString(), this.user);
+
+ var pushoverResponse = new PushoverRequest().push(VALIDATION_URL, body, this.proxyHost, this.proxyPort);
+
+ var valid = false;
+ if (pushoverResponse.getHttpStatus() == 200) {
+ var response = pushoverResponse.getResponse();
+ if (response != null && response.contains("\"status\":1")) {
+ valid = true;
+ }
+ }
+
+ return valid;
+ }
+
+ /**
+ * Sends a message to pushover
+ *
+ * @return PushoverResponse instance
+ *
+ * @throws IOException if sending the message fails
+ * @throws InterruptedException if sending the message fails
+ */
+ public final PushoverResponse push() throws IOException, InterruptedException {
+ Objects.requireNonNull(this.token, "Token is required for a message");
+ Objects.requireNonNull(this.user, "User is required for a message");
+ Objects.requireNonNull(this.message, "Message is required for a message");
+
+ if (Priority.EMERGENCY.equals(this.priority)) {
+ Objects.requireNonNull(this.retry, "Retry is required on priority emergency");
+ Objects.requireNonNull(this.expire, "Expire is required on priority emergency");
+ }
+
+ NavigableMap body = new TreeMap<>();
+ body.put(Param.TOKEN.toString(), this.token);
+ body.put(Param.USER.toString(), this.user);
+ body.put(Param.MESSAGE.toString(), this.message);
+ body.put(Param.DEVICE.toString(), this.device);
+ body.put(Param.TITLE.toString(), this.title);
+ body.put(Param.URL.toString(), this.url);
+ body.put(Param.RETRY.toString(), this.retry);
+ body.put(Param.EXPIRE.toString(), this.expire);
+ body.put(Param.CALLBACK.toString(), this.callback);
+ body.put(Param.URLTITLE.toString(), this.urlTitle);
+ body.put(Param.PRIORITY.toString(), this.priority.toString());
+ body.put(Param.TIMESTAMP.toString(), this.timestamp);
+ body.put(Param.SOUND.toString(), this.sound.toString());
+ body.put(Param.HTML.toString(), this.html ? "1" : "0");
+
+ return new PushoverRequest().push(MESSAGE_URL, body, this.proxyHost, this.proxyPort);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/svenkubiak/jpushover/enums/Constants.java b/src/main/java/de/svenkubiak/jpushover/enums/Param.java
similarity index 74%
rename from src/main/java/de/svenkubiak/jpushover/enums/Constants.java
rename to src/main/java/de/svenkubiak/jpushover/enums/Param.java
index 1fa18af..427e2ea 100644
--- a/src/main/java/de/svenkubiak/jpushover/enums/Constants.java
+++ b/src/main/java/de/svenkubiak/jpushover/enums/Param.java
@@ -5,14 +5,13 @@ package de.svenkubiak.jpushover.enums;
* @author svenkubiak
*
*/
-public enum Constants {
+public enum Param {
ATTACHMENT("attachment"),
CALLBACK("callback"),
DEVICE("device"),
EXPIRE("expire"),
HTML("html"),
MESSAGE("message"),
- MESSAGES_URL("https://api.pushover.net/1/messages.json"),
PRIORITY("priority"),
RETRY("retry"),
SOUND("sound"),
@@ -22,11 +21,14 @@ public enum Constants {
URL("url"),
URLTITLE("urltitle"),
USER("user"),
- VALIDATION_URL("https://api.pushover.net/1/users/validate.json");
+ TEXT("text"),
+ SUBTEXT("subtext"),
+ COUNT("count"),
+ PERCENT("percent");
private final String value;
- Constants (String value) {
+ Param (String value) {
this.value = value;
}
diff --git a/src/main/java/de/svenkubiak/jpushover/http/PushoverRequest.java b/src/main/java/de/svenkubiak/jpushover/http/PushoverRequest.java
new file mode 100644
index 0000000..ec63cf8
--- /dev/null
+++ b/src/main/java/de/svenkubiak/jpushover/http/PushoverRequest.java
@@ -0,0 +1,68 @@
+package de.svenkubiak.jpushover.http;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.time.Duration;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.Objects;
+
+/**
+ *
+ * @author svenkubiak
+ *
+ */
+public class PushoverRequest {
+
+ public PushoverResponse push(String url, NavigableMap body, String proxyHost, int proxyPort) throws IOException, InterruptedException {
+ Objects.requireNonNull(url, "API URL can not be null");
+ Objects.requireNonNull(body, "body can not be null");
+
+ var httpResponse = getResponse(toJson(body), url, proxyHost, proxyPort);
+
+ var jPushoverResponse = new PushoverResponse().isSuccessful(false);
+
+ jPushoverResponse
+ .httpStatus(httpResponse.statusCode())
+ .response(httpResponse.body())
+ .isSuccessful((httpResponse.statusCode() == 200) ? true : false);
+
+ return jPushoverResponse;
+ }
+
+ private HttpResponse getResponse(String body, String url, String proxyHost, int proxyPort) throws IOException, InterruptedException {
+ var httpRequest = HttpRequest.newBuilder()
+ .uri(URI.create(url))
+ .timeout(Duration.ofSeconds(5))
+ .header("Content-Type", "application/json")
+ .POST(HttpRequest.BodyPublishers.ofString(body))
+ .build();
+
+ var httpClientBuilder = HttpClient.newBuilder();
+
+ if (proxyHost != null && proxyPort > 0) {
+ httpClientBuilder.proxy(ProxySelector.of(new InetSocketAddress(proxyHost, proxyPort)));
+ }
+
+ return httpClientBuilder.build().send(httpRequest, HttpResponse.BodyHandlers.ofString());
+ }
+
+ private String toJson(NavigableMap body) {
+ StringBuilder buffer = new StringBuilder();
+ buffer.append("{");
+ for (Map.Entry entry : body.entrySet()) {
+ buffer.append("\"").append(entry.getKey()).append("\"");
+ buffer.append(":");
+ buffer.append("\"").append(entry.getValue()).append("\"");
+ buffer.append(",");
+ }
+ buffer.append("}");
+
+ return buffer.toString().replace(",}", "}");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/svenkubiak/jpushover/JPushoverResponse.java b/src/main/java/de/svenkubiak/jpushover/http/PushoverResponse.java
similarity index 70%
rename from src/main/java/de/svenkubiak/jpushover/JPushoverResponse.java
rename to src/main/java/de/svenkubiak/jpushover/http/PushoverResponse.java
index a4fc7dd..3bbb49f 100644
--- a/src/main/java/de/svenkubiak/jpushover/JPushoverResponse.java
+++ b/src/main/java/de/svenkubiak/jpushover/http/PushoverResponse.java
@@ -1,26 +1,26 @@
-package de.svenkubiak.jpushover;
+package de.svenkubiak.jpushover.http;
/**
*
* @author svenkubiak
*
*/
-public class JPushoverResponse {
+public class PushoverResponse {
private String pushoverResponse;
private int pushoverHttpStatus;
private boolean pushoverSuccessful;
- public JPushoverResponse response(String response) {
+ public PushoverResponse response(String response) {
this.pushoverResponse = response;
return this;
}
- public JPushoverResponse httpStatus(int httpStatus) {
+ public PushoverResponse httpStatus(int httpStatus) {
this.pushoverHttpStatus = httpStatus;
return this;
}
- public JPushoverResponse isSuccessful(boolean successful) {
+ public PushoverResponse isSuccessful(boolean successful) {
this.pushoverSuccessful = successful;
return this;
}
@@ -40,7 +40,7 @@ public class JPushoverResponse {
}
/**
- * @return true if the api returned a HTTP status code 200, false otherwise
+ * @return true if the API returned a HTTP status code 200, false otherwise
*/
public boolean isSuccessful() {
return pushoverSuccessful;
diff --git a/src/main/java/de/svenkubiak/jpushover/utils/Urls.java b/src/main/java/de/svenkubiak/jpushover/utils/Urls.java
new file mode 100644
index 0000000..85445e4
--- /dev/null
+++ b/src/main/java/de/svenkubiak/jpushover/utils/Urls.java
@@ -0,0 +1,35 @@
+package de.svenkubiak.jpushover.utils;
+
+/**
+ *
+ * @author svenkubiak
+ *
+ */
+public class Urls {
+ public static String getGlanceUrl() {
+ String mode = System.getProperty("mode");
+ if (("test").equals(mode)) {
+ return "http://127.0.0.1:8080/1/glances.json";
+ }
+
+ return "https://api.pushover.net/1/glances.json";
+ }
+
+ public static String getMessageUrl() {
+ String mode = System.getProperty("mode");
+ if (("test").equals(mode)) {
+ return "http://127.0.0.1:8080/1/messages.json";
+ }
+
+ return "https://api.pushover.net/1/messages.json";
+ }
+
+ public static String getValidationUrl() {
+ String mode = System.getProperty("mode");
+ if (("test").equals(mode)) {
+ return "http://127.0.0.1:8080/1/users/validate.json";
+ }
+
+ return "https://api.pushover.net/1/users/validate.json";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/svenkubiak/jpushover/utils/Validate.java b/src/main/java/de/svenkubiak/jpushover/utils/Validate.java
new file mode 100644
index 0000000..01c1e75
--- /dev/null
+++ b/src/main/java/de/svenkubiak/jpushover/utils/Validate.java
@@ -0,0 +1,22 @@
+package de.svenkubiak.jpushover.utils;
+
+/**
+ *
+ * @author svenkubiak
+ *
+ */
+public final class Validate {
+ /**
+ * Ensures the truth of an expression involving one or more parameters to the calling method.
+ *
+ * @param expression a boolean expression
+ * @param errorMessage the exception message to use if the check fails; will be converted to a string using {@link String#valueOf(Object)}
+ *
+ * @throws IllegalArgumentException if {@code expression} is false
+ */
+ public static void checkArgument(boolean expression, Object errorMessage) {
+ if (!expression) {
+ throw new IllegalArgumentException(String.valueOf(errorMessage));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
deleted file mode 100644
index 5c20eb0..0000000
--- a/src/main/java/module-info.java
+++ /dev/null
@@ -1,5 +0,0 @@
-module jpushover {
- requires java.net.http;
- exports de.svenkubiak.jpushover;
- exports de.svenkubiak.jpushover.enums;
-}
\ No newline at end of file
diff --git a/src/test/java/jpushover/JPushoverTest.java b/src/test/java/jpushover/JPushoverTest.java
new file mode 100644
index 0000000..8011634
--- /dev/null
+++ b/src/test/java/jpushover/JPushoverTest.java
@@ -0,0 +1,23 @@
+package jpushover;
+
+import static org.junit.Assert.assertTrue;
+
+import de.svenkubiak.jpushover.JPushover;
+import de.svenkubiak.jpushover.apis.Glance;
+import de.svenkubiak.jpushover.apis.Message;
+
+/**
+ *
+ * @author svenkubiak
+ *
+ */
+public class JPushoverTest {
+
+ public void testNewGlance() {
+ assertTrue(JPushover.newGlance() instanceof Glance);
+ }
+
+ public void testNewMessage() {
+ assertTrue(JPushover.newMessage() instanceof Message);
+ }
+}
diff --git a/src/test/java/jpushover/MockServer.java b/src/test/java/jpushover/MockServer.java
new file mode 100644
index 0000000..c1cd26a
--- /dev/null
+++ b/src/test/java/jpushover/MockServer.java
@@ -0,0 +1,29 @@
+package jpushover;
+
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+
+/**
+ *
+ * @author svenkubiak
+ *
+ */
+public final class MockServer {
+ private static WireMockServer wireMockServer;
+ private static boolean started;
+
+ public MockServer() {
+ }
+
+ public static void start() {
+ if (!started) {
+ System.setProperty("mode", "test");
+ wireMockServer = new WireMockServer(options()
+ .bindAddress("127.0.0.1")
+ );
+ wireMockServer.start();
+ started = true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/jpushover/apis/GlanceTest.java b/src/test/java/jpushover/apis/GlanceTest.java
new file mode 100644
index 0000000..5440a4f
--- /dev/null
+++ b/src/test/java/jpushover/apis/GlanceTest.java
@@ -0,0 +1,77 @@
+package jpushover.apis;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import de.svenkubiak.jpushover.JPushover;
+import de.svenkubiak.jpushover.http.PushoverResponse;
+import jpushover.MockServer;
+
+/**
+ *
+ * @author svenkubiak
+ *
+ */
+public class GlanceTest {
+ private static final String APPLICATION_JSON = "application/json; charset=utf-8";
+ private static final String CONTENT_TYPE = "Content-Type";
+
+ private GlanceTest () {
+ MockServer.start();
+ }
+
+ @Test()
+ public void testTokenRequired() throws IOException, InterruptedException {
+ stubFor(post(urlEqualTo("/1/glances.json"))
+ .willReturn(aResponse()
+ .withStatus(400)
+ .withHeader(CONTENT_TYPE, APPLICATION_JSON)));
+
+ Assertions.assertThrows(NullPointerException.class, () -> {
+ JPushover.newGlance().push();
+ });
+ }
+
+ @Test()
+ public void testUserRequired() throws IOException, InterruptedException {
+ stubFor(post(urlEqualTo("/1/glances.json"))
+ .willReturn(aResponse()
+ .withStatus(400)
+ .withHeader(CONTENT_TYPE, APPLICATION_JSON)));
+
+ Assertions.assertThrows(NullPointerException.class, () -> {
+ JPushover.newGlance().withToken("token").push();
+ });
+ }
+
+ @Test()
+ public void testPushWithoutContent() throws IOException, InterruptedException {
+ stubFor(post(urlEqualTo("/1/glances.json"))
+ .willReturn(aResponse()
+ .withStatus(400)
+ .withHeader(CONTENT_TYPE, APPLICATION_JSON)));
+
+ PushoverResponse response = JPushover.newGlance().withToken("token").withUser("user").push();
+ assertFalse(response.isSuccessful());
+ }
+
+ @Test
+ public void testPush() throws IOException, InterruptedException {
+ stubFor(post(urlEqualTo("/1/glances.json"))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withHeader(CONTENT_TYPE, APPLICATION_JSON)));
+
+ PushoverResponse response = JPushover.newGlance().withToken("foo").withUser("bla").withText("foobar").push();
+ assertTrue(response.isSuccessful());
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/jpushover/apis/MessageTest.java b/src/test/java/jpushover/apis/MessageTest.java
new file mode 100644
index 0000000..b23f870
--- /dev/null
+++ b/src/test/java/jpushover/apis/MessageTest.java
@@ -0,0 +1,89 @@
+package jpushover.apis;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.post;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import de.svenkubiak.jpushover.JPushover;
+import de.svenkubiak.jpushover.http.PushoverResponse;
+import jpushover.MockServer;
+
+/**
+ *
+ * @author svenkubiak
+ *
+ */
+public class MessageTest {
+ private static final String APPLICATION_JSON = "application/json; charset=utf-8";
+ private static final String CONTENT_TYPE = "Content-Type";
+
+ private MessageTest () {
+ MockServer.start();
+ }
+
+ @Test()
+ public void testTokenRequired() throws IOException, InterruptedException {
+ stubFor(post(urlEqualTo("/1/messages.json"))
+ .willReturn(aResponse()
+ .withStatus(400)
+ .withHeader(CONTENT_TYPE, APPLICATION_JSON)));
+
+ Assertions.assertThrows(NullPointerException.class, () -> {
+ JPushover.newMessage().push();
+ });
+ }
+
+ @Test()
+ public void testUserRequired() throws IOException, InterruptedException {
+ stubFor(post(urlEqualTo("/1/messages.json"))
+ .willReturn(aResponse()
+ .withStatus(400)
+ .withHeader(CONTENT_TYPE, APPLICATION_JSON)));
+
+ Assertions.assertThrows(NullPointerException.class, () -> {
+ JPushover.newMessage().withToken("token").push();
+ });
+ }
+
+ @Test()
+ public void testMessageRequired() throws IOException, InterruptedException {
+ stubFor(post(urlEqualTo("/1/messages.json"))
+ .willReturn(aResponse()
+ .withStatus(400)
+ .withHeader(CONTENT_TYPE, APPLICATION_JSON)));
+
+ Assertions.assertThrows(NullPointerException.class, () -> {
+ JPushover.newMessage().withToken("token").withUser("user").push();
+ });
+ }
+
+ @Test()
+ public void testPushWithoutContent() throws IOException, InterruptedException {
+ stubFor(post(urlEqualTo("/1/messages.json"))
+ .willReturn(aResponse()
+ .withStatus(400)
+ .withHeader(CONTENT_TYPE, APPLICATION_JSON)));
+
+ PushoverResponse response = JPushover.newMessage().withToken("token").withUser("user").withMessage("").push();
+ assertFalse(response.isSuccessful());
+ }
+
+ @Test
+ public void testPush() throws IOException, InterruptedException {
+ stubFor(post(urlEqualTo("/1/messages.json"))
+ .willReturn(aResponse()
+ .withStatus(200)
+ .withHeader(CONTENT_TYPE, APPLICATION_JSON)));
+
+ PushoverResponse response = JPushover.newMessage().withToken("foo").withUser("bla").withMessage("foobar").push();
+ assertTrue(response.isSuccessful());
+ }
+}
diff --git a/src/test/java/jpushover/utils/ValidateTest.java b/src/test/java/jpushover/utils/ValidateTest.java
new file mode 100644
index 0000000..519908b
--- /dev/null
+++ b/src/test/java/jpushover/utils/ValidateTest.java
@@ -0,0 +1,26 @@
+package jpushover.utils;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import de.svenkubiak.jpushover.utils.Validate;
+
+/**
+ *
+ * @author svenkubiak
+ *
+ */
+public class ValidateTest {
+
+ @Test
+ public void testTrue() {
+ Validate.checkArgument(true, "foo");
+ }
+
+ @Test
+ public void testFalse() {
+ Assertions.assertThrows(IllegalArgumentException.class, () -> {
+ Validate.checkArgument(false, "bar");
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml
new file mode 100644
index 0000000..77b7820
--- /dev/null
+++ b/src/test/resources/log4j2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file