Simple utilities bot for Telegram. /ping, /whoami and such.
1import asyncio
2import logging
3import platform
4import signal
5from pathlib import Path
6
7import click
8import coloredlogs
9import tomllib
10
11from bot import PingBot
12
13logger = logging.getLogger(__name__)
14
15
16def load_config(config_path: Path | None) -> dict:
17 assert config_path is not None
18 with open(config_path, "rb") as f:
19 return tomllib.load(f)
20
21
22def _raise_exit():
23 raise SystemExit
24
25
26async def run_bot(bot: PingBot) -> None:
27 async with bot.app:
28 await bot.start_app()
29 # wait for shutdown signal
30 stop_event = asyncio.Event()
31
32 def signal_handler(sig, frame):
33 logger.info("received shutdown signal")
34 stop_event.set()
35
36 signal.signal(signal.SIGINT, signal_handler)
37 signal.signal(signal.SIGTERM, signal_handler)
38
39 await stop_event.wait()
40 await bot.stop_app()
41
42
43@click.command()
44@click.option(
45 "--config",
46 type=click.Path(exists=True, path_type=Path),
47 default="./config.toml",
48 help="path to config.toml file (default: ./config.toml)",
49)
50@click.option("--token", help="telegram bot token (overrides config file)")
51def main(config: Path | None, token: str | None) -> None:
52 if not token and not (config and config.exists()):
53 raise click.UsageError(
54 (
55 "must provide either --token, --config or a valid config file "
56 "at ./config.toml"
57 )
58 )
59
60 bot_name = "Telegram Bot"
61 if token:
62 bot_token = token
63 else:
64 cfg = load_config(config)
65 bot_token = cfg["telegram"]["token"]
66 bot_name = cfg.get("telegram", {}).get("name", bot_name)
67
68 log_format = (
69 "[{}][%(asctime)s]%(name)s::%(levelname)s:%(message)s"
70 ).format(bot_name.upper())
71
72 logging.basicConfig(format=log_format, level=logging.INFO)
73 coloredlogs.install(level=logging.INFO, fmt=log_format)
74
75 bot = PingBot(bot_token, bot_name)
76 asyncio.run(run_bot(bot))
77
78
79if __name__ == "__main__":
80 main()