diff --git a/src/bot.py b/src/bot.py index 73b4ae5..a781ef0 100644 --- a/src/bot.py +++ b/src/bot.py @@ -9,7 +9,9 @@ BREAKFAST, DINNER, SETTINGS, - HIDE_CUISINE + HIDE_CUISINE, + ADD_FAVORITE, + REMOVE_FAVORITE ) from commands.meal import handle_menu from commands.general import ( @@ -17,7 +19,8 @@ handle_help, handle_error ) -from commands.settings import handle_settings, handle_hidden_cuisine, handle_hide_cuisine +from commands.settings import (handle_settings, handle_hidden_cuisine, handle_hide_cuisine, + handle_add_favorite, handle_remove_favorite, handle_remove_favorite_callback) from database.database import connect # Enable logging @@ -41,12 +44,15 @@ def main(): dispatcher.add_handler(CommandHandler(DINNER, handle_menu(meal=DINNER))) dispatcher.add_handler(CommandHandler(SETTINGS, handle_settings)) dispatcher.add_handler(CommandHandler(HIDE_CUISINE, handle_hidden_cuisine)) + dispatcher.add_handler(CommandHandler(ADD_FAVORITE, handle_add_favorite)) + dispatcher.add_handler(CommandHandler(REMOVE_FAVORITE, handle_remove_favorite)) # add callback_query handler dispatcher.add_handler(CallbackQueryHandler(handle_start, pattern='^start.+')) dispatcher.add_handler(CallbackQueryHandler(handle_settings, pattern='^settings.home')) dispatcher.add_handler(CallbackQueryHandler(handle_hidden_cuisine, pattern='^settings.hidden')) dispatcher.add_handler(CallbackQueryHandler(handle_hide_cuisine, pattern='^menu.+')) + dispatcher.add_handler(CallbackQueryHandler(handle_remove_favorite_callback, pattern='^favorites.+')) # log all errors dispatcher.add_error_handler(handle_error) diff --git a/src/commands/general.py b/src/commands/general.py index 34bc40f..7186610 100644 --- a/src/commands/general.py +++ b/src/commands/general.py @@ -4,7 +4,7 @@ def handle_start(update, context): context.bot.send_message(chat_id=update.effective_chat.id, text=welcome_msg( - update.message.chat.first_name)) + update.effective_chat.first_name)) def handle_help(update, context): diff --git a/src/commands/meal.py b/src/commands/meal.py index ae66efa..db02572 100644 --- a/src/commands/meal.py +++ b/src/commands/meal.py @@ -20,7 +20,7 @@ def get_breakfast_or_dinner_menu(update, context): if menu is None: # if no menu, reply with no menu message context.bot.send_message(chat_id=update.effective_chat.id, text=no_menu_msg(meal)) else: # else reply user of the menu - menu = menu_msg(date.today(), meal, parse_menu(menu)) + menu = menu_msg(date.today(), meal, parse_menu(menu, hidden_cuisines)) # send formatted menu to client update.message.reply_text(menu, parse_mode='HTML') diff --git a/src/commands/settings.py b/src/commands/settings.py index b2809a2..428b53b 100644 --- a/src/commands/settings.py +++ b/src/commands/settings.py @@ -1,8 +1,11 @@ from util.messages import settings_msg -from util.kb_mark_up import settings_kb, hidden_cuisine_kb +from util.kb_mark_up import settings_kb, hidden_cuisine_kb, favorites_kb from util.util import parse_callback -from database.database import get_hidden_cuisines, update_hidden_cuisine -from util.messages import no_hidden_cuisine_msg, hidden_cuisine_msg +from database.database import get_hidden_cuisines, update_hidden_cuisine, get_favorite_foods, update_favorite_foods +from util.messages import (no_hidden_cuisine_msg, hidden_cuisine_msg, add_favorite_no_input_msg, + add_favorite_already_exists_msg, added_favorites_msg, no_favorites_msg, favorites_msg) +from util.formatting import normalize +import logging def handle_settings(update, context): @@ -19,3 +22,44 @@ def handle_hide_cuisine(update, context): cuisine_to_hide = parse_callback(update.callback_query.data)[1] updated_hidden_cuisine = update_hidden_cuisine(update.effective_chat.id, cuisine_to_hide) update.callback_query.edit_message_reply_markup(reply_markup=hidden_cuisine_kb(updated_hidden_cuisine)) + + +def handle_add_favorite(update, context): + food_to_add = ' '.join(context.args) + if food_to_add == '': + update.message.reply_text(add_favorite_no_input_msg()) + return + + food_to_add = normalize(food_to_add) + favorites = update_favorite_foods(update.effective_chat.id, food_to_add) + + if favorites is None: + update.message.reply_text(add_favorite_already_exists_msg()) + return + + update.message.reply_text(added_favorites_msg(favorites)) + + +def handle_remove_favorite(update, context): + favorites = get_favorite_foods(update.effective_chat.id) + if len(favorites) == 0: + update.message.reply_text(no_favorites_msg()) + return + + update.message.reply_text('Select your favorite food to remove:', reply_markup=favorites_kb(favorites)) + + +def handle_remove_favorite_callback(update, context): + food_to_remove = parse_callback(update.callback_query.data)[1] + favorites = update_favorite_foods(update.effective_chat.id, food_to_remove, False) + + if favorites is None: + update.callback_query.edit_message_text('You already removed that!') + return + + if len(favorites) == 0: + context.bot.edit_message_text(text=no_favorites_msg(), chat_id=update.effective_chat.id, + message_id=update.callback_query.message.message_id, reply_markup=favorites_kb(favorites)) + else: + context.bot.edit_message_text(text=favorites_msg(favorites), chat_id=update.effective_chat.id, + message_id=update.callback_query.message.message_id, reply_markup=favorites_kb(favorites)) diff --git a/src/database/database.py b/src/database/database.py index bff8e2b..6e58cc5 100644 --- a/src/database/database.py +++ b/src/database/database.py @@ -6,7 +6,7 @@ import psycopg2.extras from database.queries import menu_query, settings_query, settings_insert, settings_update -from util.const import HIDE_CUISINE +from util.const import HIDE_CUISINE, FAVORITES # global connection _connection = None @@ -49,7 +49,17 @@ def get_menu(meal, date): conn = connect() cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) cursor.execute(menu_query(meal), (date,)) - return cursor.fetchone() + menu = cursor.fetchone() + cursor.close() + return menu + + +def insert_default_user_pref(chat_id): + conn = connect() + cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cursor.execute(settings_insert(), (chat_id, '{}', '{}')) + conn.commit() + cursor.close() def get_hidden_cuisines(chat_id): @@ -60,11 +70,13 @@ def get_hidden_cuisines(chat_id): if data is None: # insert default settings - cursor.execute(settings_insert(), (chat_id, '{}', '{}')) - conn.commit() - return [] # return empty hidden food array + insert_default_user_pref(chat_id) + hidden = [] + else: + hidden = data[HIDE_CUISINE] - return data[HIDE_CUISINE] # returns hidden cuisines in user_pref + cursor.close() + return hidden # returns hidden cuisines in user_pref def update_hidden_cuisine(chat_id, cuisine_to_hide): @@ -80,7 +92,41 @@ def update_hidden_cuisine(chat_id, cuisine_to_hide): cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) cursor.execute(settings_update(HIDE_CUISINE), (hidden_cuisines, chat_id)) conn.commit() - cursor.execute(settings_query(HIDE_CUISINE), (chat_id,)) + cursor.close() + + return hidden_cuisines + + +def get_favorite_foods(chat_id): + conn = connect() + cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cursor.execute(settings_query(FAVORITES), (chat_id,)) data = cursor.fetchone() - return data[HIDE_CUISINE] + if data is None: + insert_default_user_pref(chat_id) + favorites = [] + else: + favorites = data[FAVORITES] + + cursor.close() + return favorites # returns favorites in user_pref + + +def update_favorite_foods(chat_id, favorite_food, is_add=True): + favorites = get_favorite_foods(chat_id) + + if favorite_food in favorites and is_add or favorite_food not in favorites and not is_add: + return None + elif favorite_food in favorites and not is_add: + favorites.remove(favorite_food) + else: + favorites.append(favorite_food) + + conn = connect() + cursor = conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cursor.execute(settings_update(FAVORITES), (favorites, chat_id)) + conn.commit() + cursor.close() + + return favorites diff --git a/src/util/const.py b/src/util/const.py index e5db32e..8201b81 100644 --- a/src/util/const.py +++ b/src/util/const.py @@ -5,23 +5,24 @@ DINNER = 'dinner' MENU = 'menu' SETTINGS = 'settings' -FAVORITE = 'favorite' +FAVORITES = 'favorites' NOTIFICATION = 'notification' HOME = 'home' HIDE_CUISINE = 'hidden' +ADD_FAVORITE = 'add_favorite' +REMOVE_FAVORITE = 'remove_favorite' # menu messages BREAKFAST_COMMAND = f'/{BREAKFAST}' BREAKFAST_DESC = f'{BREAKFAST_COMMAND} - view today\'s breakfast menu\n' -DINNER_COMMAND = f'DINNER' +DINNER_COMMAND = f'/{DINNER}' DINNER_DESC = f'{DINNER_COMMAND} - view today\'s dinner menu\n' # setting messages SETTINGS_COMMAND = f'/{SETTINGS}' SETTINGS_DESC = f'{SETTINGS_COMMAND} - customize menu visibility and display settings\n' -ADD_FAVORITE_COMMAND = '/add_favorite' +ADD_FAVORITE_COMMAND = f'/{ADD_FAVORITE}' ADD_FAVORITE_DESC = f'{ADD_FAVORITE_COMMAND} - add favorite food for notifications\n' -REMOVE_FAVORITE_COMMAND = '/remove_favorite' +REMOVE_FAVORITE_COMMAND = f'/{REMOVE_FAVORITE}' REMOVE_FAVORITE_DESC = f'{REMOVE_FAVORITE_COMMAND} - remove favorite food from notifications\n' -NO_FAVORITES_MSG = f'You have no favorite foods! Use {ADD_FAVORITE_COMMAND} to add one!' HELP_COMMAND = '/help' HELP_DESC = f'{HELP_COMMAND} - show the help message\n' SET_BREAKFAST_NOTIFICATION_COMMAND = '/set_breakfast_time' diff --git a/src/util/kb_mark_up.py b/src/util/kb_mark_up.py index f484c25..8e699c0 100644 --- a/src/util/kb_mark_up.py +++ b/src/util/kb_mark_up.py @@ -1,12 +1,16 @@ from telegram import InlineKeyboardButton, InlineKeyboardMarkup +def back_to_start_btn(): + return InlineKeyboardButton("Back to start", callback_data="start.home") + + def settings_kb(): button_list = [ InlineKeyboardButton("Toggle Menu Visibility", callback_data="settings.hidden"), - InlineKeyboardButton("View Favourite Foods", callback_data="settings.favorite"), + InlineKeyboardButton("View Favourite Foods", callback_data="settings.favorites"), InlineKeyboardButton("View Notification Settings", callback_data="settings.notification"), - InlineKeyboardButton("Back to start", callback_data="start.home") + back_to_start_btn() ] return InlineKeyboardMarkup(build_menu(button_list, n_cols=1)) @@ -25,6 +29,12 @@ def hidden_cuisine_kb(hidden_cuisine): return InlineKeyboardMarkup(build_menu(kb_markup, n_cols=2, footer_buttons=[setting_button])) +def favorites_kb(favorites): + kb_markup = list(map(lambda food: + InlineKeyboardButton(food, callback_data=f'favorites.{food}'), favorites)) + return InlineKeyboardMarkup(build_menu(kb_markup, 1, footer_buttons=[back_to_start_btn()])) + + def build_menu(buttons, n_cols, header_buttons=None, diff --git a/src/util/messages.py b/src/util/messages.py index 1a1d2cb..b285b55 100644 --- a/src/util/messages.py +++ b/src/util/messages.py @@ -1,4 +1,4 @@ -from util.const import COMMAND_LIST +from util.const import COMMAND_LIST, ADD_FAVORITE_DESC, ADD_FAVORITE_COMMAND # general messages @@ -38,12 +38,24 @@ def hidden_cuisine_msg(name): def favorites_msg(favorites): - return f'These are your current favorites:\n{favorites}' + return f"These are your current favorites:\n{', '.join(favorites)}" + + +def add_favorite_no_input_msg(): + return f'You did not indicate a favorite food!\n{ADD_FAVORITE_DESC}' + + +def add_favorite_already_exists_msg(): + return 'You already favorited this food!' def added_favorites_msg(favorites): return f'You have updated your favorites. {favorites_msg(favorites)}' +def no_favorites_msg(): + return f'You have no favorite foods! Use {ADD_FAVORITE_COMMAND} to add one!' + + def menu_has_favorite_msg(favorite): return f'Hey! This meal contains {favorite}' diff --git a/src/util/util.py b/src/util/util.py index 432618b..cb9613c 100644 --- a/src/util/util.py +++ b/src/util/util.py @@ -1,4 +1,4 @@ -from util.formatting import bold, italicize +from util.formatting import bold, italicize, normalize def parse_menu(data, hidden_cuisines): @@ -6,7 +6,7 @@ def parse_menu(data, hidden_cuisines): for key in data.keys(): if key == 'date' or key in hidden_cuisines: continue - menu += bold(key.capitalize()) + '\n' + menu += bold(normalize(key)) + '\n' for item in data[key]: if item == 'OR': menu += italicize(item) + '\n'