mirror of
https://github.com/hemna/aprsd-telegram-plugin.git
synced 2024-11-26 18:08:39 -05:00
First version working
This commit is contained in:
parent
375037d6a2
commit
754ff73d7e
79
README.rst
79
README.rst
@ -7,43 +7,20 @@ aprsd-telegram-plugin
|
|||||||
|
|
||||||
|pre-commit|
|
|pre-commit|
|
||||||
|
|
||||||
.. |PyPI| image:: https://img.shields.io/pypi/v/aprsd-telegram-plugin.svg
|
|
||||||
:target: https://pypi.org/project/aprsd-telegram-plugin/
|
|
||||||
:alt: PyPI
|
|
||||||
.. |Status| image:: https://img.shields.io/pypi/status/aprsd-telegram-plugin.svg
|
|
||||||
:target: https://pypi.org/project/aprsd-telegram-plugin/
|
|
||||||
:alt: Status
|
|
||||||
.. |Python Version| image:: https://img.shields.io/pypi/pyversions/aprsd-telegram-plugin
|
|
||||||
:target: https://pypi.org/project/aprsd-telegram-plugin
|
|
||||||
:alt: Python Version
|
|
||||||
.. |License| image:: https://img.shields.io/pypi/l/aprsd-telegram-plugin
|
|
||||||
:target: https://opensource.org/licenses/MIT
|
|
||||||
:alt: License
|
|
||||||
.. |Read the Docs| image:: https://img.shields.io/readthedocs/aprsd-telegram-plugin/latest.svg?label=Read%20the%20Docs
|
|
||||||
:target: https://aprsd-telegram-plugin.readthedocs.io/
|
|
||||||
:alt: Read the documentation at https://aprsd-telegram-plugin.readthedocs.io/
|
|
||||||
.. |Tests| image:: https://github.com/hemna/aprsd-telegram-plugin/workflows/Tests/badge.svg
|
|
||||||
:target: https://github.com/hemna/aprsd-telegram-plugin/actions?workflow=Tests
|
|
||||||
:alt: Tests
|
|
||||||
.. |Codecov| image:: https://codecov.io/gh/hemna/aprsd-telegram-plugin/branch/main/graph/badge.svg
|
|
||||||
:target: https://codecov.io/gh/hemna/aprsd-telegram-plugin
|
|
||||||
:alt: Codecov
|
|
||||||
.. |pre-commit| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white
|
|
||||||
:target: https://github.com/pre-commit/pre-commit
|
|
||||||
:alt: pre-commit
|
|
||||||
|
|
||||||
|
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
|
|
||||||
* TODO
|
* Have a 2 way chat with users of Telegram messenger `http://telegram.org`
|
||||||
|
|
||||||
|
|
||||||
Requirements
|
Requirements
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* TODO
|
* You have to create a telegram bot and start the bot
|
||||||
|
* Telegram users have to add that bot and then /start
|
||||||
|
* Telegram user can then message the bot
|
||||||
|
* Only after a telegram user has successfully completed the above
|
||||||
|
can you then message a telegram user from an APRS enabled HAM Radio.
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
@ -55,6 +32,22 @@ You can install *aprsd-telegram-plugin* via pip_ from PyPI_:
|
|||||||
$ pip install aprsd-telegram-plugin
|
$ pip install aprsd-telegram-plugin
|
||||||
|
|
||||||
|
|
||||||
|
Now edit your aprsd.yml config file and add the plugin
|
||||||
|
|
||||||
|
.. code:: yaml
|
||||||
|
|
||||||
|
aprsd:
|
||||||
|
enabled_plugins:
|
||||||
|
- aprsd_telegram_plugin.telegram.TelegramChatPlugin
|
||||||
|
|
||||||
|
services:
|
||||||
|
telegram:
|
||||||
|
apiKey: <Your Telegram bot APIkey>
|
||||||
|
shortcuts:
|
||||||
|
'wb': hemna6969
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Usage
|
Usage
|
||||||
-----
|
-----
|
||||||
|
|
||||||
@ -97,3 +90,31 @@ This project was generated from `@hemna`_'s `APRSD Plugin Python Cookiecutter`_
|
|||||||
.. github-only
|
.. github-only
|
||||||
.. _Contributor Guide: CONTRIBUTING.rst
|
.. _Contributor Guide: CONTRIBUTING.rst
|
||||||
.. _Usage: https://aprsd-telegram-plugin.readthedocs.io/en/latest/usage.html
|
.. _Usage: https://aprsd-telegram-plugin.readthedocs.io/en/latest/usage.html
|
||||||
|
|
||||||
|
|
||||||
|
.. badges
|
||||||
|
|
||||||
|
.. |PyPI| image:: https://img.shields.io/pypi/v/aprsd-telegram-plugin.svg
|
||||||
|
:target: https://pypi.org/project/aprsd-telegram-plugin/
|
||||||
|
:alt: PyPI
|
||||||
|
.. |Status| image:: https://img.shields.io/pypi/status/aprsd-telegram-plugin.svg
|
||||||
|
:target: https://pypi.org/project/aprsd-telegram-plugin/
|
||||||
|
:alt: Status
|
||||||
|
.. |Python Version| image:: https://img.shields.io/pypi/pyversions/aprsd-telegram-plugin
|
||||||
|
:target: https://pypi.org/project/aprsd-telegram-plugin
|
||||||
|
:alt: Python Version
|
||||||
|
.. |License| image:: https://img.shields.io/pypi/l/aprsd-telegram-plugin
|
||||||
|
:target: https://opensource.org/licenses/MIT
|
||||||
|
:alt: License
|
||||||
|
.. |Read the Docs| image:: https://img.shields.io/readthedocs/aprsd-telegram-plugin/latest.svg?label=Read%20the%20Docs
|
||||||
|
:target: https://aprsd-telegram-plugin.readthedocs.io/
|
||||||
|
:alt: Read the documentation at https://aprsd-telegram-plugin.readthedocs.io/
|
||||||
|
.. |Tests| image:: https://github.com/hemna/aprsd-telegram-plugin/workflows/Tests/badge.svg
|
||||||
|
:target: https://github.com/hemna/aprsd-telegram-plugin/actions?workflow=Tests
|
||||||
|
:alt: Tests
|
||||||
|
.. |Codecov| image:: https://codecov.io/gh/hemna/aprsd-telegram-plugin/branch/main/graph/badge.svg
|
||||||
|
:target: https://codecov.io/gh/hemna/aprsd-telegram-plugin
|
||||||
|
:alt: Codecov
|
||||||
|
.. |pre-commit| image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white
|
||||||
|
:target: https://github.com/pre-commit/pre-commit
|
||||||
|
:alt: pre-commit
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
from aprsd import messaging, plugin, trace
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger("APRSD")
|
|
||||||
|
|
||||||
|
|
||||||
class TelegramChatPlugin(plugin.APRSDRegexCommandPluginBase):
|
|
||||||
|
|
||||||
version = "1.0"
|
|
||||||
# Look for any command that starts with w or W
|
|
||||||
command_regex = "^[wW]"
|
|
||||||
# the command is for ?
|
|
||||||
command_name = "weather"
|
|
||||||
|
|
||||||
enabled = False
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
# Do some checks here?
|
|
||||||
self.enabled = True
|
|
||||||
|
|
||||||
def create_threads(self):
|
|
||||||
"""This allows you to create and return a custom APRSDThread object.
|
|
||||||
|
|
||||||
Create a child of the aprsd.threads.APRSDThread object and return it
|
|
||||||
It will automatically get started.
|
|
||||||
|
|
||||||
You can see an example of one here:
|
|
||||||
https://github.com/craigerl/aprsd/blob/master/aprsd/threads.py#L141
|
|
||||||
"""
|
|
||||||
if self.enabled:
|
|
||||||
# You can create a background APRSDThread object here
|
|
||||||
# Just return it for example:
|
|
||||||
# https://github.com/hemna/aprsd-weewx-plugin/blob/master/aprsd_weewx_plugin/aprsd_weewx_plugin.py#L42-L50
|
|
||||||
#
|
|
||||||
return []
|
|
||||||
|
|
||||||
@trace.trace
|
|
||||||
def process(self, packet):
|
|
||||||
|
|
||||||
"""This is called when a received packet matches self.command_regex."""
|
|
||||||
|
|
||||||
LOG.info("TelegramChatPlugin Plugin")
|
|
||||||
|
|
||||||
packet.get("from")
|
|
||||||
packet.get("message_text", None)
|
|
||||||
|
|
||||||
if self.enabled:
|
|
||||||
# Now we can process
|
|
||||||
return "some reply message"
|
|
||||||
else:
|
|
||||||
LOG.warning("TelegramChatPlugin is disabled.")
|
|
||||||
return messaging.NULL_MESSAGE
|
|
210
aprsd_telegram_plugin/telegram.py
Normal file
210
aprsd_telegram_plugin/telegram.py
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from aprsd import messaging, objectstore, plugin, threads, trace
|
||||||
|
from telegram.ext import Filters, MessageHandler, Updater
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger("APRSD")
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramUsers(objectstore.ObjectStoreMixin):
|
||||||
|
"""Class to automatically store telegram user ids between starts.
|
||||||
|
|
||||||
|
Telegram doesn't provide an API for looking up an userid from
|
||||||
|
username, so we have to save it off for better user experience.
|
||||||
|
|
||||||
|
Unfortunately, we can't get the userid, until the telegram user
|
||||||
|
sends a message to the bot FIRST.
|
||||||
|
"""
|
||||||
|
_instance = None
|
||||||
|
data = {}
|
||||||
|
config = None
|
||||||
|
_shortcuts = {}
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
if cls._instance is None:
|
||||||
|
cls._instance = super().__new__(cls)
|
||||||
|
cls._instance.lock = threading.Lock()
|
||||||
|
cls._instance.config = kwargs["config"]
|
||||||
|
cls._instance.data = {}
|
||||||
|
if kwargs["config"].exists("services.telegram.shortcuts"):
|
||||||
|
cls._instance._shortcuts = kwargs["config"].get("services.telegram.shortcuts")
|
||||||
|
else:
|
||||||
|
cls._instance._shortcuts = None
|
||||||
|
cls._instance._init_store()
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
def __getitem__(self, item):
|
||||||
|
with self.lock:
|
||||||
|
if item in self._shortcuts:
|
||||||
|
item = self._shortcuts[item]
|
||||||
|
return self.data[item]
|
||||||
|
|
||||||
|
def __setitem__(self, item, value):
|
||||||
|
with self.lock:
|
||||||
|
self.data[item] = value
|
||||||
|
|
||||||
|
def __delitem__(self, item):
|
||||||
|
del self.data[item]
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
if item in self._shortcuts:
|
||||||
|
item = self._shortcuts[item]
|
||||||
|
|
||||||
|
if item in self.data:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_shortcuts(self):
|
||||||
|
return self._shortcuts
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramChatPlugin(plugin.APRSDRegexCommandPluginBase):
|
||||||
|
|
||||||
|
version = "1.0"
|
||||||
|
# Look for any command that starts with w or W
|
||||||
|
command_regex = "^[tT][gG]"
|
||||||
|
# the command is for ?
|
||||||
|
command_name = "telegram"
|
||||||
|
|
||||||
|
enabled = False
|
||||||
|
users = None
|
||||||
|
|
||||||
|
def help(self):
|
||||||
|
_help = [
|
||||||
|
"telegram: Chat with a user on telegram Messenger.",
|
||||||
|
"telegram: username has to message you first."
|
||||||
|
"tg: Send tg <shortcut/username> <message>",
|
||||||
|
]
|
||||||
|
return _help
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
self.enabled = True
|
||||||
|
# Do some checks here?
|
||||||
|
try:
|
||||||
|
self.config.check_option(["services", "telegram", "apiKey"])
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.error(f"Failed to find config telegram:apiKey {ex}")
|
||||||
|
self.enabled = False
|
||||||
|
return
|
||||||
|
|
||||||
|
token = self.config.get("services.telegram.apiKey")
|
||||||
|
|
||||||
|
self.users = TelegramUsers(config=self.config)
|
||||||
|
self.users.load()
|
||||||
|
|
||||||
|
# self.bot = telegram.Bot(token=token)
|
||||||
|
# LOG.info(self.bot.get_me())
|
||||||
|
self.updater = Updater(
|
||||||
|
token=token,
|
||||||
|
use_context=True,
|
||||||
|
persistence=False,
|
||||||
|
)
|
||||||
|
self.dispatcher = self.updater.dispatcher
|
||||||
|
self.dispatcher.add_handler(
|
||||||
|
MessageHandler(
|
||||||
|
Filters.text & (~Filters.command),
|
||||||
|
self.message_handler,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def message_handler(self, update, context):
|
||||||
|
"""This is called when a telegram users texts the bot."""
|
||||||
|
LOG.info(f"{self.__class__.__name__}: Got message {update.message.text}")
|
||||||
|
# LOG.info(f"Text {update.message.text}")
|
||||||
|
# LOG.info(f"Chat {update.message.chat}")
|
||||||
|
# LOG.info(f"From {update.message.from.username} : ")
|
||||||
|
fromcall = self.config.get("aprs.login")
|
||||||
|
tocall = self.config.get("ham.callsign")
|
||||||
|
|
||||||
|
if update.message.chat.type == "private":
|
||||||
|
LOG.info(f"Username {update.message.chat.username} - ID {update.message.chat.id}")
|
||||||
|
message = "Telegram({}): {}".format(
|
||||||
|
update.message.chat.username,
|
||||||
|
update.message.text,
|
||||||
|
)
|
||||||
|
self.users[update.message.chat.username] = update.message.chat.id
|
||||||
|
# LOG.debug(self.users)
|
||||||
|
# LOG.info(f"{message}")
|
||||||
|
msg = messaging.TextMessage(fromcall, tocall, message)
|
||||||
|
msg.send()
|
||||||
|
elif update.message.chat.type == "group":
|
||||||
|
group_name = "noidea"
|
||||||
|
message = "TelegramGroup({}): {}".format(
|
||||||
|
group_name,
|
||||||
|
update.message.text,
|
||||||
|
)
|
||||||
|
msg = messaging.TextMessage(fromcall, tocall, message)
|
||||||
|
msg.send()
|
||||||
|
|
||||||
|
def create_threads(self):
|
||||||
|
if self.enabled:
|
||||||
|
return TelegramThread(self.config, self.updater)
|
||||||
|
|
||||||
|
@trace.trace
|
||||||
|
def process(self, packet):
|
||||||
|
"""This is called when a received packet matches self.command_regex."""
|
||||||
|
LOG.info("TelegramChatPlugin Plugin")
|
||||||
|
|
||||||
|
from_callsign = packet.get("from")
|
||||||
|
message = packet.get("message_text", None)
|
||||||
|
|
||||||
|
if self.enabled:
|
||||||
|
# Now we can process
|
||||||
|
# Only allow aprsd owner to use this.
|
||||||
|
mycall = self.config["ham"]["callsign"]
|
||||||
|
|
||||||
|
# Only allow the owner of aprsd to send a tweet
|
||||||
|
if not from_callsign.startswith(mycall):
|
||||||
|
return "Unauthorized"
|
||||||
|
|
||||||
|
# Always should have format of
|
||||||
|
# <command> <username> <message>
|
||||||
|
parts = message.split(" ")
|
||||||
|
LOG.info(parts)
|
||||||
|
|
||||||
|
if len(parts) < 3:
|
||||||
|
return "invalid request"
|
||||||
|
# parts[0] is the command
|
||||||
|
username = parts[1]
|
||||||
|
msg = " ".join(parts[2:])
|
||||||
|
if username not in self.users:
|
||||||
|
# Unfortunately there is no way to lookup a user ID
|
||||||
|
# from a username right now.
|
||||||
|
return f"Need a message from {username} first"
|
||||||
|
|
||||||
|
bot = self.updater.bot
|
||||||
|
bot.sendMessage(
|
||||||
|
chat_id=self.users[username],
|
||||||
|
text=msg,
|
||||||
|
)
|
||||||
|
|
||||||
|
return messaging.NULL_MESSAGE
|
||||||
|
else:
|
||||||
|
LOG.warning("TelegramChatPlugin is disabled.")
|
||||||
|
return messaging.NULL_MESSAGE
|
||||||
|
|
||||||
|
|
||||||
|
class TelegramThread(threads.APRSDThread):
|
||||||
|
def __init__(self, config, updater):
|
||||||
|
super().__init__(self.__class__.__name__)
|
||||||
|
self.config = config
|
||||||
|
self.past = datetime.datetime.now()
|
||||||
|
self.updater = updater
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self.thread_stop = True
|
||||||
|
self.updater.stop()
|
||||||
|
TelegramUsers(config=self.config).save()
|
||||||
|
|
||||||
|
def loop(self):
|
||||||
|
"""We have to loop, so we can stop the thread upon CTRL-C"""
|
||||||
|
self.updater.start_polling(
|
||||||
|
timeout=2,
|
||||||
|
drop_pending_updates=True,
|
||||||
|
)
|
||||||
|
# so we can continue looping
|
||||||
|
return True
|
@ -1,2 +1,3 @@
|
|||||||
pbr
|
pbr
|
||||||
aprsd>=2.2.0
|
aprsd>=2.4.0
|
||||||
|
python-telegram-bot
|
||||||
|
Loading…
Reference in New Issue
Block a user