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 event channels and Answer overload #9251

Merged
merged 30 commits into from
Sep 18, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
7d1d472
Add event channels and Answer overload
CrazyIvan359 Dec 5, 2020
7fe68eb
requested changes
CrazyIvan359 Dec 9, 2020
dca4e54
Merge branch 'main' into telegram-updates
Skinah Sep 6, 2021
a4d878d
Merge branch 'main' into telegram-updates
Skinah Sep 12, 2021
f97a1fa
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
e91301e
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
439ae53
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
6e4127b
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
ddd9909
Update bundles/org.openhab.binding.telegram/src/main/java/org/openhab…
Skinah Sep 12, 2021
08947d3
Update bundles/org.openhab.binding.telegram/src/main/java/org/openhab…
Skinah Sep 12, 2021
a449706
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
6354ea8
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
c64a27b
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
fce6472
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
f826466
Fix merge conflict issues.
Skinah Sep 12, 2021
1b91514
Add event channels and Answer overload
CrazyIvan359 Dec 5, 2020
a1ec5f5
requested changes
CrazyIvan359 Dec 9, 2020
d948b20
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
af0313a
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
df2a26b
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
6aa24d0
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
8d3ca16
Update bundles/org.openhab.binding.telegram/src/main/java/org/openhab…
Skinah Sep 12, 2021
294b23f
Update bundles/org.openhab.binding.telegram/src/main/java/org/openhab…
Skinah Sep 12, 2021
a72ee7e
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
9333d48
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
0ac958b
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
661ea70
Update bundles/org.openhab.binding.telegram/src/main/resources/OH-INF…
Skinah Sep 12, 2021
016bec1
Remove deprecated methods used.
Skinah Sep 12, 2021
5b32b66
Fix conflicts
Skinah Sep 12, 2021
f4d00c7
remove redundant @NonNullByDefault
Skinah Sep 12, 2021
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
61 changes: 59 additions & 2 deletions bundles/org.openhab.binding.telegram/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Otherwise you will not be able to receive those messages.

**telegramBot** - A Telegram Bot that can send and receive messages.

The Telegram binding supports the following things which originate from the last message sent to the Telegram bot:
The Telegram binding supports the following state channels which originate from the last message sent to the Telegram bot:

* message text or URL
* message date
Expand All @@ -49,6 +49,8 @@ The Telegram binding supports the following things which originate from the last
* chat id (used to identify the chat of the last message)
* reply id (used to identify an answer from a user of a previously sent message by the binding)

There are also event channels that provide received messages or query callback responses as JSON payloads for easier handling in rules.

Please note that the binding channels cannot be used to send messages.
In order to send a message, an action must be used instead.

Expand Down Expand Up @@ -104,7 +106,7 @@ or HTTP proxy server
Thing telegram:telegramBot:Telegram_Bot [ chatIds="ID", botToken="TOKEN", proxyHost="localhost", proxyPort="8123", proxyType="HTTP" ]
```

## Channels
## State Channels

| Channel Type ID | Item Type | Description |
|--------------------------------------|-----------|-----------------------------------------------------------------|
Expand All @@ -121,6 +123,52 @@ Either `lastMessageText` or `lastMessageURL` are populated for a given message.
If the message did contain text, the content is written to `lastMessageText`.
If the message did contain an audio, photo, video or voice, the URL to retrieve that content can be found in `lastMessageURL`.

## Event Channels

### messageEvent

When a message is received this channel will be triggered with a simplified version of the message data as the `event`, payload encoded as a JSON string.
The following table shows the possible fields, any `null` values will be missing from the JSON payload.

| Field | Type | Description |
|------------------|--------|---------------------------------------|
| `message_id` | Long | Unique message ID in this chat |
| `from` | String | First and/or last name of sender |
| `chat_id` | Long | Unique chat ID |
| `text` | String | Message text |
| `animation_url` | String | URL to download animation from |
| `audio_url` | String | URL to download audio from |
| `document_url` | String | URL to download file from |
| `photo_url` | Array | Array of URLs to download photos from |
| `sticker_url` | String | URL to download sticker from |
| `video_url` | String | URL to download video from |
| `video_note_url` | String | URL to download video note from |
| `voice_url` | String | URL to download voice clip from |

### messageRawEvent

When a message is received this channel will be triggered with the raw message data as the `event` payload, encoded as a JSON string.
See the [`Message` class for details](https://github.com/pengrad/java-telegram-bot-api/blob/4.9.0/library/src/main/java/com/pengrad/telegrambot/model/Message.java)

### callbackEvent

When a Callback Query response is received this channel will be triggered with a simplified version of the callback data as the `event`, payload encoded as a JSON string.
The following table shows the possible fields, any `null` values will be missing from the JSON payload.

| Field | Type | Description |
|---------------|--------|------------------------------------------------------------|
| `message_id` | Long | Unique message ID of the original Query message |
| `from` | String | First and/or last name of sender |
| `chat_id` | Long | Unique chat ID |
| `callback_id` | String | Unique callback ID to send receipt confirmation to |
| `reply_id` | String | Plain text name of original Query |
| `text` | String | Selected response text from options give in original Query |

### callbackRawEvent

When a Callback Query response is received this channel will be triggered with the raw callback data as the `event` payload, encoded as a JSON string.
See the [`CallbackQuery` class for details](https://github.com/pengrad/java-telegram-bot-api/blob/4.9.0/library/src/main/java/com/pengrad/telegrambot/model/CallbackQuery.java)

## Rule Actions

This binding includes a number of rule actions, which allow the sending of Telegram messages from within rules.
Expand Down Expand Up @@ -171,6 +219,15 @@ Just put the chat id (must be a long value!) followed by an "L" as the first arg
telegramAction.sendTelegram(1234567L, "Hello world!")
```

### Advanced Callback Query Response

This binding stores the `callbackId` and recalls it using the `replyId`, but this information is lost if openHAB restarts.
If you store the `callbackId`, `chatId`, and optionally `messageId` somewhere that will be persisted when openHAB shuts down, you can use the following overload of `sendTelegramAnswer` to respond to any Callback Query.

```
telegramAction.sendTelegramAnswer(chatId, callbackId, messageId, message)
```

## Full Example

### Send a text message to telegram chat
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,8 @@ public class TelegramBindingConstants {
public static final String CHATID = "chatId";
public static final String REPLYID = "replyId";
public static final String LONGPOLLINGTIME = "longPollingTime";
public static final String MESSAGEEVENT = "messageEvent";
public static final String MESSAGERAWEVENT = "messageRawEvent";
public static final String CALLBACKEVENT = "callbackEvent";
public static final String CALLBACKRAWEVENT = "callbackRawEvent";
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,15 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.pengrad.telegrambot.TelegramBot;
import com.pengrad.telegrambot.TelegramException;
import com.pengrad.telegrambot.UpdatesListener;
import com.pengrad.telegrambot.model.CallbackQuery;
import com.pengrad.telegrambot.model.Message;
import com.pengrad.telegrambot.model.PhotoSize;
import com.pengrad.telegrambot.model.Update;
Expand All @@ -70,6 +76,7 @@
* @author Jens Runge - Initial contribution
* @author Alexander Krasnogolowy - using Telegram library from pengrad
* @author Jan N. Klug - handle file attachments
* @author Michael Murton - add trigger channel
*/
@NonNullByDefault
public class TelegramHandler extends BaseThingHandler {
Expand Down Expand Up @@ -106,6 +113,9 @@ public boolean equals(@Nullable Object obj) {
}
}

private static Gson gson = new Gson();
private static JsonParser json = new JsonParser();

private final List<Long> authorizedSenderChatId = new ArrayList<>();
private final List<Long> receiverChatId = new ArrayList<>();

Expand Down Expand Up @@ -267,6 +277,7 @@ private int handleUpdates(List<Update> updates) {
String replyId = null;

Message message = update.message();
CallbackQuery callbackQuery = update.callbackQuery();

if (message != null) {
chatId = message.chat().id();
Expand All @@ -278,6 +289,99 @@ private int handleUpdates(List<Update> updates) {
// chat
}

// build and publish messageEvent trigger channel payload
JsonObject messageRaw = json.parse(gson.toJson(message)).getAsJsonObject();
JsonObject messagePayload = new JsonObject();
messagePayload.addProperty("message_id", message.messageId());
messagePayload.addProperty("from",
String.join(" ", new String[] { message.from().firstName(), message.from().lastName() }));
messagePayload.addProperty("chat_id", message.chat().id());
if (messageRaw.has("text")) {
messagePayload.addProperty("text", message.text());
}
if (messageRaw.has("animation")) {
JsonObject animationPayload = messageRaw.getAsJsonObject("animation");
String animationURL = getFullDownloadUrl(
animationPayload.getAsJsonPrimitive("file_id").getAsString());
animationPayload.addProperty("file_url", animationURL);
messagePayload.addProperty("animation_url", animationURL);
if (animationPayload.has("thumb")) {
animationPayload.getAsJsonObject("thumb").addProperty("file_url", getFullDownloadUrl(
animationPayload.getAsJsonObject("thumb").getAsJsonPrimitive("file_id").getAsString()));
}
}
if (messageRaw.has("audio")) {
JsonObject audioPayload = messageRaw.getAsJsonObject("audio");
String audioURL = getFullDownloadUrl(audioPayload.getAsJsonPrimitive("file_id").getAsString());
audioPayload.addProperty("file_url", audioURL);
messagePayload.addProperty("audio_url", audioURL);
if (audioPayload.has("thumb")) {
audioPayload.getAsJsonObject("thumb").addProperty("file_url", getFullDownloadUrl(
audioPayload.getAsJsonObject("thumb").getAsJsonPrimitive("file_id").getAsString()));
}
}
if (messageRaw.has("document")) {
JsonObject documentPayload = messageRaw.getAsJsonObject("document");
String documentURL = getFullDownloadUrl(
documentPayload.getAsJsonPrimitive("file_id").getAsString());
documentPayload.addProperty("file_url", documentURL);
messagePayload.addProperty("document_url", documentURL);
if (documentPayload.has("thumb")) {
documentPayload.getAsJsonObject("thumb").addProperty("file_url", getFullDownloadUrl(
documentPayload.getAsJsonObject("thumb").getAsJsonPrimitive("file_id").getAsString()));
}
CrazyIvan359 marked this conversation as resolved.
Show resolved Hide resolved
}
if (messageRaw.has("photo")) {
JsonArray photoURLArray = new JsonArray();
for (JsonElement photoPayload : messageRaw.getAsJsonArray("photo")) {
JsonObject photoPayloadObject = photoPayload.getAsJsonObject();
String photoURL = getFullDownloadUrl(
photoPayloadObject.getAsJsonPrimitive("file_id").getAsString());
photoPayloadObject.addProperty("file_url", photoURL);
photoURLArray.add(photoURL);
}
messagePayload.add("photo_url", photoURLArray);
}
if (messageRaw.has("sticker")) {
JsonObject stickerPayload = messageRaw.getAsJsonObject("sticker");
String stickerURL = getFullDownloadUrl(stickerPayload.getAsJsonPrimitive("file_id").getAsString());
stickerPayload.addProperty("file_url", stickerURL);
messagePayload.addProperty("sticker_url", stickerURL);
if (stickerPayload.has("thumb")) {
stickerPayload.getAsJsonObject("thumb").addProperty("file_url", getFullDownloadUrl(
stickerPayload.getAsJsonObject("thumb").getAsJsonPrimitive("file_id").getAsString()));
}
}
if (messageRaw.has("video")) {
JsonObject videoPayload = messageRaw.getAsJsonObject("video");
String videoURL = getFullDownloadUrl(videoPayload.getAsJsonPrimitive("file_id").getAsString());
videoPayload.addProperty("file_url", videoURL);
messagePayload.addProperty("video_url", videoURL);
if (videoPayload.has("thumb")) {
videoPayload.getAsJsonObject("thumb").addProperty("file_url", getFullDownloadUrl(
videoPayload.getAsJsonObject("thumb").getAsJsonPrimitive("file_id").getAsString()));
}
}
if (messageRaw.has("video_note")) {
JsonObject videoNotePayload = messageRaw.getAsJsonObject("animation");
String videoNoteURL = getFullDownloadUrl(
videoNotePayload.getAsJsonPrimitive("file_id").getAsString());
videoNotePayload.addProperty("file_url", videoNoteURL);
messagePayload.addProperty("video_note_url", videoNoteURL);
if (videoNotePayload.has("thumb")) {
videoNotePayload.getAsJsonObject("thumb").addProperty("file_url", getFullDownloadUrl(
videoNotePayload.getAsJsonObject("thumb").getAsJsonPrimitive("file_id").getAsString()));
}
}
if (messageRaw.has("voice")) {
JsonObject voicePayload = messageRaw.getAsJsonObject("voice");
String voiceURL = getFullDownloadUrl(voicePayload.getAsJsonPrimitive("file_id").getAsString());
voicePayload.addProperty("file_url", voiceURL);
messagePayload.addProperty("voice_url", voiceURL);
}
triggerEvent(MESSAGEEVENT, messagePayload.toString());
triggerEvent(MESSAGERAWEVENT, messageRaw.toString());

// process content
if (message.audio() != null) {
lastMessageURL = getFullDownloadUrl(message.audio().fileId());
Expand All @@ -300,28 +404,44 @@ private int handleUpdates(List<Update> updates) {
}

// process metadata
lastMessageDate = message.date();
lastMessageFirstName = message.from().firstName();
lastMessageLastName = message.from().lastName();
lastMessageUsername = message.from().username();
} else if (update.callbackQuery() != null && update.callbackQuery().message() != null
&& update.callbackQuery().message().text() != null) {
String[] callbackData = update.callbackQuery().data().split(" ", 2);
if (lastMessageURL != null || lastMessageText != null) {
lastMessageDate = message.date();
lastMessageFirstName = message.from().firstName();
lastMessageLastName = message.from().lastName();
lastMessageUsername = message.from().username();
}
} else if (callbackQuery != null && callbackQuery.message() != null
&& callbackQuery.message().text() != null) {
String[] callbackData = callbackQuery.data().split(" ", 2);

if (callbackData.length == 2) {
replyId = callbackData[0];
lastMessageText = callbackData[1];
lastMessageDate = update.callbackQuery().message().date();
lastMessageFirstName = update.callbackQuery().from().firstName();
lastMessageLastName = update.callbackQuery().from().lastName();
lastMessageUsername = update.callbackQuery().from().username();
chatId = update.callbackQuery().message().chat().id();
replyIdToCallbackId.put(new ReplyKey(chatId, replyId), update.callbackQuery().id());
logger.debug("Received callbackId {} for chatId {} and replyId {}", update.callbackQuery().id(),
chatId, replyId);
lastMessageDate = callbackQuery.message().date();
lastMessageFirstName = callbackQuery.from().firstName();
lastMessageLastName = callbackQuery.from().lastName();
lastMessageUsername = callbackQuery.from().username();
chatId = callbackQuery.message().chat().id();
replyIdToCallbackId.put(new ReplyKey(chatId, replyId), callbackQuery.id());

// build and publish callbackEvent trigger channel payload
JsonObject callbackRaw = json.parse(gson.toJson(callbackQuery)).getAsJsonObject();
JsonObject callbackPayload = new JsonObject();
callbackPayload.addProperty("message_id", callbackQuery.message().messageId());
callbackPayload.addProperty("from", String.join(" ",
new String[] { callbackQuery.from().firstName(), callbackQuery.from().lastName() }));
Skinah marked this conversation as resolved.
Show resolved Hide resolved
callbackPayload.addProperty("chat_id", callbackQuery.message().chat().id());
CrazyIvan359 marked this conversation as resolved.
Show resolved Hide resolved
callbackPayload.addProperty("callback_id", callbackQuery.id());
callbackPayload.addProperty("reply_id", callbackQuery.data().split(" ", 2)[0]);
callbackPayload.addProperty("text", callbackQuery.data().split(" ", 2)[1]);
CrazyIvan359 marked this conversation as resolved.
Show resolved Hide resolved
triggerEvent(CALLBACKEVENT, callbackPayload.toString());
triggerEvent(CALLBACKRAWEVENT, callbackRaw.toString());

logger.debug("Received callbackId {} for chatId {} and replyId {}", callbackQuery.id(), chatId,
replyId);
} else {
logger.warn("The received callback query {} has not the right format (must be seperated by spaces)",
update.callbackQuery().data());
callbackQuery.data());
}
}
updateChannel(LASTMESSAGETEXT, lastMessageText != null ? new StringType(lastMessageText) : UnDefType.NULL);
Expand Down Expand Up @@ -376,6 +496,10 @@ public void updateChannel(String channelName, State state) {
updateState(new ChannelUID(getThing().getUID(), channelName), state);
}

public void triggerEvent(String channelName, String payload) {
triggerChannel(new ChannelUID(getThing().getUID(), channelName), payload);
Skinah marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Collections.singleton(TelegramActions.class);
Expand Down
Loading