Asynchronous commands

This commit is contained in:
Johnny Gear 2025-11-16 10:34:52 -06:00
parent 9f5cec705e
commit 32a185060b
6 changed files with 88 additions and 38 deletions

View file

@ -5,7 +5,8 @@ import asyncio
from scheduling import OrderScheduler from scheduling import OrderScheduler
from orders import order_issue, order_check from orders import order_issue, order_check
from telegram import handle_commands from telegram.telegram import handle_commands
from telegram.commands import commands
from db.queries import initdb from db.queries import initdb
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -41,5 +42,8 @@ if __name__=='__main__':
else: else:
loop = asyncio.new_event_loop() loop = asyncio.new_event_loop()
s = OrderScheduler(loop) s = OrderScheduler(loop)
loop.run_until_complete(handle_commands()) loop.run_until_complete(handle_commands(
commands=commands,
loop=loop
))
loop.run_forever() loop.run_forever()

View file

@ -5,7 +5,7 @@ from util import make_session
from generate import generate_order, generate_punishment from generate import generate_order, generate_punishment
from db.queries import order_status_put, punishment_status_put, order_status_outstanding, order_status_confirm from db.queries import order_status_put, punishment_status_put, order_status_outstanding, order_status_confirm
from mastodon import Mastodon from mastodon import Mastodon
from telegram import Telegram from telegram.telegram import Telegram
from settings import MASTODON_USERNAME, ORDER_TIMEOUT, ENV from settings import MASTODON_USERNAME, ORDER_TIMEOUT, ENV
from util import timezone from util import timezone

View file

@ -15,6 +15,7 @@ MASTODON_VISIBILITY = os.environ.get('MASTODON_VISIBILITY', 'direct')
TELEGRAM_API_TOKEN = os.environ.get('TELEGRAM_API_TOKEN') TELEGRAM_API_TOKEN = os.environ.get('TELEGRAM_API_TOKEN')
TELEGRAM_CHAT_ID = int(os.environ.get('TELEGRAM_CHAT_ID')) TELEGRAM_CHAT_ID = int(os.environ.get('TELEGRAM_CHAT_ID'))
TELEGRAM_COMMAND_TIMEOUT = int(os.environ.get('TELEGRAM_COMMAND_TIMEOUT', 120))
SQLITE_DB = os.environ.get('SQLITE_DB', 'db.sqlite3') SQLITE_DB = os.environ.get('SQLITE_DB', 'db.sqlite3')

0
telegram/__init__.py Normal file
View file

31
telegram/commands.py Normal file
View file

@ -0,0 +1,31 @@
import re
import logging
import peewee
from db.queries import skip_day_put
from .telegram import TelegramCommand
logger = logging.getLogger(__name__)
class SkipDayAddCommand(TelegramCommand):
command_text = "/skip_day"
date_regex = re.compile(r"^(?P<date>\d{4}-\d{2}-\d{2})$")
async def exec_inner(self, text, update, session, forResponse=None, reply=None):
try:
yield "Please enter a skip day"
response = await forResponse()
m = self.date_regex.match(response)
if(m is not None):
skip_day_put(m.group('date'))
yield f"Skip day {m.group('date')} has been added"
else:
yield "Please enter a valid date"
except peewee.IntegrityError:
yield "That day has already been added"
commands = [
SkipDayAddCommand()
]

View file

@ -1,9 +1,7 @@
import re
import logging import logging
import asyncio import asyncio
from settings import TELEGRAM_API_TOKEN, TELEGRAM_CHAT_ID from settings import TELEGRAM_API_TOKEN, TELEGRAM_CHAT_ID, TELEGRAM_COMMAND_TIMEOUT
from db.queries import skip_day_put
from util import make_session from util import make_session
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -66,33 +64,28 @@ class TelegramCommand():
(self.command_regex is not None and self.command_regex.match(text) is not None) (self.command_regex is not None and self.command_regex.match(text) is not None)
) )
async def exec(self, text, update, session): async def exec(self, *args, **kwargs):
try:
async for reply in self.exec_inner(*args, **kwargs):
yield reply
except Exception as e:
yield "There was a problem with your command"
logger.error(f"{self.__class__.__name__} {e}")
async def exec_inner(self, text, update, session):
pass pass
class SkipDayAddCommand(TelegramCommand): async def handle_commands(commands=[], loop=None):
command_regex = re.compile(r"^\/skip_day( (?P<date>\d{4}-\d{2}-\d{2}))?$")
async def exec(self, text, update, session):
date_str = text.split(' ')[1]
skip_day_put(date_str)
yield f"Added skip day {date_str}"
commands = [
SkipDayAddCommand()
]
async def do_reply(data, t):
if(type(data) is str):
await t.message_send(
data
)
async def handle_commands(commands=commands):
async with make_session() as session: async with make_session() as session:
t = Telegram(session) t = Telegram(session)
command_tasks = set()
command_futures = {}
def forResponse():
f = loop.create_future()
command_futures[chat_id] = f
return f
offset = 0 offset = 0
while True: while True:
try: try:
@ -106,19 +99,40 @@ async def handle_commands(commands=commands):
if(chat_id != TELEGRAM_CHAT_ID): if(chat_id != TELEGRAM_CHAT_ID):
continue continue
if(chat_id in command_futures):
command_futures[chat_id].set_result(
update['message']['text']
)
del command_futures[chat_id]
continue
for command in commands: for command in commands:
if(command.matches(update['message']['text'])): if(command.matches(update['message']['text'])):
try: async def timeoutTask():
result = command.exec(update['message']['text'], update, session) try:
async with asyncio.timeout(TELEGRAM_COMMAND_TIMEOUT):
async for reply in command.exec(
update['message']['text'],
update,
session,
forResponse=forResponse
):
await t.message_send(reply)
except TimeoutError:
if chat_id in command_futures:
del command_futures[chat_id]
if result.__class__.__name__ == 'async_generator': await t.message_send("Your command has timed out")
async for r in result: except Exception:
await do_reply(r, t) await t.message_send('There was a problem with your command')
else: logger.exception('Problem while executing a command')
await do_reply(await result, t)
except: task = asyncio.create_task(
await t.message_send('There was a problem with your command') timeoutTask()
logger.exception('Problem while executing a command') )
command_tasks.add(task)
task.add_done_callback(command_tasks.discard)
break break
offset = update['update_id'] + 1 offset = update['update_id'] + 1