Skip to content

Commit 1b04849

Browse files
committed
Refactoring
1 parent 963adb0 commit 1b04849

File tree

8 files changed

+132
-57
lines changed

8 files changed

+132
-57
lines changed

bot/configs/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class Settings(BaseSettings):
2121
tg_bot: TgBot
2222
logging: dict[str, Any] = {
2323
'version': 1,
24-
'disable_existing_loggers': True,
24+
'disable_existing_loggers': False,
2525
'formatters': {
2626
'default': {
2727
'format': '%(levelname)-8s [%(asctime)s] [%(name)s] %(message)s',

bot/handlers/handlers.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,40 +14,47 @@ def __init__(self, bot: Bot):
1414

1515
async def start_command(self, message: Message, ctx: HandlerContext, user_lang: str | None):
1616
await message.answer(
17-
text=ctx.lexicon.get_text('Hi user! This start message.', user_lang),
17+
text=ctx.lexicon.get_text('start_message', user_lang),
1818
reply_markup=ctx.keyboards.get_start_button(ctx.lexicon, user_lang),
1919
)
2020

2121
async def help_command(self, message: Message, ctx: HandlerContext, user_lang: str | None):
22-
await message.answer(text=ctx.lexicon.get_text('This help message.', user_lang))
22+
await message.answer(text=ctx.lexicon.get_text('help_message', user_lang))
2323

24-
async def answer(self, message: Message, answer: str):
25-
await self.__send_message(message.chat.id, answer, message.message_id)
24+
async def answer(self, message: Message, ctx: HandlerContext, user_lang: str | None):
25+
text = ctx.lexicon.get_text('default_answer', user_lang)
26+
await self.__send_message(message.chat.id, text, message.message_id)
2627

27-
async def answer_fsm(self, message: Message, state: FSMContext):
28-
await message.answer('Waiting Step 1')
28+
async def answer_fsm(self, message: Message, state: FSMContext, ctx: HandlerContext, user_lang: str | None):
29+
await message.answer(ctx.lexicon.get_text('fsm_wait_step_1', user_lang))
2930
await state.set_state(BotState.waiting_step_1)
3031

31-
async def answer_fsm_state_1(self, message: Message, state: FSMContext):
32+
async def answer_fsm_state_1(self, message: Message, state: FSMContext, ctx: HandlerContext, user_lang: str | None):
3233
await state.update_data(step_1=message.text)
33-
await message.answer('Waiting Step 2')
34+
await message.answer(ctx.lexicon.get_text('fsm_wait_step_2', user_lang))
3435
await state.set_state(BotState.waiting_step_2)
3536

36-
async def answer_fsm_state_2(self, message: Message, state: FSMContext):
37+
async def answer_fsm_state_2(self, message: Message, state: FSMContext, ctx: HandlerContext, user_lang: str | None):
3738
await state.update_data(step_2=message.text)
3839
data = await state.get_data()
39-
await message.answer(f'Step 1: {data["step_1"]}\nStep 2: {data["step_2"]}')
40+
await message.answer(
41+
ctx.lexicon.get_text('fsm_result', user_lang).format(
42+
step_1=data.get('step_1', ''),
43+
step_2=data.get('step_2', ''),
44+
)
45+
)
4046
await state.clear()
4147

4248
async def answer_inline_button(self, message: Message, ctx: HandlerContext, user_lang: str | None):
4349
callback = SaveCallbackFactory(message_id=message.message_id).pack()
4450
await message.answer(
45-
text=ctx.lexicon.get_text('Inline button message'),
51+
text=ctx.lexicon.get_text('inline_prompt', user_lang),
4652
reply_markup=ctx.keyboards.get_inline_button(ctx.lexicon, user_lang, callback),
4753
)
4854

4955
async def reply(self, message: Message, ctx: HandlerContext):
50-
await message.reply(text=ctx.bot_service.upper(message.text))
56+
text = message.text or ''
57+
await message.reply(text=ctx.bot_service.upper(text))
5158

5259
async def process_any_inline_button_press(self, callback: CallbackQuery, callback_data: SaveCallbackFactory):
5360
await callback.message.answer(text=callback_data.pack())

bot/i18n/en.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,15 @@
1-
LEXICON_EN: dict[str, str] = {}
1+
LEXICON_EN: dict[str, str] = {
2+
'start_message': 'Hi user! This is the start message.',
3+
'help_message': 'This is the help message.',
4+
'start_button_hi': 'Hi',
5+
'start_button_test': 'Test',
6+
'start_button_inline': 'Inline button',
7+
'start_button_fsm': 'FSM',
8+
'inline_prompt': 'Here is an inline button.',
9+
'inline_save_button': 'Save',
10+
'fsm_wait_step_1': 'Waiting for step 1',
11+
'fsm_wait_step_2': 'Waiting for step 2',
12+
'fsm_result': 'Step 1: {step_1}\nStep 2: {step_2}',
13+
'default_answer': 'Hello',
14+
'throttled_message': 'Too many requests. Please slow down.',
15+
}

bot/i18n/lexicon.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
1+
from collections.abc import Mapping
2+
13
from bot.i18n.en import LEXICON_EN
24
from bot.i18n.ru import LEXICON_RU
35

46

57
class Lexicon:
68
def __init__(self, default_language: str):
79
self.default_language = default_language
10+
self._lexicons: dict[str, Mapping[str, str]] = {
11+
'en': LEXICON_EN,
12+
'ru': LEXICON_RU,
13+
}
14+
15+
def get_text(self, key: str, lang: str | None = None) -> str:
16+
language = lang or self.default_language
17+
translation = self._lexicons.get(language, {})
18+
19+
if key in translation:
20+
return translation[key]
821

9-
def get_text(self, param: str, lang: str | None = None) -> str:
10-
if lang is None:
11-
lang = self.default_language
22+
default_translation = self._lexicons.get(self.default_language, {})
23+
if key in default_translation:
24+
return default_translation[key]
1225

13-
match lang:
14-
case 'ru':
15-
ret = LEXICON_RU.get(param, param)
16-
case 'en':
17-
ret = LEXICON_EN.get(param, param)
18-
case _:
19-
ret = LEXICON_EN.get(param, param)
26+
fallback_translation = self._lexicons.get('en', {})
27+
return fallback_translation.get(key, key)
2028

21-
return ret
29+
def get_translations(self, key: str) -> set[str]:
30+
values = {translations[key] for translations in self._lexicons.values() if key in translations}
31+
return values or {key}

bot/i18n/ru.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,15 @@
11
LEXICON_RU: dict[str, str] = {
2-
'hi': 'привет',
3-
'Save': 'Сохранить',
4-
'Hi user! This start message.': 'Привет, пользователь! Это стартовое сообщение.',
5-
'This help message.': 'Это справочное сообщение.',
2+
'start_message': 'Привет, пользователь! Это стартовое сообщение.',
3+
'help_message': 'Это справочное сообщение.',
4+
'start_button_hi': 'Привет',
5+
'start_button_test': 'Тест',
6+
'start_button_inline': 'Инлайн кнопка',
7+
'start_button_fsm': 'FSM',
8+
'inline_prompt': 'Сообщение с инлайн-кнопкой.',
9+
'inline_save_button': 'Сохранить',
10+
'fsm_wait_step_1': 'Ожидаю шаг 1',
11+
'fsm_wait_step_2': 'Ожидаю шаг 2',
12+
'fsm_result': 'Шаг 1: {step_1}\nШаг 2: {step_2}',
13+
'default_answer': 'Привет',
14+
'throttled_message': 'Слишком много запросов. Пожалуйста, снизьте частоту.',
615
}

bot/keyboards/keyboards.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,14 @@ def __init__(self):
99

1010
def get_start_button(self, lexicon: Lexicon, user_lang: str | None) -> ReplyKeyboardMarkup:
1111
kb_list = [
12-
[KeyboardButton(text=lexicon.get_text('hi', user_lang)), KeyboardButton(text='test')],
13-
[KeyboardButton(text='inline button'), KeyboardButton(text='fsm')],
12+
[
13+
KeyboardButton(text=lexicon.get_text('start_button_hi', user_lang)),
14+
KeyboardButton(text=lexicon.get_text('start_button_test', user_lang)),
15+
],
16+
[
17+
KeyboardButton(text=lexicon.get_text('start_button_inline', user_lang)),
18+
KeyboardButton(text=lexicon.get_text('start_button_fsm', user_lang)),
19+
],
1420
]
1521

1622
return ReplyKeyboardMarkup(keyboard=kb_list, resize_keyboard=True, one_time_keyboard=True)
@@ -20,7 +26,10 @@ def get_inline_button(
2026
) -> InlineKeyboardMarkup:
2127
inline_kb_list = [
2228
[
23-
InlineKeyboardButton(text=lexicon.get_text('Save', user_lang), callback_data=callback_data),
29+
InlineKeyboardButton(
30+
text=lexicon.get_text('inline_save_button', user_lang),
31+
callback_data=callback_data,
32+
),
2433
],
2534
]
2635

bot/middlewares/throttling.py

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
from typing import Any
44

55
from aiogram import BaseMiddleware
6-
from aiogram.types import TelegramObject, User
6+
from aiogram.types import CallbackQuery, Message, TelegramObject, Update, User
77
from cachetools import TTLCache
88

9-
logger = logging.getLogger(__name__)
9+
logger = logging.getLogger('main')
1010

11-
CACHE = TTLCache(maxsize=10_000, ttl=2) # Максимальный размер кэша - 10000 ключей, время жизни ключа - 2 секунды
11+
CACHE = TTLCache(maxsize=10_000, ttl=2)
1212

1313

1414
class ThrottlingMiddleware(BaseMiddleware):
@@ -23,8 +23,37 @@ async def __call__(
2323
if user is not None:
2424
if user.id in CACHE:
2525
logger.debug(f'Throttling for user {user.id}')
26+
text = 'Too many requests. Please slow down.'
27+
lexicon = data.get('lexicon')
28+
if lexicon is not None:
29+
text = lexicon.get_text('throttled_message', data.get('user_lang'))
30+
await self._notify_throttled(event, text)
2631
return
2732

2833
CACHE[user.id] = True
2934

3035
return await handler(event, data)
36+
37+
@staticmethod
38+
async def _notify_throttled(event: TelegramObject, text: str) -> None:
39+
if isinstance(event, Message):
40+
await event.answer(text)
41+
return
42+
43+
if isinstance(event, CallbackQuery):
44+
if event.message is not None:
45+
await event.message.answer(text)
46+
await event.answer()
47+
return
48+
49+
if isinstance(event, Update):
50+
if event.message is not None:
51+
await event.message.answer(text)
52+
return
53+
if event.callback_query is not None:
54+
if event.callback_query.message is not None:
55+
await event.callback_query.message.answer(text)
56+
await event.callback_query.answer()
57+
return
58+
59+
logger.debug('Throttling notification skipped: unsupported event type %s', type(event))

main.py

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import logging
2+
import logging.config
23
from typing import Callable, Optional
34

45
import uvicorn
@@ -36,11 +37,7 @@ async def startup_handler() -> None:
3637
drop_pending_updates=True,
3738
)
3839

39-
await bot.set_my_commands(
40-
[
41-
BotCommand(command='/help', description='Help'),
42-
]
43-
)
40+
await bot.set_my_commands([BotCommand(command='/help', description='Help')])
4441

4542
logger.debug('startup_handler')
4643

@@ -78,15 +75,18 @@ def create_dispatcher(storage: Optional[BaseStorage] = None) -> Dispatcher:
7875
return Dispatcher(storage=storage)
7976

8077

81-
def register_handlers(dp: Dispatcher, hd: Handlers) -> None:
78+
def register_handlers(dp: Dispatcher, hd: Handlers, lexicon: Lexicon) -> None:
8279
dp.message.register(hd.start_command, CommandStart())
8380
dp.message.register(hd.help_command, Command(commands='help'))
84-
dp.message.register(hd.answer, F.text == 'hi')
85-
dp.message.register(hd.answer_inline_button, F.text == 'inline button')
86-
dp.message.register(hd.answer_fsm, F.text == 'fsm')
81+
dp.message.register(hd.answer, F.text.in_(tuple(sorted(lexicon.get_translations('start_button_hi')))))
82+
dp.message.register(
83+
hd.answer_inline_button,
84+
F.text.in_(tuple(sorted(lexicon.get_translations('start_button_inline')))),
85+
)
86+
dp.message.register(hd.answer_fsm, F.text.in_(tuple(sorted(lexicon.get_translations('start_button_fsm')))))
8787
dp.message.register(hd.answer_fsm_state_1, BotState.waiting_step_1)
8888
dp.message.register(hd.answer_fsm_state_2, BotState.waiting_step_2)
89-
dp.message.register(hd.reply)
89+
dp.message.register(hd.reply, F.text)
9090
dp.callback_query.register(hd.process_any_inline_button_press, SaveCallbackFactory.filter())
9191

9292

@@ -96,16 +96,12 @@ def register_middlewares(dp: Dispatcher) -> None:
9696
dp.update.outer_middleware(ContextMiddleware())
9797

9898

99-
def register_workflow_data(dp: Dispatcher, settings: Settings) -> None:
100-
dp.workflow_data.update({'answer': 'Hello', 'lexicon': Lexicon(settings.app.default_language)})
101-
dp.workflow_data.update(
102-
{
103-
'answer': 'Hello',
104-
'lexicon': Lexicon(settings.app.default_language),
105-
'keyboards': Keyboards(),
106-
'bot_service': BotService(),
107-
}
108-
)
99+
def register_workflow_data(dp: Dispatcher, lexicon: Lexicon) -> None:
100+
dp.workflow_data.update({
101+
'lexicon': lexicon,
102+
'keyboards': Keyboards(),
103+
'bot_service': BotService(),
104+
})
109105

110106

111107
def create_app(bot: Bot, dp: Dispatcher, settings: App) -> FastAPI:
@@ -124,9 +120,10 @@ def main() -> None:
124120

125121
bot = create_bot(settings.tg_bot)
126122
dp = create_dispatcher(MemoryStorage())
123+
lexicon = Lexicon(settings.app.default_language)
127124
app = create_app(bot, dp, settings.app)
128-
register_handlers(dp, Handlers(bot))
129-
register_workflow_data(dp, settings)
125+
register_workflow_data(dp, lexicon)
126+
register_handlers(dp, Handlers(bot), lexicon)
130127
register_middlewares(dp)
131128

132129
logger.debug('Application is running')

0 commit comments

Comments
 (0)