Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[telegram] Add action that supports various options #17801

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 22 additions & 21 deletions bundles/org.openhab.binding.telegram/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The Telegram binding allows sending and receiving messages to and from Telegram clients (<https://telegram.org>), by using the Telegram Bot API.

# Prerequisites
## Prerequisites

As described in the Telegram Bot API (<https://core.telegram.org/bots#6-botfather>), this is the manual procedure needed in order to get the necessary information.

Expand Down Expand Up @@ -59,15 +59,15 @@ In order to send a message, an action must be used instead.

**telegramBot** parameters:

| Property | Default | Required | Description |
|-|-|-|-|
| `chatIds` | | Yes | A list of chatIds that are entered one per line in the UI, or are comma separated values when using textual config. |
| `botToken` | | Yes | Authentication token that looks like 1122334455:AABBCCDDEEFFGG1122334455667788 |
| `parseMode` | None | No | Support for formatted messages, values: Markdown or HTML. |
| `proxyHost` | None | No | Proxy host for telegram binding. |
| `proxyPort` | None | No | Proxy port for telegram binding. |
| `proxyType` | SOCKS5 | No | Type of proxy server for telegram binding (SOCKS5 or HTTP). |
| `longPollingTime` | 25 | No | Timespan in seconds for long polling the telegram API. |
| Property | Default | Required | Description |
|-------------------|---------|----------|---------------------------------------------------------------------------------------------------------------------|
| `chatIds` | | Yes | A list of chatIds that are entered one per line in the UI, or are comma separated values when using textual config. |
| `botToken` | | Yes | Authentication token that looks like 1122334455:AABBCCDDEEFFGG1122334455667788 |
| `parseMode` | None | No | Support for formatted messages, values: Markdown or HTML. |
| `proxyHost` | None | No | Proxy host for telegram binding. |
| `proxyPort` | None | No | Proxy port for telegram binding. |
| `proxyType` | SOCKS5 | No | Type of proxy server for telegram binding (SOCKS5 or HTTP). |
| `longPollingTime` | 25 | No | Timespan in seconds for long polling the telegram API. |

By default chat ids are bi-directionally, i.e. they can send and receive messages.
They can be prefixed with an access modifier:
Expand Down Expand Up @@ -193,17 +193,18 @@ Each of the actions returns true on success or false on failure.

These actions will send a message to all chat ids configured for this bot.

| Action | Description |
|----------------------------|--------------|
| sendTelegram(String message) | Sends a message. |
| sendTelegram(String format, Object... args) | Sends a formatted message (See <https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Formatter.html> for more information).
| sendTelegramQuery(String message, String replyId, String... buttons) | Sends a question to the user that can be answered via the defined buttons. The replyId can be freely choosen and is sent back with the answer. Then, the id is required to identify what question has been answered (e.g. in case of multiple open questions). The final result looks like this: ![Telegram Inline Keyboard](doc/queryExample.png) |
| sendTelegramAnswer(String replyId, String message) | Sends a message after the user has answered a question. You should _always_ call this method after you received an answer. It will remove buttons from the specific question and will also stop the progress bar displayed at the client side. If no message is necessary, just pass `null` here. |
| deleteTelegramQuery(String replyId) | Deletes a question in the chat. The replyId must be the same as used for the corresponding sendTelegramQuery() action. |
| sendTelegramPhoto(String photoURL, String caption) | Sends a picture. Can be one of the URL formats, see the Note below, or a base64 encoded image (simple base64 data or data URI scheme). |
| sendTelegramPhoto(String photoURL, String caption, String username, String password) | Sends a picture which is downloaded from a username/password protected http/https address. |
| sendTelegramAnimation(String animationURL, String caption) | Send animation files either GIF or H.264/MPEG-4 AVC video without sound. |
| sendTelegramVideo(String videoURL, String caption) | Send MP4 video files up to 50MB. |
| Action | Description |
|--------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| sendTelegram(String message) | Sends a message. |
| sendTelegram(String format, Object... args) | Sends a formatted message (See <https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Formatter.html> for more information) |
| sendTelegramTo(Long[] chatIds, String message, Integer replyMessageId,Boolean silent, Integer messageThreadId, Object... args) | Sends a message with various options |
| sendTelegramQuery(String message, String replyId, String... buttons) | Sends a question to the user that can be answered via the defined buttons. The replyId can be freely choosen and is sent back with the answer. Then, the id is required to identify what question has been answered (e.g. in case of multiple open questions). The final result looks like this: ![Telegram Inline Keyboard](doc/queryExample.png) |
| sendTelegramAnswer(String replyId, String message) | Sends a message after the user has answered a question. You should _always_ call this method after you received an answer. It will remove buttons from the specific question and will also stop the progress bar displayed at the client side. If no message is necessary, just pass `null` here. |
| deleteTelegramQuery(String replyId) | Deletes a question in the chat. The replyId must be the same as used for the corresponding sendTelegramQuery() action. |
| sendTelegramPhoto(String photoURL, String caption) | Sends a picture. Can be one of the URL formats, see the Note below, or a base64 encoded image (simple base64 data or data URI scheme). |
| sendTelegramPhoto(String photoURL, String caption, String username, String password) | Sends a picture which is downloaded from a username/password protected http/https address. |
| sendTelegramAnimation(String animationURL, String caption) | Send animation files either GIF or H.264/MPEG-4 AVC video without sound. |
| sendTelegramVideo(String videoURL, String caption) | Send MP4 video files up to 50MB. |

**Note:** In actions that require a file URL, the following formats are acceptable:

Expand Down
2 changes: 1 addition & 1 deletion bundles/org.openhab.binding.telegram/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
<dependency>
<groupId>com.github.pengrad</groupId>
<artifactId>java-telegram-bot-api</artifactId>
<version>7.1.0</version>
<version>7.11.0</version>
<scope>compile</scope>
</dependency>
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,12 @@ private void handleExceptions(@Nullable TelegramException exception) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Unauthorized attempt to connect to the Telegram server, please check if the bot token is valid");
return;
case 429:
cancelThingOnlineStatusJob();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Too Many Requests, temporary delaying");
delayThingOnlineStatus();
return;
case 502:
cancelThingOnlineStatusJob();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
Expand Down Expand Up @@ -391,26 +397,26 @@ private int handleUpdates(List<Update> updates) {
lastMessageUsername = message.from().username();
}
}
} else if (callbackQuery != null && callbackQuery.message() != null
&& callbackQuery.message().text() != null) {
} else if (callbackQuery != null && callbackQuery.maybeInaccessibleMessage() instanceof Message cbMessage
&& cbMessage.text() != null) {
String[] callbackData = callbackQuery.data().split(" ", 2);

if (callbackData.length == 2) {
replyId = callbackData[0];
lastMessageText = callbackData[1];
lastMessageDate = callbackQuery.message().date();
lastMessageDate = cbMessage.date();
lastMessageFirstName = callbackQuery.from().firstName();
lastMessageLastName = callbackQuery.from().lastName();
lastMessageUsername = callbackQuery.from().username();
chatId = callbackQuery.message().chat().id();
chatId = cbMessage.chat().id();
replyIdToCallbackId.put(new ReplyKey(chatId, replyId), callbackQuery.id());

// build and publish callbackEvent trigger channel payload
JsonObject callbackRaw = JsonParser.parseString(gson.toJson(callbackQuery)).getAsJsonObject();
JsonObject callbackPayload = new JsonObject();
callbackPayload.addProperty("message_id", callbackQuery.message().messageId());
callbackPayload.addProperty("message_id", cbMessage.messageId());
callbackPayload.addProperty("from", lastMessageFirstName + " " + lastMessageLastName);
callbackPayload.addProperty("chat_id", callbackQuery.message().chat().id());
callbackPayload.addProperty("chat_id", cbMessage.chat().id());
callbackPayload.addProperty("callback_id", callbackQuery.id());
callbackPayload.addProperty("reply_id", callbackData[0]);
callbackPayload.addProperty("text", callbackData[1]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

Expand Down Expand Up @@ -195,7 +196,7 @@ public String toString() {
public @ActionOutput(label = "Success", type = "java.lang.Boolean") boolean sendTelegram(
@ActionInput(name = "chatId") @Nullable Long chatId,
@ActionInput(name = "message") @Nullable String message) {
return sendTelegramGeneral(chatId, message, (String) null);
return sendTelegramGeneral(chatId, message, (String) null, null, null, null);
}

@RuleAction(label = "send a message", description = "Send a Telegram message using the Telegram API.")
Expand All @@ -218,7 +219,7 @@ public String toString() {
@ActionInput(name = "message") @Nullable String message,
@ActionInput(name = "replyId") @Nullable String replyId,
@ActionInput(name = "buttons") @Nullable String... buttons) {
return sendTelegramGeneral(chatId, message, replyId, buttons);
return sendTelegramGeneral(chatId, message, replyId, null, null, null, buttons);
}

@RuleAction(label = "send a query", description = "Send a Telegram Query using the Telegram API.")
Expand All @@ -238,7 +239,8 @@ public String toString() {
}

private boolean sendTelegramGeneral(@Nullable Long chatId, @Nullable String message, @Nullable String replyId,
@Nullable String... buttons) {
@Nullable Integer replyToMessageId, @Nullable Boolean disableNotification,
@Nullable Integer messageThreadId, @Nullable String... buttons) {
if (message == null) {
logger.warn("Message not defined; action skipped.");
return false;
Expand All @@ -250,6 +252,15 @@ private boolean sendTelegramGeneral(@Nullable Long chatId, @Nullable String mess
TelegramHandler localHandler = handler;
if (localHandler != null) {
SendMessage sendMessage = new SendMessage(chatId, message);
if (replyToMessageId != null) {
sendMessage.replyToMessageId(replyToMessageId);
}
if (disableNotification != null) {
sendMessage.disableNotification(disableNotification);
}
if (messageThreadId != null) {
sendMessage.messageThreadId(messageThreadId);
}
if (localHandler.getParseMode() != null) {
sendMessage.parseMode(localHandler.getParseMode());
}
Expand Down Expand Up @@ -352,6 +363,37 @@ private boolean sendTelegramGeneral(@Nullable Long chatId, @Nullable String mess
return true;
}

@RuleAction(label = "send a message", description = "Send a Telegram using the Telegram API.")
public @ActionOutput(label = "Success", type = "java.lang.Boolean") boolean sendTelegramTo(
@ActionInput(name = "chatId") @Nullable Long @Nullable [] chatIds,
@ActionInput(name = "message") @Nullable String message,
@ActionInput(name = "replyMessageId") @Nullable Integer replyMessageId,
@ActionInput(name = "silent") @Nullable Boolean silent,
@ActionInput(name = "messageThreadId") @Nullable Integer messageThreadId,
@ActionInput(name = "args") @Nullable Object... args) {
if (message == null) {
return false;
}
TelegramHandler localHandler = handler;
List<Long> chatIdentifiers = List.of();
if (chatIds != null) {
Long[] chatIdentifiersArray = chatIds; // compiler vs null annotation fix
chatIdentifiers = List.of(chatIdentifiersArray);
} else if (localHandler != null) {
chatIdentifiers = localHandler.getReceiverChatIds();
}

boolean successful = true;
for (Long chatId : chatIdentifiers) {

// supports both with and without args
successful = successful == sendTelegramGeneral(chatId, String.format(message, args), (String) null,
replyMessageId, silent, messageThreadId);
}

return successful;
}

@RuleAction(label = "send a photo", description = "Send a picture using the Telegram API.")
public @ActionOutput(label = "Success", type = "java.lang.Boolean") boolean sendTelegramPhoto(
@ActionInput(name = "chatId") @Nullable Long chatId,
Expand Down Expand Up @@ -704,6 +746,13 @@ public static boolean sendTelegram(ThingActions actions, @Nullable Long chatId,
return ((TelegramActions) actions).sendTelegram(chatId, format, args);
}

public static boolean sendTelegramTo(ThingActions actions, @Nullable Long @Nullable [] chatIds,
@Nullable String message, @Nullable Integer replyMessageId, @Nullable Boolean silent,
@Nullable Integer messageThreadId, @Nullable Object... args) {
return ((TelegramActions) actions).sendTelegramTo(chatIds, message, replyMessageId, silent, messageThreadId,
args);
}

public static boolean sendTelegramQuery(ThingActions actions, @Nullable Long chatId, @Nullable String message,
@Nullable String replyId, @Nullable String... buttons) {
return ((TelegramActions) actions).sendTelegramQuery(chatId, message, replyId, buttons);
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
<netty.version>4.1.104.Final</netty.version>
<okhttp3.version>4.12.0</okhttp3.version>
<okio.version>3.9.0</okio.version>
<gson.version>2.9.1</gson.version>
<gson.version>2.10.1</gson.version>
<kotlin.version>1.9.23</kotlin.version>
<sat.version>0.16.0</sat.version>
<slf4j.version>2.0.12</slf4j.version>
Expand Down