2 * Copyright (c) 2010-2020 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.telegram.internal.action;
15 import java.io.ByteArrayInputStream;
16 import java.io.IOException;
17 import java.io.InputStream;
18 import java.net.MalformedURLException;
21 import java.nio.charset.StandardCharsets;
22 import java.nio.file.Paths;
23 import java.util.Base64;
24 import java.util.concurrent.ExecutionException;
25 import java.util.concurrent.TimeUnit;
26 import java.util.concurrent.TimeoutException;
28 import org.apache.commons.io.IOUtils;
29 import org.eclipse.jdt.annotation.NonNullByDefault;
30 import org.eclipse.jdt.annotation.Nullable;
31 import org.eclipse.jetty.client.HttpClient;
32 import org.eclipse.jetty.client.api.Authentication;
33 import org.eclipse.jetty.client.api.AuthenticationStore;
34 import org.eclipse.jetty.client.api.ContentResponse;
35 import org.eclipse.jetty.client.api.Request;
36 import org.eclipse.jetty.http.HttpHeader;
37 import org.eclipse.jetty.http.HttpMethod;
38 import org.eclipse.jetty.util.B64Code;
39 import org.openhab.binding.telegram.internal.TelegramHandler;
40 import org.openhab.core.automation.annotation.ActionInput;
41 import org.openhab.core.automation.annotation.RuleAction;
42 import org.openhab.core.thing.binding.ThingActions;
43 import org.openhab.core.thing.binding.ThingActionsScope;
44 import org.openhab.core.thing.binding.ThingHandler;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
48 import com.pengrad.telegrambot.model.request.InlineKeyboardButton;
49 import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup;
50 import com.pengrad.telegrambot.request.AnswerCallbackQuery;
51 import com.pengrad.telegrambot.request.EditMessageReplyMarkup;
52 import com.pengrad.telegrambot.request.SendMessage;
53 import com.pengrad.telegrambot.request.SendPhoto;
54 import com.pengrad.telegrambot.response.BaseResponse;
55 import com.pengrad.telegrambot.response.SendResponse;
58 * Provides the actions for the Telegram API.
60 * @author Alexander Krasnogolowy - Initial contribution
62 @ThingActionsScope(name = "telegram")
64 public class TelegramActions implements ThingActions {
65 private final Logger logger = LoggerFactory.getLogger(TelegramActions.class);
66 private @Nullable TelegramHandler handler;
68 private boolean evaluateResponse(@Nullable BaseResponse response) {
69 if (response != null && !response.isOk()) {
70 logger.warn("Failed to send telegram message: {}", response.description());
76 private static class BasicResult implements Authentication.Result {
78 private final HttpHeader header;
79 private final URI uri;
80 private final String value;
82 public BasicResult(HttpHeader header, URI uri, String value) {
94 public void apply(@Nullable Request request) {
95 if (request != null) {
96 request.header(this.header, this.value);
101 public String toString() {
102 return String.format("Basic authentication result for %s", this.uri);
106 @RuleAction(label = "send an answer", description = "Send a Telegram answer using the Telegram API.")
107 public boolean sendTelegramAnswer(@ActionInput(name = "chatId") @Nullable Long chatId,
108 @ActionInput(name = "replyId") @Nullable String replyId,
109 @ActionInput(name = "message") @Nullable String message) {
110 if (replyId == null) {
111 logger.warn("ReplyId not defined; action skipped.");
114 if (chatId == null) {
115 logger.warn("chatId not defined; action skipped.");
118 TelegramHandler localHandler = handler;
119 if (localHandler != null) {
120 String callbackId = localHandler.getCallbackId(chatId, replyId);
121 if (callbackId != null) {
122 AnswerCallbackQuery answerCallbackQuery = new AnswerCallbackQuery(
123 localHandler.getCallbackId(chatId, replyId));
124 logger.debug("AnswerCallbackQuery for chatId {} and replyId {} is the callbackId {}", chatId, replyId,
125 localHandler.getCallbackId(chatId, replyId));
126 // we could directly set the text here, but this
127 // doesn't result in a real message only in a
128 // little popup or in an alert, so the only purpose
129 // is to stop the progress bar on client side
130 if (!evaluateResponse(localHandler.execute(answerCallbackQuery))) {
134 Integer messageId = localHandler.removeMessageId(chatId, replyId);
135 logger.debug("remove messageId {} for chatId {} and replyId {}", messageId, chatId, replyId);
137 EditMessageReplyMarkup editReplyMarkup = new EditMessageReplyMarkup(chatId, messageId.intValue())
138 .replyMarkup(new InlineKeyboardMarkup(new InlineKeyboardButton[0]));// remove reply markup from
140 if (!evaluateResponse(localHandler.execute(editReplyMarkup))) {
143 return message != null ? sendTelegram(chatId, message) : true;
148 @RuleAction(label = "send an answer", description = "Send a Telegram answer using the Telegram API.")
149 public boolean sendTelegramAnswer(@ActionInput(name = "replyId") @Nullable String replyId,
150 @ActionInput(name = "message") @Nullable String message) {
151 TelegramHandler localHandler = handler;
152 if (localHandler != null) {
153 for (Long chatId : localHandler.getReceiverChatIds()) {
154 if (!sendTelegramAnswer(chatId, replyId, message)) {
162 @RuleAction(label = "send a message", description = "Send a Telegram message using the Telegram API.")
163 public boolean sendTelegram(@ActionInput(name = "chatId") @Nullable Long chatId,
164 @ActionInput(name = "message") @Nullable String message) {
165 return sendTelegramGeneral(chatId, message, (String) null);
168 @RuleAction(label = "send a message", description = "Send a Telegram message using the Telegram API.")
169 public boolean sendTelegram(@ActionInput(name = "message") @Nullable String message) {
170 TelegramHandler localHandler = handler;
171 if (localHandler != null) {
172 for (Long chatId : localHandler.getReceiverChatIds()) {
173 if (!sendTelegram(chatId, message)) {
181 @RuleAction(label = "send a message", description = "Send a Telegram using the Telegram API.")
182 public boolean sendTelegramQuery(@ActionInput(name = "chatId") @Nullable Long chatId,
183 @ActionInput(name = "message") @Nullable String message,
184 @ActionInput(name = "replyId") @Nullable String replyId,
185 @ActionInput(name = "buttons") @Nullable String... buttons) {
186 return sendTelegramGeneral(chatId, message, replyId, buttons);
189 @RuleAction(label = "send a message", description = "Send a Telegram using the Telegram API.")
190 public boolean sendTelegramQuery(@ActionInput(name = "message") @Nullable String message,
191 @ActionInput(name = "replyId") @Nullable String replyId,
192 @ActionInput(name = "buttons") @Nullable String... buttons) {
193 TelegramHandler localHandler = handler;
194 if (localHandler != null) {
195 for (Long chatId : localHandler.getReceiverChatIds()) {
196 if (!sendTelegramQuery(chatId, message, replyId, buttons)) {
204 private boolean sendTelegramGeneral(@ActionInput(name = "chatId") @Nullable Long chatId, @Nullable String message,
205 @Nullable String replyId, @Nullable String... buttons) {
206 if (message == null) {
207 logger.warn("Message not defined; action skipped.");
210 if (chatId == null) {
211 logger.warn("chatId not defined; action skipped.");
214 TelegramHandler localHandler = handler;
215 if (localHandler != null) {
216 SendMessage sendMessage = new SendMessage(chatId, message);
217 if (localHandler.getParseMode() != null) {
218 sendMessage.parseMode(localHandler.getParseMode());
220 if (replyId != null) {
221 if (!replyId.contains(" ")) {
222 if (buttons.length > 0) {
223 InlineKeyboardButton[][] keyboard2D = new InlineKeyboardButton[1][];
224 InlineKeyboardButton[] keyboard = new InlineKeyboardButton[buttons.length];
225 keyboard2D[0] = keyboard;
226 for (int i = 0; i < buttons.length; i++) {
227 keyboard[i] = new InlineKeyboardButton(buttons[i]).callbackData(replyId + " " + buttons[i]);
229 InlineKeyboardMarkup keyBoardMarkup = new InlineKeyboardMarkup(keyboard2D);
230 sendMessage.replyMarkup(keyBoardMarkup);
233 "The replyId {} for message {} is given, but no buttons are defined. ReplyMarkup will be ignored.",
237 logger.warn("replyId {} must not contain spaces. ReplyMarkup will be ignored.", replyId);
240 SendResponse retMessage = localHandler.execute(sendMessage);
241 if (!evaluateResponse(retMessage)) {
244 if (replyId != null && retMessage != null) {
245 logger.debug("Adding chatId {}, replyId {} and messageId {}", chatId, replyId,
246 retMessage.message().messageId());
247 localHandler.addMessageId(chatId, replyId, retMessage.message().messageId());
254 @RuleAction(label = "send a message", description = "Send a Telegram using the Telegram API.")
255 public boolean sendTelegram(@ActionInput(name = "chatId") @Nullable Long chatId,
256 @ActionInput(name = "message") @Nullable String message,
257 @ActionInput(name = "args") @Nullable Object... args) {
258 return sendTelegram(chatId, String.format(message, args));
261 @RuleAction(label = "send a message", description = "Send a Telegram using the Telegram API.")
262 public boolean sendTelegram(@ActionInput(name = "message") @Nullable String message,
263 @ActionInput(name = "args") @Nullable Object... args) {
264 TelegramHandler localHandler = handler;
265 if (localHandler != null) {
266 for (Long chatId : localHandler.getReceiverChatIds()) {
267 if (!sendTelegram(chatId, message, args)) {
275 @RuleAction(label = "send a photo", description = "Send a picture using the Telegram API.")
276 public boolean sendTelegramPhoto(@ActionInput(name = "chatId") @Nullable Long chatId,
277 @ActionInput(name = "photoURL") @Nullable String photoURL,
278 @ActionInput(name = "caption") @Nullable String caption) {
279 return sendTelegramPhoto(chatId, photoURL, caption, null, null);
282 @RuleAction(label = "send a photo", description = "Send a picture using the Telegram API.")
283 public boolean sendTelegramPhoto(@ActionInput(name = "chatId") @Nullable Long chatId,
284 @ActionInput(name = "photoURL") @Nullable String photoURL,
285 @ActionInput(name = "caption") @Nullable String caption,
286 @ActionInput(name = "username") @Nullable String username,
287 @ActionInput(name = "password") @Nullable String password) {
288 if (photoURL == null) {
289 logger.warn("Photo URL not defined; unable to retrieve photo for sending.");
292 if (chatId == null) {
293 logger.warn("chatId not defined; action skipped.");
297 TelegramHandler localHandler = handler;
298 if (localHandler != null) {
299 final SendPhoto sendPhoto;
301 if (photoURL.toLowerCase().startsWith("http")) {
302 // load image from url
303 logger.debug("Photo URL provided.");
304 HttpClient client = localHandler.getClient();
305 if (client == null) {
308 Request request = client.newRequest(photoURL).method(HttpMethod.GET).timeout(30, TimeUnit.SECONDS);
309 if (username != null && password != null) {
310 AuthenticationStore auth = client.getAuthenticationStore();
311 URI uri = URI.create(photoURL);
312 auth.addAuthenticationResult(new BasicResult(HttpHeader.AUTHORIZATION, uri,
313 "Basic " + B64Code.encode(username + ":" + password, StandardCharsets.ISO_8859_1)));
316 ContentResponse contentResponse = request.send();
317 if (contentResponse.getStatus() == 200) {
318 byte[] fileContent = contentResponse.getContent();
319 sendPhoto = new SendPhoto(chatId, fileContent);
321 logger.warn("Download from {} failed with status: {}", photoURL, contentResponse.getStatus());
324 } catch (InterruptedException | TimeoutException | ExecutionException e) {
325 logger.warn("Download from {} failed with exception: {}", photoURL, e.getMessage());
328 } else if (photoURL.toLowerCase().startsWith("file")) {
329 // Load image from local file system
330 logger.debug("Read file from local file system: {}", photoURL);
332 URL url = new URL(photoURL);
333 sendPhoto = new SendPhoto(chatId, Paths.get(url.getPath()).toFile());
334 } catch (MalformedURLException e) {
335 logger.warn("Malformed URL: {}", photoURL);
339 // Load image from provided base64 image
340 logger.debug("Photo base64 provided; converting to binary.");
341 final String photoB64Data;
342 if (photoURL.startsWith("data:")) { // support data URI scheme
343 String[] photoURLParts = photoURL.split(",");
344 if (photoURLParts.length > 1) {
345 photoB64Data = photoURLParts[1];
347 logger.warn("The provided base64 string is not a valid data URI scheme");
351 photoB64Data = photoURL;
353 InputStream is = Base64.getDecoder()
354 .wrap(new ByteArrayInputStream(photoB64Data.getBytes(StandardCharsets.UTF_8)));
356 byte[] photoBytes = IOUtils.toByteArray(is);
357 sendPhoto = new SendPhoto(chatId, photoBytes);
358 } catch (IOException e) {
359 logger.warn("Malformed base64 string: {}", e.getMessage());
363 sendPhoto.caption(caption);
364 if (localHandler.getParseMode() != null) {
365 sendPhoto.parseMode(localHandler.getParseMode());
367 return evaluateResponse(localHandler.execute(sendPhoto));
372 @RuleAction(label = "send a photo", description = "Send a Picture using the Telegram API.")
373 public boolean sendTelegramPhoto(@ActionInput(name = "photoURL") @Nullable String photoURL,
374 @ActionInput(name = "caption") @Nullable String caption,
375 @ActionInput(name = "username") @Nullable String username,
376 @ActionInput(name = "password") @Nullable String password) {
377 TelegramHandler localHandler = handler;
378 if (localHandler != null) {
379 for (Long chatId : localHandler.getReceiverChatIds()) {
380 if (!sendTelegramPhoto(chatId, photoURL, caption, username, password)) {
388 @RuleAction(label = "send a photo", description = "Send a Picture using the Telegram API.")
389 public boolean sendTelegramPhoto(@ActionInput(name = "photoURL") @Nullable String photoURL,
390 @ActionInput(name = "caption") @Nullable String caption) {
391 return sendTelegramPhoto(photoURL, caption, null, null);
394 // legacy delegate methods
395 /* APIs without chatId parameter */
396 public static boolean sendTelegram(@Nullable ThingActions actions, @Nullable String format,
397 @Nullable Object... args) {
398 if (actions instanceof TelegramActions) {
399 return ((TelegramActions) actions).sendTelegram(format, args);
401 throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
405 public static boolean sendTelegramQuery(@Nullable ThingActions actions, @Nullable String message,
406 @Nullable String replyId, @Nullable String... buttons) {
407 if (actions instanceof TelegramActions) {
408 return ((TelegramActions) actions).sendTelegramQuery(message, replyId, buttons);
410 throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
414 public static boolean sendTelegramPhoto(@Nullable ThingActions actions, @Nullable String photoURL,
415 @Nullable String caption) {
416 if (actions instanceof TelegramActions) {
417 return ((TelegramActions) actions).sendTelegramPhoto(photoURL, caption, null, null);
419 throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
423 public static boolean sendTelegramPhoto(@Nullable ThingActions actions, @Nullable String photoURL,
424 @Nullable String caption, @Nullable String username, @Nullable String password) {
425 if (actions instanceof TelegramActions) {
426 return ((TelegramActions) actions).sendTelegramPhoto(photoURL, caption, username, password);
428 throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
432 public static boolean sendTelegramAnswer(@Nullable ThingActions actions, @Nullable String replyId,
433 @Nullable String message) {
434 if (actions instanceof TelegramActions) {
435 return ((TelegramActions) actions).sendTelegramAnswer(replyId, message);
437 throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
441 /* APIs with chatId parameter */
443 public static boolean sendTelegram(@Nullable ThingActions actions, @Nullable Long chatId, @Nullable String format,
444 @Nullable Object... args) {
445 if (actions instanceof TelegramActions) {
446 return ((TelegramActions) actions).sendTelegram(chatId, format, args);
448 throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
452 public static boolean sendTelegramQuery(@Nullable ThingActions actions, @Nullable Long chatId,
453 @Nullable String message, @Nullable String replyId, @Nullable String... buttons) {
454 if (actions instanceof TelegramActions) {
455 return ((TelegramActions) actions).sendTelegramQuery(chatId, message, replyId, buttons);
457 throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
461 public static boolean sendTelegramPhoto(@Nullable ThingActions actions, @Nullable Long chatId,
462 @Nullable String photoURL, @Nullable String caption) {
463 if (actions instanceof TelegramActions) {
464 return ((TelegramActions) actions).sendTelegramPhoto(chatId, photoURL, caption, null, null);
466 throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
470 public static boolean sendTelegramPhoto(@Nullable ThingActions actions, @Nullable Long chatId,
471 @Nullable String photoURL, @Nullable String caption, @Nullable String username, @Nullable String password) {
472 if (actions instanceof TelegramActions) {
473 return ((TelegramActions) actions).sendTelegramPhoto(chatId, photoURL, caption, username, password);
475 throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
479 public static boolean sendTelegramAnswer(@Nullable ThingActions actions, @Nullable Long chatId,
480 @Nullable String replyId, @Nullable String message) {
481 if (actions instanceof TelegramActions) {
482 return ((TelegramActions) actions).sendTelegramAnswer(chatId, replyId, message);
484 throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
488 public static boolean sendTelegramAnswer(@Nullable ThingActions actions, @Nullable String chatId,
489 @Nullable String replyId, @Nullable String message) {
490 if (actions instanceof TelegramActions) {
491 return ((TelegramActions) actions).sendTelegramAnswer(Long.valueOf(chatId), replyId, message);
493 throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
498 public void setThingHandler(@Nullable ThingHandler handler) {
499 this.handler = (TelegramHandler) handler;
503 public @Nullable ThingHandler getThingHandler() {