Add delete functionality for tasks and events in Telegram bot. Implemented commands for deleting items, including weekly tasks with scope options. Enhanced main menu and button handlers for better user interaction. Updated state management for deletion process.
This commit is contained in:
parent
8bde59e953
commit
457dc74485
1 changed files with 290 additions and 28 deletions
|
|
@ -4,7 +4,7 @@ from telegram.ext import (
|
||||||
ContextTypes, ConversationHandler, filters
|
ContextTypes, ConversationHandler, filters
|
||||||
)
|
)
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select, and_
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import pytz
|
import pytz
|
||||||
import os
|
import os
|
||||||
|
|
@ -24,6 +24,7 @@ BOT_TOKEN = os.getenv("TELEGRAM_BOT_TOKEN", "")
|
||||||
# Состояния для ConversationHandler
|
# Состояния для ConversationHandler
|
||||||
SELECTING_DATE, SELECTING_HOUR, SELECTING_MINUTE, SELECTING_DURATION_HOUR, SELECTING_DURATION_MIN, ENTERING_TITLE = range(6)
|
SELECTING_DATE, SELECTING_HOUR, SELECTING_MINUTE, SELECTING_DURATION_HOUR, SELECTING_DURATION_MIN, ENTERING_TITLE = range(6)
|
||||||
SELECTING_TASK_DATE, ENTERING_TASK_TITLE, SELECTING_REPEAT, SELECTING_COPY_DAYS = range(10, 14)
|
SELECTING_TASK_DATE, ENTERING_TASK_TITLE, SELECTING_REPEAT, SELECTING_COPY_DAYS = range(10, 14)
|
||||||
|
SELECTING_DELETE_DATE, SELECTING_DELETE_ITEM = range(20, 22)
|
||||||
|
|
||||||
user_states = {} # Временное хранилище состояний пользователей
|
user_states = {} # Временное хранилище состояний пользователей
|
||||||
|
|
||||||
|
|
@ -100,23 +101,70 @@ def calculate_end_time(start_time: str, duration_min: int) -> str:
|
||||||
return f"{end_hour:02d}:{end_minute:02d}"
|
return f"{end_hour:02d}:{end_minute:02d}"
|
||||||
|
|
||||||
|
|
||||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def show_main_menu(message):
|
||||||
"""Обработчик команды /start"""
|
"""Показать главное меню с кнопками"""
|
||||||
await register_user(str(update.effective_chat.id))
|
|
||||||
keyboard = [
|
keyboard = [
|
||||||
[InlineKeyboardButton("📅 Сегодня", callback_data="schedule_today")],
|
[InlineKeyboardButton("📅 Сегодня", callback_data="schedule_today")],
|
||||||
[InlineKeyboardButton("📅 Завтра", callback_data="schedule_tomorrow")],
|
[InlineKeyboardButton("📅 Завтра", callback_data="schedule_tomorrow")],
|
||||||
[InlineKeyboardButton("📅 Оставшиеся дни недели", callback_data="schedule_week")],
|
[InlineKeyboardButton("➕ Добавить занятие", callback_data="add_event")],
|
||||||
|
[InlineKeyboardButton("➕ Добавить задачу", callback_data="add_task")],
|
||||||
|
[InlineKeyboardButton("🗑 Удалить", callback_data="delete_start")],
|
||||||
|
]
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
await message.reply_text(
|
||||||
|
"Выберите действие:",
|
||||||
|
reply_markup=reply_markup
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Обработчик команды /start"""
|
||||||
|
await register_user(str(update.effective_chat.id))
|
||||||
|
await show_main_menu(update.message)
|
||||||
|
|
||||||
|
|
||||||
|
async def today_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Обработчик команды /today"""
|
||||||
|
await register_user(str(update.effective_chat.id))
|
||||||
|
today = datetime.now(TZ).date()
|
||||||
|
date_str = today.strftime("%Y-%m-%d")
|
||||||
|
text = await get_schedule_for_date(date_str)
|
||||||
|
await update.message.reply_text(text)
|
||||||
|
|
||||||
|
|
||||||
|
async def nextday_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Обработчик команды /nextday"""
|
||||||
|
await register_user(str(update.effective_chat.id))
|
||||||
|
today = datetime.now(TZ).date()
|
||||||
|
tomorrow = today + timedelta(days=1)
|
||||||
|
date_str = tomorrow.strftime("%Y-%m-%d")
|
||||||
|
text = await get_schedule_for_date(date_str)
|
||||||
|
await update.message.reply_text(text)
|
||||||
|
|
||||||
|
|
||||||
|
async def addtask_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Обработчик команды /addtask"""
|
||||||
|
await register_user(str(update.effective_chat.id))
|
||||||
|
keyboard = [
|
||||||
[InlineKeyboardButton("➕ Добавить занятие", callback_data="add_event")],
|
[InlineKeyboardButton("➕ Добавить занятие", callback_data="add_event")],
|
||||||
[InlineKeyboardButton("➕ Добавить задачу", callback_data="add_task")],
|
[InlineKeyboardButton("➕ Добавить задачу", callback_data="add_task")],
|
||||||
]
|
]
|
||||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
"Выберите действие:",
|
"Что вы хотите добавить?",
|
||||||
reply_markup=reply_markup
|
reply_markup=reply_markup
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def deltask_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
|
"""Обработчик команды /deltask"""
|
||||||
|
await register_user(str(update.effective_chat.id))
|
||||||
|
chat_id = str(update.effective_chat.id)
|
||||||
|
user_states[chat_id] = {"action": "delete"}
|
||||||
|
await show_date_selection_for_delete(update.message)
|
||||||
|
return SELECTING_DELETE_DATE
|
||||||
|
|
||||||
|
|
||||||
async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
"""Обработчик кнопок"""
|
"""Обработчик кнопок"""
|
||||||
query = update.callback_query
|
query = update.callback_query
|
||||||
|
|
@ -126,26 +174,23 @@ async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
today = datetime.now(TZ).date()
|
today = datetime.now(TZ).date()
|
||||||
|
|
||||||
if query.data == "schedule_today":
|
if query.data == "schedule_today":
|
||||||
|
# Расписание на сегодня (аналог /today) - отправляем новое сообщение
|
||||||
date_str = today.strftime("%Y-%m-%d")
|
date_str = today.strftime("%Y-%m-%d")
|
||||||
text = await get_schedule_for_date(date_str)
|
text = await get_schedule_for_date(date_str)
|
||||||
await query.edit_message_text(text)
|
await query.message.reply_text(text)
|
||||||
|
|
||||||
elif query.data == "schedule_tomorrow":
|
elif query.data == "schedule_tomorrow":
|
||||||
|
# Расписание на завтра (аналог /nextday) - отправляем новое сообщение
|
||||||
tomorrow = today + timedelta(days=1)
|
tomorrow = today + timedelta(days=1)
|
||||||
date_str = tomorrow.strftime("%Y-%m-%d")
|
date_str = tomorrow.strftime("%Y-%m-%d")
|
||||||
text = await get_schedule_for_date(date_str)
|
text = await get_schedule_for_date(date_str)
|
||||||
await query.edit_message_text(text)
|
await query.message.reply_text(text)
|
||||||
|
|
||||||
elif query.data == "schedule_week":
|
elif query.data == "delete_start":
|
||||||
# Оставшиеся дни недели
|
# Начало процесса удаления (аналог /deltask)
|
||||||
weekday = today.weekday()
|
user_states[chat_id] = {"action": "delete"}
|
||||||
text = ""
|
await show_date_selection_for_delete(query)
|
||||||
for i in range(weekday, 7):
|
return SELECTING_DELETE_DATE
|
||||||
date = today + timedelta(days=i - weekday)
|
|
||||||
date_str = date.strftime("%Y-%m-%d")
|
|
||||||
day_text = await get_schedule_for_date(date_str)
|
|
||||||
text += day_text + "\n"
|
|
||||||
await query.edit_message_text(text[:4000]) # Telegram limit
|
|
||||||
|
|
||||||
elif query.data == "add_event":
|
elif query.data == "add_event":
|
||||||
# Начинаем процесс добавления event
|
# Начинаем процесс добавления event
|
||||||
|
|
@ -159,6 +204,15 @@ async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
await show_date_selection(query, "task")
|
await show_date_selection(query, "task")
|
||||||
return SELECTING_TASK_DATE
|
return SELECTING_TASK_DATE
|
||||||
|
|
||||||
|
elif query.data.startswith("del_date_"):
|
||||||
|
# Выбрана дата для удаления
|
||||||
|
date_str = query.data.replace("del_date_", "")
|
||||||
|
if chat_id not in user_states:
|
||||||
|
user_states[chat_id] = {}
|
||||||
|
user_states[chat_id]["delete_date"] = date_str
|
||||||
|
await show_items_for_deletion(query, date_str)
|
||||||
|
return SELECTING_DELETE_ITEM
|
||||||
|
|
||||||
elif query.data.startswith("date_"):
|
elif query.data.startswith("date_"):
|
||||||
# Выбрана дата
|
# Выбрана дата
|
||||||
date_str = query.data.replace("date_", "")
|
date_str = query.data.replace("date_", "")
|
||||||
|
|
@ -197,16 +251,31 @@ async def button_handler(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
await query.edit_message_text("Введите название занятия:")
|
await query.edit_message_text("Введите название занятия:")
|
||||||
return ENTERING_TITLE
|
return ENTERING_TITLE
|
||||||
|
|
||||||
# Возвращаемся в главное меню
|
elif query.data.startswith("delete_"):
|
||||||
keyboard = [
|
# Удаление элемента (формат: delete_ID_kind)
|
||||||
[InlineKeyboardButton("📅 Сегодня", callback_data="schedule_today")],
|
parts = query.data.replace("delete_", "").split("_")
|
||||||
[InlineKeyboardButton("📅 Завтра", callback_data="schedule_tomorrow")],
|
if len(parts) >= 2:
|
||||||
[InlineKeyboardButton("📅 Оставшиеся дни недели", callback_data="schedule_week")],
|
item_id = int(parts[0])
|
||||||
[InlineKeyboardButton("➕ Добавить занятие", callback_data="add_event")],
|
kind = parts[1]
|
||||||
[InlineKeyboardButton("➕ Добавить задачу", callback_data="add_task")],
|
await delete_item_by_id(query, item_id, kind)
|
||||||
]
|
if chat_id in user_states:
|
||||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
del user_states[chat_id]
|
||||||
await query.edit_message_text("Выберите действие:", reply_markup=reply_markup)
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
elif query.data.startswith("delete_scope_"):
|
||||||
|
# Удаление weekly task с указанием области
|
||||||
|
parts = query.data.replace("delete_scope_", "").split("_")
|
||||||
|
if len(parts) >= 3:
|
||||||
|
item_id = int(parts[0])
|
||||||
|
kind = parts[1]
|
||||||
|
scope = parts[2] # "one" или "series"
|
||||||
|
await delete_item_by_scope(query, item_id, kind, scope)
|
||||||
|
if chat_id in user_states:
|
||||||
|
del user_states[chat_id]
|
||||||
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
# Возвращаемся в главное меню после завершения операции
|
||||||
|
await show_main_menu(query.message)
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -237,6 +306,37 @@ async def show_date_selection(query, kind="event"):
|
||||||
await query.edit_message_text("Выберите дату:", reply_markup=reply_markup)
|
await query.edit_message_text("Выберите дату:", reply_markup=reply_markup)
|
||||||
|
|
||||||
|
|
||||||
|
async def show_date_selection_for_delete(query_or_message):
|
||||||
|
"""Показать выбор даты для удаления"""
|
||||||
|
today = datetime.now(TZ).date()
|
||||||
|
monday = today - timedelta(days=today.weekday())
|
||||||
|
next_monday = monday + timedelta(days=7)
|
||||||
|
|
||||||
|
keyboard = []
|
||||||
|
weekdays = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"]
|
||||||
|
|
||||||
|
# Текущая неделя
|
||||||
|
for i in range(7):
|
||||||
|
date = monday + timedelta(days=i)
|
||||||
|
date_str = date.strftime("%Y-%m-%d")
|
||||||
|
label = f"{weekdays[i]} {date.day}"
|
||||||
|
keyboard.append([InlineKeyboardButton(label, callback_data=f"del_date_{date_str}")])
|
||||||
|
|
||||||
|
# Следующая неделя
|
||||||
|
for i in range(7):
|
||||||
|
date = next_monday + timedelta(days=i)
|
||||||
|
date_str = date.strftime("%Y-%m-%d")
|
||||||
|
label = f"{weekdays[i]} {date.day}"
|
||||||
|
keyboard.append([InlineKeyboardButton(label, callback_data=f"del_date_{date_str}")])
|
||||||
|
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
# Проверяем, это query или message
|
||||||
|
if hasattr(query_or_message, 'edit_message_text'):
|
||||||
|
await query_or_message.edit_message_text("Выберите дату для удаления:", reply_markup=reply_markup)
|
||||||
|
else:
|
||||||
|
await query_or_message.reply_text("Выберите дату для удаления:", reply_markup=reply_markup)
|
||||||
|
|
||||||
|
|
||||||
async def show_hour_selection(query):
|
async def show_hour_selection(query):
|
||||||
"""Показать выбор часа"""
|
"""Показать выбор часа"""
|
||||||
keyboard = []
|
keyboard = []
|
||||||
|
|
@ -395,6 +495,151 @@ async def handle_repeat_choice(update: Update, context: ContextTypes.DEFAULT_TYP
|
||||||
return ConversationHandler.END
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
|
||||||
|
async def show_items_for_deletion(query, date_str: str):
|
||||||
|
"""Показать список задач и занятий для удаления"""
|
||||||
|
async with AsyncSessionLocal() as db:
|
||||||
|
from backend.utils import materialize_weekly_tasks
|
||||||
|
|
||||||
|
# Получаем tasks (разовые)
|
||||||
|
result = await db.execute(
|
||||||
|
select(Task).where(
|
||||||
|
and_(Task.date == date_str, Task.repeat_weekly == False)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tasks = result.scalars().all()
|
||||||
|
|
||||||
|
# Получаем weekly tasks для этой даты
|
||||||
|
weekly_items = await materialize_weekly_tasks(db, date_str, date_str)
|
||||||
|
weekly_tasks = [item for item in weekly_items if item.kind == "task"]
|
||||||
|
|
||||||
|
# Получаем events
|
||||||
|
result = await db.execute(
|
||||||
|
select(Event).where(Event.date == date_str)
|
||||||
|
)
|
||||||
|
events = result.scalars().all()
|
||||||
|
events = sorted(events, key=lambda e: e.start_time)
|
||||||
|
|
||||||
|
keyboard = []
|
||||||
|
items_list = []
|
||||||
|
|
||||||
|
# Добавляем tasks
|
||||||
|
for task in tasks:
|
||||||
|
items_list.append(("task", task.id, task.title, None))
|
||||||
|
keyboard.append([InlineKeyboardButton(f"📋 {task.title}", callback_data=f"delete_{task.id}_task")])
|
||||||
|
|
||||||
|
# Добавляем weekly tasks
|
||||||
|
for task_item in weekly_tasks:
|
||||||
|
# Для weekly tasks нужно найти оригинальную задачу
|
||||||
|
result = await db.execute(
|
||||||
|
select(Task).where(Task.id == task_item.id)
|
||||||
|
)
|
||||||
|
task = result.scalar_one_or_none()
|
||||||
|
if task:
|
||||||
|
items_list.append(("task", task.id, task.title, None))
|
||||||
|
keyboard.append([InlineKeyboardButton(f"📋 {task.title} (повторяется)", callback_data=f"delete_{task.id}_task")])
|
||||||
|
|
||||||
|
# Добавляем events
|
||||||
|
for event in events:
|
||||||
|
end_time = calculate_end_time(event.start_time, event.duration_min)
|
||||||
|
items_list.append(("event", event.id, event.title, event.start_time))
|
||||||
|
keyboard.append([InlineKeyboardButton(f"⏰ {event.start_time}-{end_time} {event.title}", callback_data=f"delete_{event.id}_event")])
|
||||||
|
|
||||||
|
if not keyboard:
|
||||||
|
await query.edit_message_text(f"На {format_date_russian(date_str)} нет записей для удаления.")
|
||||||
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
await query.edit_message_text(
|
||||||
|
f"Выберите запись для удаления на {format_date_russian(date_str)}:",
|
||||||
|
reply_markup=reply_markup
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_item_by_id(query, item_id: int, kind: str):
|
||||||
|
"""Удалить элемент по ID"""
|
||||||
|
async with AsyncSessionLocal() as db:
|
||||||
|
if kind == "task":
|
||||||
|
result = await db.execute(select(Task).where(Task.id == item_id))
|
||||||
|
task = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if task:
|
||||||
|
if task.repeat_weekly:
|
||||||
|
# Для weekly task нужно спросить область применения
|
||||||
|
chat_id = str(query.from_user.id)
|
||||||
|
if chat_id not in user_states:
|
||||||
|
user_states[chat_id] = {}
|
||||||
|
user_states[chat_id]["delete_item_id"] = item_id
|
||||||
|
user_states[chat_id]["delete_kind"] = "task"
|
||||||
|
user_states[chat_id]["delete_date"] = user_states[chat_id].get("delete_date", "")
|
||||||
|
|
||||||
|
keyboard = [
|
||||||
|
[InlineKeyboardButton("Только на эту дату", callback_data=f"delete_scope_{item_id}_task_one")],
|
||||||
|
[InlineKeyboardButton("Для всего ряда", callback_data=f"delete_scope_{item_id}_task_series")],
|
||||||
|
]
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
await query.edit_message_text(
|
||||||
|
f"Задача '{task.title}' повторяется каждую неделю. Что удалить?",
|
||||||
|
reply_markup=reply_markup
|
||||||
|
)
|
||||||
|
return SELECTING_DELETE_ITEM
|
||||||
|
else:
|
||||||
|
await db.delete(task)
|
||||||
|
await db.commit()
|
||||||
|
await query.edit_message_text(f"✅ Задача '{task.title}' удалена.")
|
||||||
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
elif kind == "event":
|
||||||
|
result = await db.execute(select(Event).where(Event.id == item_id))
|
||||||
|
event = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if event:
|
||||||
|
title = event.title
|
||||||
|
await db.delete(event)
|
||||||
|
await db.commit()
|
||||||
|
await query.edit_message_text(f"✅ Занятие '{title}' удалено.")
|
||||||
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
await query.edit_message_text("❌ Запись не найдена.")
|
||||||
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_item_by_scope(query, item_id: int, kind: str, scope: str):
|
||||||
|
"""Удалить элемент с указанием области (для weekly tasks)"""
|
||||||
|
async with AsyncSessionLocal() as db:
|
||||||
|
if kind == "task":
|
||||||
|
result = await db.execute(select(Task).where(Task.id == item_id))
|
||||||
|
task = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if task and task.repeat_weekly:
|
||||||
|
chat_id = str(query.from_user.id)
|
||||||
|
state = user_states.get(chat_id, {})
|
||||||
|
date_str = state.get("delete_date", "")
|
||||||
|
|
||||||
|
if scope == "one":
|
||||||
|
# Создаем исключение на удаление для конкретной даты
|
||||||
|
from backend.database import WeeklyTaskException
|
||||||
|
weekday = get_weekday_from_date(date_str)
|
||||||
|
exception = WeeklyTaskException(
|
||||||
|
weekday=weekday,
|
||||||
|
date=date_str,
|
||||||
|
action="delete"
|
||||||
|
)
|
||||||
|
db.add(exception)
|
||||||
|
await db.commit()
|
||||||
|
await query.edit_message_text(f"✅ Задача '{task.title}' удалена только на {format_date_russian(date_str)}.")
|
||||||
|
elif scope == "series":
|
||||||
|
# Удаляем весь ряд weekly tasks
|
||||||
|
await db.execute(
|
||||||
|
Task.__table__.delete().where(Task.weekday == task.weekday)
|
||||||
|
)
|
||||||
|
await db.commit()
|
||||||
|
await query.edit_message_text(f"✅ Задача '{task.title}' удалена для всего ряда.")
|
||||||
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
await query.edit_message_text("❌ Ошибка при удалении.")
|
||||||
|
return ConversationHandler.END
|
||||||
|
|
||||||
|
|
||||||
async def send_reminders():
|
async def send_reminders():
|
||||||
"""Отправка напоминаний о событиях"""
|
"""Отправка напоминаний о событиях"""
|
||||||
while True:
|
while True:
|
||||||
|
|
@ -477,7 +722,24 @@ async def start_bot():
|
||||||
name="task_conv",
|
name="task_conv",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Команды
|
||||||
application.add_handler(CommandHandler("start", start))
|
application.add_handler(CommandHandler("start", start))
|
||||||
|
application.add_handler(CommandHandler("today", today_command))
|
||||||
|
application.add_handler(CommandHandler("nextday", nextday_command))
|
||||||
|
application.add_handler(CommandHandler("addtask", addtask_command))
|
||||||
|
|
||||||
|
# Conversation handler для удаления
|
||||||
|
delete_conv = ConversationHandler(
|
||||||
|
entry_points=[CommandHandler("deltask", deltask_command)],
|
||||||
|
states={
|
||||||
|
SELECTING_DELETE_DATE: [CallbackQueryHandler(button_handler, pattern="^del_date_")],
|
||||||
|
SELECTING_DELETE_ITEM: [CallbackQueryHandler(button_handler, pattern="^(delete_|delete_scope_)")],
|
||||||
|
},
|
||||||
|
fallbacks=[],
|
||||||
|
name="delete_conv",
|
||||||
|
)
|
||||||
|
|
||||||
|
application.add_handler(delete_conv)
|
||||||
application.add_handler(event_conv)
|
application.add_handler(event_conv)
|
||||||
application.add_handler(task_conv)
|
application.add_handler(task_conv)
|
||||||
application.add_handler(CallbackQueryHandler(button_handler))
|
application.add_handler(CallbackQueryHandler(button_handler))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue