added migrated 2.x add-ons
Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
@@ -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