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