Initial, nothing works

This commit is contained in:
Ilya Bezrukov 2024-07-19 04:32:27 +03:00
commit 41e459521f
13 changed files with 214 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.idea/
__pycache__/
venv/
.venv/
logs/
*.env

3
i18n.yaml Normal file
View File

@ -0,0 +1,3 @@
en:
start: "Hello, {message.from_user.full_name}"
help: ""

12
mybot/__init__.py Normal file
View File

@ -0,0 +1,12 @@
from .config import Config
from .logger import create_logger
from .bot import create_bot
from .i18n import I18N
def main():
config = Config()
logger = create_logger("mybot", config.LOG_LEVEL)
i18n = I18N(logger, config.I18N_PATH)
bot = create_bot(config, logger, i18n)
bot.infinity_polling(config.TIMEOUT, config.DROP_PENDING, config.POLLING_TIMEOUT)

5
mybot/__main__.py Normal file
View File

@ -0,0 +1,5 @@
from . import main
if __name__ == "__main__":
main()

20
mybot/bot.py Normal file
View File

@ -0,0 +1,20 @@
from telebot import TeleBot
from .handlers import register_handlers
from .middlewares import setup_middlewares
def create_bot(config, logger, i18n):
bot = TeleBot(config.BOT_TOKEN,
parse_mode=config.PARSE_MODE,
skip_pending=config.DROP_PENDING,
num_threads=config.NUM_THREADS,
use_class_middlewares=True)
logger.debug("Setting up middlewares")
setup_middlewares(bot, logger, i18n)
logger.debug("Registering handlers")
register_handlers(bot)
return bot

23
mybot/config.py Normal file
View File

@ -0,0 +1,23 @@
import os
class Config:
# bot setup
BOT_TOKEN: str = os.getenv("BOT_TOKEN")
OWNER_ID: int = int(os.getenv("OWNER_ID", 1))
LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
# bot behaviour
DROP_PENDING: bool = bool(int(os.getenv("DROP_PENDING", True)))
TIMEOUT: int = int(os.getenv("TIMEOUT", 20))
POLLING_TIMEOUT: int = int(os.getenv("POLLING_TIMEOUT", 20))
NUM_THREADS: int = int(os.getenv("NUM_THREADS", 2))
PARSE_MODE: str = os.getenv("PARSE_MODE", "html")
# i18n
I18N_PATH: str = os.getenv("I18N_PATH", "i18n.yaml")
I18N_LANG: str = os.getenv("I18N_LANG", "en")
def __init__(self):
if not self.BOT_TOKEN:
raise RuntimeError("Missing BOT_TOKEN")

View File

@ -0,0 +1,7 @@
from telebot import TeleBot
from . import basic
def register_handlers(bot: TeleBot):
basic.register_handlers(bot)

15
mybot/handlers/basic.py Normal file
View File

@ -0,0 +1,15 @@
from telebot import TeleBot
from telebot import types as tt
def start(message: tt.Message, bot: TeleBot, logger, t):
bot.send_message(message.chat.id, t("start"))
def help_(message: tt.Message, bot: TeleBot, logger, t):
bot.send_message(message.chat.id, t("help"))
def register_handlers(bot: TeleBot):
bot.register_message_handler(start, commands=["start"])
bot.register_message_handler(help_, commands=["help"])

48
mybot/i18n.py Normal file
View File

@ -0,0 +1,48 @@
import logging
from typing import Optional
from yaml import safe_load
class I18N:
def __init__(self, logger: logging.Logger, path="i18n.yaml", fallback_lang="en"):
self.logger = logger
self._path = path
self._fallback_lang = fallback_lang
self._dict = dict()
self.load()
@property
def path(self):
return self._path
@property
def fallback_lang(self):
return self._fallback_lang
def load(self):
self._dict.clear()
with open(self._path) as f:
self._dict = safe_load(f)
if self.fallback_lang not in self._dict:
raise RuntimeError("I18N file doesn't contain fallback language section")
def get(self, phrase: str, lang: Optional[str] = None):
lang = lang or self.fallback_lang
if lang not in self._dict:
self.logger.warning(f"Language '{lang}' not found in i18n, using fallback")
lang = self.fallback_lang
lang_dict = self._dict.get(lang)
result = lang_dict.get(phrase)
if result is None:
self.logger.error(f"Phrase '{phrase}' not found in language '{lang}'")
result = f"<Phrase '{phrase}' not found in language '{lang}'>"
return result
def __call__(self, phrase: str, lang: Optional[str] = None, *args, **kwargs):
return self.get(phrase, lang).format(*args, **kwargs)
def custom_call(self, lang: Optional[str] = None, *args, **kwargs):
def call(phrase: str, ilang: Optional[str] = None, *iargs, **ikwargs):
return self(phrase, lang or ilang, *args, *iargs, **kwargs, **ikwargs)
return call

30
mybot/logger.py Normal file
View File

@ -0,0 +1,30 @@
import sys
import os
import logging
from typing import Optional
def create_logger(name: str,
level: [str, int],
stream_stderr: bool = False,
stream_format: str = "%(asctime)s | %(name)s [%(levelname)s] %(message)s",
file: bool = False,
file_path: Optional[None] = None,
file_format: str = "%(asctime)s | %(name)s [%(levelname)s] %(message)s"):
logger = logging.getLogger(name)
logger.setLevel(level)
stream_handler = logging.StreamHandler(sys.stderr if stream_stderr else sys.stdout)
stream_handler.setFormatter(logging.Formatter(stream_format))
logger.addHandler(stream_handler)
if file:
if not file_path:
file_path = os.path.join("logs", name)
os.makedirs(os.path.dirname(file_path), exist_ok=True)
file_handler = logging.FileHandler(file_path)
file_handler.setFormatter(logging.Formatter(file_format))
logger.addHandler(file_handler)
return logger

View File

@ -0,0 +1,9 @@
import logging
from telebot import TeleBot
from .arguments import ExtraArguments
def setup_middlewares(bot: TeleBot, logger: logging.Logger, i18n):
bot.setup_middleware(ExtraArguments(bot, logger, i18n))

View File

@ -0,0 +1,34 @@
import logging
from functools import singledispatch
from telebot import TeleBot
from telebot.handler_backends import BaseMiddleware
from telebot import types as tt
class ExtraArguments(BaseMiddleware):
def __init__(self, bot: TeleBot, logger: logging.Logger, i18n):
super().__init__()
self.bot = bot
self.logger = logger
self.i18n = i18n
self.update_types = ["message", "callback_query"]
@singledispatch
def pre_process(self, obj, data: dict):
raise TypeError()
@pre_process.register
def _(self, message: tt.Message, data: dict):
data["bot"] = self.bot
data["logger"] = self.logger
data["t"] = self.i18n.custom_call(message=message)
@pre_process.register
def _(self, callback: tt.CallbackQuery, data: dict):
data["bot"] = self.bot
data["logger"] = self.logger
data["t"] = self.i18n.custom_call(callback=callback)
def post_process(self, message, data: dict, exception: BaseException):
pass

2
requirements.txt Normal file
View File

@ -0,0 +1,2 @@
pytelegrambotapi
pyyaml