Личные временные хендлеры в aiogram
Я пытаюсь сделать функцию выбора продолжительности, по типу 1 минута, 2 часа и т.д с помощью кнопок в классе, дело в том, что бота могут использовать сразу несколько человек, или 1 человек использует эту функцию дважды, тогда все хендлеры, которые я регистрирую в старте класса, просто начинают путаться. Так как хендлеры после первого использования уже были зарегистрированы, то во время второго использования они регистрироваться не будут, следовательно вместо второго сообщения, бот будет редактировать первое. Я пробовал опираться на user_id
указывая callback_data
в хендлере, но при использовании 1 человеком дважды также бессмысленно, сделал свой генератор id
даже, который также провалился, почему не знаю. Удалять хендлеры после завершения выбора продолжительности смысла нет, так как они слетят и у второго.
вот мой код:
def generate_unique_id(length=10):
return ''.join(choices(ascii_letters + digits, k=length))
class DurationSelector:
def __init__(self, msg: types.Message, user_id: int = None):
self.user_id = user_id
self.uniq_id = generate_unique_id()
self.message = msg
self.num = 1
self.ind_init = 0
self.max_nums = {0: 60, 1: 24, 2: 30, 3: 4, 4: 12, 5: 1}
self.max_ind = 5
self.forms = [["минута", "минуты", "минут"], ["час", "часа", "часов"], ["день", "дня", "дней"], ["неделя", "недели", "недель"], ["месяц", "месяца", "месяцев"], ["год", "года", "лет"]]
self.max = self.max_nums[self.ind_init]
def format_number(self, n, forms):
n = abs(n) % 100
if 11 <= n <= 19:
form = forms[2]
else:
n = n % 10
if n == 1:
form = forms[0]
elif 2 <= n <= 4:
form = forms[1]
else:
form = forms[2]
return form
def get_uniq(self, action: str):
print(f"{action}_{self.uniq_id}")
return f"{action}_{self.uniq_id}"
async def config_nums(self):
self.max = self.max_nums[self.ind_init]
if self.num > self.max:
self.num = self.max
if self.num < 1:
self.num = 1
await self.next()
def get_keyboard(self) -> InlineKeyboardMarkup:
keyboard = [
[get_inline_button('dur-5', id=self.uniq_id) if self.num > 1 else get_inline_button('none_button'),
get_inline_button('dur-1', id=self.uniq_id) if self.num > 1 else get_inline_button('none_button'),
InlineKeyboardButton(text=str(self.num), callback_data='none'),
get_inline_button('dur+1', id=self.uniq_id) if self.num < self.max else get_inline_button('none_button'),
get_inline_button('dur+5', id=self.uniq_id) if self.num < self.max else get_inline_button('none_button')],
[get_inline_button('unit-1', id=self.uniq_id) if self.ind_init > 0 else get_inline_button('none_button'),
InlineKeyboardButton(text=self.format_number(self.num, self.forms[self.ind_init]), callback_data='none'),
get_inline_button('unit+1', id=self.uniq_id) if self.ind_init < self.max_ind else get_inline_button('none_button')]]
return InlineKeyboardMarkup(inline_keyboard=keyboard)
async def start(self):
private.callback_query.register(self.inc, F.data == self.get_uniq("dur+1"), AddProduct.duration)
private.callback_query.register(self.dec, F.data == self.get_uniq("dur-1"), AddProduct.duration)
private.callback_query.register(self.inc5, F.data == self.get_uniq("dur+5"), AddProduct.duration)
private.callback_query.register(self.dec5, F.data == self.get_uniq("dur-5"), AddProduct.duration)
private.callback_query.register(self.inc_unit, F.data == self.get_uniq("unit+1"), AddProduct.duration)
private.callback_query.register(self.dec_unit, F.data == self.get_uniq("unit-1"), AddProduct.duration)
private.callback_query.register(self.other, AddProduct.duration)
await self.next()
async def end(self):
...
async def next(self):
await self.message.edit_text(**get_text('duration_select', num=self.num, form=self.format_number(self.num, self.forms[self.ind_init])).as_kwargs(), reply_markup=self.get_keyboard())
async def dec(self, clbck: types.CallbackQuery, state: FSMContext):
self.num -= 1
await self.config_nums()
async def inc(self, clbck: types.CallbackQuery, state: FSMContext):
self.num += 1
await self.config_nums()
async def dec5(self, clbck: types.CallbackQuery, state: FSMContext):
self.num -= 5
await self.config_nums()
async def inc5(self, clbck: types.CallbackQuery, state: FSMContext):
self.num += 5
await self.config_nums()
async def dec_unit(self, clbck: types.CallbackQuery, state: FSMContext):
self.ind_init -= 1
await self.config_nums()
async def inc_unit(self, clbck: types.CallbackQuery, state: FSMContext):
self.ind_init += 1
await self.config_nums()
async def other(self, clbck: types.CallbackQuery, state: FSMContext):
print('other', clbck.data)
Цель: исправить ошибки, чтобы с выбором могли работать сразу несколько людей, или 1 человек дважды.
Ответы (1 шт):
Проблема может быть из-за того, что вы используете уникальные идентификаторы, но обработчики (callback_query.register
) остаются привязанными к общим состояниям, которые перезаписываются при повторном вызове класса. Попробуйте использовать словарь с ID
сессий.
Проверяйте уникальность через callback_data
.
Генерация должна включать уникальный идентификатор, чтобы можно было различать разные сессии:
def get_inline_button(action: str, id: str) -> InlineKeyboardButton:
return InlineKeyboardButton(text=action, callback_data=f"{action}_{id}")
Вместо регистрации отдельных хендлеров для каждого действия, регистрируйте один общий хендлер, который будет разбирать callback_data
и обрабатывать действия на основе уникального идентификатора.
session_storage = defaultdict(dict)
@private.callback_query.register(F.data.startswith("dur_"))
async def handle_duration_selection(clbck: types.CallbackQuery, state: FSMContext):
data = clbck.data.split("_")
action, session_id = data[0], data[1]
# Извлекаем состояние текущей сессии
if session_id not in session_storage:
return
session = session_storage[session_id]
selector = session["selector"]
# Обрабатываем действие
if action == "dur+1":
await selector.inc(clbck, state)
elif action == "dur-1":
await selector.dec(clbck, state)
elif action == "dur+5":
await selector.inc5(clbck, state)
elif action == "dur-5":
await selector.dec5(clbck, state)
elif action == "unit+1":
await selector.inc_unit(clbck, state)
elif action == "unit-1":
await selector.dec_unit(clbck, state)
else:
Когда пользователь начинает выбор, создавайте уникальную запись в хранилище и привязывайте её к уникальному идентификатору:
async def start_duration_selection(msg: types.Message):
unique_id = generate_unique_id()
selector = DurationSelector(msg)
session_storage[unique_id] = {"selector": selector}
await selector.start()
После завершения выбора, удаляйте соответствующую запись из хранилища:
async def end_duration_selection(unique_id: str):
if unique_id in session_storage:
del session_storage[unique_id]
В методе end
класса DurationSelector
вызывайте эту функцию:
async def end(self):
end_duration_selection(self.uniq_id)