]> git.basschouten.com Git - openhab-addons.git/blob
3b43d4a4e34d5a28e0aa4d093c362569cef17dde
[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.telegram.internal.action;
14
15 import static org.openhab.binding.telegram.internal.TelegramBindingConstants.PHOTO_EXTENSIONS;
16
17 import java.io.ByteArrayInputStream;
18 import java.io.IOException;
19 import java.io.InputStream;
20 import java.net.MalformedURLException;
21 import java.net.URI;
22 import java.net.URL;
23 import java.nio.charset.StandardCharsets;
24 import java.nio.file.Path;
25 import java.util.Base64;
26 import java.util.concurrent.ExecutionException;
27 import java.util.concurrent.TimeUnit;
28
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.client.util.FutureResponseListener;
37 import org.eclipse.jetty.http.HttpHeader;
38 import org.eclipse.jetty.http.HttpMethod;
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;
47
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.SendAnimation;
53 import com.pengrad.telegrambot.request.SendMessage;
54 import com.pengrad.telegrambot.request.SendPhoto;
55 import com.pengrad.telegrambot.request.SendVideo;
56 import com.pengrad.telegrambot.response.BaseResponse;
57 import com.pengrad.telegrambot.response.SendResponse;
58
59 /**
60  * Provides the actions for the Telegram API.
61  *
62  * @author Alexander Krasnogolowy - Initial contribution
63  */
64 @ThingActionsScope(name = "telegram")
65 @NonNullByDefault
66 public class TelegramActions implements ThingActions {
67     private final Logger logger = LoggerFactory.getLogger(TelegramActions.class);
68     private @Nullable TelegramHandler handler;
69
70     private boolean evaluateResponse(@Nullable BaseResponse response) {
71         if (response != null && !response.isOk()) {
72             logger.warn("Failed to send telegram message: {}", response.description());
73             return false;
74         }
75         return true;
76     }
77
78     private static class BasicResult implements Authentication.Result {
79
80         private final HttpHeader header;
81         private final URI uri;
82         private final String value;
83
84         public BasicResult(HttpHeader header, URI uri, String value) {
85             this.header = header;
86             this.uri = uri;
87             this.value = value;
88         }
89
90         @Override
91         public URI getURI() {
92             return this.uri;
93         }
94
95         @Override
96         public void apply(@Nullable Request request) {
97             if (request != null) {
98                 request.header(this.header, this.value);
99             }
100         }
101
102         @Override
103         public String toString() {
104             return String.format("Basic authentication result for %s", this.uri);
105         }
106     }
107
108     @RuleAction(label = "send an answer", description = "Send a Telegram answer using the Telegram API.")
109     public boolean sendTelegramAnswer(@ActionInput(name = "chatId") @Nullable Long chatId,
110             @ActionInput(name = "callbackId") @Nullable String callbackId,
111             @ActionInput(name = "messageId") @Nullable Long messageId,
112             @ActionInput(name = "message") @Nullable String message) {
113         if (chatId == null) {
114             logger.warn("chatId not defined; action skipped.");
115             return false;
116         }
117         if (messageId == null) {
118             logger.warn("messageId not defined; action skipped.");
119             return false;
120         }
121         TelegramHandler localHandler = handler;
122         if (localHandler != null) {
123             if (callbackId != null) {
124                 AnswerCallbackQuery answerCallbackQuery = new AnswerCallbackQuery(callbackId);
125                 // we could directly set the text here, but this
126                 // doesn't result in a real message only in a
127                 // little popup or in an alert, so the only purpose
128                 // is to stop the progress bar on client side
129                 logger.debug("Answering query with callbackId '{}'", callbackId);
130                 if (!evaluateResponse(localHandler.execute(answerCallbackQuery))) {
131                     return false;
132                 }
133             }
134             EditMessageReplyMarkup editReplyMarkup = new EditMessageReplyMarkup(chatId, messageId.intValue())
135                     .replyMarkup(new InlineKeyboardMarkup(new InlineKeyboardButton[0]));// remove reply markup from
136                                                                                         // old message
137             if (!evaluateResponse(localHandler.execute(editReplyMarkup))) {
138                 return false;
139             }
140             return message != null ? sendTelegram(chatId, message) : true;
141         }
142         return false;
143     }
144
145     @RuleAction(label = "send an answer", description = "Send a Telegram answer using the Telegram API.")
146     public boolean sendTelegramAnswer(@ActionInput(name = "chatId") @Nullable Long chatId,
147             @ActionInput(name = "replyId") @Nullable String replyId,
148             @ActionInput(name = "message") @Nullable String message) {
149         if (replyId == null) {
150             logger.warn("ReplyId not defined; action skipped.");
151             return false;
152         }
153         if (chatId == null) {
154             logger.warn("chatId not defined; action skipped.");
155             return false;
156         }
157         TelegramHandler localHandler = handler;
158         if (localHandler != null) {
159             String callbackId = localHandler.getCallbackId(chatId, replyId);
160             if (callbackId != null) {
161                 logger.debug("AnswerCallbackQuery for chatId {} and replyId {} is the callbackId {}", chatId, replyId,
162                         callbackId);
163             }
164             Integer messageId = localHandler.removeMessageId(chatId, replyId);
165             logger.debug("remove messageId {} for chatId {} and replyId {}", messageId, chatId, replyId);
166
167             return sendTelegramAnswer(chatId, callbackId, messageId != null ? Long.valueOf(messageId) : null, message);
168         }
169         return false;
170     }
171
172     @RuleAction(label = "send an answer", description = "Send a Telegram answer using the Telegram API.")
173     public boolean sendTelegramAnswer(@ActionInput(name = "replyId") @Nullable String replyId,
174             @ActionInput(name = "message") @Nullable String message) {
175         TelegramHandler localHandler = handler;
176         if (localHandler != null) {
177             for (Long chatId : localHandler.getReceiverChatIds()) {
178                 if (!sendTelegramAnswer(chatId, replyId, message)) {
179                     return false;
180                 }
181             }
182         }
183         return true;
184     }
185
186     @RuleAction(label = "send a message", description = "Send a Telegram message using the Telegram API.")
187     public boolean sendTelegram(@ActionInput(name = "chatId") @Nullable Long chatId,
188             @ActionInput(name = "message") @Nullable String message) {
189         return sendTelegramGeneral(chatId, message, (String) null);
190     }
191
192     @RuleAction(label = "send a message", description = "Send a Telegram message using the Telegram API.")
193     public boolean sendTelegram(@ActionInput(name = "message") @Nullable String message) {
194         TelegramHandler localHandler = handler;
195         if (localHandler != null) {
196             for (Long chatId : localHandler.getReceiverChatIds()) {
197                 if (!sendTelegram(chatId, message)) {
198                     return false;
199                 }
200             }
201         }
202         return true;
203     }
204
205     @RuleAction(label = "send a message", description = "Send a Telegram using the Telegram API.")
206     public boolean sendTelegramQuery(@ActionInput(name = "chatId") @Nullable Long chatId,
207             @ActionInput(name = "message") @Nullable String message,
208             @ActionInput(name = "replyId") @Nullable String replyId,
209             @ActionInput(name = "buttons") @Nullable String... buttons) {
210         return sendTelegramGeneral(chatId, message, replyId, buttons);
211     }
212
213     @RuleAction(label = "send a message", description = "Send a Telegram using the Telegram API.")
214     public boolean sendTelegramQuery(@ActionInput(name = "message") @Nullable String message,
215             @ActionInput(name = "replyId") @Nullable String replyId,
216             @ActionInput(name = "buttons") @Nullable String... buttons) {
217         TelegramHandler localHandler = handler;
218         if (localHandler != null) {
219             for (Long chatId : localHandler.getReceiverChatIds()) {
220                 if (!sendTelegramQuery(chatId, message, replyId, buttons)) {
221                     return false;
222                 }
223             }
224         }
225         return true;
226     }
227
228     private boolean sendTelegramGeneral(@ActionInput(name = "chatId") @Nullable Long chatId, @Nullable String message,
229             @Nullable String replyId, @Nullable String... buttons) {
230         if (message == null) {
231             logger.warn("Message not defined; action skipped.");
232             return false;
233         }
234         if (chatId == null) {
235             logger.warn("chatId not defined; action skipped.");
236             return false;
237         }
238         TelegramHandler localHandler = handler;
239         if (localHandler != null) {
240             SendMessage sendMessage = new SendMessage(chatId, message);
241             if (localHandler.getParseMode() != null) {
242                 sendMessage.parseMode(localHandler.getParseMode());
243             }
244             if (replyId != null) {
245                 if (!replyId.contains(" ")) {
246                     if (buttons.length > 0) {
247                         InlineKeyboardButton[][] keyboard2D = new InlineKeyboardButton[1][];
248                         InlineKeyboardButton[] keyboard = new InlineKeyboardButton[buttons.length];
249                         keyboard2D[0] = keyboard;
250                         for (int i = 0; i < buttons.length; i++) {
251                             keyboard[i] = new InlineKeyboardButton(buttons[i]).callbackData(replyId + " " + buttons[i]);
252                         }
253                         InlineKeyboardMarkup keyBoardMarkup = new InlineKeyboardMarkup(keyboard2D);
254                         sendMessage.replyMarkup(keyBoardMarkup);
255                     } else {
256                         logger.warn(
257                                 "The replyId {} for message {} is given, but no buttons are defined. ReplyMarkup will be ignored.",
258                                 replyId, message);
259                     }
260                 } else {
261                     logger.warn("replyId {} must not contain spaces. ReplyMarkup will be ignored.", replyId);
262                 }
263             }
264             SendResponse retMessage = null;
265             try {
266                 retMessage = localHandler.execute(sendMessage);
267             } catch (Exception e) {
268                 logger.warn("Exception occured whilst sending message:{}", e.getMessage());
269             }
270             if (!evaluateResponse(retMessage)) {
271                 return false;
272             }
273             if (replyId != null && retMessage != null) {
274                 logger.debug("Adding chatId {}, replyId {} and messageId {}", chatId, replyId,
275                         retMessage.message().messageId());
276                 localHandler.addMessageId(chatId, replyId, retMessage.message().messageId());
277             }
278             return true;
279         }
280         return false;
281     }
282
283     @RuleAction(label = "send a message", description = "Send a Telegram using the Telegram API.")
284     public boolean sendTelegram(@ActionInput(name = "chatId") @Nullable Long chatId,
285             @ActionInput(name = "message") @Nullable String message,
286             @ActionInput(name = "args") @Nullable Object... args) {
287         if (message == null) {
288             return false;
289         }
290         return sendTelegram(chatId, String.format(message, args));
291     }
292
293     @RuleAction(label = "send a message", description = "Send a Telegram using the Telegram API.")
294     public boolean sendTelegram(@ActionInput(name = "message") @Nullable String message,
295             @ActionInput(name = "args") @Nullable Object... args) {
296         TelegramHandler localHandler = handler;
297         if (localHandler != null) {
298             for (Long chatId : localHandler.getReceiverChatIds()) {
299                 if (!sendTelegram(chatId, message, args)) {
300                     return false;
301                 }
302             }
303         }
304         return true;
305     }
306
307     @RuleAction(label = "send a photo", description = "Send a picture using the Telegram API.")
308     public boolean sendTelegramPhoto(@ActionInput(name = "chatId") @Nullable Long chatId,
309             @ActionInput(name = "photoURL") @Nullable String photoURL,
310             @ActionInput(name = "caption") @Nullable String caption) {
311         return sendTelegramPhoto(chatId, photoURL, caption, null, null);
312     }
313
314     @RuleAction(label = "send a photo", description = "Send a picture using the Telegram API.")
315     public boolean sendTelegramPhoto(@ActionInput(name = "chatId") @Nullable Long chatId,
316             @ActionInput(name = "photoURL") @Nullable String photoURL,
317             @ActionInput(name = "caption") @Nullable String caption,
318             @ActionInput(name = "username") @Nullable String username,
319             @ActionInput(name = "password") @Nullable String password) {
320         if (photoURL == null) {
321             logger.warn("Photo URL not defined; unable to retrieve photo for sending.");
322             return false;
323         }
324         if (chatId == null) {
325             logger.warn("chatId not defined; action skipped.");
326             return false;
327         }
328         String lowercasePhotoUrl = photoURL.toLowerCase();
329         TelegramHandler localHandler = handler;
330         if (localHandler != null) {
331             final SendPhoto sendPhoto;
332             if (lowercasePhotoUrl.startsWith("http")) {
333                 logger.debug("Http based URL for photo provided.");
334                 HttpClient client = localHandler.getClient();
335                 if (client == null) {
336                     return false;
337                 }
338                 Request request = client.newRequest(photoURL).method(HttpMethod.GET).timeout(30, TimeUnit.SECONDS);
339                 if (username != null && password != null) {
340                     AuthenticationStore auth = client.getAuthenticationStore();
341                     URI uri = URI.create(photoURL);
342                     auth.addAuthenticationResult(
343                             new BasicResult(HttpHeader.AUTHORIZATION, uri, "Basic " + Base64.getEncoder()
344                                     .encodeToString((username + ":" + password).getBytes(StandardCharsets.UTF_8))));
345                 }
346                 try {
347                     // API has 10mb limit to jpg file size, without this it can only accept 2mb
348                     FutureResponseListener listener = new FutureResponseListener(request, 10 * 1024 * 1024);
349                     request.send(listener);
350                     ContentResponse contentResponse = listener.get();
351                     if (contentResponse.getStatus() == 200) {
352                         byte[] fileContent = contentResponse.getContent();
353                         sendPhoto = new SendPhoto(chatId, fileContent);
354                     } else {
355                         if (contentResponse.getStatus() == 401
356                                 && contentResponse.getHeaders().get(HttpHeader.WWW_AUTHENTICATE).contains("igest")) {
357                             logger.warn("Download from {} failed due to no BASIC http auth support.", photoURL);
358                         } else {
359                             logger.warn("Download from {} failed with status: {}", photoURL,
360                                     contentResponse.getStatus());
361                         }
362                         sendTelegram(chatId, caption + ":Download failed with status " + contentResponse.getStatus());
363                         return false;
364                     }
365                 } catch (InterruptedException | ExecutionException e) {
366                     logger.warn("Download from {} failed with exception: {}", photoURL, e.getMessage());
367                     return false;
368                 }
369             } else if (lowercasePhotoUrl.startsWith("file:")
370                     || PHOTO_EXTENSIONS.stream().anyMatch(lowercasePhotoUrl::endsWith)) {
371                 logger.debug("Read file from local file system: {}", photoURL);
372                 String temp = photoURL;
373                 if (!lowercasePhotoUrl.startsWith("file:")) {
374                     temp = "file://" + photoURL;
375                 }
376                 try {
377                     sendPhoto = new SendPhoto(chatId, Path.of(new URL(temp).getPath()).toFile());
378                 } catch (MalformedURLException e) {
379                     logger.warn("Malformed URL: {}", photoURL);
380                     return false;
381                 }
382             } else {
383                 logger.debug("Base64 image provided; converting to binary.");
384                 final String photoB64Data;
385                 if (photoURL.startsWith("data:")) { // support data URI scheme
386                     String[] photoURLParts = photoURL.split(",");
387                     if (photoURLParts.length > 1) {
388                         photoB64Data = photoURLParts[1];
389                     } else {
390                         logger.warn("The provided base64 string is not a valid data URI scheme");
391                         return false;
392                     }
393                 } else {
394                     photoB64Data = photoURL;
395                 }
396                 InputStream is = Base64.getDecoder()
397                         .wrap(new ByteArrayInputStream(photoB64Data.getBytes(StandardCharsets.UTF_8)));
398                 try {
399                     byte[] photoBytes = is.readAllBytes();
400                     sendPhoto = new SendPhoto(chatId, photoBytes);
401                 } catch (IOException e) {
402                     logger.warn("Malformed base64 string: {}", e.getMessage());
403                     return false;
404                 }
405             }
406             if (caption != null) {
407                 sendPhoto.caption(caption);
408             }
409             if (localHandler.getParseMode() != null) {
410                 sendPhoto.parseMode(localHandler.getParseMode());
411             }
412             return evaluateResponse(localHandler.execute(sendPhoto));
413         }
414         return false;
415     }
416
417     @RuleAction(label = "send a photo", description = "Send a Picture using the Telegram API.")
418     public boolean sendTelegramPhoto(@ActionInput(name = "photoURL") @Nullable String photoURL,
419             @ActionInput(name = "caption") @Nullable String caption,
420             @ActionInput(name = "username") @Nullable String username,
421             @ActionInput(name = "password") @Nullable String password) {
422         TelegramHandler localHandler = handler;
423         if (localHandler != null) {
424             for (Long chatId : localHandler.getReceiverChatIds()) {
425                 if (!sendTelegramPhoto(chatId, photoURL, caption, username, password)) {
426                     return false;
427                 }
428             }
429         }
430         return true;
431     }
432
433     @RuleAction(label = "send a photo", description = "Send a Picture using the Telegram API.")
434     public boolean sendTelegramPhoto(@ActionInput(name = "photoURL") @Nullable String photoURL,
435             @ActionInput(name = "caption") @Nullable String caption) {
436         return sendTelegramPhoto(photoURL, caption, null, null);
437     }
438
439     @RuleAction(label = "send animation", description = "Send an Animation using the Telegram API.")
440     public boolean sendTelegramAnimation(@ActionInput(name = "animationURL") @Nullable String animationURL,
441             @ActionInput(name = "caption") @Nullable String caption) {
442         TelegramHandler localHandler = handler;
443         if (localHandler != null) {
444             for (Long chatId : localHandler.getReceiverChatIds()) {
445                 if (!sendTelegramAnimation(chatId, animationURL, caption)) {
446                     return false;
447                 }
448             }
449         }
450         return true;
451     }
452
453     @RuleAction(label = "send animation", description = "Send an Animation using the Telegram API.")
454     public boolean sendTelegramAnimation(@ActionInput(name = "chatId") @Nullable Long chatId,
455             @ActionInput(name = "animationURL") @Nullable String animationURL,
456             @ActionInput(name = "caption") @Nullable String caption) {
457         if (animationURL == null) {
458             logger.warn("Animation URL not defined; unable to retrieve video for sending.");
459             return false;
460         }
461         if (chatId == null) {
462             logger.warn("chatId not defined; action skipped.");
463             return false;
464         }
465         TelegramHandler localHandler = handler;
466         if (localHandler != null) {
467             final SendAnimation sendAnimation;
468             if (animationURL.toLowerCase().startsWith("http")) {
469                 // load image from url
470                 logger.debug("Animation URL provided.");
471                 HttpClient client = localHandler.getClient();
472                 if (client == null) {
473                     return false;
474                 }
475                 Request request = client.newRequest(animationURL).method(HttpMethod.GET).timeout(30, TimeUnit.SECONDS);
476                 try {
477                     // 50mb limit to file size
478                     FutureResponseListener listener = new FutureResponseListener(request, 50 * 1024 * 1024);
479                     request.send(listener);
480                     ContentResponse contentResponse = listener.get();
481                     if (contentResponse.getStatus() == 200) {
482                         byte[] fileContent = contentResponse.getContent();
483                         sendAnimation = new SendAnimation(chatId, fileContent);
484                     } else {
485                         logger.warn("Download from {} failed with status: {}", animationURL,
486                                 contentResponse.getStatus());
487                         sendTelegram(chatId, caption + ":Download failed with status " + contentResponse.getStatus());
488                         return false;
489                     }
490                 } catch (InterruptedException | ExecutionException e) {
491                     logger.warn("Download from {} failed with exception: {}", animationURL, e.getMessage());
492                     return false;
493                 }
494             } else {
495                 String temp = animationURL;
496                 if (!animationURL.toLowerCase().startsWith("file:")) {
497                     temp = "file://" + animationURL;
498                 }
499                 // Load video from local file system
500                 logger.debug("Read file from local file system: {}", animationURL);
501                 try {
502                     sendAnimation = new SendAnimation(chatId, Path.of(new URL(temp).getPath()).toFile());
503                 } catch (MalformedURLException e) {
504                     logger.warn("Malformed URL, should start with http or file: {}", animationURL);
505                     return false;
506                 }
507             }
508             if (caption != null) {
509                 sendAnimation.caption(caption);
510             }
511             if (localHandler.getParseMode() != null) {
512                 sendAnimation.parseMode(localHandler.getParseMode());
513             }
514             return evaluateResponse(localHandler.execute(sendAnimation));
515         }
516         return false;
517     }
518
519     @RuleAction(label = "send video", description = "Send a Video using the Telegram API.")
520     public boolean sendTelegramVideo(@ActionInput(name = "videoURL") @Nullable String videoURL,
521             @ActionInput(name = "caption") @Nullable String caption) {
522         TelegramHandler localHandler = handler;
523         if (localHandler != null) {
524             for (Long chatId : localHandler.getReceiverChatIds()) {
525                 if (!sendTelegramVideo(chatId, videoURL, caption)) {
526                     return false;
527                 }
528             }
529         }
530         return true;
531     }
532
533     @RuleAction(label = "send video", description = "Send a Video using the Telegram API.")
534     public boolean sendTelegramVideo(@ActionInput(name = "chatId") @Nullable Long chatId,
535             @ActionInput(name = "videoURL") @Nullable String videoURL,
536             @ActionInput(name = "caption") @Nullable String caption) {
537         final SendVideo sendVideo;
538         if (videoURL == null) {
539             logger.warn("Video URL not defined; unable to retrieve video for sending.");
540             return false;
541         }
542         if (chatId == null) {
543             logger.warn("chatId not defined; action skipped.");
544             return false;
545         }
546         TelegramHandler localHandler = handler;
547         if (localHandler != null) {
548             if (videoURL.toLowerCase().startsWith("http")) {
549                 logger.debug("Video http://URL provided.");
550                 HttpClient client = localHandler.getClient();
551                 if (client == null) {
552                     return false;
553                 }
554                 Request request = client.newRequest(videoURL).method(HttpMethod.GET).timeout(30, TimeUnit.SECONDS);
555                 try {
556                     // 50mb limit to file size
557                     FutureResponseListener listener = new FutureResponseListener(request, 50 * 1024 * 1024);
558                     request.send(listener);
559                     ContentResponse contentResponse = listener.get();
560                     if (contentResponse.getStatus() == 200) {
561                         byte[] fileContent = contentResponse.getContent();
562                         sendVideo = new SendVideo(chatId, fileContent);
563                     } else {
564                         if (contentResponse.getStatus() == 401
565                                 && contentResponse.getHeaders().get(HttpHeader.WWW_AUTHENTICATE).contains("igest")) {
566                             logger.warn("Download from {} failed due to no BASIC http auth support.", videoURL);
567                         } else {
568                             logger.warn("Download from {} failed with status: {}", videoURL,
569                                     contentResponse.getStatus());
570                         }
571                         sendTelegram(chatId, caption + ":Download failed with status " + contentResponse.getStatus());
572                         return false;
573                     }
574                 } catch (InterruptedException | ExecutionException e) {
575                     logger.warn("Download from {} failed with exception: {}", videoURL, e.getMessage());
576                     return false;
577                 }
578             } else {
579                 String temp = videoURL;
580                 if (!videoURL.toLowerCase().startsWith("file:")) {
581                     temp = "file://" + videoURL;
582                 }
583                 // Load video from local file system with file://path
584                 logger.debug("Read file from local file: {}", videoURL);
585                 try {
586                     sendVideo = new SendVideo(chatId, Path.of(new URL(temp).getPath()).toFile());
587                 } catch (MalformedURLException e) {
588                     logger.warn("Malformed URL, should start with http or file: {}", videoURL);
589                     return false;
590                 }
591             }
592             if (caption != null) {
593                 sendVideo.caption(caption);
594             }
595             if (localHandler.getParseMode() != null) {
596                 sendVideo.parseMode(localHandler.getParseMode());
597             }
598             return evaluateResponse(localHandler.execute(sendVideo));
599         }
600         return false;
601     }
602
603     // legacy delegate methods
604     /* APIs without chatId parameter */
605     public static boolean sendTelegram(ThingActions actions, @Nullable String format, @Nullable Object... args) {
606         return ((TelegramActions) actions).sendTelegram(format, args);
607     }
608
609     public static boolean sendTelegramQuery(ThingActions actions, @Nullable String message, @Nullable String replyId,
610             @Nullable String... buttons) {
611         return ((TelegramActions) actions).sendTelegramQuery(message, replyId, buttons);
612     }
613
614     public static boolean sendTelegramPhoto(ThingActions actions, @Nullable String photoURL, @Nullable String caption) {
615         return ((TelegramActions) actions).sendTelegramPhoto(photoURL, caption, null, null);
616     }
617
618     public static boolean sendTelegramPhoto(ThingActions actions, @Nullable String photoURL, @Nullable String caption,
619             @Nullable String username, @Nullable String password) {
620         return ((TelegramActions) actions).sendTelegramPhoto(photoURL, caption, username, password);
621     }
622
623     public static boolean sendTelegramAnimation(ThingActions actions, @Nullable String animationURL,
624             @Nullable String caption) {
625         return ((TelegramActions) actions).sendTelegramVideo(animationURL, caption);
626     }
627
628     public static boolean sendTelegramVideo(ThingActions actions, @Nullable String videoURL, @Nullable String caption) {
629         return ((TelegramActions) actions).sendTelegramVideo(videoURL, caption);
630     }
631
632     public static boolean sendTelegramAnswer(ThingActions actions, @Nullable String replyId, @Nullable String message) {
633         return ((TelegramActions) actions).sendTelegramAnswer(replyId, message);
634     }
635
636     /* APIs with chatId parameter */
637
638     public static boolean sendTelegram(ThingActions actions, @Nullable Long chatId, @Nullable String format,
639             @Nullable Object... args) {
640         return ((TelegramActions) actions).sendTelegram(chatId, format, args);
641     }
642
643     public static boolean sendTelegramQuery(ThingActions actions, @Nullable Long chatId, @Nullable String message,
644             @Nullable String replyId, @Nullable String... buttons) {
645         return ((TelegramActions) actions).sendTelegramQuery(chatId, message, replyId, buttons);
646     }
647
648     public static boolean sendTelegramPhoto(ThingActions actions, @Nullable Long chatId, @Nullable String photoURL,
649             @Nullable String caption) {
650         return ((TelegramActions) actions).sendTelegramPhoto(chatId, photoURL, caption, null, null);
651     }
652
653     public static boolean sendTelegramPhoto(ThingActions actions, @Nullable Long chatId, @Nullable String photoURL,
654             @Nullable String caption, @Nullable String username, @Nullable String password) {
655         return ((TelegramActions) actions).sendTelegramPhoto(chatId, photoURL, caption, username, password);
656     }
657
658     public static boolean sendTelegramAnimation(ThingActions actions, @Nullable Long chatId,
659             @Nullable String animationURL, @Nullable String caption) {
660         return ((TelegramActions) actions).sendTelegramVideo(chatId, animationURL, caption);
661     }
662
663     public static boolean sendTelegramVideo(ThingActions actions, @Nullable Long chatId, @Nullable String videoURL,
664             @Nullable String caption) {
665         return ((TelegramActions) actions).sendTelegramVideo(chatId, videoURL, caption);
666     }
667
668     public static boolean sendTelegramAnswer(ThingActions actions, @Nullable Long chatId, @Nullable String replyId,
669             @Nullable String message) {
670         return ((TelegramActions) actions).sendTelegramAnswer(chatId, replyId, message);
671     }
672
673     public static boolean sendTelegramAnswer(ThingActions actions, @Nullable String chatId, @Nullable String replyId,
674             @Nullable String message) {
675         if (actions instanceof TelegramActions telegramActions) {
676             if (chatId == null) {
677                 return false;
678             }
679             return telegramActions.sendTelegramAnswer(Long.valueOf(chatId), replyId, message);
680         } else {
681             throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
682         }
683     }
684
685     public static boolean sendTelegramAnswer(ThingActions actions, @Nullable Long chatId, @Nullable String callbackId,
686             @Nullable Long messageId, @Nullable String message) {
687         return ((TelegramActions) actions).sendTelegramAnswer(chatId, callbackId, messageId, message);
688     }
689
690     public static boolean sendTelegramAnswer(ThingActions actions, @Nullable String chatId, @Nullable String callbackId,
691             @Nullable String messageId, @Nullable String message) {
692         if (actions instanceof TelegramActions telegramActions) {
693             if (chatId == null) {
694                 return false;
695             }
696             return telegramActions.sendTelegramAnswer(Long.valueOf(chatId), callbackId,
697                     messageId != null ? Long.parseLong(messageId) : null, message);
698         } else {
699             throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
700         }
701     }
702
703     @Override
704     public void setThingHandler(@Nullable ThingHandler handler) {
705         this.handler = (TelegramHandler) handler;
706     }
707
708     @Override
709     public @Nullable ThingHandler getThingHandler() {
710         return handler;
711     }
712 }