From dd32a848ddec5a951f2c6c3490bf2fc0953e4361 Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 19 Sep 2022 16:20:20 +0400 Subject: [PATCH 1/5] Add start + menu commands --- bot.py | 2 + src/db_helper.py | 16 ++-- src/handlers/menu_handlers.py | 144 +++++++++++++++++++++---------- src/handlers/profile_handlers.py | 2 +- src/models/static_models.py | 1 + 5 files changed, 109 insertions(+), 56 deletions(-) diff --git a/bot.py b/bot.py index 83623f6..a161aea 100644 --- a/bot.py +++ b/bot.py @@ -65,6 +65,8 @@ def debug_main(): def add_handlers(): application.add_handler(CommandHandler(command=Command.start, callback=start_handler)) + application.add_handler(CommandHandler(command=Command.menu, callback=menu_handler)) + application.add_handler(CallbackQueryHandler(callback=pause_handler, pattern=f"^{MenuCallback.pause}$")) application.add_handler(profile_conversation_handler()) diff --git a/src/db_helper.py b/src/db_helper.py index d4081b9..94ee670 100644 --- a/src/db_helper.py +++ b/src/db_helper.py @@ -1,15 +1,15 @@ -from collections import namedtuple from typing import Optional -from src.vars import PROD -from src.models.user import User -from src.models.static_models import MeetingFormatCallback +from typing import Optional import boto3 -from boto3.dynamodb.conditions import Key, Attr +from boto3.dynamodb.conditions import Attr +from src.models.static_models import MeetingFormatCallback +from src.models.user import User +from src.vars import PROD -class DBHelper(object): +class DBHelper(object): def __new__(cls): if not hasattr(cls, 'instance'): cls.instance = super(DBHelper, cls).__new__(cls) @@ -55,9 +55,9 @@ def update_user_profile( 'bio': user.bio, 'is_paused': user.is_paused } - if user.meeting_format == MeetingFormatCallback.offline: + if user.meeting_format == MeetingFormatCallback.offline.id: item['city'] = user.city - item['country']: user.country + item['country'] = user.country self._usersTable.put_item(Item=item) diff --git a/src/handlers/menu_handlers.py b/src/handlers/menu_handlers.py index 4d1afd9..f78f982 100644 --- a/src/handlers/menu_handlers.py +++ b/src/handlers/menu_handlers.py @@ -2,6 +2,7 @@ from telegram import ( error, + ChatMember, ChatMemberBanned, ChatMemberLeft, Update, @@ -17,32 +18,60 @@ logging.getLogger().setLevel('INFO') db_helper = DBHelper() -current_session_user: User +current_db_user: User +chat_member: ChatMember async def start_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): try: logging.info('Getting chat member..') - member = await context.bot.get_chat_member(chat_id=MEMBERSHIP_CHAT_ID, user_id=update.effective_user.id) + member = await _get_chat_member(update, context) logging.info('Done..') if member.status is not (ChatMemberLeft or ChatMemberBanned): logging.info('Member is fine, show keyboard..') - db_user = db_helper.get_user(user_id=member.user.id) + db_user = _get_db_user(update, context) + + if db_user is None: + await update.message.reply_text( + text="Привет, я Random Coffee bot!\n\n" + "Чтобы начать, заполни профиль ниже. Еще можно временно остановить участие и оставить отзыв", + ) - reply_keyboard: list[list] = [] + menu_markup = _menu_buttons(context, member, db_user) - if db_user is not None: - _add_existing_user_buttons(context, member, db_user, reply_keyboard) + await update.message.reply_text( + text='Меню', + reply_markup=menu_markup + ) else: - reply_keyboard.append( - [InlineKeyboardButton('👤 Заполнить профиль', callback_data=MenuCallback.fill_profile)] + await update.message.reply_text( + text="Ты уже стартовал, вызови /menu, чтобы заполнить или отредактировать профиль" ) + else: + logging.info('Sending membership message request...') + await send_membership_message(update, context) + logging.info('Done') + except error.BadRequest as e: + logging.info('Bad request') + logging.info(e) + await send_membership_message(update, context) + + +async def menu_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): + try: + logging.info('Getting chat member..') + member = await context.bot.get_chat_member(chat_id=MEMBERSHIP_CHAT_ID, user_id=update.effective_user.id) + logging.info('Done..') + + if member.status is not (ChatMemberLeft or ChatMemberBanned): + logging.info('Member is fine, show keyboard..') + db_user = _get_db_user(update, context) + reply_keyboard_markup = _menu_buttons(context, member, db_user) await update.message.reply_text( - "Привет, я Random Coffee bot!\n\n" - "Чтобы начать, заполни профиль ниже. Еще можно временно остановить участие и оставить отзыв", - reply_markup=InlineKeyboardMarkup(reply_keyboard) + 'Меню', + reply_markup=reply_keyboard_markup ) logging.info('Keyboard shown') @@ -58,31 +87,22 @@ async def start_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): async def pause_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): logging.info('Setting pause on/off') - global current_session_user + db_user = _get_db_user(update, context) - try: - current_session_user - except NameError: - current_session_user = db_helper.get_user(str(update.effective_user.id)) + should_pause = not db_user.is_paused + db_user.is_paused = should_pause + context.user_data['is_paused'] = db_user.is_paused - should_pause = not current_session_user.is_paused - current_session_user.is_paused = should_pause + member = await _get_chat_member(update, context) - db_helper.pause_user(should_pause, current_session_user) - inline_keyboard = update.callback_query.message.reply_markup.inline_keyboard + db_helper.pause_user(should_pause, db_user) + inline_markup = _menu_buttons(context, member, db_user) - for idx, inline_buttons in enumerate(inline_keyboard): - if inline_buttons[0].callback_data == MenuCallback.pause: - if not should_pause: - inline_keyboard[idx] = \ - [InlineKeyboardButton(text='⏸️ Поставить на паузу', callback_data=MenuCallback.pause)] - else: - inline_keyboard[idx] = \ - [InlineKeyboardButton(text='▶️ Снять с паузы', callback_data=MenuCallback.pause)] - break + await update.effective_message.edit_reply_markup(inline_markup) - await update.callback_query.message.edit_reply_markup( - InlineKeyboardMarkup(inline_keyboard=inline_keyboard) + await update.effective_message.reply_text( + text='Скрыл твою анкету. Если захочешь участвовать снова, сними с паузы' if should_pause + else 'Открыл твою анкету, ты снова участвуешь' ) logging.info('Done pause') @@ -125,33 +145,63 @@ async def send_membership_message(update: Update, context: ContextTypes.DEFAULT_ # Private -def _add_existing_user_buttons(context, member, db_user, reply_keyboard_markup): - context.user_data['is_paused'] = db_user.is_paused - global current_session_user - current_session_user = db_user +def _get_db_user(update, context) -> User: + global current_db_user + + try: + current_db_user + except NameError: + current_db_user = db_helper.get_user(str(update.effective_user.id)) + + if current_db_user is not None: + context.user_data['is_paused'] = current_db_user.is_paused + + return current_db_user + + +async def _get_chat_member(update, context) -> ChatMember: + global chat_member + + try: + chat_member + except NameError: + chat_member = await context.bot.get_chat_member(chat_id=MEMBERSHIP_CHAT_ID, user_id=update.effective_user.id) + + return chat_member - reply_keyboard_markup.append( - [InlineKeyboardButton('👤 Редактировать профиль', callback_data=MenuCallback.fill_profile)] - ) - if not db_user.is_paused: - reply_keyboard_markup.append( - [InlineKeyboardButton(text='⏸️ Поставить на паузу', callback_data=MenuCallback.pause)] +def _menu_buttons(context, member, db_user) -> InlineKeyboardMarkup: + reply_keyboard: list[list[InlineKeyboardButton]] = [] + + if db_user is not None: + reply_keyboard.append( + [InlineKeyboardButton('👤 Редактировать профиль', callback_data=MenuCallback.fill_profile)] + ) + + if not db_user.is_paused: + reply_keyboard.append( + [InlineKeyboardButton(text='⏸️ Поставить на паузу', callback_data=MenuCallback.pause)] + ) + else: + reply_keyboard.append( + [InlineKeyboardButton(text='▶️ Снять с паузы', callback_data=MenuCallback.pause)] + ) + + reply_keyboard.append( + [InlineKeyboardButton(text='💡 Отправить отзыв', callback_data=MenuCallback.send_feedback)] ) else: - reply_keyboard_markup.append( - [InlineKeyboardButton(text='▶️ Снять с паузы', callback_data=MenuCallback.pause)] + reply_keyboard.append( + [InlineKeyboardButton('👤 Заполнить профиль', callback_data=MenuCallback.fill_profile)] ) if str(member.user.id) in ADMIN_ACCOUNTS: logging.info(f'{member.user.id} is admin. adding generate pairs handler...') - reply_keyboard_markup.append( + reply_keyboard.append( [InlineKeyboardButton(text='🎲 Сгенерировать пары', callback_data=MenuCallback.generate_pairs)] ) context.application.add_handler( CallbackQueryHandler(callback=generate_pairs_handler, pattern=f"{MenuCallback.generate_pairs}") ) - reply_keyboard_markup.append( - [InlineKeyboardButton(text='💡 Отправить отзыв', callback_data=MenuCallback.send_feedback)] - ) + return InlineKeyboardMarkup(inline_keyboard=reply_keyboard) diff --git a/src/handlers/profile_handlers.py b/src/handlers/profile_handlers.py index 5c42a57..d20de8a 100644 --- a/src/handlers/profile_handlers.py +++ b/src/handlers/profile_handlers.py @@ -178,5 +178,5 @@ async def update_user_in_db(update, context): db_helper.update_user_profile(user) await update.message.reply_text( - "Записано, жди понедельника!" + "Записано, жди понедельника! Можно вызвать меню, чтобы отредактировать профиль, поставить на паузу или оставить отзыв" ) diff --git a/src/models/static_models.py b/src/models/static_models.py index 8fa6472..38f305b 100644 --- a/src/models/static_models.py +++ b/src/models/static_models.py @@ -4,6 +4,7 @@ class Command: start = 'start' + menu = 'menu' generate_pairs = 'generate' cancel = 'cancel' From 14bd0ec435d02edf5fa53962f35f12a27adb111e Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 19 Sep 2022 17:21:40 +0400 Subject: [PATCH 2/5] Fix hanging queries --- bot.py | 2 +- src/db_helper.py | 3 +-- src/handlers/menu_handlers.py | 8 +++++++- src/handlers/pair_handlers.py | 3 +++ src/handlers/profile_handlers.py | 3 +++ 5 files changed, 15 insertions(+), 4 deletions(-) diff --git a/bot.py b/bot.py index a161aea..1b373a7 100644 --- a/bot.py +++ b/bot.py @@ -99,7 +99,7 @@ def profile_conversation_handler() -> ConversationHandler: CommandHandler(Command.cancel, cancel_handler) ], FillProfileCallback.city: [ - MessageHandler(filters=filters.LOCATION & ~filters.COMMAND, callback=city_handler), + MessageHandler(filters=filters.LOCATION & ~filters.COMMAND & ~filters.TEXT, callback=city_handler), CommandHandler(Command.cancel, cancel_handler) ], FillProfileCallback.bio: [ diff --git a/src/db_helper.py b/src/db_helper.py index 94ee670..301a065 100644 --- a/src/db_helper.py +++ b/src/db_helper.py @@ -1,8 +1,7 @@ from typing import Optional -from typing import Optional import boto3 -from boto3.dynamodb.conditions import Attr +from boto3.dynamodb.conditions import Key, Attr from src.models.static_models import MeetingFormatCallback from src.models.user import User diff --git a/src/handlers/menu_handlers.py b/src/handlers/menu_handlers.py index f78f982..e8d844a 100644 --- a/src/handlers/menu_handlers.py +++ b/src/handlers/menu_handlers.py @@ -61,7 +61,7 @@ async def start_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): async def menu_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): try: logging.info('Getting chat member..') - member = await context.bot.get_chat_member(chat_id=MEMBERSHIP_CHAT_ID, user_id=update.effective_user.id) + member = await _get_chat_member(update, context) logging.info('Done..') if member.status is not (ChatMemberLeft or ChatMemberBanned): @@ -85,6 +85,9 @@ async def menu_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): async def pause_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): + query = update.callback_query + await query.answer() + logging.info('Setting pause on/off') db_user = _get_db_user(update, context) @@ -109,6 +112,9 @@ async def pause_handler(update: Update, context: ContextTypes.DEFAULT_TYPE): async def feedback_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: + query = update.callback_query + await query.answer() + user_id = update.effective_user.id await context.bot.send_message( diff --git a/src/handlers/pair_handlers.py b/src/handlers/pair_handlers.py index 053758e..f93eab7 100644 --- a/src/handlers/pair_handlers.py +++ b/src/handlers/pair_handlers.py @@ -16,6 +16,9 @@ async def generate_pairs(update: Update, context: CallbackContext): + query = update.callback_query + await query.answer() + logging.info('Generating pairs...') pairs: list[list] = [] diff --git a/src/handlers/profile_handlers.py b/src/handlers/profile_handlers.py index d20de8a..07f16cf 100644 --- a/src/handlers/profile_handlers.py +++ b/src/handlers/profile_handlers.py @@ -24,6 +24,9 @@ # Conversation handlers async def profile_handler(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: + query = update.callback_query + await query.answer() + await context.bot.send_chat_action(chat_id=update.effective_chat.id, action=ChatAction.TYPING) logging.info('Member is fine, show keyboard..') From 3fb0727e82b7bd3740b366fb9cbf68e8623d04a8 Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 19 Sep 2022 21:02:37 +0400 Subject: [PATCH 3/5] Clean up --- src/handlers/menu_handlers.py | 3 --- src/handlers/pair_handlers.py | 2 +- src/models/static_models.py | 1 - 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/handlers/menu_handlers.py b/src/handlers/menu_handlers.py index e8d844a..93ebb9d 100644 --- a/src/handlers/menu_handlers.py +++ b/src/handlers/menu_handlers.py @@ -1,11 +1,8 @@ -import logging - from telegram import ( error, ChatMember, ChatMemberBanned, ChatMemberLeft, - Update, InlineKeyboardButton, InlineKeyboardMarkup ) diff --git a/src/handlers/pair_handlers.py b/src/handlers/pair_handlers.py index f93eab7..09bd131 100644 --- a/src/handlers/pair_handlers.py +++ b/src/handlers/pair_handlers.py @@ -70,7 +70,7 @@ async def _send_pair_messages(update, context, pair: (User, User)): chat_id=first_user_id if PROD else update.effective_user.id, text=f"Штош, @{first_user_name}!\n\n" \ f"Твоя пара на эту неделю @{second_user_name}. " \ - f"Вы оба хотели {meeting_format}.\n\n" \ + f"Вы хотели {meeting_format}.\n\n" \ f"Можно начать разговор с обсуждения интересов собеседника: {second_user_bio}" ) await context.bot.send_photo( diff --git a/src/models/static_models.py b/src/models/static_models.py index 38f305b..fb9ee83 100644 --- a/src/models/static_models.py +++ b/src/models/static_models.py @@ -1,5 +1,4 @@ from collections import namedtuple -from dataclasses import dataclass class Command: From d971f3215091800373f13567e615c29d0b457cf5 Mon Sep 17 00:00:00 2001 From: MK Date: Mon, 19 Sep 2022 21:07:28 +0400 Subject: [PATCH 4/5] Change texts --- src/handlers/profile_handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handlers/profile_handlers.py b/src/handlers/profile_handlers.py index 07f16cf..bd060c0 100644 --- a/src/handlers/profile_handlers.py +++ b/src/handlers/profile_handlers.py @@ -104,7 +104,7 @@ async def meeting_format_handler(update: Update, context: CallbackContext) -> in new_keyboard = [[KeyboardButton(text='Поделиться локацией', request_location=True)]] await context.bot.send_message( chat_id=update.effective_user.id, - text='Где ты находишься? В локации будут использованы только страна и город', + text='Где ты находишься? Нажми кнопку "Поделиться локацией" снизу. \n\nИз локации будут использованы только страна и город', reply_markup=ReplyKeyboardMarkup(new_keyboard, one_time_keyboard=True, resize_keyboard=True) ) return FillProfileCallback.city From af4d92aa5be49d1c409498beb691ffdefee10f11 Mon Sep 17 00:00:00 2001 From: Max Kraev <31866271+havebeenfitz@users.noreply.github.com> Date: Mon, 19 Sep 2022 21:09:08 +0400 Subject: [PATCH 5/5] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f7d9d00..7a126d8 100644 --- a/README.md +++ b/README.md @@ -20,15 +20,15 @@ Dependencies: - AWS cron job - Generate pairs weekly - Bot asks if a user contacted his/her pair on Wed/Thu -- Inline Menu - - Pause/Resume buttons - - Generate pairs for admins - - Provide Feedback button + send to separate channel +- ~~Inline Menu~~ + - ~~Pause/Resume buttons~~ + - ~~Generate pairs for admins~~ + - ~~Provide Feedback button + send to separate channel~~ ### Nice to have: - Select offline location from a list - Notify members about the next round -- Edit profile from inline menu +- ~~Edit profile from inline menu~~ - Move all strings to a separate file/module