]> git.basschouten.com Git - openhab-addons.git/blob
b9d2c3c786ab39b357d9125f26a6371f4619cec2
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.pushsafer.internal.connection;
14
15 import java.io.File;
16 import java.io.IOException;
17 import java.nio.charset.StandardCharsets;
18 import java.nio.file.Files;
19 import java.util.Arrays;
20 import java.util.Base64;
21 import java.util.List;
22 import java.util.Properties;
23 import java.util.stream.Collectors;
24
25 import org.eclipse.jdt.annotation.NonNullByDefault;
26 import org.eclipse.jdt.annotation.Nullable;
27 import org.eclipse.jetty.client.api.ContentProvider;
28 import org.eclipse.jetty.client.util.MultiPartContentProvider;
29 import org.eclipse.jetty.client.util.StringContentProvider;
30 import org.openhab.core.io.net.http.HttpUtil;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33
34 /**
35  * The {@link PushsaferMessageBuilder} builds the body for Pushsafer Messages API requests.
36  *
37  * @author Kevin Siml - Initial contribution, forked from Christoph Weitkamp
38  */
39 @NonNullByDefault
40 public class PushsaferMessageBuilder {
41
42     private final Logger logger = LoggerFactory.getLogger(PushsaferMessageBuilder.class);
43
44     public static final String MESSAGE_KEY_TOKEN = "k";
45     public static final String MESSAGE_KEY_USER = "u";
46     private static final String MESSAGE_KEY_MESSAGE = "m";
47     private static final String MESSAGE_KEY_TITLE = "t";
48     private static final String MESSAGE_KEY_DEVICE = "d";
49     private static final String MESSAGE_KEY_ICON = "i";
50     private static final String MESSAGE_KEY_COLOR = "c";
51     private static final String MESSAGE_KEY_VIBRATION = "v";
52     private static final String MESSAGE_KEY_PRIORITY = "pr";
53     private static final String MESSAGE_KEY_RETRY = "re";
54     private static final String MESSAGE_KEY_EXPIRE = "ex";
55     private static final String MESSAGE_KEY_URL = "u";
56     private static final String MESSAGE_KEY_URL_TITLE = "ut";
57     private static final String MESSAGE_KEY_SOUND = "s";
58     private static final String MESSAGE_KEY_TIME2LIVE = "l";
59     private static final String MESSAGE_KEY_ANSWER = "a";
60     private static final String MESSAGE_KEY_CONFIRM = "cr";
61     private static final String MESSAGE_KEY_ATTACHMENT = "p";
62     public static final String MESSAGE_KEY_HTML = "html";
63     public static final String MESSAGE_KEY_MONOSPACE = "monospace";
64
65     private static final int MAX_MESSAGE_LENGTH = 4096;
66     private static final int MAX_TITLE_LENGTH = 250;
67     private static final int MAX_DEVICE_LENGTH = 25;
68     private static final List<Integer> VALID_PRIORITY_LIST = Arrays.asList(-2, -1, 0, 1, 2);
69     private static final int DEFAULT_PRIORITY = 0;
70     public static final int EMERGENCY_PRIORITY = 2;
71     private static final int MIN_RETRY_SECONDS = 0;
72     private static final int MAX_EXPIRE_SECONDS = 10800;
73     private static final int MAX_URL_LENGTH = 512;
74     private static final int MAX_URL_TITLE_LENGTH = 100;
75     public static final String DEFAULT_CONTENT_TYPE = "jpeg";
76     public static final String DEFAULT_AUTH = "";
77
78     private final MultiPartContentProvider body = new MultiPartContentProvider();
79
80     private @Nullable String message;
81     private @Nullable String title;
82     private @Nullable String device;
83     private int priority = DEFAULT_PRIORITY;
84     private int retry = 300;
85     private int expire = 3600;
86     private @Nullable String url;
87     private @Nullable String urlTitle;
88     private @Nullable String sound;
89     private @Nullable String icon;
90     private int confirm;
91     private int time2live;
92     private boolean answer;
93     private @Nullable String color;
94     private @Nullable String vibration;
95     private @Nullable String attachment;
96     private String contentType = DEFAULT_CONTENT_TYPE;
97     private String authentication = DEFAULT_AUTH;
98     private boolean html = false;
99     private boolean monospace = false;
100
101     private PushsaferMessageBuilder(String apikey, String device) throws PushsaferConfigurationException {
102         body.addFieldPart(MESSAGE_KEY_TOKEN, new StringContentProvider(apikey), null);
103         body.addFieldPart(MESSAGE_KEY_DEVICE, new StringContentProvider(device), null);
104     }
105
106     public static PushsaferMessageBuilder getInstance(@Nullable String apikey, @Nullable String device)
107             throws PushsaferConfigurationException {
108         if (apikey == null || apikey.isEmpty()) {
109             throw new PushsaferConfigurationException("@text/offline.conf-error-missing-apikey");
110         }
111
112         if (device == null || device.isEmpty()) {
113             throw new PushsaferConfigurationException("@text/offline.conf-error-missing-device");
114         }
115
116         return new PushsaferMessageBuilder(apikey, device);
117     }
118
119     public PushsaferMessageBuilder withMessage(String message) {
120         this.message = message;
121         return this;
122     }
123
124     public PushsaferMessageBuilder withTitle(String title) {
125         this.title = title;
126         return this;
127     }
128
129     public PushsaferMessageBuilder withDevice(String device) {
130         this.device = device;
131         return this;
132     }
133
134     public PushsaferMessageBuilder withPriority(int priority) {
135         this.priority = priority;
136         return this;
137     }
138
139     public PushsaferMessageBuilder withRetry(int retry) {
140         this.retry = retry;
141         return this;
142     }
143
144     public PushsaferMessageBuilder withExpire(int expire) {
145         this.expire = expire;
146         return this;
147     }
148
149     public PushsaferMessageBuilder withUrl(String url) {
150         this.url = url;
151         return this;
152     }
153
154     public PushsaferMessageBuilder withUrlTitle(String urlTitle) {
155         this.urlTitle = urlTitle;
156         return this;
157     }
158
159     public PushsaferMessageBuilder withSound(String sound) {
160         this.sound = sound;
161         return this;
162     }
163
164     public PushsaferMessageBuilder withIcon(String icon) {
165         this.icon = icon;
166         return this;
167     }
168
169     public PushsaferMessageBuilder withColor(String color) {
170         this.color = color;
171         return this;
172     }
173
174     public PushsaferMessageBuilder withVibration(String vibration) {
175         this.vibration = vibration;
176         return this;
177     }
178
179     public PushsaferMessageBuilder withAnswer(boolean answer) {
180         this.answer = answer;
181         return this;
182     }
183
184     public PushsaferMessageBuilder withTime2live(int time2live) {
185         this.time2live = time2live;
186         return this;
187     }
188
189     public PushsaferMessageBuilder withConfirm(int confirm) {
190         this.confirm = confirm;
191         return this;
192     }
193
194     public PushsaferMessageBuilder withAttachment(String attachment) {
195         this.attachment = attachment;
196         return this;
197     }
198
199     public PushsaferMessageBuilder withContentType(String contentType) {
200         this.contentType = contentType;
201         return this;
202     }
203
204     public PushsaferMessageBuilder withAuthentication(String authentication) {
205         this.authentication = authentication;
206         return this;
207     }
208
209     public PushsaferMessageBuilder withHtmlFormatting() {
210         this.html = true;
211         return this;
212     }
213
214     public PushsaferMessageBuilder withMonospaceFormatting() {
215         this.monospace = true;
216         return this;
217     }
218
219     public ContentProvider build() throws PushsaferCommunicationException {
220         if (message != null) {
221             if (message.length() > MAX_MESSAGE_LENGTH) {
222                 throw new IllegalArgumentException(String.format(
223                         "Skip sending the message as 'message' is longer than %d characters.", MAX_MESSAGE_LENGTH));
224             }
225             body.addFieldPart(MESSAGE_KEY_MESSAGE, new StringContentProvider(message), null);
226         }
227
228         if (title != null) {
229             if (title.length() > MAX_TITLE_LENGTH) {
230                 throw new IllegalArgumentException(String
231                         .format("Skip sending the message as 'title' is longer than %d characters.", MAX_TITLE_LENGTH));
232             }
233             body.addFieldPart(MESSAGE_KEY_TITLE, new StringContentProvider(title), null);
234         }
235
236         if (device != null) {
237             if (device.length() > MAX_DEVICE_LENGTH) {
238                 logger.warn("Skip 'device' as it is longer than {} characters. Got: {}.", MAX_DEVICE_LENGTH, device);
239             } else {
240                 body.addFieldPart(MESSAGE_KEY_DEVICE, new StringContentProvider(device), null);
241             }
242         }
243
244         if (priority != DEFAULT_PRIORITY) {
245             if (VALID_PRIORITY_LIST.contains(priority)) {
246                 body.addFieldPart(MESSAGE_KEY_PRIORITY, new StringContentProvider(String.valueOf(priority)), null);
247
248                 if (priority == EMERGENCY_PRIORITY) {
249                     if (retry < MIN_RETRY_SECONDS) {
250                         logger.warn("Retry value of {} is too small. Using default value of {}.", retry,
251                                 MIN_RETRY_SECONDS);
252                         body.addFieldPart(MESSAGE_KEY_RETRY,
253                                 new StringContentProvider(String.valueOf(MIN_RETRY_SECONDS)), null);
254                     } else {
255                         body.addFieldPart(MESSAGE_KEY_RETRY, new StringContentProvider(String.valueOf(retry)), null);
256                     }
257
258                     if (0 < expire && expire <= MAX_EXPIRE_SECONDS) {
259                         body.addFieldPart(MESSAGE_KEY_EXPIRE, new StringContentProvider(String.valueOf(expire)), null);
260                     } else {
261                         logger.warn("Expire value of {} is invalid. Using default value of {}.", expire,
262                                 MAX_EXPIRE_SECONDS);
263                         body.addFieldPart(MESSAGE_KEY_EXPIRE,
264                                 new StringContentProvider(String.valueOf(MAX_EXPIRE_SECONDS)), null);
265                     }
266                 }
267             } else {
268                 logger.warn("Invalid 'priority', skipping. Expected: {}. Got: {}.",
269                         VALID_PRIORITY_LIST.stream().map(i -> i.toString()).collect(Collectors.joining(",")), priority);
270             }
271         }
272
273         if (url != null) {
274             if (url.length() > MAX_URL_LENGTH) {
275                 throw new IllegalArgumentException(String
276                         .format("Skip sending the message as 'url' is longer than %d characters.", MAX_URL_LENGTH));
277             }
278             body.addFieldPart(MESSAGE_KEY_URL, new StringContentProvider(url), null);
279
280             if (urlTitle != null) {
281                 if (urlTitle.length() > MAX_URL_TITLE_LENGTH) {
282                     throw new IllegalArgumentException(
283                             String.format("Skip sending the message as 'urlTitle' is longer than %d characters.",
284                                     MAX_URL_TITLE_LENGTH));
285                 }
286                 body.addFieldPart(MESSAGE_KEY_URL_TITLE, new StringContentProvider(urlTitle), null);
287             }
288         }
289
290         if (sound != null) {
291             body.addFieldPart(MESSAGE_KEY_SOUND, new StringContentProvider(sound), null);
292         }
293
294         if (icon != null) {
295             body.addFieldPart(MESSAGE_KEY_ICON, new StringContentProvider(icon), null);
296         }
297
298         if (color != null) {
299             body.addFieldPart(MESSAGE_KEY_COLOR, new StringContentProvider(color), null);
300         }
301
302         if (vibration != null) {
303             body.addFieldPart(MESSAGE_KEY_VIBRATION, new StringContentProvider(vibration), null);
304         }
305
306         body.addFieldPart(MESSAGE_KEY_CONFIRM, new StringContentProvider(String.valueOf(confirm)), null);
307
308         body.addFieldPart(MESSAGE_KEY_ANSWER, new StringContentProvider(String.valueOf(answer)), null);
309
310         body.addFieldPart(MESSAGE_KEY_TIME2LIVE, new StringContentProvider(String.valueOf(time2live)), null);
311
312         if (attachment != null) {
313             String localAttachment = attachment;
314             final String encodedString;
315             try {
316                 if (localAttachment.startsWith("http")) {
317                     Properties headers = new Properties();
318                     headers.put("User-Agent", "Mozilla/5.0");
319                     if (!authentication.isBlank()) {
320                         headers.put("Authorization", "Basic "
321                                 + Base64.getEncoder().encodeToString(authentication.getBytes(StandardCharsets.UTF_8)));
322                     }
323                     String content = HttpUtil.executeUrl("GET", attachment, headers, null, null, 10);
324                     if (content == null) {
325                         throw new IllegalArgumentException(
326                                 String.format("Skip sending the message as content '%s' does not exist.", attachment));
327                     }
328                     encodedString = "data:image/" + contentType + ";base64," + content;
329                 } else if (localAttachment.startsWith("data:")) {
330                     encodedString = localAttachment;
331                 } else {
332                     File file = new File(attachment);
333                     if (!file.exists()) {
334                         throw new IllegalArgumentException(
335                                 String.format("Skip sending the message as file '%s' does not exist.", attachment));
336                     }
337                     byte[] fileContent = Files.readAllBytes(file.toPath());
338                     encodedString = "data:image/" + contentType + ";base64,"
339                             + Base64.getEncoder().encodeToString(fileContent);
340                 }
341                 body.addFieldPart(MESSAGE_KEY_ATTACHMENT, new StringContentProvider(encodedString), null);
342             } catch (IOException e) {
343                 logger.debug("IOException occurred - skip sending message: {}", e.getLocalizedMessage(), e);
344                 throw new PushsaferCommunicationException(
345                         String.format("Skip sending the message: %s", e.getLocalizedMessage()), e);
346             }
347         }
348
349         if (html) {
350             body.addFieldPart(MESSAGE_KEY_HTML, new StringContentProvider("1"), null);
351         } else if (monospace) {
352             body.addFieldPart(MESSAGE_KEY_MONOSPACE, new StringContentProvider("1"), null);
353         }
354
355         body.close();
356         return body;
357     }
358 }