Sources of Telegram bot for cryptopotato chat. https://t.me/devpotato_bot
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

132 lines
5.0 KiB

import datetime
import logging
import os
import sys
from typing import Set, Dict, Optional
import pytz
import telegram
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from telegram import Bot
from telegram.ext import Updater, Defaults
from dataclasses import dataclass, field
# noinspection PyUnresolvedReferences
from . import sqlite_fk # enable foreign key support for SQLite
@dataclass
class RunnerConfig:
bot_token: str
db_url: str
daily_titles_job_enabled: bool = True
developer_ids: Set[int] = field(default_factory=set)
bot_timezone: pytz.BaseTzInfo = pytz.utc
daily_titles_job_time: datetime.time = datetime.time()
@staticmethod
def from_env(env: Dict[str, str]) -> Optional['RunnerConfig']:
logger = logging.getLogger(__name__)
config = RunnerConfig(env.get('BOT_TOKEN'), env.get('DB_URL'))
fail = False
if tz_name := env.get('BOT_TIMEZONE'):
try:
config.bot_timezone = pytz.timezone(tz_name)
except pytz.UnknownTimeZoneError:
logger.error('BOT_TIMEZONE value "%s" is not a valid timezone name', tz_name)
fail = True
if enable_job_str := env.get('DAILY_TITLES_JOB_ENABLED'):
try:
config.daily_titles_job_enabled = bool(int(enable_job_str))
except ValueError:
logger.error('DAILY_TITLES_JOB_ENABLED value "%s" does not correspond to 0 or 1', enable_job_str)
fail = True
if time_str := env.get('DAILY_TITLES_JOB_TIME'):
try:
time_args = map(int, time_str.split(':'))
config.daily_titles_job_time = datetime.time(*time_args)
except ValueError as e:
logger.error(
'DAILY_TITLES_JOB_TIME value "%s" does not represent a valid time: %s',
time_str, e.args[0]
)
fail = True
if developer_ids_str := env.get('DEVELOPER_IDS'):
try:
config.developer_ids = set(map(int, developer_ids_str.split(',')))
except ValueError:
logger.error(
'DEVELOPER_IDS value "%s" is not a valid comma-separated list of integers',
developer_ids_str
)
fail = True
return None if fail else config
class Runner:
DAILY_TITLES_JOB_NAME = 'assign titles'
def __init__(self, config: RunnerConfig):
self.logger = logging.getLogger(__name__)
bot_defaults = Defaults(disable_web_page_preview=True, tzinfo=config.bot_timezone)
self.updater = Updater(token=config.bot_token, defaults=bot_defaults)
self.engine = create_engine(config.db_url)
self.session_factory = sessionmaker(bind=self.engine)
self.developer_ids = config.developer_ids
self._command_list = dict()
from . import commands
commands.register_handlers(self)
bot: Bot = self.updater.bot
bot.set_my_commands(self._command_list.items())
dispatcher = self.updater.dispatcher
from devpotato_bot.error_handler import create_callback
dispatcher.add_error_handler(create_callback(self.developer_ids))
logger = logging.getLogger(__name__)
if config.daily_titles_job_enabled:
job_time = config.daily_titles_job_time
logger.info('Scheduling daily titles assignment job @ %s', job_time)
from .commands.daily_titles.daily_job import job_callback
job_queue = self.updater.job_queue
job_queue.run_daily(job_callback, job_time,
context=self.session_factory,
name=Runner.DAILY_TITLES_JOB_NAME)
else:
logger.info('Daily titles assignment job was disabled')
def add_command_description(self, command, description):
"""Add command to the list of commands to be shown to Telegram clients"""
self._command_list[command] = description
def run(self):
self.updater.start_polling()
# Run the bot until you press Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT. This should be used most of the time, since
# start_polling() is non-blocking and will stop the bot gracefully.
self.updater.idle()
def main():
"""Start the bot."""
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
logger.info('Cryptopotato bot started')
config = RunnerConfig.from_env(os.environ)
if config is None:
logger.error('Failed to create runner config')
sys.exit(-1)
try:
Runner(config).run()
except telegram.error.InvalidToken as e:
logger.error('Bot token "%s" is not valid', config.bot_token, exc_info=e)
except Exception as e:
logger.error('Unhandled exception', exc_info=e)
else:
return
sys.exit(-1)