From 62c1c37f7af74d38de473530847a92a5b145c6e6 Mon Sep 17 00:00:00 2001 From: $MYNAME <$MYGITEMAIL> Date: Thu, 25 Feb 2021 16:50:13 +0100 Subject: [PATCH 1/2] View author profile functionality --- README.md | 4 ++- chatbot/app/fp_api_manager.py | 9 ++++- chatbot/app/handlers/__init__.py | 3 ++ chatbot/app/handlers/mainmenu.py | 12 ++++--- chatbot/app/handlers/view.py | 59 +++++++++++++++++++++++++++----- chatbot/app/keyboards.py | 10 +++++- chatbot/app/patterns.py | 3 ++ chatbot/app/user_data.py | 1 + chatbot/app/views.py | 11 +++--- chatbot/main.py | 3 ++ token_data.default.yaml | 2 +- 11 files changed, 95 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 4a6c72d0..34dc88bd 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,9 @@ or if you have make make install ``` -3. Generate a chatbot token and update `TELEGRAM_TOKEN` value in `token_data.yaml` file (the other constants below are only needed to run the tests) +3. Copy `token_data.default.yaml` to `token_data.yaml`. + +4. Generate a chatbot token and update `TELEGRAM_TOKEN` value in `token_data.yaml` file (the other constants below are only needed to run the tests) Follow the below steps which are already mentioned in the main.py file in the package. - Import logging library to connect and authenticate bot with Telegram API diff --git a/chatbot/app/fp_api_manager.py b/chatbot/app/fp_api_manager.py index 6aec5e00..e2eb28e1 100644 --- a/chatbot/app/fp_api_manager.py +++ b/chatbot/app/fp_api_manager.py @@ -65,7 +65,7 @@ def get_posts(payload): def get_post(post_id): - url = FP_BASE_URL + "posts/" + post_id + url = FP_BASE_URL + f"posts/{post_id}" with requests.Session() as s: return _try_get(s, url) @@ -79,6 +79,13 @@ def get_current_user_profile(token): return _try_get(s, url) +def get_user_profile(user_id): + url = FP_BASE_URL + f"users/{user_id}" + + with requests.Session() as s: + return _try_get(s, url) + + def get_user_location(latitude, longitude): payload = { 'lat': latitude, diff --git a/chatbot/app/handlers/__init__.py b/chatbot/app/handlers/__init__.py index 43097bbb..99336118 100644 --- a/chatbot/app/handlers/__init__.py +++ b/chatbot/app/handlers/__init__.py @@ -1,5 +1,6 @@ from .mainmenu import ( MainMenuCmdHandler, + MainMenuQueryHandler, ) from .info import ( HelpCmdHandler, @@ -14,6 +15,8 @@ ViewMyPostsQueryHandler, DisplaySelectedPostsQueryHandler, ViewPostsConvHandler, + ViewAuthorProfileQueryHandler, + GoBackViewAuthorQueryHandler, ) from .create_post import ( CreatePostConvHandler, diff --git a/chatbot/app/handlers/mainmenu.py b/chatbot/app/handlers/mainmenu.py index 39cec45e..e865321d 100644 --- a/chatbot/app/handlers/mainmenu.py +++ b/chatbot/app/handlers/mainmenu.py @@ -1,6 +1,7 @@ -from telegram.ext import CommandHandler +from telegram.ext import CommandHandler, CallbackQueryHandler -from chatbot.app import keyboards, user_data +from chatbot.app import keyboards, user_data, patterns +from chatbot.app.handlers import util def main_menu(update, context): @@ -10,10 +11,13 @@ def main_menu(update, context): user_signed_in = user_data.is_user_signed_in(context=context) if not user_signed_in: text += " To see more options, create posts etc you need to first login." - update.message.reply_text( + util.reply( + update=update, + context=context, text=text, - reply_markup=keyboards.main_menu(user_signed_in), + keyboard=keyboards.main_menu(user_signed_in), ) MainMenuCmdHandler = CommandHandler("mainmenu", main_menu) +MainMenuQueryHandler = CallbackQueryHandler(main_menu, pattern=patterns.MAINMENU) diff --git a/chatbot/app/handlers/view.py b/chatbot/app/handlers/view.py index d0443dcd..595329cd 100644 --- a/chatbot/app/handlers/view.py +++ b/chatbot/app/handlers/view.py @@ -1,4 +1,5 @@ import json +import logging from enum import Enum, auto from telegram.ext import ( @@ -33,8 +34,7 @@ def view_my_profile(update, context): return response = fpapi.get_current_user_profile(token=token) - if isinstance(response, fpapi.Error): # TODO handle error better - raise ConnectionError("Could not get user profile") + _assert_not_error(response) user_info_view = views.UserProfile(response).display() update.effective_message.reply_text(text=user_info_view) @@ -187,8 +187,7 @@ def _get_posts(context, payload): # keep a copy of post_payload in user_data for future calls - TODO is it used? context.user_data[user_data.VIEW_POST_PAYLOAD] = payload posts = fpapi.get_posts(payload) - if isinstance(posts, fpapi.Error): # TODO handle error better - raise ConnectionError("Could not get posts") + _assert_not_error(posts) return posts @@ -320,6 +319,7 @@ def _handle_post_id_choice(update, context, user_choice): _update_user_post_id(context, post_id=post_id) _show_user_single_post( update=update, + context=context, post_id=post_id, ) @@ -354,17 +354,23 @@ def _get_real_post_id(context, user_choice): return context.user_data[user_data.VIEW_POST_IDS][int(user_choice) - 1] -def _show_user_single_post(update, post_id): +def _show_user_single_post(update, context, post_id): post = fpapi.get_post(post_id) - if isinstance(post, fpapi.Error): # TODO handle error better - raise ConnectionError("Could not get post") + _assert_not_error(post) reply_text = views.Post(post_json=post).display() - update.effective_message.reply_text( + util.reply( + update=update, + context=context, text=reply_text, - reply_markup=keyboards.display_selected_post(), + keyboard=keyboards.display_selected_post(), ) +def _assert_not_error(post): + if isinstance(post, fpapi.Error): # TODO handle error better + raise ConnectionError("Could not get post") + + def _get_header_message_with_categories(context): formatted_categories = ", ".join(context.user_data[user_data.POST_CATEGORIES]) page = _get_current_user_page(context) @@ -377,7 +383,42 @@ def _get_header_message_user_posts(context): return f"Page {page} of your posts" + +def view_author_profile(update, context): + post_id = context.user_data.get(user_data.VIEW_POST_ID) + if post_id is None: + logging.warning("No post ID to view author for") + return + author = _get_author_profile_from_post_id(post_id=post_id) + util.reply( + update=update, + context=context, + text=author.display(), + keyboard=keyboards.view_author(), + ) + + +def handle_go_back_view_author(update, context): + post_id = context.user_data[user_data.VIEW_POST_ID] + _show_user_single_post( + update=update, + context=context, + post_id=post_id, + ) + + +def _get_author_profile_from_post_id(post_id): + raw_post = fpapi.get_post(post_id) + _assert_not_error(raw_post) + post = views.Post(post_json=raw_post) + response = fpapi.get_user_profile(user_id=post.author_id) + _assert_not_error(response) + return views.UserProfile(response) + + ViewMyProfileQueryHandler = CallbackQueryHandler(view_my_profile, pattern=patterns.VIEW_MY_PROFILE) ViewMyPostsQueryHandler = CallbackQueryHandler(view_my_posts, pattern=patterns.VIEW_MY_POSTS) DisplaySelectedPostsQueryHandler = CallbackQueryHandler(display_selected_post, pattern=patterns.DISPLAY_SELECTED_POST) ViewPostsConvHandler = view_posts_conversation() +ViewAuthorProfileQueryHandler = CallbackQueryHandler(view_author_profile, pattern=patterns.VIEW_AUTHOR_PROFILE) +GoBackViewAuthorQueryHandler = CallbackQueryHandler(handle_go_back_view_author, pattern=patterns.GO_BACK_VIEW_AUTHOR) diff --git a/chatbot/app/keyboards.py b/chatbot/app/keyboards.py index 59659d87..ea3dbe53 100644 --- a/chatbot/app/keyboards.py +++ b/chatbot/app/keyboards.py @@ -113,11 +113,19 @@ def view_posts(): def display_selected_post(): - _construct_inline_keyboard([[ + return _construct_inline_keyboard([[ patterns.SEE_COMMENTS, patterns.VIEW_AUTHOR_PROFILE, patterns.POST_COMMENT ]]) +def view_author(): + return InlineKeyboardMarkup([[ + _construct_inline_button(patterns.MAINMENU), + InlineKeyboardButton('Go Back', callback_data=patterns.GO_BACK_VIEW_AUTHOR) + ]]) + + + def no_location(): return InlineKeyboardMarkup([[ InlineKeyboardButton('I don\'t want to share my location right now', callback_data='view_posts'), diff --git a/chatbot/app/patterns.py b/chatbot/app/patterns.py index 77ac93c6..2e295630 100644 --- a/chatbot/app/patterns.py +++ b/chatbot/app/patterns.py @@ -1,3 +1,5 @@ +MAINMENU = "Main Menu" + OFFER_HELP = "Offer Help" REQUEST_HELP = "Request Help" VIEW_MY_POSTS = "View My Posts" @@ -27,3 +29,4 @@ OFFER_HELP_POSTS = "Posts that offer help" DISPLAY_SELECTED_POST = "display_selected_post" +GO_BACK_VIEW_AUTHOR = "go_back_view_author" diff --git a/chatbot/app/user_data.py b/chatbot/app/user_data.py index 3fe83950..19f9530b 100644 --- a/chatbot/app/user_data.py +++ b/chatbot/app/user_data.py @@ -7,6 +7,7 @@ LOCATION = 'location' AUTHOR = 'author' AUTHOR_NAME = 'name' +AUTHOR_ID = 'id' POST_TITLE = "title" POST_DESCRIPTION = "content" diff --git a/chatbot/app/views.py b/chatbot/app/views.py index 072576c8..4d11edc8 100644 --- a/chatbot/app/views.py +++ b/chatbot/app/views.py @@ -12,11 +12,12 @@ def __init__(self, post_json): self._post_json = post_json self.title = self._extract_field(user_data.POST_TITLE) - author_data = self._extract_field(user_data.AUTHOR) - self.author = author_data[user_data.AUTHOR_NAME] + self.author_data = self._extract_field(user_data.AUTHOR) + self.author_id = self.author_data[user_data.AUTHOR_ID] + self.author = self.author_data[user_data.AUTHOR_NAME] self.categories = self._extract_field(user_data.POST_CATEGORIES) self.content = self._extract_field(user_data.POST_DESCRIPTION) - self.location = self._extract_location(author_data[user_data.LOCATION]) + self.location = self._extract_location(self.author_data[user_data.LOCATION]) self.num_comments = self._extract_field(user_data.NUM_COMMENTS) def _extract_field(self, field): @@ -76,10 +77,10 @@ def _get_data_from_post_json(self): class UserProfile(object): def __init__(self, json_data): - self.email = json_data['email'] + self.email = json_data.get('email') self.firstName = json_data['firstName'] self.lastName = json_data['lastName'] - self.address = json_data['location']['address'] + self.address = json_data['location'].get('address') self.is_volunteer = "No" if json_data['objectives']['volunteer']: self.is_volunteer = "Yes" diff --git a/chatbot/main.py b/chatbot/main.py index 10ffc44f..14403989 100644 --- a/chatbot/main.py +++ b/chatbot/main.py @@ -41,11 +41,14 @@ def main(log_level="INFO"): dp.add_handler(handlers.ViewPostsConvHandler) # Callback query handlers + dp.add_handler(handlers.MainMenuQueryHandler) dp.add_handler(handlers.AboutQueryHandler) dp.add_handler(handlers.SignoutQueryHandler) dp.add_handler(handlers.ViewMyProfileQueryHandler) dp.add_handler(handlers.ViewMyPostsQueryHandler) dp.add_handler(handlers.DisplaySelectedPostsQueryHandler) + dp.add_handler(handlers.ViewAuthorProfileQueryHandler) + dp.add_handler(handlers.GoBackViewAuthorQueryHandler) # To start polling Telegram for any chat updates on Telegram updater.start_polling() diff --git a/token_data.default.yaml b/token_data.default.yaml index d477145a..38048883 100644 --- a/token_data.default.yaml +++ b/token_data.default.yaml @@ -1,5 +1,5 @@ # Base URL -FP_BASE_URL: "http://127.0.0.1:8000/api/" +FP_BASE_URL: "https://fightpandemics.com/api/" ################# # Access tokens # From 9e17ea3b1eb9b11d9a8384083b4d0d5f07f4f185 Mon Sep 17 00:00:00 2001 From: $MYNAME <$MYGITEMAIL> Date: Thu, 25 Feb 2021 17:01:41 +0100 Subject: [PATCH 2/2] Fixed tests and linting --- chatbot/app/handlers/view.py | 21 ++++++++++++--------- chatbot/app/keyboards.py | 1 - chatbot/app/views.py | 2 +- token_data.default.yaml | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/chatbot/app/handlers/view.py b/chatbot/app/handlers/view.py index 595329cd..6cf29e58 100644 --- a/chatbot/app/handlers/view.py +++ b/chatbot/app/handlers/view.py @@ -34,7 +34,8 @@ def view_my_profile(update, context): return response = fpapi.get_current_user_profile(token=token) - _assert_not_error(response) + if _is_error(response): + raise ConnectionError("Could not get current profile") # TODO handle errors better user_info_view = views.UserProfile(response).display() update.effective_message.reply_text(text=user_info_view) @@ -187,7 +188,8 @@ def _get_posts(context, payload): # keep a copy of post_payload in user_data for future calls - TODO is it used? context.user_data[user_data.VIEW_POST_PAYLOAD] = payload posts = fpapi.get_posts(payload) - _assert_not_error(posts) + if _is_error(posts): + raise ConnectionError("Could not get posts") # TODO handle errors better return posts @@ -356,7 +358,8 @@ def _get_real_post_id(context, user_choice): def _show_user_single_post(update, context, post_id): post = fpapi.get_post(post_id) - _assert_not_error(post) + if _is_error(post): + raise ConnectionError("Could not get post") # TODO handle errors better reply_text = views.Post(post_json=post).display() util.reply( update=update, @@ -366,9 +369,8 @@ def _show_user_single_post(update, context, post_id): ) -def _assert_not_error(post): - if isinstance(post, fpapi.Error): # TODO handle error better - raise ConnectionError("Could not get post") +def _is_error(post): + return isinstance(post, fpapi.Error) def _get_header_message_with_categories(context): @@ -383,7 +385,6 @@ def _get_header_message_user_posts(context): return f"Page {page} of your posts" - def view_author_profile(update, context): post_id = context.user_data.get(user_data.VIEW_POST_ID) if post_id is None: @@ -409,10 +410,12 @@ def handle_go_back_view_author(update, context): def _get_author_profile_from_post_id(post_id): raw_post = fpapi.get_post(post_id) - _assert_not_error(raw_post) + if _is_error(raw_post): + raise ConnectionError("Could not get post") # TODO handle errors better post = views.Post(post_json=raw_post) response = fpapi.get_user_profile(user_id=post.author_id) - _assert_not_error(response) + if _is_error(response): + raise ConnectionError("Could not get user profile") # TODO handle errors better return views.UserProfile(response) diff --git a/chatbot/app/keyboards.py b/chatbot/app/keyboards.py index ea3dbe53..5812f3cf 100644 --- a/chatbot/app/keyboards.py +++ b/chatbot/app/keyboards.py @@ -125,7 +125,6 @@ def view_author(): ]]) - def no_location(): return InlineKeyboardMarkup([[ InlineKeyboardButton('I don\'t want to share my location right now', callback_data='view_posts'), diff --git a/chatbot/app/views.py b/chatbot/app/views.py index 4d11edc8..c1cf5f34 100644 --- a/chatbot/app/views.py +++ b/chatbot/app/views.py @@ -13,7 +13,7 @@ def __init__(self, post_json): self.title = self._extract_field(user_data.POST_TITLE) self.author_data = self._extract_field(user_data.AUTHOR) - self.author_id = self.author_data[user_data.AUTHOR_ID] + self.author_id = self.author_data.get(user_data.AUTHOR_ID) self.author = self.author_data[user_data.AUTHOR_NAME] self.categories = self._extract_field(user_data.POST_CATEGORIES) self.content = self._extract_field(user_data.POST_DESCRIPTION) diff --git a/token_data.default.yaml b/token_data.default.yaml index 38048883..d477145a 100644 --- a/token_data.default.yaml +++ b/token_data.default.yaml @@ -1,5 +1,5 @@ # Base URL -FP_BASE_URL: "https://fightpandemics.com/api/" +FP_BASE_URL: "http://127.0.0.1:8000/api/" ################# # Access tokens #