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