Initial, nothing works
This commit is contained in:
commit
41e459521f
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.idea/
|
||||||
|
__pycache__/
|
||||||
|
venv/
|
||||||
|
.venv/
|
||||||
|
logs/
|
||||||
|
*.env
|
||||||
3
i18n.yaml
Normal file
3
i18n.yaml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
en:
|
||||||
|
start: "Hello, {message.from_user.full_name}"
|
||||||
|
help: ""
|
||||||
12
mybot/__init__.py
Normal file
12
mybot/__init__.py
Normal 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
5
mybot/__main__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from . import main
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
20
mybot/bot.py
Normal file
20
mybot/bot.py
Normal 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
23
mybot/config.py
Normal 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")
|
||||||
7
mybot/handlers/__init__.py
Normal file
7
mybot/handlers/__init__.py
Normal 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
15
mybot/handlers/basic.py
Normal 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
48
mybot/i18n.py
Normal 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
30
mybot/logger.py
Normal 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
|
||||||
|
|
||||||
9
mybot/middlewares/__init__.py
Normal file
9
mybot/middlewares/__init__.py
Normal 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))
|
||||||
34
mybot/middlewares/arguments.py
Normal file
34
mybot/middlewares/arguments.py
Normal 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
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
pytelegrambotapi
|
||||||
|
pyyaml
|
||||||
Loading…
x
Reference in New Issue
Block a user