From 6a314800908f1dcdcfd26763e237e1788c03b80c Mon Sep 17 00:00:00 2001 From: Prokhorov Mark Date: Sun, 12 Mar 2023 15:08:01 +0300 Subject: [PATCH 1/4] =?UTF-8?q?=D0=9A=D0=B0=D0=BB=D1=8C=D0=BA=D1=83=D0=BB?= =?UTF-8?q?=D1=8F=D1=82=D0=BE=D1=80=20=D0=BA=D0=B0=D0=BB=D0=BE=D1=80=D0=B8?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- untils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/untils.py b/untils.py index e69de29..d76b47b 100644 --- a/untils.py +++ b/untils.py @@ -0,0 +1,6 @@ +def calorie_сalculation(gender, age, height, weight, level_of_physical_activity): + if gender == "Женский": + calorie_count = 655.1 + (9.563*weight) + (1.85*height) - (4.676*age) + return calorie_count + calorie_count = 66.5 + (13.75*weight) + (5.003*height) - (6.775*age) + return calorie_count From a65fbf4fabb772dd138aa5c94b7cd249b19126c3 Mon Sep 17 00:00:00 2001 From: Prokhorov Mark Date: Sun, 12 Mar 2023 21:26:36 +0300 Subject: [PATCH 2/4] Data collection (weight, sex, ...), keyboard added, calorie counting function implemented --- bot.py | 30 ++++++++++++- handlers.py | 28 ++++++++++++ questionnaire.py | 108 +++++++++++++++++++++++++++++++++++++++++++++++ untils.py | 42 +++++++++++++++++- 4 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 questionnaire.py diff --git a/bot.py b/bot.py index 385509a..24fb323 100644 --- a/bot.py +++ b/bot.py @@ -1,7 +1,14 @@ import logging -from telegram.ext import Updater +from telegram.ext import (CommandHandler, MessageHandler, Filters, Updater, + ConversationHandler) import settings +from questionnaire import (questionnaire_start, questionnaire_gender, questionnaire_age, + questionnaire_height, questionnaire_weight, level_of_physical_activity, + data_validation, questionnaire_dontknow) + +from handlers import greet_user, calorie_count + logging.basicConfig(filename="bot.log", format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S', level=logging.INFO) @@ -11,6 +18,27 @@ def main(): dp = bot.dispatcher + questionnaire = ConversationHandler( + entry_points=[ + MessageHandler(Filters.regex('^(Ввести данные)$'), questionnaire_start) + ], + states={ + "gender": [MessageHandler(Filters.text, questionnaire_gender)], + "age": [MessageHandler(Filters.text, questionnaire_age)], + "height": [MessageHandler(Filters.text, questionnaire_height)], + "weight": [MessageHandler(Filters.text, questionnaire_weight)], + "level_of_physical_activity": [MessageHandler(Filters.text, level_of_physical_activity)], + "data_validation": [MessageHandler(Filters.text, data_validation)] + }, + fallbacks=[ + MessageHandler(Filters.video | Filters.photo | Filters.document | Filters.location, + questionnaire_dontknow) + ] + ) + dp.add_handler(questionnaire) + dp.add_handler(CommandHandler("start", greet_user)) + dp.add_handler(MessageHandler(Filters.regex('^Расчёт калорий$'), calorie_count)) + logging.info("bot started") bot.start_polling() bot.idle() diff --git a/handlers.py b/handlers.py index e69de29..e7ac70d 100644 --- a/handlers.py +++ b/handlers.py @@ -0,0 +1,28 @@ +from untils import main_keyboard, questionnaire_completion, calorie_сalculation + + +def greet_user(update, context): + update.message.reply_text( + "Здравствуй пользователь!", + reply_markup=main_keyboard(), + ) + + +def calorie_count(update, context): + user_information = questionnaire_completion(context) + if user_information is None: + update.message.reply_text( + "Заполните анкету!", + reply_markup=main_keyboard() + ) + else: + user_gender = user_information[0] + user_age = int(user_information[1]) + user_height = int(user_information[2]) + user_weight = int(user_information[3]) + user_activity_level_multiplier = user_information[4] + calorie_count = calorie_сalculation( + user_gender, user_age, user_height, user_weight, user_activity_level_multiplier) + update.message.reply_text( + f"Вам нужно потреблять {round(calorie_count, 0)} калорий", + reply_markup=main_keyboard()) diff --git a/questionnaire.py b/questionnaire.py new file mode 100644 index 0000000..a8082f3 --- /dev/null +++ b/questionnaire.py @@ -0,0 +1,108 @@ +from telegram import ParseMode, ReplyKeyboardMarkup +from telegram.ext import ConversationHandler +from untils import gender_selection_button, main_keyboard + + +def list_of_activity_levels(): + return [ + ["минимальный (сидячая работа, отсутствие физических нагрузок)"], + ["низкий (тренировки не менее 20 мин 1-3 раза в неделю)"], + ["умеренный (тренировки 30-60 мин 3-4 раза в неделю)"], + ["высокий (тренировки 30-60 мин 5-7 раза в неделю;тяжелая физическая работа"], + ["экстремальный (несколько интенсивных тренировок в день 6-7 раз в неделю; очень трудоемкая работа)"] + ] + + +def data_output(context): + text = f"""Пол: {context.user_data['questionnaire']['gender']} +Возраст: {context.user_data['questionnaire']['age']} +Рост: {context.user_data['questionnaire']['height']} +Вест: {context.user_data['questionnaire']['weight']} +Уровень физической активности: {context.user_data['questionnaire']['level_of_physical_activity']} +""" + return text + + +def questionnaire_start(update, context): + update.message.reply_text( + "Выберете пол", + reply_markup=gender_selection_button() + ) + return "gender" + + +def questionnaire_gender(update, context): + user_gender = update.message.text + if user_gender != "Мужской" and user_gender != "Женский": + update.message.reply_text("Выберете пол") + return "gender" + else: + update.message.reply_text("Введите возраст") + context.user_data["questionnaire"] = {"gender": user_gender} + return "age" + + +def questionnaire_age(update, context): + user_age = update.message.text + if user_age.isdigit() is False or user_age == '0': + update.message.reply_text("Укажите корректный возраст") + return "age" + else: + update.message.reply_text("Введите рост") + context.user_data["questionnaire"]["age"] = user_age + return "height" + + +def questionnaire_height(update, context): + user_height = update.message.text + if user_height.isdigit() is False or user_height == '0': + update.message.reply_text("Укажите корректный рост") + return "height" + else: + update.message.reply_text("Введите вес") + context.user_data["questionnaire"]["height"] = user_height + return "weight" + + +def questionnaire_weight(update, context): + user_weight = update.message.text + if user_weight.isdigit() is False or user_weight == '0': + update.message.reply_text("Укажите корректный вес") + return "height" + else: + context.user_data["questionnaire"]["weight"] = user_weight + reply_keyboard = list_of_activity_levels() + update.message.reply_text( + "Выберете уровень физической активности", + reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True) + ) + return "level_of_physical_activity" + + +def level_of_physical_activity(update, context): + user_level_of_physical_activity = update.message.text + level_of_physical_activity = [level[0] for level in list_of_activity_levels()] + if user_level_of_physical_activity not in level_of_physical_activity: + update.message.reply_text("Выберете уровень физической активности") + return "level_of_physical_activity" + else: + context.user_data["questionnaire"]["level_of_physical_activity"] = user_level_of_physical_activity + user_text = data_output(context) + reply_keyboard = [["Данные верны", "Данные неверны. Заполнить снова"]] + update.message.reply_text(user_text, parse_mode=ParseMode.HTML, + reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True) + ) + return "data_validation" + + +def data_validation(update, context): + user_response = update.message.text + if user_response == "Данные верны": + update.message.reply_text("Поздравляем!", reply_markup=main_keyboard()) + return ConversationHandler.END + else: + print("(((") + + +def questionnaire_dontknow(update, context): + update.message.reply_text("Некорректные данные(") diff --git a/untils.py b/untils.py index d76b47b..2c87564 100644 --- a/untils.py +++ b/untils.py @@ -1,6 +1,46 @@ -def calorie_сalculation(gender, age, height, weight, level_of_physical_activity): +from telegram import ReplyKeyboardMarkup + + +def activity_level_multiplier(level_of_physical_activity): + if level_of_physical_activity == "минимальный (сидячая работа, отсутствие физических нагрузок)": + return 1.2 + elif level_of_physical_activity == "низкий (тренировки не менее 20 мин 1-3 раза в неделю)": + return 1.375 + elif level_of_physical_activity == "умеренный (тренировки 30-60 мин 3-4 раза в неделю)": + return 1.55 + elif level_of_physical_activity == "высокий (тренировки 30-60 мин 5-7 раза в неделю;тяжелая физическая работа": + return 1.7 + return 1.9 + + +def calorie_сalculation(gender, age, height, weight, activity_level_multiplier): if gender == "Женский": calorie_count = 655.1 + (9.563*weight) + (1.85*height) - (4.676*age) return calorie_count calorie_count = 66.5 + (13.75*weight) + (5.003*height) - (6.775*age) return calorie_count + + +def questionnaire_completion(context): + user_information = context.user_data.get('questionnaire', None) + if user_information is None: + return None + user_gender = context.user_data['questionnaire'].get('gender') + user_age = context.user_data['questionnaire'].get('age') + user_height = context.user_data['questionnaire'].get('height') + user_weight = context.user_data['questionnaire'].get('weight') + user_level_of_physical_activity = context.user_data['questionnaire'].get('level_of_physical_activity') + return [user_gender, user_age, user_height, user_weight, + activity_level_multiplier(user_level_of_physical_activity)] + + +def main_keyboard(): + return ReplyKeyboardMarkup([["Ввести данные"], ["Расчёт калорий"], + ["Пищевая ценность", "Дейли рацион"], + ["Рецепт", "Настройки"]]) + + +def gender_selection_button(): + return ReplyKeyboardMarkup([ + ['Мужской', 'Женский']], one_time_keyboard=True + ) From ba42d5f86ac0b68b17301d7d577c0e59792c042b Mon Sep 17 00:00:00 2001 From: Prokhorov Mark Date: Sun, 2 Apr 2023 16:08:30 +0300 Subject: [PATCH 3/4] =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5=20?= =?UTF-8?q?=D1=81=20=D0=B0=D0=BD=D0=BA=D0=B5=D1=82=D1=8B=20=D1=81=D0=BE?= =?UTF-8?q?=D1=85=D1=80=D0=B0=D0=BD=D1=8F=D1=8E=D1=82=D1=81=D1=8F=20=D0=B2?= =?UTF-8?q?=20=D0=91=D0=94=20MongoDB,=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D1=81=D0=BC=D0=B0=D0=B9=D0=BB=D0=B8?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BA=20=D0=BD=D0=B5=D0=BA=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D1=8B=D0=BC=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + bot.py | 11 +++++--- db.py | 36 +++++++++++++++++++++++++++ handlers.py | 29 +++++++++++++-------- questionnaire.py | 65 ++++++++++++++++++++---------------------------- requirements.txt | 16 ++++++++++++ untils.py | 60 +++++++++++++++++++++++++++++++------------- 7 files changed, 148 insertions(+), 70 deletions(-) create mode 100644 db.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore index c41509d..abf027c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ settings.py +tests.py # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/bot.py b/bot.py index 24fb323..f669f65 100644 --- a/bot.py +++ b/bot.py @@ -4,10 +4,10 @@ import settings from questionnaire import (questionnaire_start, questionnaire_gender, questionnaire_age, - questionnaire_height, questionnaire_weight, level_of_physical_activity, + questionnaire_height, questionnaire_current_weight, level_of_physical_activity, data_validation, questionnaire_dontknow) -from handlers import greet_user, calorie_count +from handlers import greet_user, calorie_count, unknown_command logging.basicConfig(filename="bot.log", format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S', level=logging.INFO) @@ -20,13 +20,13 @@ def main(): questionnaire = ConversationHandler( entry_points=[ - MessageHandler(Filters.regex('^(Ввести данные)$'), questionnaire_start) + MessageHandler(Filters.regex('^(Заполнить данные)|^(Данные неверны. Заполнить снова)'), questionnaire_start) ], states={ "gender": [MessageHandler(Filters.text, questionnaire_gender)], "age": [MessageHandler(Filters.text, questionnaire_age)], "height": [MessageHandler(Filters.text, questionnaire_height)], - "weight": [MessageHandler(Filters.text, questionnaire_weight)], + "current_weight": [MessageHandler(Filters.text, questionnaire_current_weight)], "level_of_physical_activity": [MessageHandler(Filters.text, level_of_physical_activity)], "data_validation": [MessageHandler(Filters.text, data_validation)] }, @@ -38,6 +38,9 @@ def main(): dp.add_handler(questionnaire) dp.add_handler(CommandHandler("start", greet_user)) dp.add_handler(MessageHandler(Filters.regex('^Расчёт калорий$'), calorie_count)) + dp.add_handler(MessageHandler( + Filters.text | Filters.video | Filters.photo | Filters.document | Filters.location, + unknown_command)) logging.info("bot started") bot.start_polling() diff --git a/db.py b/db.py new file mode 100644 index 0000000..78d2a84 --- /dev/null +++ b/db.py @@ -0,0 +1,36 @@ +from datetime import datetime +from pymongo import MongoClient +import settings + +client = MongoClient(settings.MONGO_LINK) + +db = client[settings.MONGO_DB] + + +def get_or_create_user(db, effective_user, chat_id): + user = db.users.find_one({"user_id": effective_user.id}) + if not user: + user = { + "user_id": effective_user.id, + "first_name": effective_user.first_name, + "last_name": effective_user.last_name, + "username": effective_user.username, + "chat_id": chat_id + } + db.users.insert_one(user) + return user + + +def save_questionnaire(db, user_id, questionnaire_data): + user = db.users.find_one({"user_id": user_id}) + questionnaire_data['created'] = datetime.now() + if 'questionnaire' not in user: + db.users.update_one( + {'_id': user['_id']}, + {'$set': {'questionnaire': [questionnaire_data]}} + ) + else: + db.users.update_one( + {'_id': user['_id']}, + {'$push': {'questionnaire': questionnaire_data}} + ) diff --git a/handlers.py b/handlers.py index e7ac70d..0aff8b7 100644 --- a/handlers.py +++ b/handlers.py @@ -1,28 +1,35 @@ -from untils import main_keyboard, questionnaire_completion, calorie_сalculation +from untils import initial_keyboard, main_keyboard, questionnaire_completion, calorie_сalculation, get_emoji + +from db import db, get_or_create_user def greet_user(update, context): + user = get_or_create_user(db, update.effective_user, update.message.chat.id) update.message.reply_text( - "Здравствуй пользователь!", - reply_markup=main_keyboard(), + f"Здравствуй {user['first_name']}! {get_emoji('waving_hand')}", + reply_markup=initial_keyboard(), ) def calorie_count(update, context): - user_information = questionnaire_completion(context) - if user_information is None: + user_data = questionnaire_completion(context) + if user_data is None: update.message.reply_text( - "Заполните анкету!", + f"Заполните анкету! {get_emoji('pencil')}", reply_markup=main_keyboard() ) else: - user_gender = user_information[0] - user_age = int(user_information[1]) - user_height = int(user_information[2]) - user_weight = int(user_information[3]) - user_activity_level_multiplier = user_information[4] + user_gender = user_data[0] + user_age = int(user_data[1]) + user_height = int(user_data[2]) + user_weight = int(user_data[3]) + user_activity_level_multiplier = user_data[4] calorie_count = calorie_сalculation( user_gender, user_age, user_height, user_weight, user_activity_level_multiplier) update.message.reply_text( f"Вам нужно потреблять {round(calorie_count, 0)} калорий", reply_markup=main_keyboard()) + + +def unknown_command(update, context): + update.message.reply_text(f"Неизвестная команда {get_emoji('red_question_mark')}", reply_markup=main_keyboard()) diff --git a/questionnaire.py b/questionnaire.py index a8082f3..a4999ca 100644 --- a/questionnaire.py +++ b/questionnaire.py @@ -1,26 +1,10 @@ from telegram import ParseMode, ReplyKeyboardMarkup from telegram.ext import ConversationHandler -from untils import gender_selection_button, main_keyboard - - -def list_of_activity_levels(): - return [ - ["минимальный (сидячая работа, отсутствие физических нагрузок)"], - ["низкий (тренировки не менее 20 мин 1-3 раза в неделю)"], - ["умеренный (тренировки 30-60 мин 3-4 раза в неделю)"], - ["высокий (тренировки 30-60 мин 5-7 раза в неделю;тяжелая физическая работа"], - ["экстремальный (несколько интенсивных тренировок в день 6-7 раз в неделю; очень трудоемкая работа)"] - ] - - -def data_output(context): - text = f"""Пол: {context.user_data['questionnaire']['gender']} -Возраст: {context.user_data['questionnaire']['age']} -Рост: {context.user_data['questionnaire']['height']} -Вест: {context.user_data['questionnaire']['weight']} -Уровень физической активности: {context.user_data['questionnaire']['level_of_physical_activity']} -""" - return text +from untils import (data_output, gender_selection_button, main_keyboard, + get_emoji, activity_level_selection_button, + list_of_activity_levels_and_multiplier) +# from handlers import calorie_count +from db import db, get_or_create_user, save_questionnaire def questionnaire_start(update, context): @@ -33,7 +17,7 @@ def questionnaire_start(update, context): def questionnaire_gender(update, context): user_gender = update.message.text - if user_gender != "Мужской" and user_gender != "Женский": + if "Мужской" not in user_gender and "Женский" not in user_gender: update.message.reply_text("Выберете пол") return "gender" else: @@ -59,49 +43,54 @@ def questionnaire_height(update, context): update.message.reply_text("Укажите корректный рост") return "height" else: - update.message.reply_text("Введите вес") + update.message.reply_text("Введите текущий вес") context.user_data["questionnaire"]["height"] = user_height - return "weight" + return "current_weight" -def questionnaire_weight(update, context): +def questionnaire_current_weight(update, context): user_weight = update.message.text if user_weight.isdigit() is False or user_weight == '0': update.message.reply_text("Укажите корректный вес") - return "height" + return "current_weight" else: - context.user_data["questionnaire"]["weight"] = user_weight - reply_keyboard = list_of_activity_levels() update.message.reply_text( "Выберете уровень физической активности", - reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True) + reply_markup=activity_level_selection_button() ) + context.user_data["questionnaire"]["current_weight"] = user_weight return "level_of_physical_activity" def level_of_physical_activity(update, context): - user_level_of_physical_activity = update.message.text - level_of_physical_activity = [level[0] for level in list_of_activity_levels()] - if user_level_of_physical_activity not in level_of_physical_activity: + user_response = update.message.text + level_of_physical_activity = [level[0] for level in list_of_activity_levels_and_multiplier()] + if user_response not in level_of_physical_activity: update.message.reply_text("Выберете уровень физической активности") return "level_of_physical_activity" else: - context.user_data["questionnaire"]["level_of_physical_activity"] = user_level_of_physical_activity + context.user_data["questionnaire"]["level_of_physical_activity"] = user_response user_text = data_output(context) - reply_keyboard = [["Данные верны", "Данные неверны. Заполнить снова"]] + reply_keyboard = [ + [f"Данные верны {get_emoji('check_mark')}", f"Данные неверны. {get_emoji('cross_mark')}"] + ] update.message.reply_text(user_text, parse_mode=ParseMode.HTML, - reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True) + reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True, + resize_keyboard=True) ) return "data_validation" def data_validation(update, context): + user = get_or_create_user(db, update.effective_user, update.message.chat.id) user_response = update.message.text - if user_response == "Данные верны": - update.message.reply_text("Поздравляем!", reply_markup=main_keyboard()) + if "Данные верны" in user_response: + save_questionnaire(db, user['user_id'], context.user_data['questionnaire']) + update.message.reply_text("Ваши данные сохранены.", reply_markup=main_keyboard()) return ConversationHandler.END else: - print("(((") + update.message.reply_text("Ваши данные не сохранены.", reply_markup=main_keyboard()) + return ConversationHandler.END def questionnaire_dontknow(update, context): diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d1fde19 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,16 @@ +APScheduler==3.6.3 +cachetools==4.2.2 +certifi==2022.12.7 +dnspython==2.3.0 +emoji==0.5.4 +mypy==1.1.1 +mypy-extensions==1.0.0 +pymongo==4.3.3 +python-telegram-bot==13.14 +pytz==2022.7.1 +pytz-deprecation-shim==0.1.0.post0 +six==1.16.0 +tornado==6.1 +typing_extensions==4.5.0 +tzdata==2022.7 +tzlocal==4.2 \ No newline at end of file diff --git a/untils.py b/untils.py index 2c87564..ce1a26c 100644 --- a/untils.py +++ b/untils.py @@ -1,27 +1,26 @@ from telegram import ReplyKeyboardMarkup +from emoji import emojize -def activity_level_multiplier(level_of_physical_activity): - if level_of_physical_activity == "минимальный (сидячая работа, отсутствие физических нагрузок)": - return 1.2 - elif level_of_physical_activity == "низкий (тренировки не менее 20 мин 1-3 раза в неделю)": - return 1.375 - elif level_of_physical_activity == "умеренный (тренировки 30-60 мин 3-4 раза в неделю)": - return 1.55 - elif level_of_physical_activity == "высокий (тренировки 30-60 мин 5-7 раза в неделю;тяжелая физическая работа": - return 1.7 - return 1.9 +def list_of_activity_levels_and_multiplier(): + return [ + [f"Минимальный {get_emoji('yawning_face')}", 1.2], + [f"Низкий {get_emoji('flushed_face')}", 1.375], + [f"Умеренный {get_emoji('smiling_face_with_sunglasses')}", 1.55], + [f"Высокий {get_emoji('face_with_steam_from_nose')}", 1.7], + [f"Экстремальный {get_emoji('smiling_face_with_horns')}", 1.9] + ] -def calorie_сalculation(gender, age, height, weight, activity_level_multiplier): - if gender == "Женский": +def calorie_сalculation(gender: str, age: int, height: int, weight: float, activity_level_multiplier: float) -> float: + if "Женский" in gender: calorie_count = 655.1 + (9.563*weight) + (1.85*height) - (4.676*age) return calorie_count calorie_count = 66.5 + (13.75*weight) + (5.003*height) - (6.775*age) return calorie_count -def questionnaire_completion(context): +def questionnaire_completion(context): user_information = context.user_data.get('questionnaire', None) if user_information is None: return None @@ -34,13 +33,40 @@ def questionnaire_completion(context): activity_level_multiplier(user_level_of_physical_activity)] +def initial_keyboard(): + return ReplyKeyboardMarkup([[f"Заполнить данные {get_emoji('pencil')}"], ["Расчёт калорий"], + ["Пищевая ценность", "Дейли рацион"], + ["Рецепт", f"Настройки {get_emoji('gear')}"] + ]) + + def main_keyboard(): - return ReplyKeyboardMarkup([["Ввести данные"], ["Расчёт калорий"], + return ReplyKeyboardMarkup([["Расчёт калорий"], ["Пищевая ценность", "Дейли рацион"], - ["Рецепт", "Настройки"]]) + ["Рецепт блюда", f"Профиль {get_emoji('bust_in_silhouette')}"] + ]) def gender_selection_button(): return ReplyKeyboardMarkup([ - ['Мужской', 'Женский']], one_time_keyboard=True - ) + [f"Мужской {get_emoji('man')}", f"Женский {get_emoji('woman')}"] + ], one_time_keyboard=True, resize_keyboard=True) + + +def activity_level_selection_button(): + buttons = [[level[0]] for level in list_of_activity_levels_and_multiplier()] + return ReplyKeyboardMarkup(buttons, one_time_keyboard=True) + + +def get_emoji(emoji_name: str): + return emojize(f":{emoji_name}:") + + +def data_output(context): + text = f"""Пол: {context.user_data['questionnaire']['gender']} +Возраст: {context.user_data['questionnaire']['age']} +Рост: {context.user_data['questionnaire']['height']} +Текцщий вес: {context.user_data['questionnaire']['current_weight']} +Уровень физической активности: {context.user_data['questionnaire']['level_of_physical_activity']} +""" + return text From 13643d864c61903bf35418e68527dbc88cdfff21 Mon Sep 17 00:00:00 2001 From: Prokhorov Mark Date: Mon, 3 Apr 2023 13:53:03 +0300 Subject: [PATCH 4/4] =?UTF-8?q?=D0=9A=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=97=D0=90=D0=9F=D0=9E=D0=9B=D0=AC=D0=9D=D0=98=D0=A2=D0=AC=20?= =?UTF-8?q?=D0=94=D0=90=D0=9D=D0=9D=D0=AB=D0=95=20=D0=B8=20=D0=A0=D0=90?= =?UTF-8?q?=D0=A1=D0=A7=D0=81=D0=A2=20=D0=9A=D0=90=D0=9B=D0=9E=D0=A0=D0=98?= =?UTF-8?q?=D0=99=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=BD=D0=BE=D1=81=D1=82=D1=8C?= =?UTF-8?q?=D1=8E=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D1=8B,=20=D0=B4=D0=B0=D1=8B=D0=BD=D0=BD=D0=B5=20=D0=BE?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D1=8F=D1=8E?= =?UTF-8?q?=D1=82=D1=81=D1=8F=20=D0=B2=20=D0=91=D0=94=20+=20=D0=B6=D0=B5?= =?UTF-8?q?=D0=BB=D0=B0=D0=B5=D0=BC=D1=8B=D0=B9=20=D0=B2=D0=B5=D1=81=20?= =?UTF-8?q?=D0=B8=20=D0=BD=D0=BE=D1=80=D0=BC=D0=B0=20=D0=BA=D0=B0=D0=BB?= =?UTF-8?q?=D0=BE=D1=80=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bot.py | 25 ++++++++++-- calorie_calculation.py | 87 ++++++++++++++++++++++++++++++++++++++++++ db.py | 8 ++++ handlers.py | 28 +++----------- questionnaire.py | 32 ++++++++++------ untils.py | 82 +++++++++++++++++++++++++-------------- 6 files changed, 197 insertions(+), 65 deletions(-) create mode 100644 calorie_calculation.py diff --git a/bot.py b/bot.py index f669f65..668e26e 100644 --- a/bot.py +++ b/bot.py @@ -3,11 +3,14 @@ ConversationHandler) import settings +from calorie_calculation import (calorie_calculation_start, target_selection, target_weight, + сalculate_сalorie_сount) + from questionnaire import (questionnaire_start, questionnaire_gender, questionnaire_age, questionnaire_height, questionnaire_current_weight, level_of_physical_activity, data_validation, questionnaire_dontknow) -from handlers import greet_user, calorie_count, unknown_command +from handlers import greet_user, unknown_command logging.basicConfig(filename="bot.log", format='%(asctime)s - %(message)s', datefmt='%d-%b-%y %H:%M:%S', level=logging.INFO) @@ -20,7 +23,7 @@ def main(): questionnaire = ConversationHandler( entry_points=[ - MessageHandler(Filters.regex('^(Заполнить данные)|^(Данные неверны. Заполнить снова)'), questionnaire_start) + MessageHandler(Filters.regex('^(Заполнить данные)|^(Да, заполнить данные снова)'), questionnaire_start) ], states={ "gender": [MessageHandler(Filters.text, questionnaire_gender)], @@ -35,9 +38,25 @@ def main(): questionnaire_dontknow) ] ) + + calorie_calculation = ConversationHandler( + entry_points=[ + MessageHandler(Filters.regex('^(Расчёт калорий)'), calorie_calculation_start) + ], + states={ + "target_selection": [MessageHandler(Filters.text, target_selection)], + "target_weight": [MessageHandler(Filters.text, target_weight)], + "сalculate_сalorie_сount": [MessageHandler(Filters.text, сalculate_сalorie_сount)] + }, + fallbacks=[ + MessageHandler(Filters.video | Filters.photo | Filters.document | Filters.location, + questionnaire_dontknow) + ] + ) + dp.add_handler(questionnaire) + dp.add_handler(calorie_calculation) dp.add_handler(CommandHandler("start", greet_user)) - dp.add_handler(MessageHandler(Filters.regex('^Расчёт калорий$'), calorie_count)) dp.add_handler(MessageHandler( Filters.text | Filters.video | Filters.photo | Filters.document | Filters.location, unknown_command)) diff --git a/calorie_calculation.py b/calorie_calculation.py new file mode 100644 index 0000000..53c9da4 --- /dev/null +++ b/calorie_calculation.py @@ -0,0 +1,87 @@ +from untils import (emoji_to_the_number_of, initial_keyboard, calorie_сalculation, + activity_level_multiplier, keypad_with_weight_selection, keypad_with_target_selection, + main_keyboard) +from telegram.ext import ConversationHandler +from db import db, get_or_create_user, add_or_replace_something + + +def calorie_calculation_start(update, context): + user = get_or_create_user(db, update.effective_user, update.message.chat.id) + if 'questionnaire' not in user: + update.message.reply_text("Чтобы расчитать калорий нужно заполнить данные!", + reply_markup=initial_keyboard()) + return ConversationHandler.END + update.message.reply_text("Укажите вашу цель", + reply_markup=keypad_with_target_selection()) + return "target_selection" + + +def target_selection(update, context): + user_response = update.message.text + if "Сбросить вес" in user_response: + update.message.reply_text("Сколько кг вы хотите сбросить? Нажмите на кнопку или введите число с клавиатуры", + reply_markup=keypad_with_weight_selection()) + context.user_data["calorie_calculation"] = {"change_multiplier": -1} + return "target_weight" + elif "Набрать вес" in user_response: + update.message.reply_text("Сколько кг вы хотите набрать? Нажмите на кнопку или введите число с клавиатуры", + reply_markup=keypad_with_weight_selection()) + context.user_data["calorie_calculation"] = {"change_multiplier": 1} + return "target_weight" + elif "Сохранить вес" in user_response: + user = get_or_create_user(db, update.effective_user, update.message.chat.id) + current_weight = user["questionnaire"][-1]["current_weight"] + context.user_data["calorie_calculation"] = {"target_weight": current_weight} + сalculate_сalorie_сount(update, context) + return ConversationHandler.END + else: + update.message.reply_text("Выберите один из вариантов", + reply_markup=keypad_with_target_selection()) + return "target_selection" + + +def target_weight(update, context): + user = get_or_create_user(db, update.effective_user, update.message.chat.id) + current_weight = user["questionnaire"][-1]["current_weight"] + change_multiplier = context.user_data["calorie_calculation"]["change_multiplier"] + user_response = update.message.text + + if user_response.isdigit(): + delta_weight = int(user_response) + target_weight = current_weight + change_multiplier*delta_weight + elif emoji_to_the_number_of(user_response).isdigit(): + delta_weight = int(emoji_to_the_number_of(user_response)) + target_weight = current_weight + change_multiplier*delta_weight + else: + update.message.reply_text("Введите целое число кг", reply_markup=keypad_with_weight_selection()) + return "target_weight" + + if target_weight < 0: + update.message.reply_text("Желаемый вес меньше нуля", reply_markup=keypad_with_weight_selection()) + return "target_weight" + else: + context.user_data["calorie_calculation"]["target_weight"] = target_weight + сalculate_сalorie_сount(update, context) + return ConversationHandler.END + + +def сalculate_сalorie_сount(update, context): + user = get_or_create_user(db, update.effective_user, update.message.chat.id) + user_data = user["questionnaire"][-1] + + gender = user_data["gender"] + age = user_data["age"] + height = user_data["height"] + weight = context.user_data["calorie_calculation"]["target_weight"] + level_of_physical_activity = user_data["level_of_physical_activity"] + multiplier_activity_level = activity_level_multiplier(level_of_physical_activity) + + calorie_count = calorie_сalculation( + gender, age, height, weight, multiplier_activity_level + ) + + add_or_replace_something(db, user['user_id'], "calorie_count", calorie_count) + add_or_replace_something(db, user['user_id'], "target_weight", weight) + + update.message.reply_text(f"Вам нужно потреблять {calorie_count} калорий ежедневно", reply_markup=main_keyboard()) + \ No newline at end of file diff --git a/db.py b/db.py index 78d2a84..efcab24 100644 --- a/db.py +++ b/db.py @@ -34,3 +34,11 @@ def save_questionnaire(db, user_id, questionnaire_data): {'_id': user['_id']}, {'$push': {'questionnaire': questionnaire_data}} ) + + +def add_or_replace_something(db, user_id, name, value): + user = db.users.find_one({"user_id": user_id}) + db.users.update_one( + {'_id': user['_id']}, + {'$set': {name: value}} + ) diff --git a/handlers.py b/handlers.py index 0aff8b7..c829ae3 100644 --- a/handlers.py +++ b/handlers.py @@ -1,35 +1,19 @@ -from untils import initial_keyboard, main_keyboard, questionnaire_completion, calorie_сalculation, get_emoji +from untils import initial_keyboard, main_keyboard, get_emoji from db import db, get_or_create_user def greet_user(update, context): user = get_or_create_user(db, update.effective_user, update.message.chat.id) + if 'questionnaire' not in user: + keyboard = initial_keyboard() + else: + keyboard = main_keyboard() update.message.reply_text( f"Здравствуй {user['first_name']}! {get_emoji('waving_hand')}", - reply_markup=initial_keyboard(), + reply_markup=keyboard, ) -def calorie_count(update, context): - user_data = questionnaire_completion(context) - if user_data is None: - update.message.reply_text( - f"Заполните анкету! {get_emoji('pencil')}", - reply_markup=main_keyboard() - ) - else: - user_gender = user_data[0] - user_age = int(user_data[1]) - user_height = int(user_data[2]) - user_weight = int(user_data[3]) - user_activity_level_multiplier = user_data[4] - calorie_count = calorie_сalculation( - user_gender, user_age, user_height, user_weight, user_activity_level_multiplier) - update.message.reply_text( - f"Вам нужно потреблять {round(calorie_count, 0)} калорий", - reply_markup=main_keyboard()) - - def unknown_command(update, context): update.message.reply_text(f"Неизвестная команда {get_emoji('red_question_mark')}", reply_markup=main_keyboard()) diff --git a/questionnaire.py b/questionnaire.py index a4999ca..2c28793 100644 --- a/questionnaire.py +++ b/questionnaire.py @@ -2,8 +2,7 @@ from telegram.ext import ConversationHandler from untils import (data_output, gender_selection_button, main_keyboard, get_emoji, activity_level_selection_button, - list_of_activity_levels_and_multiplier) -# from handlers import calorie_count + list_of_activity_levels) from db import db, get_or_create_user, save_questionnaire @@ -17,7 +16,7 @@ def questionnaire_start(update, context): def questionnaire_gender(update, context): user_gender = update.message.text - if "Мужской" not in user_gender and "Женский" not in user_gender: + if not ("Мужской" in user_gender or "Женский" in user_gender): update.message.reply_text("Выберете пол") return "gender" else: @@ -33,7 +32,7 @@ def questionnaire_age(update, context): return "age" else: update.message.reply_text("Введите рост") - context.user_data["questionnaire"]["age"] = user_age + context.user_data["questionnaire"]["age"] = int(user_age) return "height" @@ -44,7 +43,7 @@ def questionnaire_height(update, context): return "height" else: update.message.reply_text("Введите текущий вес") - context.user_data["questionnaire"]["height"] = user_height + context.user_data["questionnaire"]["height"] = int(user_height) return "current_weight" @@ -58,24 +57,25 @@ def questionnaire_current_weight(update, context): "Выберете уровень физической активности", reply_markup=activity_level_selection_button() ) - context.user_data["questionnaire"]["current_weight"] = user_weight + context.user_data["questionnaire"]["current_weight"] = int(user_weight) return "level_of_physical_activity" def level_of_physical_activity(update, context): user_response = update.message.text - level_of_physical_activity = [level[0] for level in list_of_activity_levels_and_multiplier()] + level_of_physical_activity = [level[0] for level in list_of_activity_levels()] if user_response not in level_of_physical_activity: update.message.reply_text("Выберете уровень физической активности") return "level_of_physical_activity" else: context.user_data["questionnaire"]["level_of_physical_activity"] = user_response user_text = data_output(context) - reply_keyboard = [ - [f"Данные верны {get_emoji('check_mark')}", f"Данные неверны. {get_emoji('cross_mark')}"] + keyboard = [ + [f"Данные верны {get_emoji('check_mark')}", + f"Данные неверны. {get_emoji('cross_mark')}"] ] update.message.reply_text(user_text, parse_mode=ParseMode.HTML, - reply_markup=ReplyKeyboardMarkup(reply_keyboard, one_time_keyboard=True, + reply_markup=ReplyKeyboardMarkup(keyboard, one_time_keyboard=True, resize_keyboard=True) ) return "data_validation" @@ -86,10 +86,18 @@ def data_validation(update, context): user_response = update.message.text if "Данные верны" in user_response: save_questionnaire(db, user['user_id'], context.user_data['questionnaire']) - update.message.reply_text("Ваши данные сохранены.", reply_markup=main_keyboard()) + update.message.reply_text(f"Ваши данные сохранены {get_emoji('memo')}") + update.message.reply_text(f"Изменить данные можно в Профиле {get_emoji('bust_in_silhouette')}", + reply_markup=main_keyboard()) return ConversationHandler.END else: - update.message.reply_text("Ваши данные не сохранены.", reply_markup=main_keyboard()) + keyboard = [ + [f"Да, заполнить данные снова {get_emoji('counterclockwise_arrows_button')}"], + [f"Нет. Вернутсья на главную {get_emoji('BACK_arrow')}"] + ] + update.message.reply_text("Ваши данные не сохранены. Заполнить данные снова?", + reply_markup=ReplyKeyboardMarkup(keyboard, one_time_keyboard=True, + resize_keyboard=True)) return ConversationHandler.END diff --git a/untils.py b/untils.py index ce1a26c..f4d72b2 100644 --- a/untils.py +++ b/untils.py @@ -2,48 +2,47 @@ from emoji import emojize -def list_of_activity_levels_and_multiplier(): +def list_of_activity_levels(): return [ - [f"Минимальный {get_emoji('yawning_face')}", 1.2], - [f"Низкий {get_emoji('flushed_face')}", 1.375], - [f"Умеренный {get_emoji('smiling_face_with_sunglasses')}", 1.55], - [f"Высокий {get_emoji('face_with_steam_from_nose')}", 1.7], - [f"Экстремальный {get_emoji('smiling_face_with_horns')}", 1.9] + [f"Минимальный {get_emoji('yawning_face')}"], + [f"Низкий {get_emoji('flushed_face')}"], + [f"Умеренный {get_emoji('smiling_face_with_sunglasses')}"], + [f"Высокий {get_emoji('face_with_steam_from_nose')}"], + [f"Экстремальный {get_emoji('smiling_face_with_horns')}"] ] -def calorie_сalculation(gender: str, age: int, height: int, weight: float, activity_level_multiplier: float) -> float: +def activity_level_multiplier(level): + if level == f"Минимальный {get_emoji('yawning_face')}": + return 1.2 + elif level == f"Низкий {get_emoji('flushed_face')}": + return 1.375 + elif level == f"Умеренный {get_emoji('smiling_face_with_sunglasses')}": + return 1.55 + elif level == f"Высокий {get_emoji('face_with_steam_from_nose')}": + return 1.7 + return 1.9 + + +def calorie_сalculation(gender: str, age: int, height: int, weight: int, activity_level_multiplier: float) -> int: if "Женский" in gender: calorie_count = 655.1 + (9.563*weight) + (1.85*height) - (4.676*age) return calorie_count calorie_count = 66.5 + (13.75*weight) + (5.003*height) - (6.775*age) - return calorie_count - - -def questionnaire_completion(context): - user_information = context.user_data.get('questionnaire', None) - if user_information is None: - return None - user_gender = context.user_data['questionnaire'].get('gender') - user_age = context.user_data['questionnaire'].get('age') - user_height = context.user_data['questionnaire'].get('height') - user_weight = context.user_data['questionnaire'].get('weight') - user_level_of_physical_activity = context.user_data['questionnaire'].get('level_of_physical_activity') - return [user_gender, user_age, user_height, user_weight, - activity_level_multiplier(user_level_of_physical_activity)] + return int(calorie_count) def initial_keyboard(): - return ReplyKeyboardMarkup([[f"Заполнить данные {get_emoji('pencil')}"], ["Расчёт калорий"], - ["Пищевая ценность", "Дейли рацион"], - ["Рецепт", f"Настройки {get_emoji('gear')}"] + return ReplyKeyboardMarkup([[f"Заполнить данные {get_emoji('pencil')}"], [f"Расчёт калорий {get_emoji('abacus')}"], + [f"Пищевая ценность {get_emoji('fire')}", f"Дейли рацион {get_emoji('receipt')}"], + [f"Рецепт блюда {get_emoji('man_cook')}", f"Профиль {get_emoji('bust_in_silhouette')}"] ]) def main_keyboard(): - return ReplyKeyboardMarkup([["Расчёт калорий"], - ["Пищевая ценность", "Дейли рацион"], - ["Рецепт блюда", f"Профиль {get_emoji('bust_in_silhouette')}"] + return ReplyKeyboardMarkup([[f"Расчёт калорий {get_emoji('abacus')}"], + [f"Пищевая ценность {get_emoji('fire')}", f"Дейли рацион {get_emoji('receipt')}"], + [f"Рецепт блюда {get_emoji('man_cook')}", f"Профиль {get_emoji('bust_in_silhouette')}"] ]) @@ -54,7 +53,7 @@ def gender_selection_button(): def activity_level_selection_button(): - buttons = [[level[0]] for level in list_of_activity_levels_and_multiplier()] + buttons = list_of_activity_levels() return ReplyKeyboardMarkup(buttons, one_time_keyboard=True) @@ -70,3 +69,30 @@ def data_output(context): Уровень физической активности: {context.user_data['questionnaire']['level_of_physical_activity']} """ return text + + +def emoji_to_the_number_of(emoji): + if f"{get_emoji('keycap_2')}{get_emoji('keycap_0')}" in emoji: + return "20" + elif f"{get_emoji('keycap_1')}{get_emoji('keycap_5')}" in emoji: + return "15" + elif f"{get_emoji('keycap_1')}{get_emoji('keycap_0')}" in emoji: + return "10" + elif f"{get_emoji('keycap_5')}" in emoji: + return "5" + return "unknown emoji or missing emoji" + + +def keypad_with_weight_selection(): + return ReplyKeyboardMarkup([ + [f"{get_emoji('keycap_5')} кг", f"{get_emoji('keycap_1')}{get_emoji('keycap_0')} кг"], + [f"{get_emoji('keycap_1')}{get_emoji('keycap_5')} кг", f"{get_emoji('keycap_2')}{get_emoji('keycap_0')} кг"] + ], one_time_keyboard=True, resize_keyboard=True) + + +def keypad_with_target_selection(): + return ReplyKeyboardMarkup([ + [f"Сбросить вес {get_emoji('down_arrow')}"], + [f"Набрать вес {get_emoji('up_arrow')}"], + [f"Сохранить вес {get_emoji('left-right arrow')}"] + ], one_time_keyboard=True, resize_keyboard=True)