added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
32
bundles/org.openhab.binding.telegram/.classpath
Normal file
32
bundles/org.openhab.binding.telegram/.classpath
Normal file
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||
<attributes>
|
||||
<attribute name="test" value="true"/>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||
<attributes>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||
<attributes>
|
||||
<attribute name="optional" value="true"/>
|
||||
<attribute name="maven.pomderived" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
23
bundles/org.openhab.binding.telegram/.project
Normal file
23
bundles/org.openhab.binding.telegram/.project
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>org.openhab.binding.telegram</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
<buildCommand>
|
||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
||||
25
bundles/org.openhab.binding.telegram/NOTICE
Normal file
25
bundles/org.openhab.binding.telegram/NOTICE
Normal file
@@ -0,0 +1,25 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
||||
|
||||
== Third-party Content
|
||||
|
||||
Java Telegram Bot API
|
||||
* License: Apache License 2.0
|
||||
* Project: https://github.com/pengrad/java-telegram-bot-api
|
||||
* Source: https://github.com/pengrad/java-telegram-bot-api
|
||||
|
||||
okhttp
|
||||
* License: Apache License 2.0
|
||||
* Project: https://square.github.io/okhttp/
|
||||
* Source: https://github.com/square/okhttp
|
||||
340
bundles/org.openhab.binding.telegram/README.md
Normal file
340
bundles/org.openhab.binding.telegram/README.md
Normal file
@@ -0,0 +1,340 @@
|
||||
# Telegram Binding
|
||||
|
||||
The Telegram binding allows sending and receiving messages to and from Telegram clients (https://telegram.org), by using the Telegram Bot API.
|
||||
|
||||
# Prerequisites
|
||||
|
||||
As described in the Telegram Bot API, this is the manual procedure needed in order to get the necessary information.
|
||||
|
||||
1. Create the Bot and get the Token
|
||||
|
||||
- On a Telegram client open a chat with BotFather.
|
||||
- Send `/newbot` to BotFather and fill in all the needed information. The authentication token that is given will be needed in the next steps.
|
||||
|
||||
2. Create the destination chat
|
||||
|
||||
- Open a chat with your new Bot and send any message to it. The next step will not work unless you send a message to your bot first.
|
||||
|
||||
3. Get the chatID
|
||||
|
||||
- Open a browser and invoke `https://api.telegram.org/bot<token>/getUpdates` (where `<token>` is the authentication token previously obtained)
|
||||
- Look at the JSON result to find the value of `id`: that's the chatID.
|
||||
|
||||
Note that if using a Telegram group chat, the group chatIDs are prefixed with a dash that must be included in the config (e.g. `-22334455`).
|
||||
If this does not work for you (the JSON response may be empty), or you want to send to *more* than one recipient (= another chatID), the alternative is to contact (= open a chat with) a Telegram bot to respond with the chatID.
|
||||
There's a number of them such as `@myidbot` or `@chatid_echo_bot` - open a chat, eventually tap `/start` and it will return the chatID you're looking for.
|
||||
Another option is `@getidsbot` which gives you much more information.
|
||||
Note bots may work or not at any time so eventually you need to try another one.
|
||||
|
||||
4. Test the bot
|
||||
|
||||
- Open this URL in your web browser, replacing <token> with the authentication token and <chatId> with the chatId:
|
||||
- `https://api.telegram.org/bot<token>/sendMessage?chat_id=<chatId>&text=testing`
|
||||
- Your Telegram-bot should send you a message with the text: `testing`
|
||||
|
||||
**Notice:** By default your bot will only receive messages that either start with the '/' symbol or mention the bot by username (or if you talk to it directly). However, if you add your bot to a group you must either talk to BotFather and send the command "/setprivacy" and then disable it or you give admin rights to your bot in that group. Otherwise you will not be able to receive those messages.
|
||||
|
||||
## Supported Things
|
||||
|
||||
**telegramBot** - A Telegram Bot that can send and receive messages.
|
||||
|
||||
The Telegram binding supports the following things which origin from the latest message sent to the Telegram bot:
|
||||
|
||||
* message text or URL
|
||||
* message date
|
||||
* full name of sender (first name + last name)
|
||||
* username of sender
|
||||
* 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)
|
||||
|
||||
Please note that the things cannot be used to send messages.
|
||||
In order to send a message, an action must be used instead.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
**telegramBot** parameters:
|
||||
|
||||
| Property | Default | Required | Description |
|
||||
|-------------------------|---------|:--------:|----------------------------------------------------------------------------------------------|
|
||||
| `chatIds` | | Yes | Comma-separated list of chat ids |
|
||||
| `botToken` | | Yes | authentication token |
|
||||
| `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). Default: SOCKS5 |
|
||||
| `longPollingTime` | 25 | No | Timespan 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:
|
||||
|
||||
- `<` restricts the chat to send only, i.e. this chat id can send messages to openHAB, but will never receive a notification.
|
||||
- `>` restricts the chat to receive only, i.e. this chat id will receive all notifications, but messages from this chat id will be discarded.
|
||||
|
||||
To use the reply function, chat ids need to be bi-directional.
|
||||
|
||||
telegram.thing (no proxy):
|
||||
|
||||
```
|
||||
Thing telegram:telegramBot:Telegram_Bot [ chatIds="ID", botToken="TOKEN" ]
|
||||
```
|
||||
|
||||
telegram.thing (multiple chat ids, one bi-directional chat (ID1), one outbound-only (ID2)):
|
||||
|
||||
```
|
||||
Thing telegram:telegramBot:Telegram_Bot [ chatIds="ID1",">ID2", botToken="TOKEN" ]
|
||||
```
|
||||
|
||||
|
||||
telegram.thing (markdown format):
|
||||
|
||||
```
|
||||
Thing telegram:telegramBot:Telegram_Bot [ chatIds="ID", botToken="TOKEN", parseMode ="Markdown" ]
|
||||
```
|
||||
|
||||
telegram.thing (SOCKS5 proxy server is used):
|
||||
|
||||
```
|
||||
Thing telegram:telegramBot:Telegram_Bot [ chatIds="ID", botToken="TOKEN", proxyHost="HOST", proxyPort="PORT", proxyType="TYPE" ]
|
||||
```
|
||||
|
||||
or HTTP proxy server
|
||||
|
||||
```
|
||||
Thing telegram:telegramBot:Telegram_Bot [ chatIds="ID", botToken="TOKEN", proxyHost="localhost", proxyPort="8123", proxyType="HTTP" ]
|
||||
```
|
||||
|
||||
|
||||
## Channels
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
|--------------------------------------|-----------|-----------------------------------------------------------------|
|
||||
| lastMessageText | String | The last received message |
|
||||
| lastMessageURL | String | The URL of the last received message content |
|
||||
| lastMessageDate | DateTime | The date of the last received message (UTC) |
|
||||
| lastMessageName | String | The full name of the sender of the last received message |
|
||||
| lastMessageUsername | String | The username of the sender of the last received message |
|
||||
| chatId | String | The id of the chat of the last received message |
|
||||
| replyId | String | The id of the reply which was passed to sendTelegram() as replyId argument. This id can be used to have an unambiguous assignment of the users reply to the message which was sent by the bot |
|
||||
|
||||
All channels are read-only.
|
||||
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`.
|
||||
|
||||
## Rule Actions
|
||||
|
||||
This binding includes a rule action, which allows to send Telegram messages from within rules.
|
||||
|
||||
```
|
||||
val telegramAction = getActions("telegram","telegram:telegramBot:<uid>")
|
||||
```
|
||||
|
||||
where uid is the Thing UID of the Telegram thing (not the chat id!).
|
||||
|
||||
Once this action instance is retrieved, you can invoke the `sendTelegram' method on it:
|
||||
|
||||
```
|
||||
telegramAction.sendTelegram("Hello world!")
|
||||
```
|
||||
|
||||
The following actions are supported.
|
||||
Each of the actions returns true on success or false on failure.
|
||||
|
||||
### Actions to send messages to all configured chats
|
||||
|
||||
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/javase/8/docs/api/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: . |
|
||||
| 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. |
|
||||
| sendTelegramPhoto(String photoURL, String caption) | Sends a picture. The URL can be specified using the http, https, and file protocols 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. |
|
||||
|
||||
### Actions to send messages to a particular chat
|
||||
|
||||
Just put the chat id (must be a long value!) as the first argument to one of the above mentioned APIs:
|
||||
|
||||
```
|
||||
telegramAction.sendTelegram(1234567L, "Hello world!")
|
||||
```
|
||||
|
||||
## Full Example
|
||||
|
||||
### Send a text message to telegram chat
|
||||
|
||||
telegram.rules
|
||||
|
||||
```java
|
||||
rule "Send telegram with Fixed Message"
|
||||
when
|
||||
Item Foo changed
|
||||
then
|
||||
val telegramAction = getActions("telegram","telegram:telegramBot:2b155b22")
|
||||
telegramAction.sendTelegram("item Foo changed")
|
||||
end
|
||||
```
|
||||
|
||||
### Send a text message with a formatted message
|
||||
|
||||
telegram.rules
|
||||
|
||||
```java
|
||||
rule "Send telegram with Formatted Message"
|
||||
when
|
||||
Item Foo changed
|
||||
then
|
||||
val telegramAction = getActions("telegram","telegram:telegramBot:2b155b22")
|
||||
telegramAction.sendTelegram("item Foo changed to %s and number is %.1f", Foo.state.toString, 23.56)
|
||||
end
|
||||
```
|
||||
|
||||
### Send an image to telegram chat
|
||||
|
||||
`http`, `https`, and `file` are the only protocols allowed or a base64 encoded image.
|
||||
|
||||
telegram.rules
|
||||
|
||||
```java
|
||||
rule "Send telegram with image and caption from image accessible by url"
|
||||
when
|
||||
Item Light_GF_Living_Table changed
|
||||
then
|
||||
val telegramAction = getActions("telegram","telegram:telegramBot:2b155b22")
|
||||
telegramAction.sendTelegramPhoto("http://www.openhab.org/assets/images/openhab-logo-top.png",
|
||||
"sent from openHAB")
|
||||
end
|
||||
```
|
||||
|
||||
telegram.rules
|
||||
|
||||
```java
|
||||
rule "Send telegram with image without caption from image accessible by url"
|
||||
when
|
||||
Item Light_GF_Living_Table changed
|
||||
then
|
||||
val telegramAction = getActions("telegram","telegram:telegramBot:2b155b22")
|
||||
telegramAction.sendTelegramPhoto("http://www.openhab.org/assets/images/openhab-logo-top.png",
|
||||
null)
|
||||
end
|
||||
```
|
||||
|
||||
telegram.rules
|
||||
|
||||
```java
|
||||
rule "Send telegram with image from password protected http source"
|
||||
when
|
||||
Item Light_GF_Living_Table changed
|
||||
then
|
||||
val telegramAction = getActions("telegram","telegram:telegramBot:2b155b22")
|
||||
telegramAction.sendTelegramPhoto("http://192.168.1.5/doorcam/picture.jpg", "Door Camera", "user", "mypassword")
|
||||
end
|
||||
```
|
||||
|
||||
To send a base64 jpeg or png image:
|
||||
|
||||
telegram.rules
|
||||
|
||||
```java
|
||||
rule "Send telegram with base64 image and caption"
|
||||
when
|
||||
Item Light_GF_Living_Table changed
|
||||
then
|
||||
val telegramAction = getActions("telegram","telegram:telegramBot:2b155b22")
|
||||
// image as base64 string
|
||||
var String base64Image = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAS1BMVEUAAABAQEA9QUc7P0Y0OD88QEY+QUhmaW7c3N3w8PBlaG0+QUjb29w5PUU3O0G+vsigoas6P0WfoKo4O0I9QUdkZ2w9Qkg+QkkkSUnT3FKbAAAAGXRSTlMACJbx//CV9v//9pT/7Ur//+z/SfD2kpMHrnfDaAAAAGhJREFUeAHt1bUBAzAMRFGZmcL7LxpOalN5r/evLIlgGwBgXMhxSjP64sa6cdYH+hLWzYiKvqSbI4kQeEt5PlBealsMFIkAAgi8HNriOLcjduLTafWwBB9n3p8v/+Ma1Mxxvd4IAGCzB4xDPuBRkEZiAAAAAElFTkSuQmCC"
|
||||
telegramAction.sendTelegramPhoto(base64Image, "battery of motion sensor is empty")
|
||||
|
||||
// image as base64 string in data URI scheme
|
||||
var String base64ImageDataURI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAS1BMVEUAAABAQEA9QUc7P0Y0OD88QEY+QUhmaW7c3N3w8PBlaG0+QUjb29w5PUU3O0G+vsigoas6P0WfoKo4O0I9QUdkZ2w9Qkg+QkkkSUnT3FKbAAAAGXRSTlMACJbx//CV9v//9pT/7Ur//+z/SfD2kpMHrnfDaAAAAGhJREFUeAHt1bUBAzAMRFGZmcL7LxpOalN5r/evLIlgGwBgXMhxSjP64sa6cdYH+hLWzYiKvqSbI4kQeEt5PlBealsMFIkAAgi8HNriOLcjduLTafWwBB9n3p8v/+Ma1Mxxvd4IAGCzB4xDPuBRkEZiAAAAAElFTkSuQmCC"
|
||||
telegramAction.sendTelegramPhoto(base64ImageDataURI, "battery of motion sensor is empty")
|
||||
end
|
||||
```
|
||||
|
||||
To send an image that resides on the local computer file system:
|
||||
|
||||
telegram.rules
|
||||
|
||||
```java
|
||||
rule "Send telegram with local image and caption"
|
||||
when
|
||||
Item Light_GF_Living_Table changed
|
||||
then
|
||||
val telegramAction = getActions("telegram","telegram:telegramBot:2b155b22")
|
||||
telegramAction.sendTelegramPhoto("file://C:/mypicture.jpg", "sent from openHAB")
|
||||
end
|
||||
```
|
||||
|
||||
To send an image based on an Image Item:
|
||||
|
||||
telegram.rules
|
||||
|
||||
```java
|
||||
rule "Send telegram with Image Item image and caption"
|
||||
when
|
||||
Item Webcam_Image changed
|
||||
then
|
||||
val telegramAction = getActions("telegram","telegram:telegramBot:2b155b22")
|
||||
telegramAction.sendTelegramPhoto(Webcam_Image.state.toFullString, "sent from openHAB")
|
||||
end
|
||||
```
|
||||
|
||||
To receive a message and react on that:
|
||||
|
||||
telegram.items
|
||||
|
||||
```php
|
||||
String telegramMessage "Telegram Message" { channel = "telegram:telegramBot:2b155b22:lastMessageText" }
|
||||
```
|
||||
|
||||
telegram.rules
|
||||
|
||||
```java
|
||||
rule "Receive telegram"
|
||||
when
|
||||
Item telegramMessage received update "lights off"
|
||||
then
|
||||
gLights.sendCommand(OFF)
|
||||
end
|
||||
```
|
||||
|
||||
To send a question with two alternatives and a reply from the bot:
|
||||
|
||||
telegram.items
|
||||
|
||||
```php
|
||||
String telegramReplyId "Telegram Reply Id" { channel = "telegram:telegramBot:2b155b22:replyId" }
|
||||
```
|
||||
|
||||
telegram.rules
|
||||
|
||||
```java
|
||||
rule "Send telegram with question"
|
||||
when
|
||||
Item Presence changed to OFF
|
||||
then
|
||||
val telegramAction = getActions("telegram","telegram:telegramBot:2b155b22")
|
||||
telegramAction.sendTelegramQuery("No one is at home, but some lights are still on. Do you want me to turn off the lights?", "Reply_Lights", "Yes", "No")
|
||||
end
|
||||
|
||||
|
||||
rule "Reply handler for lights"
|
||||
when
|
||||
Item telegramReplyId received update Reply_Lights
|
||||
then
|
||||
val telegramAction = getActions("telegram","telegram:telegramBot:2b155b22")
|
||||
|
||||
if (telegramMessage.state.toString == "Yes")
|
||||
{
|
||||
gLights.sendCommand(OFF)
|
||||
telegramAction.sendTelegramAnswer(telegramReplyId.state.toString, "Ok, lights are *off* now.")
|
||||
}
|
||||
else
|
||||
{
|
||||
telegramAction.sendTelegramAnswer(telegramReplyId.state.toString, "Ok, I'll leave them *on*.")
|
||||
}
|
||||
end
|
||||
```
|
||||
|
||||
BIN
bundles/org.openhab.binding.telegram/doc/queryExample.png
Normal file
BIN
bundles/org.openhab.binding.telegram/doc/queryExample.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
47
bundles/org.openhab.binding.telegram/pom.xml
Normal file
47
bundles/org.openhab.binding.telegram/pom.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.telegram</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: Telegram Binding</name>
|
||||
|
||||
<properties>
|
||||
<bnd.importpackage>!android.*,!com.android.org.*,!dalvik.*,!javax.annotation.meta.*,!org.apache.harmony.*,!org.conscrypt.*,!sun.*</bnd.importpackage>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.pengrad</groupId>
|
||||
<artifactId>java-telegram-bot-api</artifactId>
|
||||
<version>4.4.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>okhttp</artifactId>
|
||||
<version>3.12.3</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okio</groupId>
|
||||
<artifactId>okio</artifactId>
|
||||
<version>1.15.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.squareup.okhttp3</groupId>
|
||||
<artifactId>logging-interceptor</artifactId>
|
||||
<version>3.12.3</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.telegram-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||
|
||||
<feature name="openhab-binding-telegram" description="Telegram Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.telegram/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.telegram.bot;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Provides the actions for the Telegram API.
|
||||
*
|
||||
* @author Laurent Garnier - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface ITelegramActions {
|
||||
|
||||
public boolean sendTelegramAnswer(@Nullable Long chatId, @Nullable String replyId, @Nullable String message);
|
||||
|
||||
public boolean sendTelegramAnswer(@Nullable String replyId, @Nullable String message);
|
||||
|
||||
public boolean sendTelegram(@Nullable Long chatId, @Nullable String message);
|
||||
|
||||
public boolean sendTelegram(@Nullable String message);
|
||||
|
||||
public boolean sendTelegramQuery(@Nullable Long chatId, @Nullable String message, @Nullable String replyId,
|
||||
@Nullable String... buttons);
|
||||
|
||||
public boolean sendTelegramQuery(@Nullable String message, @Nullable String replyId, @Nullable String... buttons);
|
||||
|
||||
public boolean sendTelegram(@Nullable Long chatId, @Nullable String message, @Nullable Object... args);
|
||||
|
||||
public boolean sendTelegram(@Nullable String message, @Nullable Object... args);
|
||||
|
||||
public boolean sendTelegramPhoto(@Nullable Long chatId, @Nullable String photoURL, @Nullable String caption,
|
||||
@Nullable String username, @Nullable String password);
|
||||
|
||||
public boolean sendTelegramPhoto(@Nullable String photoURL, @Nullable String caption, @Nullable String username,
|
||||
@Nullable String password);
|
||||
}
|
||||
@@ -0,0 +1,500 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.telegram.bot;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Base64;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.Authentication;
|
||||
import org.eclipse.jetty.client.api.AuthenticationStore;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
import org.eclipse.jetty.http.HttpMethod;
|
||||
import org.eclipse.jetty.util.B64Code;
|
||||
import org.openhab.binding.telegram.internal.TelegramHandler;
|
||||
import org.openhab.core.automation.annotation.ActionInput;
|
||||
import org.openhab.core.automation.annotation.RuleAction;
|
||||
import org.openhab.core.thing.binding.ThingActions;
|
||||
import org.openhab.core.thing.binding.ThingActionsScope;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardButton;
|
||||
import com.pengrad.telegrambot.model.request.InlineKeyboardMarkup;
|
||||
import com.pengrad.telegrambot.request.AnswerCallbackQuery;
|
||||
import com.pengrad.telegrambot.request.EditMessageReplyMarkup;
|
||||
import com.pengrad.telegrambot.request.SendMessage;
|
||||
import com.pengrad.telegrambot.request.SendPhoto;
|
||||
import com.pengrad.telegrambot.response.BaseResponse;
|
||||
import com.pengrad.telegrambot.response.SendResponse;
|
||||
|
||||
/**
|
||||
* Provides the actions for the Telegram API.
|
||||
* <p>
|
||||
* <b>Note:</b>The static method <b>invokeMethodOf</b> handles the case where
|
||||
* the test <i>actions instanceof TelegramActions</i> fails. This test can fail
|
||||
* due to an issue in openHAB core v2.5.0 where the {@link TelegramActions} class
|
||||
* can be loaded by a different classloader than the <i>actions</i> instance.
|
||||
*
|
||||
* @author Alexander Krasnogolowy - Initial contribution
|
||||
*
|
||||
*/
|
||||
@ThingActionsScope(name = "telegram")
|
||||
@NonNullByDefault
|
||||
public class TelegramActions implements ThingActions, ITelegramActions {
|
||||
private final Logger logger = LoggerFactory.getLogger(TelegramActions.class);
|
||||
private @Nullable TelegramHandler handler;
|
||||
|
||||
private boolean evaluateResponse(@Nullable BaseResponse response) {
|
||||
if (response != null && !response.isOk()) {
|
||||
logger.warn("Failed to send telegram message: {}", response.description());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@NonNullByDefault
|
||||
private static class BasicResult implements Authentication.Result {
|
||||
|
||||
private final HttpHeader header;
|
||||
private final URI uri;
|
||||
private final String value;
|
||||
|
||||
public BasicResult(HttpHeader header, URI uri, String value) {
|
||||
this.header = header;
|
||||
this.uri = uri;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI getURI() {
|
||||
return this.uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(@Nullable Request request) {
|
||||
if (request != null) {
|
||||
request.header(this.header, this.value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Basic authentication result for %s", this.uri);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Telegram answer", description = "Sends a Telegram answer via Telegram API")
|
||||
public boolean sendTelegramAnswer(@ActionInput(name = "chatId") @Nullable Long chatId,
|
||||
@ActionInput(name = "replyId") @Nullable String replyId,
|
||||
@ActionInput(name = "message") @Nullable String message) {
|
||||
if (replyId == null) {
|
||||
logger.warn("ReplyId not defined; action skipped.");
|
||||
return false;
|
||||
}
|
||||
if (chatId == null) {
|
||||
logger.warn("chatId not defined; action skipped.");
|
||||
return false;
|
||||
}
|
||||
TelegramHandler localHandler = handler;
|
||||
if (localHandler != null) {
|
||||
String callbackId = localHandler.getCallbackId(chatId, replyId);
|
||||
if (callbackId != null) {
|
||||
AnswerCallbackQuery answerCallbackQuery = new AnswerCallbackQuery(
|
||||
localHandler.getCallbackId(chatId, replyId));
|
||||
logger.debug("AnswerCallbackQuery for chatId {} and replyId {} is the callbackId {}", chatId, replyId,
|
||||
localHandler.getCallbackId(chatId, replyId));
|
||||
// we could directly set the text here, but this
|
||||
// doesn't result in a real message only in a
|
||||
// little popup or in an alert, so the only purpose
|
||||
// is to stop the progress bar on client side
|
||||
if (!evaluateResponse(localHandler.execute(answerCallbackQuery))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Integer messageId = localHandler.removeMessageId(chatId, replyId);
|
||||
logger.debug("remove messageId {} for chatId {} and replyId {}", messageId, chatId, replyId);
|
||||
|
||||
EditMessageReplyMarkup editReplyMarkup = new EditMessageReplyMarkup(chatId, messageId.intValue())
|
||||
.replyMarkup(new InlineKeyboardMarkup(new InlineKeyboardButton[0]));// remove reply markup from
|
||||
// old message
|
||||
if (!evaluateResponse(localHandler.execute(editReplyMarkup))) {
|
||||
return false;
|
||||
}
|
||||
return message != null ? sendTelegram(chatId, message) : true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Telegram answer", description = "Sends a Telegram answer via Telegram API")
|
||||
public boolean sendTelegramAnswer(@ActionInput(name = "replyId") @Nullable String replyId,
|
||||
@ActionInput(name = "message") @Nullable String message) {
|
||||
TelegramHandler localHandler = handler;
|
||||
if (localHandler != null) {
|
||||
for (Long chatId : localHandler.getReceiverChatIds()) {
|
||||
if (!sendTelegramAnswer(chatId, replyId, message)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Telegram message", description = "Sends a Telegram via Telegram API")
|
||||
public boolean sendTelegram(@ActionInput(name = "chatId") @Nullable Long chatId,
|
||||
@ActionInput(name = "message") @Nullable String message) {
|
||||
return sendTelegramGeneral(chatId, message, (String) null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Telegram message", description = "Sends a Telegram via Telegram API")
|
||||
public boolean sendTelegram(@ActionInput(name = "message") @Nullable String message) {
|
||||
TelegramHandler localHandler = handler;
|
||||
if (localHandler != null) {
|
||||
for (Long chatId : localHandler.getReceiverChatIds()) {
|
||||
if (!sendTelegram(chatId, message)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Telegram message", description = "Sends a Telegram via Telegram API")
|
||||
public boolean sendTelegramQuery(@ActionInput(name = "chatId") @Nullable Long chatId,
|
||||
@ActionInput(name = "message") @Nullable String message,
|
||||
@ActionInput(name = "replyId") @Nullable String replyId,
|
||||
@ActionInput(name = "buttons") @Nullable String... buttons) {
|
||||
return sendTelegramGeneral(chatId, message, replyId, buttons);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Telegram message", description = "Sends a Telegram via Telegram API")
|
||||
public boolean sendTelegramQuery(@ActionInput(name = "message") @Nullable String message,
|
||||
@ActionInput(name = "replyId") @Nullable String replyId,
|
||||
@ActionInput(name = "buttons") @Nullable String... buttons) {
|
||||
TelegramHandler localHandler = handler;
|
||||
if (localHandler != null) {
|
||||
for (Long chatId : localHandler.getReceiverChatIds()) {
|
||||
if (!sendTelegramQuery(chatId, message, replyId, buttons)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean sendTelegramGeneral(@ActionInput(name = "chatId") @Nullable Long chatId, @Nullable String message,
|
||||
@Nullable String replyId, @Nullable String... buttons) {
|
||||
if (message == null) {
|
||||
logger.warn("Message not defined; action skipped.");
|
||||
return false;
|
||||
}
|
||||
if (chatId == null) {
|
||||
logger.warn("chatId not defined; action skipped.");
|
||||
return false;
|
||||
}
|
||||
TelegramHandler localHandler = handler;
|
||||
if (localHandler != null) {
|
||||
SendMessage sendMessage = new SendMessage(chatId, message);
|
||||
if (localHandler.getParseMode() != null) {
|
||||
sendMessage.parseMode(localHandler.getParseMode());
|
||||
}
|
||||
if (replyId != null) {
|
||||
if (!replyId.contains(" ")) {
|
||||
if (buttons.length > 0) {
|
||||
InlineKeyboardButton[][] keyboard2D = new InlineKeyboardButton[1][];
|
||||
InlineKeyboardButton[] keyboard = new InlineKeyboardButton[buttons.length];
|
||||
keyboard2D[0] = keyboard;
|
||||
for (int i = 0; i < buttons.length; i++) {
|
||||
keyboard[i] = new InlineKeyboardButton(buttons[i]).callbackData(replyId + " " + buttons[i]);
|
||||
}
|
||||
InlineKeyboardMarkup keyBoardMarkup = new InlineKeyboardMarkup(keyboard2D);
|
||||
sendMessage.replyMarkup(keyBoardMarkup);
|
||||
} else {
|
||||
logger.warn(
|
||||
"The replyId {} for message {} is given, but no buttons are defined. ReplyMarkup will be ignored.",
|
||||
replyId, message);
|
||||
}
|
||||
} else {
|
||||
logger.warn("replyId {} must not contain spaces. ReplyMarkup will be ignored.", replyId);
|
||||
}
|
||||
}
|
||||
SendResponse retMessage = localHandler.execute(sendMessage);
|
||||
if (!evaluateResponse(retMessage)) {
|
||||
return false;
|
||||
}
|
||||
if (replyId != null && retMessage != null) {
|
||||
logger.debug("Adding chatId {}, replyId {} and messageId {}", chatId, replyId,
|
||||
retMessage.message().messageId());
|
||||
localHandler.addMessageId(chatId, replyId, retMessage.message().messageId());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Telegram message", description = "Sends a Telegram via Telegram API")
|
||||
public boolean sendTelegram(@ActionInput(name = "chatId") @Nullable Long chatId,
|
||||
@ActionInput(name = "message") @Nullable String message,
|
||||
@ActionInput(name = "args") @Nullable Object... args) {
|
||||
return sendTelegram(chatId, String.format(message, args));
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Telegram message", description = "Sends a Telegram via Telegram API")
|
||||
public boolean sendTelegram(@ActionInput(name = "message") @Nullable String message,
|
||||
@ActionInput(name = "args") @Nullable Object... args) {
|
||||
TelegramHandler localHandler = handler;
|
||||
if (localHandler != null) {
|
||||
for (Long chatId : localHandler.getReceiverChatIds()) {
|
||||
if (!sendTelegram(chatId, message, args)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@RuleAction(label = "Telegram photo", description = "Sends a Picture via Telegram API")
|
||||
public boolean sendTelegramPhoto(@ActionInput(name = "chatId") @Nullable Long chatId,
|
||||
@ActionInput(name = "photoURL") @Nullable String photoURL,
|
||||
@ActionInput(name = "caption") @Nullable String caption) {
|
||||
return sendTelegramPhoto(chatId, photoURL, caption, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Telegram photo", description = "Sends a Picture via Telegram API")
|
||||
public boolean sendTelegramPhoto(@ActionInput(name = "chatId") @Nullable Long chatId,
|
||||
@ActionInput(name = "photoURL") @Nullable String photoURL,
|
||||
@ActionInput(name = "caption") @Nullable String caption,
|
||||
@ActionInput(name = "username") @Nullable String username,
|
||||
@ActionInput(name = "password") @Nullable String password) {
|
||||
if (photoURL == null) {
|
||||
logger.warn("Photo URL not defined; unable to retrieve photo for sending.");
|
||||
return false;
|
||||
}
|
||||
if (chatId == null) {
|
||||
logger.warn("chatId not defined; action skipped.");
|
||||
return false;
|
||||
}
|
||||
|
||||
TelegramHandler localHandler = handler;
|
||||
if (localHandler != null) {
|
||||
final SendPhoto sendPhoto;
|
||||
|
||||
if (photoURL.toLowerCase().startsWith("http")) {
|
||||
// load image from url
|
||||
logger.debug("Photo URL provided.");
|
||||
HttpClient client = localHandler.getClient();
|
||||
if (client == null) {
|
||||
return false;
|
||||
}
|
||||
Request request = client.newRequest(photoURL).method(HttpMethod.GET).timeout(30, TimeUnit.SECONDS);
|
||||
if (username != null && password != null) {
|
||||
AuthenticationStore auth = client.getAuthenticationStore();
|
||||
URI uri = URI.create(photoURL);
|
||||
auth.addAuthenticationResult(new BasicResult(HttpHeader.AUTHORIZATION, uri,
|
||||
"Basic " + B64Code.encode(username + ":" + password, StandardCharsets.ISO_8859_1)));
|
||||
}
|
||||
try {
|
||||
ContentResponse contentResponse = request.send();
|
||||
if (contentResponse.getStatus() == 200) {
|
||||
byte[] fileContent = contentResponse.getContent();
|
||||
sendPhoto = new SendPhoto(chatId, fileContent);
|
||||
} else {
|
||||
logger.warn("Download from {} failed with status: {}", photoURL, contentResponse.getStatus());
|
||||
return false;
|
||||
}
|
||||
} catch (InterruptedException | TimeoutException | ExecutionException e) {
|
||||
logger.warn("Download from {} failed with exception: {}", photoURL, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
} else if (photoURL.toLowerCase().startsWith("file")) {
|
||||
// Load image from local file system
|
||||
logger.debug("Read file from local file system: {}", photoURL);
|
||||
try {
|
||||
URL url = new URL(photoURL);
|
||||
sendPhoto = new SendPhoto(chatId, Paths.get(url.getPath()).toFile());
|
||||
} catch (MalformedURLException e) {
|
||||
logger.warn("Malformed URL: {}", photoURL);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// Load image from provided base64 image
|
||||
logger.debug("Photo base64 provided; converting to binary.");
|
||||
final String photoB64Data;
|
||||
if (photoURL.startsWith("data:")) { // support data URI scheme
|
||||
String[] photoURLParts = photoURL.split(",");
|
||||
if (photoURLParts.length > 1) {
|
||||
photoB64Data = photoURLParts[1];
|
||||
} else {
|
||||
logger.warn("The provided base64 string is not a valid data URI scheme");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
photoB64Data = photoURL;
|
||||
}
|
||||
InputStream is = Base64.getDecoder()
|
||||
.wrap(new ByteArrayInputStream(photoB64Data.getBytes(StandardCharsets.UTF_8)));
|
||||
try {
|
||||
byte[] photoBytes = IOUtils.toByteArray(is);
|
||||
sendPhoto = new SendPhoto(chatId, photoBytes);
|
||||
} catch (IOException e) {
|
||||
logger.warn("Malformed base64 string: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
sendPhoto.caption(caption);
|
||||
if (localHandler.getParseMode() != null) {
|
||||
sendPhoto.parseMode(localHandler.getParseMode());
|
||||
}
|
||||
return evaluateResponse(localHandler.execute(sendPhoto));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@RuleAction(label = "Telegram photo", description = "Sends a Picture via Telegram API")
|
||||
public boolean sendTelegramPhoto(@ActionInput(name = "photoURL") @Nullable String photoURL,
|
||||
@ActionInput(name = "caption") @Nullable String caption,
|
||||
@ActionInput(name = "username") @Nullable String username,
|
||||
@ActionInput(name = "password") @Nullable String password) {
|
||||
TelegramHandler localHandler = handler;
|
||||
if (localHandler != null) {
|
||||
for (Long chatId : localHandler.getReceiverChatIds()) {
|
||||
if (!sendTelegramPhoto(chatId, photoURL, caption, username, password)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@RuleAction(label = "Telegram photo", description = "Sends a Picture via Telegram API")
|
||||
public boolean sendTelegramPhoto(@ActionInput(name = "photoURL") @Nullable String photoURL,
|
||||
@ActionInput(name = "caption") @Nullable String caption) {
|
||||
return sendTelegramPhoto(photoURL, caption, null, null);
|
||||
}
|
||||
|
||||
// legacy delegate methods
|
||||
/* APIs without chatId parameter */
|
||||
public static boolean sendTelegram(@Nullable ThingActions actions, @Nullable String format,
|
||||
@Nullable Object... args) {
|
||||
return invokeMethodOf(actions).sendTelegram(format, args);
|
||||
}
|
||||
|
||||
public static boolean sendTelegramQuery(@Nullable ThingActions actions, @Nullable String message,
|
||||
@Nullable String replyId, @Nullable String... buttons) {
|
||||
return invokeMethodOf(actions).sendTelegramQuery(message, replyId, buttons);
|
||||
}
|
||||
|
||||
public static boolean sendTelegramPhoto(@Nullable ThingActions actions, @Nullable String photoURL,
|
||||
@Nullable String caption) {
|
||||
return invokeMethodOf(actions).sendTelegramPhoto(photoURL, caption, null, null);
|
||||
}
|
||||
|
||||
public static boolean sendTelegramPhoto(@Nullable ThingActions actions, @Nullable String photoURL,
|
||||
@Nullable String caption, @Nullable String username, @Nullable String password) {
|
||||
return invokeMethodOf(actions).sendTelegramPhoto(photoURL, caption, username, password);
|
||||
}
|
||||
|
||||
public static boolean sendTelegramAnswer(@Nullable ThingActions actions, @Nullable String replyId,
|
||||
@Nullable String message) {
|
||||
return invokeMethodOf(actions).sendTelegramAnswer(replyId, message);
|
||||
}
|
||||
|
||||
/* APIs with chatId parameter */
|
||||
|
||||
public static boolean sendTelegram(@Nullable ThingActions actions, @Nullable Long chatId, @Nullable String format,
|
||||
@Nullable Object... args) {
|
||||
return invokeMethodOf(actions).sendTelegram(chatId, format, args);
|
||||
}
|
||||
|
||||
public static boolean sendTelegramQuery(@Nullable ThingActions actions, @Nullable Long chatId,
|
||||
@Nullable String message, @Nullable String replyId, @Nullable String... buttons) {
|
||||
return invokeMethodOf(actions).sendTelegramQuery(chatId, message, replyId, buttons);
|
||||
}
|
||||
|
||||
public static boolean sendTelegramPhoto(@Nullable ThingActions actions, @Nullable Long chatId,
|
||||
@Nullable String photoURL, @Nullable String caption) {
|
||||
return invokeMethodOf(actions).sendTelegramPhoto(chatId, photoURL, caption, null, null);
|
||||
}
|
||||
|
||||
public static boolean sendTelegramPhoto(@Nullable ThingActions actions, @Nullable Long chatId,
|
||||
@Nullable String photoURL, @Nullable String caption, @Nullable String username, @Nullable String password) {
|
||||
return invokeMethodOf(actions).sendTelegramPhoto(chatId, photoURL, caption, username, password);
|
||||
}
|
||||
|
||||
public static boolean sendTelegramAnswer(@Nullable ThingActions actions, @Nullable Long chatId,
|
||||
@Nullable String replyId, @Nullable String message) {
|
||||
return invokeMethodOf(actions).sendTelegramAnswer(chatId, replyId, message);
|
||||
}
|
||||
|
||||
public static boolean sendTelegramAnswer(@Nullable ThingActions actions, @Nullable String chatId,
|
||||
@Nullable String replyId, @Nullable String message) {
|
||||
return invokeMethodOf(actions).sendTelegramAnswer(Long.valueOf(chatId), replyId, message);
|
||||
}
|
||||
|
||||
private static ITelegramActions invokeMethodOf(@Nullable ThingActions actions) {
|
||||
if (actions == null) {
|
||||
throw new IllegalArgumentException("actions cannot be null");
|
||||
}
|
||||
if (actions.getClass().getName().equals(TelegramActions.class.getName())) {
|
||||
if (actions instanceof ITelegramActions) {
|
||||
return (ITelegramActions) actions;
|
||||
} else {
|
||||
return (ITelegramActions) Proxy.newProxyInstance(ITelegramActions.class.getClassLoader(),
|
||||
new Class[] { ITelegramActions.class }, (Object proxy, Method method, Object[] args) -> {
|
||||
Method m = actions.getClass().getDeclaredMethod(method.getName(),
|
||||
method.getParameterTypes());
|
||||
return m.invoke(actions, args);
|
||||
});
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Actions is not an instance of TelegramActions");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(@Nullable ThingHandler handler) {
|
||||
this.handler = (TelegramHandler) handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.telegram.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link TelegramBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Jens Runge - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TelegramBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "telegram";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID TELEGRAM_THING = new ThingTypeUID(BINDING_ID, "telegramBot");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String LASTMESSAGETEXT = "lastMessageText";
|
||||
public static final String LASTMESSAGEURL = "lastMessageURL";
|
||||
public static final String LASTMESSAGEDATE = "lastMessageDate";
|
||||
public static final String LASTMESSAGENAME = "lastMessageName";
|
||||
public static final String LASTMESSAGEUSERNAME = "lastMessageUsername";
|
||||
public static final String CHATID = "chatId";
|
||||
public static final String REPLYID = "replyId";
|
||||
public static final String LONGPOLLINGTIME = "longPollingTime";
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.telegram.internal;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link TelegramConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Jens Runge - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TelegramConfiguration {
|
||||
|
||||
/**
|
||||
* Sample configuration parameter. Replace with your own.
|
||||
*/
|
||||
private @Nullable String botUsername;
|
||||
private @Nullable String botToken;
|
||||
private @Nullable List<String> chatIds;
|
||||
private @Nullable String proxyHost;
|
||||
private @Nullable Integer proxyPort;
|
||||
private @Nullable String proxyType;
|
||||
private String parseMode = "";
|
||||
private int longPollingTime;
|
||||
|
||||
public @Nullable String getBotUsername() {
|
||||
return botUsername;
|
||||
}
|
||||
|
||||
public @Nullable String getBotToken() {
|
||||
return botToken;
|
||||
}
|
||||
|
||||
public @Nullable List<String> getChatIds() {
|
||||
return chatIds;
|
||||
}
|
||||
|
||||
public String getParseMode() {
|
||||
return parseMode;
|
||||
}
|
||||
|
||||
public @Nullable String getProxyHost() {
|
||||
return proxyHost;
|
||||
}
|
||||
|
||||
public @Nullable Integer getProxyPort() {
|
||||
return proxyPort;
|
||||
}
|
||||
|
||||
public @Nullable String getProxyType() {
|
||||
return proxyType;
|
||||
}
|
||||
|
||||
public int getLongPollingTime() {
|
||||
return longPollingTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.telegram.internal;
|
||||
|
||||
import static org.openhab.binding.telegram.internal.TelegramBindingConstants.*;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.telegram.bot.TelegramActions;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.State;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.pengrad.telegrambot.TelegramBot;
|
||||
import com.pengrad.telegrambot.TelegramException;
|
||||
import com.pengrad.telegrambot.UpdatesListener;
|
||||
import com.pengrad.telegrambot.model.Message;
|
||||
import com.pengrad.telegrambot.model.PhotoSize;
|
||||
import com.pengrad.telegrambot.model.Update;
|
||||
import com.pengrad.telegrambot.model.request.ParseMode;
|
||||
import com.pengrad.telegrambot.request.BaseRequest;
|
||||
import com.pengrad.telegrambot.request.GetFile;
|
||||
import com.pengrad.telegrambot.request.GetUpdates;
|
||||
import com.pengrad.telegrambot.response.BaseResponse;
|
||||
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
/**
|
||||
* The {@link TelegramHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* @author Jens Runge - Initial contribution
|
||||
* @author Alexander Krasnogolowy - using Telegram library from pengrad
|
||||
* @author Jan N. Klug - handle file attachments
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TelegramHandler extends BaseThingHandler {
|
||||
|
||||
@NonNullByDefault
|
||||
private class ReplyKey {
|
||||
|
||||
final Long chatId;
|
||||
final String replyId;
|
||||
|
||||
public ReplyKey(Long chatId, String replyId) {
|
||||
this.chatId = chatId;
|
||||
this.replyId = replyId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(chatId, replyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
ReplyKey other = (ReplyKey) obj;
|
||||
return Objects.equals(chatId, other.chatId) && Objects.equals(replyId, other.replyId);
|
||||
}
|
||||
}
|
||||
|
||||
private final List<Long> authorizedSenderChatId = new ArrayList<>();
|
||||
private final List<Long> receiverChatId = new ArrayList<>();
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TelegramHandler.class);
|
||||
private @Nullable ScheduledFuture<?> thingOnlineStatusJob;
|
||||
|
||||
// Keep track of the callback id created by Telegram. This must be sent back in
|
||||
// the answerCallbackQuery
|
||||
// to stop the progress bar in the Telegram client
|
||||
private final Map<ReplyKey, String> replyIdToCallbackId = new HashMap<>();
|
||||
// Keep track of message id sent with reply markup because we want to remove the
|
||||
// markup after the user provided an
|
||||
// answer and need the id of the original message
|
||||
private final Map<ReplyKey, Integer> replyIdToMessageId = new HashMap<>();
|
||||
|
||||
private @Nullable TelegramBot bot;
|
||||
private @Nullable OkHttpClient botLibClient;
|
||||
private @Nullable HttpClient downloadDataClient;
|
||||
private @Nullable ParseMode parseMode;
|
||||
|
||||
public TelegramHandler(Thing thing, @Nullable HttpClient httpClient) {
|
||||
super(thing);
|
||||
downloadDataClient = httpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
// no commands to handle
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
TelegramConfiguration config = getConfigAs(TelegramConfiguration.class);
|
||||
|
||||
String botToken = config.getBotToken();
|
||||
|
||||
List<String> chatIds = config.getChatIds();
|
||||
if (chatIds != null) {
|
||||
createReceiverChatIdsAndAuthorizedSenderChatIds(chatIds);
|
||||
}
|
||||
String parseModeAsString = config.getParseMode();
|
||||
if (!parseModeAsString.isEmpty()) {
|
||||
try {
|
||||
parseMode = ParseMode.valueOf(parseModeAsString);
|
||||
} catch (IllegalArgumentException e) {
|
||||
logger.warn("parseMode is invalid and will be ignored. Only Markdown or HTML are allowed values");
|
||||
}
|
||||
}
|
||||
|
||||
OkHttpClient.Builder prepareConnection = new OkHttpClient.Builder().connectTimeout(75, TimeUnit.SECONDS)
|
||||
.readTimeout(75, TimeUnit.SECONDS);
|
||||
|
||||
String proxyHost = config.getProxyHost();
|
||||
Integer proxyPort = config.getProxyPort();
|
||||
String proxyType = config.getProxyType();
|
||||
|
||||
if (proxyHost != null && proxyPort != null) {
|
||||
InetSocketAddress proxyAddr = new InetSocketAddress(proxyHost, proxyPort);
|
||||
|
||||
Proxy.Type proxyTypeParam = Proxy.Type.SOCKS;
|
||||
|
||||
if ("HTTP".equals(proxyType)) {
|
||||
proxyTypeParam = Proxy.Type.HTTP;
|
||||
}
|
||||
|
||||
Proxy proxy = new Proxy(proxyTypeParam, proxyAddr);
|
||||
|
||||
logger.debug("{} Proxy {}:{} is used for telegram ", proxyTypeParam, proxyHost, proxyPort);
|
||||
prepareConnection.proxy(proxy);
|
||||
}
|
||||
|
||||
botLibClient = prepareConnection.build();
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
delayThingOnlineStatus();
|
||||
TelegramBot localBot = bot = new TelegramBot.Builder(botToken).okHttpClient(botLibClient).build();
|
||||
localBot.setUpdatesListener(this::handleUpdates, this::handleExceptions,
|
||||
getGetUpdatesRequest(config.getLongPollingTime()));
|
||||
}
|
||||
|
||||
private void createReceiverChatIdsAndAuthorizedSenderChatIds(List<String> chatIds) {
|
||||
authorizedSenderChatId.clear();
|
||||
receiverChatId.clear();
|
||||
|
||||
for (String chatIdStr : chatIds) {
|
||||
String trimmedChatId = chatIdStr.trim();
|
||||
try {
|
||||
if (trimmedChatId.startsWith("<")) {
|
||||
// inbound only
|
||||
authorizedSenderChatId.add(Long.valueOf(trimmedChatId.substring(1)));
|
||||
} else if (trimmedChatId.startsWith(">")) {
|
||||
// outbound only
|
||||
receiverChatId.add(Long.valueOf(trimmedChatId.substring(1)));
|
||||
} else {
|
||||
// bi-directional (default)
|
||||
Long chatId = Long.valueOf(trimmedChatId);
|
||||
authorizedSenderChatId.add(chatId);
|
||||
receiverChatId.add(chatId);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("The chat id {} is not a number and will be ignored", chatIdStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private GetUpdates getGetUpdatesRequest(int longPollingTime) {
|
||||
return new GetUpdates().timeout(longPollingTime * 1000);
|
||||
}
|
||||
|
||||
private void handleExceptions(TelegramException exception) {
|
||||
final TelegramBot localBot = bot;
|
||||
if (exception != null) {
|
||||
if (exception.response() != null) {
|
||||
BaseResponse localResponse = exception.response();
|
||||
if (localResponse.errorCode() == 401) { // unauthorized
|
||||
cancelThingOnlineStatusJob();
|
||||
if (localBot != null) {
|
||||
localBot.removeGetUpdatesListener();
|
||||
}
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Unauthorized attempt to connect to the Telegram server, please check if the bot token is valid");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (exception.getCause() != null) { // cause is only non-null in case of an IOException
|
||||
cancelThingOnlineStatusJob();
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, exception.getMessage());
|
||||
delayThingOnlineStatus();
|
||||
return;
|
||||
}
|
||||
logger.warn("Telegram exception: {}", exception.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private String getFullDownloadUrl(String fileId) {
|
||||
final TelegramBot bot = this.bot;
|
||||
if (bot == null) {
|
||||
return "";
|
||||
}
|
||||
return bot.getFullFilePath(bot.execute(new GetFile(fileId)).file());
|
||||
}
|
||||
|
||||
private int handleUpdates(List<Update> updates) {
|
||||
final TelegramBot localBot = bot;
|
||||
if (localBot == null) {
|
||||
logger.warn("Cannot process updates if no telegram bot is present.");
|
||||
return UpdatesListener.CONFIRMED_UPDATES_NONE;
|
||||
}
|
||||
|
||||
cancelThingOnlineStatusJob();
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
for (Update update : updates) {
|
||||
String lastMessageText = null;
|
||||
Integer lastMessageDate = null;
|
||||
String lastMessageFirstName = null;
|
||||
String lastMessageLastName = null;
|
||||
String lastMessageUsername = null;
|
||||
String lastMessageURL = null;
|
||||
Long chatId = null;
|
||||
String replyId = null;
|
||||
|
||||
Message message = update.message();
|
||||
|
||||
if (message != null) {
|
||||
chatId = message.chat().id();
|
||||
if (!authorizedSenderChatId.contains(chatId)) {
|
||||
logger.warn(
|
||||
"Ignored message from unknown chat id {}. If you know the sender of that chat, add it to the list of chat ids in the thing configuration to authorize it",
|
||||
chatId);
|
||||
continue; // this is very important regarding security to avoid commands from an unknown
|
||||
// chat
|
||||
}
|
||||
|
||||
// process content
|
||||
if (message.audio() != null) {
|
||||
lastMessageURL = getFullDownloadUrl(message.audio().fileId());
|
||||
} else if (message.document() != null) {
|
||||
lastMessageURL = getFullDownloadUrl(message.document().fileId());
|
||||
} else if (message.photo() != null) {
|
||||
PhotoSize[] photoSizes = message.photo();
|
||||
logger.trace("Received photos {}", Arrays.asList(photoSizes));
|
||||
Arrays.sort(photoSizes, Comparator.comparingInt(PhotoSize::fileSize).reversed());
|
||||
lastMessageURL = getFullDownloadUrl(photoSizes[0].fileId());
|
||||
} else if (message.text() != null) {
|
||||
lastMessageText = message.text();
|
||||
} else if (message.video() != null) {
|
||||
lastMessageURL = getFullDownloadUrl(message.video().fileId());
|
||||
} else if (message.voice() != null) {
|
||||
lastMessageURL = getFullDownloadUrl(message.voice().fileId());
|
||||
} else {
|
||||
logger.debug("Received message with unsupported content: {}", message);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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 (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);
|
||||
} else {
|
||||
logger.warn("The received callback query {} has not the right format (must be seperated by spaces)",
|
||||
update.callbackQuery().data());
|
||||
}
|
||||
}
|
||||
updateChannel(LASTMESSAGETEXT, lastMessageText != null ? new StringType(lastMessageText) : UnDefType.NULL);
|
||||
updateChannel(LASTMESSAGEURL, lastMessageURL != null ? new StringType(lastMessageURL) : UnDefType.NULL);
|
||||
updateChannel(LASTMESSAGEDATE, lastMessageDate != null
|
||||
? new DateTimeType(
|
||||
ZonedDateTime.ofInstant(Instant.ofEpochSecond(lastMessageDate.intValue()), ZoneOffset.UTC))
|
||||
: UnDefType.NULL);
|
||||
updateChannel(LASTMESSAGENAME, (lastMessageFirstName != null || lastMessageLastName != null)
|
||||
? new StringType((lastMessageFirstName != null ? lastMessageFirstName + " " : "")
|
||||
+ (lastMessageLastName != null ? lastMessageLastName : ""))
|
||||
: UnDefType.NULL);
|
||||
updateChannel(LASTMESSAGEUSERNAME,
|
||||
lastMessageUsername != null ? new StringType(lastMessageUsername) : UnDefType.NULL);
|
||||
updateChannel(CHATID, chatId != null ? new StringType(chatId.toString()) : UnDefType.NULL);
|
||||
updateChannel(REPLYID, replyId != null ? new StringType(replyId) : UnDefType.NULL);
|
||||
}
|
||||
return UpdatesListener.CONFIRMED_UPDATES_ALL;
|
||||
}
|
||||
|
||||
private synchronized void delayThingOnlineStatus() {
|
||||
thingOnlineStatusJob = scheduler.schedule(() -> {
|
||||
// if no error was returned within 10s, we assume the initialization went well
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}, 10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private synchronized void cancelThingOnlineStatusJob() {
|
||||
final ScheduledFuture<?> thingOnlineStatusJob = this.thingOnlineStatusJob;
|
||||
if (thingOnlineStatusJob != null) {
|
||||
thingOnlineStatusJob.cancel(true);
|
||||
this.thingOnlineStatusJob = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
logger.debug("Trying to dispose Telegram client");
|
||||
cancelThingOnlineStatusJob();
|
||||
OkHttpClient localClient = botLibClient;
|
||||
TelegramBot localBot = bot;
|
||||
if (localClient != null && localBot != null) {
|
||||
localBot.removeGetUpdatesListener();
|
||||
localClient.dispatcher().executorService().shutdown();
|
||||
localClient.connectionPool().evictAll();
|
||||
logger.debug("Telegram client closed");
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
public void updateChannel(String channelName, State state) {
|
||||
updateState(new ChannelUID(getThing().getUID(), channelName), state);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(TelegramActions.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* get the list of all authorized senders
|
||||
*
|
||||
* @return list of chatIds
|
||||
*/
|
||||
public List<Long> getAuthorizedSenderChatIds() {
|
||||
return authorizedSenderChatId;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the list of all receivers
|
||||
*
|
||||
* @return list of chatIds
|
||||
*/
|
||||
public List<Long> getReceiverChatIds() {
|
||||
return receiverChatId;
|
||||
}
|
||||
|
||||
public void addMessageId(Long chatId, String replyId, Integer messageId) {
|
||||
replyIdToMessageId.put(new ReplyKey(chatId, replyId), messageId);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getCallbackId(Long chatId, String replyId) {
|
||||
return replyIdToCallbackId.get(new ReplyKey(chatId, replyId));
|
||||
}
|
||||
|
||||
public Integer removeMessageId(Long chatId, String replyId) {
|
||||
return replyIdToMessageId.remove(new ReplyKey(chatId, replyId));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ParseMode getParseMode() {
|
||||
return parseMode;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Nullable
|
||||
public <T extends BaseRequest, R extends BaseResponse> R execute(BaseRequest<T, R> request) {
|
||||
TelegramBot localBot = bot;
|
||||
return localBot != null ? localBot.execute(request) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public HttpClient getClient() {
|
||||
return downloadDataClient;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.telegram.internal;
|
||||
|
||||
import static org.openhab.binding.telegram.internal.TelegramBindingConstants.TELEGRAM_THING;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link TelegramHandlerFactory} is responsible for creating things and
|
||||
* thing handlers.
|
||||
*
|
||||
* @author Jens Runge - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.telegram", service = ThingHandlerFactory.class)
|
||||
public class TelegramHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(TELEGRAM_THING);
|
||||
|
||||
private final HttpClient httpClient;
|
||||
|
||||
@Activate
|
||||
public TelegramHandlerFactory(@Reference final HttpClientFactory httpClientFactory) {
|
||||
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
|
||||
if (TELEGRAM_THING.equals(thingTypeUID)) {
|
||||
return new TelegramHandler(thing, httpClient);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<binding:binding id="telegram" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||
|
||||
<name>Telegram Binding</name>
|
||||
<description>This is the binding for Telegram. It allows to send and receive messages.</description>
|
||||
<author>Alexander Krasnogolowy</author>
|
||||
|
||||
</binding:binding>
|
||||
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="telegram"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||
|
||||
<thing-type id="telegramBot">
|
||||
<label>Telegram Bot</label>
|
||||
<description>Thing to receive the latest message send to a Telegram Bot.</description>
|
||||
|
||||
<channels>
|
||||
<channel id="lastMessageText" typeId="lastMessageText"/>
|
||||
<channel id="lastMessageURL" typeId="lastMessageURL"/>
|
||||
<channel id="lastMessageDate" typeId="lastMessageDate"/>
|
||||
<channel id="lastMessageName" typeId="lastMessageName"/>
|
||||
<channel id="lastMessageUsername" typeId="lastMessageUsername"/>
|
||||
<channel id="chatId" typeId="chatId"/>
|
||||
<channel id="replyId" typeId="replyId"/>
|
||||
</channels>
|
||||
|
||||
<config-description>
|
||||
<parameter name="botToken" type="text" required="true">
|
||||
<label>Bot Token</label>
|
||||
<description>Enter the bot token you received from the "BotFather".</description>
|
||||
</parameter>
|
||||
<parameter name="chatIds" type="text" required="true" multiple="true">
|
||||
<label>Chat Id(s)</label>
|
||||
<description>One or more chat id(s). Access modifiers ("<" for inbound only, ">" for outbound only) can be
|
||||
used as prefix (optional).</description>
|
||||
</parameter>
|
||||
<parameter name="parseMode" type="text" required="false">
|
||||
<label>Parse Mode</label>
|
||||
<options>
|
||||
<option value="">No Formatting</option>
|
||||
<option value="HTML">HTML</option>
|
||||
<option value="Markdown">Markdown</option>
|
||||
</options>
|
||||
<default></default>
|
||||
<description>Support for formatted messages, values: Markdown or HTML. Default: no formatting is used.</description>
|
||||
</parameter>
|
||||
<parameter name="proxyHost" type="text">
|
||||
<context>network-address</context>
|
||||
<label>Proxy Host</label>
|
||||
<description>Enter your proxy host. It will be used for telegram binding only and doesn't affect entire system.</description>
|
||||
</parameter>
|
||||
<parameter name="proxyPort" type="integer" max="65535" min="1" required="false">
|
||||
<label>Proxy Port</label>
|
||||
<description>Enter your proxy port.</description>
|
||||
</parameter>
|
||||
<parameter name="proxyType" type="text" required="false">
|
||||
<label>Proxy Type</label>
|
||||
<options>
|
||||
<option value="SOCKS5">SOCKS5</option>
|
||||
<option value="HTTP">HTTP</option>
|
||||
</options>
|
||||
<default>SOCKS5</default>
|
||||
<description>Enter your proxy type. Default: SOCKS5</description>
|
||||
</parameter>
|
||||
<parameter name="longPollingTime" type="integer" min="0" max="50" unit="s">
|
||||
<label>Long Polling Time</label>
|
||||
<description>Enter the long polling time in seconds.</description>
|
||||
<default>25</default>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="lastMessageText">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Message Text</label>
|
||||
<description>Contains the latest message text as a string</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lastMessageURL">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Message URL</label>
|
||||
<description>Contains the URL of the latest message</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lastMessageDate">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Last Message Date</label>
|
||||
<description>Contains the latest message date as a DateTime</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lastMessageName">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Message Name</label>
|
||||
<description>Contains the latest message senders name as a string</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="lastMessageUsername">
|
||||
<item-type>String</item-type>
|
||||
<label>Last Message Username</label>
|
||||
<description>Contains the latest message senders username as a string</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="chatId">
|
||||
<item-type>String</item-type>
|
||||
<label>Chat Id</label>
|
||||
<description>Contains the id of chat from where the message was received.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="replyId">
|
||||
<item-type>String</item-type>
|
||||
<label>Reply Id</label>
|
||||
<description>Contains the id of the reply which was passed to sendTelegram() as replyId. This id can be used to have
|
||||
an unambiguous assignment of the user reply to the message which was sent by the bot.</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
Reference in New Issue
Block a user