Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Telegram Plugin: improve web interface and docu #831

Merged
merged 3 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 32 additions & 31 deletions telegram/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
#
# This file is part of SmartHomeNG.
#
# Telegram Plugin for querying and updating items or sending messages via Telegram
#
# SmartHomeNG is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
Expand Down Expand Up @@ -66,7 +68,7 @@

class Telegram(SmartPlugin):

PLUGIN_VERSION = "2.0.0"
PLUGIN_VERSION = "2.0.1"

_items = [] # all items using attribute ``telegram_message``
_items_info = {} # dict used whith the info-command: key = attribute_value, val= item_list telegram_info
Expand All @@ -83,12 +85,12 @@ def __init__(self, sh):
"""

self.logger.info('Init telegram plugin')

# Call init code of parent class (SmartPlugin or MqttPlugin)
super().__init__()
if not self._init_complete:
return

if self.logger.isEnabledFor(logging.DEBUG):
self.logger.debug(f"init {__name__}")
self._init_complete = False
Expand All @@ -113,12 +115,12 @@ def __init__(self, sh):
self._pretty_thread_names = self.get_parameter_value('pretty_thread_names')
self._resend_delay = self.get_parameter_value('resend_delay')
self._resend_attemps = self.get_parameter_value('resend_attemps')

self._bot = None
self._queue = Queue()

self._application = Application.builder().token(self._token).build()

if self.logger.isEnabledFor(logging.DEBUG):
self.logger.debug("adding command handlers to application")

Expand All @@ -134,15 +136,15 @@ def __init__(self, sh):
self._application.add_handler(CommandHandler('control', self.cHandler_control))
# Filters.text includes also commands, starting with ``/`` so it is needed to exclude them.
self._application.add_handler(MessageHandler(filters.TEXT & (~filters.COMMAND), self.mHandler))

self.init_webinterface()
if not self.init_webinterface(WebInterface):
self.logger.error("Unable to start Webinterface")
self._init_complete = False
else:
if self.logger.isEnabledFor(logging.DEBUG):
self.logger.debug("Init complete")

self._init_complete = True

def __call__(self, msg, chat_id=None):
Expand All @@ -161,22 +163,22 @@ def run(self):
"""
if self.logger.isEnabledFor(logging.DEBUG):
self.logger.debug("Run method called")

self.logics = Logics.get_instance() # Returns the instance of the Logics class, to be used to access the logics-api

self.alive = True

self._loop.run_until_complete(self.run_coros())
if self.logger.isEnabledFor(logging.DEBUG):
self.logger.debug(f"Run method ended")

def stop(self):
"""
This is called when the plugins thread is about to stop
"""
if self.logger.isEnabledFor(logging.DEBUG):
self.logger.debug("stop telegram plugin")

try:
if self._bye_msg:
cids = [key for key, value in self._chat_ids_item().items() if value == 1]
Expand All @@ -185,7 +187,7 @@ def stop(self):
self.logger.debug("sent bye message")
except Exception as e:
self.logger.error(f"could not send bye message [{e}]")

time.sleep(1)
self.alive = False # Clears the infiniti loop in sendQueue
try:
Expand All @@ -211,18 +213,18 @@ async def run_coros(self):
self._taskConn = asyncio.create_task(self.connect())
self._taskQueue = asyncio.create_task(self.sendQueue())
await asyncio.gather(self._taskConn, self._taskQueue)

async def connect(self):
"""
Connects
Connects
"""
if self.logger.isEnabledFor(logging.DEBUG):
self.logger.debug("connect method called")
try:
await self._application.initialize()
await self._application.start()
self._updater = self._application.updater

q = await self._updater.start_polling(timeout=self._long_polling_timeout, error_callback=self.error_handler)

if self.logger.isEnabledFor(logging.DEBUG):
Expand All @@ -235,14 +237,14 @@ async def connect(self):
self.logger.debug(f"sent welcome message {self._welcome_msg}")
cids = [key for key, value in self._chat_ids_item().items() if value == 1]
self.msg_broadcast(self._welcome_msg, chat_id=cids)

except TelegramError as e:
# catch Unauthorized errors due to an invalid token
self.logger.error(f"Unable to start up Telegram conversation. Maybe an invalid token? {e}")
return False
if self.logger.isEnabledFor(logging.DEBUG):
self.logger.debug("connect method end")

def error_handler(self, update, context):
"""
Just logs an error in case of a problem
Expand All @@ -257,7 +259,7 @@ async def sendQueue(self):
Waiting for messages to be sent in the queue and sending them to Telegram.
The queue expects a dictionary with various parameters
dict txt: {"msgType":"Text", "msg":msg, "chat_id":chat_id, "reply_markup":reply_markup, "parse_mode":parse_mode }
dict photo: {"msgType":"Photo", "photofile_or_url":photofile_or_url, "chat_id":chat_id, "caption":caption, "local_prepare":local_prepare}
dict photo: {"msgType":"Photo", "photofile_or_url":photofile_or_url, "chat_id":chat_id, "caption":caption, "local_prepare":local_prepare}
"""
if self.logger.isEnabledFor(logging.DEBUG):
self.logger.debug(f"sendQueue called - queue: [{self._queue}]")
Expand All @@ -275,26 +277,26 @@ async def sendQueue(self):
resendDelay = message["resendDelay"]
if "resendAttemps" in message:
resendAttemps = message["resendAttemps"]

if resendDelay <= 0:
if self.logger.isEnabledFor(logging.DEBUG):
self.logger.debug(f"message queue {message}")
if message["msgType"] == "Text":
result = await self.async_msg_broadcast(message["msg"], message["chat_id"], message["reply_markup"], message["parse_mode"])
elif message["msgType"] == "Photo":
result = await self.async_photo_broadcast(message["photofile_or_url"], message["caption"], message["chat_id"], message["local_prepare"])

# An error occurred while sending - result: list containing the dic of the failed send attempt
if result:
if result:
for res in result:
resendAttemps+=1
if resendAttemps > self._resend_attemps:
if self.logger.isEnabledFor(logging.DEBUG):
self.logger.debug(f"don't initiate any further send attempts for: {res}")
break
break
else:
resendDelay = self._resend_delay

# Including the sendDelay and sendAttempts in the queue message for the next send attempt.
res["resendDelay"] = resendDelay
res["resendAttemps"] = resendAttemps
Expand All @@ -312,7 +314,7 @@ async def sendQueue(self):

async def disconnect(self):
"""
Stop listening to push updates and logout of this istances Apple TV
Stop listening to push updates and shutdown
"""
self.logger.info(f"disconnecting")

Expand Down Expand Up @@ -522,7 +524,7 @@ async def async_msg_broadcast(self, msg, chat_id=None, reply_markup=None, parse_
sendResult = []
if self.logger.isEnabledFor(logging.DEBUG):
self.logger.debug(f"async msg_broadcast called")

for cid in self.get_chat_id_list(chat_id):
try:
response = await self._bot.send_message(chat_id=cid, text=msg, reply_markup=reply_markup, parse_mode=parse_mode)
Expand All @@ -543,7 +545,7 @@ async def async_msg_broadcast(self, msg, chat_id=None, reply_markup=None, parse_
return None
else:
return sendResult


def msg_broadcast(self, msg, chat_id=None, reply_markup=None, parse_mode=None):
if self.alive:
Expand All @@ -554,7 +556,7 @@ def msg_broadcast(self, msg, chat_id=None, reply_markup=None, parse_mode=None):
self._queue.put(q_msg)
except Exception as e:
self.logger.debug(f"Exception '{e}' occurred, please inform plugin maintainer!")

async def async_photo_broadcast(self, photofile_or_url, caption=None, chat_id=None, local_prepare=True):
"""
Send an image to the given chat
Expand Down Expand Up @@ -1042,7 +1044,7 @@ def list_items_control(self):
if not text:
text = self.translate("no items found with the attribute %s") % ITEM_ATTR_CONTROL
#self._bot.sendMessage(chat_id=chat_id, text=text)
return text
return text

async def change_item(self, update, context, name, dicCtl):
"""
Expand Down Expand Up @@ -1146,4 +1148,3 @@ async def telegram_change_item_timeout(self, **kwargs):
self._waitAnswer = None
# self._bot.send_message(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard": self.create_control_reply_markup()})
await context.bot.sendMessage(chat_id=update.message.chat.id, text=self.translate("Control/Change item-values:"), reply_markup={"keyboard": self.create_control_reply_markup()})

Binary file added telegram/assets/telegram_webif.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed telegram/assets/webif1.png
Binary file not shown.
Binary file removed telegram/assets/webif2.png
Binary file not shown.
7 changes: 3 additions & 4 deletions telegram/plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ plugin:
en: 'Connects to the telegram messenger service'
maintainer: gamade, ivan73, bmxp
state: ready
tester: NONE
tester: onkelandy
keywords: telegram chat messenger photo
documentation: http://smarthomeng.de/user/plugins/telegram/user_doc.html
support: https://knx-user-forum.de/forum/supportforen/smarthome-py/1548691-support-thread-für-das-telegram-plugin

version: 2.0.0 # Plugin version
version: 2.0.1 # Plugin version
sh_minversion: 1.9.5 # minimum shNG version to use this plugin
# sh_maxversion: # maximum shNG version to use this plugin (leave empty if latest)
# py_minversion: 3.6 # minimum Python version to use for this plugin
Expand Down Expand Up @@ -147,7 +146,7 @@ item_attributes:
telegram_control:
type: str
description:
de: 'Item schreiben per Telegram Keyboard. Der Wert des Attributes (mit mehreren Paramtern) bestimmt das Telegram Keyboard Kommando'
de: 'Item schreiben per Telegram Keyboard. Den Wert des Attributes (mit mehreren Paramtern) bestimmt das Telegram Keyboard Kommando'
en: 'Write items with telegram keyboard. The value of the attribute (with parameters) defines the telegram keyboard command'

logic_parameters: NONE
Expand Down
63 changes: 47 additions & 16 deletions telegram/user_doc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
telegram
========

.. image:: webif/static/img/plugin_logo.svg
:alt: plugin logo
:width: 300px
:height: 300px
:scale: 50 %
:align: left

Das Plugin dient zum Senden und Empfangen von Nachrichten über den
`Telegram Nachrichten Dienst <https://telegram.org/>`_

Expand Down Expand Up @@ -71,7 +78,7 @@ Im Dictionary sind Paare von Chat-ID und Berechtigung gespeichert.
# Beispiel value: '{ 3234123342: 1, 9234123341: 0 }'
# Ein Dictionary mit chat id und:
# 2 für Lese und Schreibzugriff ohne Willkommens- und Ende Nachricht
# 1 für Lese und Schreibzugriff
# 1 für Lese und Schreibzugriff
# 0 für einen nur Lese-Zugriff
# Nachfolgend ein Chat dem Lese- und Schreibrechte gewährt werden
value: '{ 3234123342: 1 }'
Expand Down Expand Up @@ -148,7 +155,7 @@ Einfaches Beispiel
telegram_value_match_regex: (true|True|1)

Dadurch wird auf eine mehrfache Zuweisung des Items mit dem Wert ``True`` nur einmal mit einer Nachricht reagiert. Um eine weitere Nachricht zu generieren
muss das Item zunächst wieder den Wert ``False`` annehmen. Das Attribut ``telegram_value_match_regex`` filtert den Wert so das es bei der Änderung des Itemwertes
muss das Item zunächst wieder den Wert ``False`` annehmen. Das Attribut ``telegram_value_match_regex`` filtert den Wert so das es bei der Änderung des Itemwertes
auf ``False`` zu keiner Meldung *Es klingelt an der Tür* kommt.


Expand All @@ -173,8 +180,8 @@ Beispiel
cache: True
telegram_message: "TestBool: [VALUE]"
telegram_value_match_regex: 1 # nur Nachricht senden wenn 1 (True)


telegram_message_chat_id
------------------------
Ist zusätzlich zum Attribut ``telegram_message`` auch das Attribut ``telegram_message_chat_id`` gesetzt, wird die Nachricht nur an die dort angegebene Chat-ID (hier 3234123342) gesendet.
Expand Down Expand Up @@ -277,24 +284,24 @@ Bei Auswahl eines dieser Kommandos im Telegram Client kann dann ein Item vom Typ

``name``
Item wird mit diesem Namen im Bot als Kommando dargestellt
``type``
``type``
Möglichkeiten: on, off, onoff, toggle, num
on
on
* nur Einschalten ist möglich
off
off
* nur Ausschalten ist möglich
onoff
* das Ein- und Ausschalten muss mit einen weiteren Kommando vom Tastaturmenu ausgewählt werden
onoff
* das Ein- und Ausschalten muss mit einen weiteren Kommando vom Tastaturmenu ausgewählt werden
[On] [Off] (nach einem Timeout ohne Antwort wird der Befehl abgebrochen)
toggle
toggle
* der Wert des Items wird umgeschltet (0 zu 1; 1 zu 0)
num
num
* es kann eine Zahl an SH gesendet werden und das entsprechende Item wird damit geschrieben. (nach einem Timeout ohne Antwort wird der Befehl abgebrochen)
``question``
Sicherheitsabfrage vor dem Schalten des Items (verwendbar bei type:on/off/toggle - nach einem Timeout ohne Antwort wird der Befehl abgebrochen) [Yes] [No]
``min``
``min``
Minimalwert (verwendbar bei type:num)
``max``
``max``
Maximalwert (verwendbar bei type:num)
``timeout``
Zeit nach welcher der Befehl mit Antwort(onoff/question/num) abgebrochen wird (default 20Sekunden)
Expand Down Expand Up @@ -390,8 +397,8 @@ Die folgende Beispiellogik zeigt einige Nutzungsmöglichkeiten für die Funktion

.. code:: python

telegram_plugin = sh.plugins.return_plugin('telegram')
telegram_plugin = sh.plugins.return_plugin('telegram')

# Eine Nachricht `Hello world!` wird an alle vertrauten Chat Ids gesendet
msg = "Hello world!"
telegram_plugin.msg_broadcast(msg)
Expand Down Expand Up @@ -590,4 +597,28 @@ dargestellt und die entsprechenden Aktionen ausgeführt.

# Message senden
if msg != '':
telegram_plugin.msg_broadcast(msg, message_chat_id, reply_markup, parse_mode)
telegram_plugin.msg_broadcast(msg, message_chat_id, reply_markup, parse_mode)

Web Interface
=============

Das Webinterface bietet folgende Informationen:

- **Allgemeines**: Oben rechts wird das Timeout, Begrüßungs- und Verabschiedungsnachricht angezeigt

- **Output Items**: Sämtliche Items, die zum Senden einer Nachricht beitragen

- **Input Items**: Items, über die Nachrichten empfangen werden können

- **Telegram Control**: Items, die über Telegram geändert werden können

- **Telegram Infos**: Befehle mit den zugehörigen Items, deren Werte auf eine Abfrage hin kommuniziert werden

- **Chat IDs**: Registrierte Chat IDs inkl. Angabe der Zugriffe

.. image:: assets/telegram_webif.png
:height: 1584px
:width: 3340px
:scale: 25%
:alt: Web Interface
:align: center
Loading