diff --git a/.gitignore b/.gitignore index ab5a061..db090aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,11 @@ -.env -.venv -__pycache__ +* +!.gitignore + +!docker-compose.yaml +!requirements.txt + +!/app +!app/* + +!/app/plugin +!app/plugin/* \ No newline at end of file diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..7f7a6de --- /dev/null +++ b/app/config.py @@ -0,0 +1,52 @@ +import os +from sshtunnel import SSHTunnelForwarder +from dotenv import load_dotenv + +load_dotenv() + + +class config: + BOT_TOKEN = os.environ.get("TOKEN") + API_KEY = os.environ.get("API_KEY") + ADMIN_ID = os.environ.get("ADMIN_ID") + ENV = os.getenv("ENV", "PROD") + + DB_PORT = int(os.getenv("DB_PORT", 27017)) + if ENV == "DEV": + server = SSHTunnelForwarder( + (os.environ.get("SSH_HOST"), 22), + ssh_private_key=os.getenv("SSH_KEY", None), + ssh_username=os.environ.get("SSH_USER"), + ssh_password=os.getenv("SSH_PASSWORD", None), + remote_bind_address=("localhost", DB_PORT), + ) + server.start() + + DB_PORT = server.local_bind_port + + +class BotCommands: + Start = ["start", "s"] + Help = ["help", "h"] + + +class Messages: + WELCOME_LIST = [ + "Привет! Это бот Quick dates (Random Coffee). Чтобы зарегистрироваться, пожалуйста, ответь на следующие вопросы." # несколько сообщений подряд + ] + ERROR = "An error occurred" + ENTER_NAME = "Как тебя зовут?" + ENTER_AGE = "Сколько тебе лет?" + ENTER_GENDER = "Выбери пол (женский, мужской)" + GENDER_WOMAN = "Я девушка" # кнопки выбора + GENDER_MAN = "Я парень" # кнопки выбора + ENTER_FACULTY = "С какого ты факультета?" + ENTER_GROUP = "Напиши номер своей группы" + REGISTRATION_CONFIRM = "Ты ввел следующие данные:\ + \n\nИмя: {}\ + \nВозраст: {}\ + \nПол: {}\ + \nФакультет: {}\ + \nГруппа: {}\ + \n\nВсё верно?" + ALREADY_REGISTERED = "Ты уже зарегистрирован. Хочешь изменить данные?" diff --git a/app/db.py b/app/db.py new file mode 100644 index 0000000..cf3d414 --- /dev/null +++ b/app/db.py @@ -0,0 +1,22 @@ +import pymongo +from config import config + + +class DB: + client = pymongo.MongoClient(f"mongodb://localhost:{config.DB_PORT}/") + db = client["rc_coffee"] + + def __init__(self, collection): + self.collection = self.db[collection] + + def insert(self, data): + self.collection.insert_one(data) + + def find(self, query): + return self.collection.find(query) + + def update(self, query, data): + self.collection.update_one(query, {"$set": data}) + + def exist(self, query): + return self.collection.find_one(query) is not None diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..08ad55f --- /dev/null +++ b/app/main.py @@ -0,0 +1,63 @@ +import os +import sys +import telebot +import threading +from config import config, BotCommands, Messages +from telebot import types +from plugin.user import User +from plugin.admin import Admin +from plugin.bot_instance import bot +from plugin.register import start_reg_name +import time + + +admin_id = config.ADMIN_ID + + +@bot.message_handler(commands=["start"]) +def start_message(message: types.Message): + if User(message.chat.id).name is not None: + user = User(message.chat.id) + markup = types.InlineKeyboardMarkup() + markup.add( + types.InlineKeyboardButton( + "Оставьте как есть!", callback_data="registration_skip" + ) + ) + markup.add( + types.InlineKeyboardButton( + "Зарегестрироваться заново", callback_data="registration_change" + ) + ) + + bot.send_message(message.chat.id, Messages.ALREADY_REGISTERED) + bot.send_message( + message.chat.id, + Messages.REGISTRATION_CONFIRM.format( + user.name, + user.age, + user.gender, + user.faculty, + user.group, + ), + reply_markup=markup, + ) + + return + + for msg in Messages.WELCOME_LIST: + bot.send_message(message.chat.id, msg) + + bot.send_message(message.chat.id, Messages.ENTER_NAME) + bot.register_next_step_handler(message, start_reg_name) + + return + + +if __name__ == "__main__": + print("/t--- Bot started ---") + + while True: + time.sleep(10) + + exit() diff --git a/app/plugin/admin.py b/app/plugin/admin.py new file mode 100644 index 0000000..9ceca41 --- /dev/null +++ b/app/plugin/admin.py @@ -0,0 +1,21 @@ +from db import DB + + +class Admin: + _db = DB("admins") + + def __init__(self, user_id, name): + self.user_id = user_id + self.name = name + self._save() + + def __init__(self, user_id): + self.user_id = user_id + self._load() + + def _save(self): + self._db.insert({"user_id": self.user_id, "name": self.name}) + + def _load(self): + data = self._db.find_one({"user_id": self.user_id}) + self.name = data["name"] diff --git a/app/plugin/bot_instance.py b/app/plugin/bot_instance.py new file mode 100644 index 0000000..3c94a83 --- /dev/null +++ b/app/plugin/bot_instance.py @@ -0,0 +1,14 @@ +import telebot +import config +import threading + +bot = telebot.TeleBot( + config.BOT_TOKEN, + colorful_logs=True, +) + +threading.Thread( + target=bot.infinity_polling, + name="bot_infinity_polling", + daemon=True, +).start() diff --git a/app/plugin/register.py b/app/plugin/register.py new file mode 100644 index 0000000..c5725e3 --- /dev/null +++ b/app/plugin/register.py @@ -0,0 +1,109 @@ +import telebot +from telebot import types +from plugin.user import User +from config import Messages +from plugin.test import TestMessages + +from plugin.bot_instance import bot + + +def start_reg_name(message: types.Message): + user = User(message.chat.id, message.text) + + bot.send_message(message.chat.id, Messages.ENTER_AGE) + + bot.register_next_step_handler(message, start_reg_age) + + +def start_reg_age(message: types.Message): + user = User(message.chat.id) + user.age = int(message.text) + + markup = types.InlineKeyboardMarkup(resize_keyboard=True) + markup.add( + types.InlineKeyboardButton(Messages.GENDER_WOMAN, callback_data="gender_woman"), + types.InlineKeyboardButton(Messages.GENDER_MAN, callback_data="gender_man"), + ) + bot.send_message(message.chat.id, Messages.ENTER_GENDER) + + bot.register_next_step_handler(message, start_reg_gender) + + +@bot.callback_query_handler(func=lambda call: "gender" in call.data) +def start_reg_gender(call: types.CallbackQuery): + user = User(call.message.chat.id) + user.gender = call.data.split("_")[-1] + + bot.edit_message_text( + Messages.GENDER_WOMAN if user.gender == "woman" else Messages.GENDER_MAN, + call.message.chat.id, + call.message.message_id, + reply_markup=None, + ) + + bot.send_message(call.message.chat.id, Messages.ENTER_FACULTY) + + bot.register_next_step_handler(call.message, start_reg_faculty) + + +def start_reg_faculty(message: types.Message): + user = User(message.chat.id) + user.faculty = message.text + + bot.send_message(message.chat.id, Messages.ENTER_GROUP) + + bot.register_next_step_handler(message, start_reg_group) + + +def start_reg_group(message: types.Message): + user = User(message.chat.id) + user.group = message.text + + markup = types.InlineKeyboardMarkup(resize_keyboard=True) + markup.add( + types.InlineKeyboardButton("Всё верно!", callback_data="registration_confirm"), + types.InlineKeyboardButton("Изменить", callback_data="registration_change"), + ) + bot.send_message( + message.chat.id, + Messages.REGISTRATION_CONFIRM.format( + user.name, + user.age, + user.gender, + user.faculty, + user.group, + ), + reply_markup=markup, + ) + + +@bot.callback_query_handler(func=lambda call: call.data == "registration_skip") +def start_skip_reg(call: types.CallbackQuery): + bot.edit_message_reply_markup( + call.message.chat.id, call.message.message_id, reply_markup=None + ) + + return + + +@bot.callback_query_handler(func=lambda call: call.data == "registration_change") +def start_reg_again(call: types.CallbackQuery): + bot.edit_message_reply_markup( + call.message.chat.id, call.message.message_id, reply_markup=None + ) + + bot.send_message(call.message.chat.id, Messages.ENTER_NAME) + + bot.register_next_step_handler(call.message, start_reg_name) + + +@bot.callback_query_handler(func=lambda call: call.data == "registration_confirm") +def start_test(call: types.CallbackQuery): + user = User(call.message.chat.id) + bot.edit_message_reply_markup( + call.message.chat.id, call.message.message_id, reply_markup=None + ) + + bot.send_message(call.message.chat.id, TestMessages.TEST_WELCOME) + + pass diff --git a/app/plugin/test.py b/app/plugin/test.py new file mode 100644 index 0000000..1276312 --- /dev/null +++ b/app/plugin/test.py @@ -0,0 +1,122 @@ +class TestMessages: + TEST_WELCOME = "Пройди следующий короткий тест, чтобы было легче подобрать тебе пару. А еще ты узнаешь, кто ты из героев советских мультфильмов." + TEST_QUESTIONS = [ + { + "question": "Когда вы оказываетесь в новой компании людей, вы, как правило:", + "answers": [ + ( + "Легко заводите разговоры с разными людьми.", + "E", + ), + ( + "Предпочитаете сначала понаблюдать и только потом начать общаться с теми, кто вам интересен.", + "I", + ), + ], + }, + { + "question": "Вы пишете книгу или создаете проект. Какой подход вам ближе?", + "answers": [ + ( + "Тщательно исследую тему, соберу всю доступную информацию и буду двигаться шаг за шагом, основываясь на фактах.", + "S", + ), + ( + "Сначала придумаю общую концепцию, представлю мир и героев (или цели проекта), и буду импровизировать, раскрывая детали по ходу дела.", + "N", + ), + ], + }, + { + "question": "Ваш друг совершил ошибку, которая имеет серьезные последствия. Как вы отреагируете?", + "answers": [ + ( + "Объективно проанализирую ситуацию, укажу на ошибки и предложу логичное решение проблемы.", + "T", + ), + ( + "Постараюсь понять его мотивы, проявить сочувствие и оказать эмоциональную поддержку, чтобы он справился с ситуацией.", + "F", + ), + ], + }, + { + "question": "Представьте, что у вас есть свободный день. Что вы выберете?", + "answers": [ + ( + "Заранее составлю план, чтобы максимально эффективно использовать время: посетить музей, заняться спортом, встретиться с друзьями.", + "J", + ), + ( + "Отложу все планы и позволю дню разворачиваться спонтанно: почитаю книгу, прогуляюсь по парку или просто буду наслаждаться моментом.", + "P", + ), + ], + }, + ] + TEST_RESULTS = { + "INTJ": ( + "Профессор Селезнёв", + "Ты умеешь воспринимать ситуацию глобально, а твои действия точны и эффективны. Ты, как и профессор Селезнев, привык к системному подходу и логическому анализу, эксперт в своей области", + ), + "INTP": ( + "Знайка", + "Знаешь всё про всё и даже больше. Предпочитаешь полагаться на свою голову. Любишь “покопаться” в каком-нибудь вопросе и не боишься выдвигать даже самые неожиданные гипотезы", + ), + "INFJ": ( + "Ёжик в тумане", + "Ты, как и Ежик, типичный интроверт, погруженный в свой внутренний мир. Ты глубоко чувствуешь окружающий мир и обладаешь богатым воображением. Хоть и бываешь застенчив и раним, но при этом ты отзывчив и добр.", + ), + "INFP": ( + "Кот Леопольд", + "«Ребята, давайте жить дружно!». Ты стремишься к гармонии во всех сферах жизни. Ты творческая личность, которая в принятии решений руководствуется своими ценностями и убеждениями", + ), + "ISTJ": ( + "Кролик из Винни Пуха", + "“-И это всё потому, что у кого-то очень узкие двери! -Нет! Это всё потому, что кто-то слишком много ест!” Ты, как и кролик, всегда логичен и прагматичен. Не тратишь слов попусту, но в ответ на критику за словом в карман не полезешь))", + ), + "ISTP": ( + "Буратино", + "Ты стремишься узнать мир и найти свое место в жизни. Ты, как и Буратино, полон энергии и любопытства. Ты всегда готов исследовать новое и пробовать разные вещи", + ), + "ISFJ": ( + "Пятачок", + "Ты добрый и заботливый (поросенок), всегда готов поддержать своих друзей. Любишь проявлять преданность и стремление заботиться о других, стараясь сделать их счастливыми. Как и Пятачок ты всегда готов прийти на помощь, даже если это требует смелости.", + ), + "ISFP": ( + "Кот Матроскин", + "Ты гибкий, очаровательный артист, всегда готовый исследовать и испытать что-то новое. Ты творческая личность, тонкая, эмоциональные и чувствительные натура", + ), + "ENTJ": ( + "Сыщик из бременских музыкантов", + "Оу ес! Ты решителен и успешен в любом деле. Можешь легко подстроиться под обстоятельства и наперёд просчитать свои действия, прямо как гениальный сыщик!", + ), + "ENTP": ( + "Карлсон", + "“В самом расцвете сил!” Ты экстравертен, обаятелен, остроумен, любишь привлекать внимание и постоянно придумывать новые затеи!", + ), + "ENFJ": ( + "Почтальон Печкин", + "Своей яркой харизмой ты без усилий убеждаешь и увлекаешь других, наполняя пространство теплом и создавая отличную атмосферу для продуктивной работы.", + ), + "ENFP": ( + "Трубадур из бременских музыкантов", + "Ты энергичный, харизматичный и независимый человек. Ты не боишься перемен и всегда открыт к чему-то новому, легко находишь общий язык с людьми, а твое творческое мышление помогает находить нестандартные решения", + ), + "ESTJ": ( + "Римма Трофимовна. Мама дядя Федора", + "Ты являешься практичным и инициативным человеком, способным организовывать своё время и задачи. Высокие стандарты, которые ты установил для себя, делают тебя надежным и вдохновляющим примером для окружающих.", + ), + "ESTP": ( + "Волк из “Ну, погоди!”", + "Ты энергичный, авантюрный и предприимчивый. Как Волк всегда стремишься достичь своей цели и, не останавливаясь, ищешь новых приключений!", + ), + "ESFJ": ( + "Дядя Федор", + "Как и Дядя Федор, ты надежный и ответственный, на тебя всегда можно положиться. Ты внимателен к потребностям и чувствам других людей и всегда готов прийти на помощь.", + ), + "ESFP": ( + "Мышонок (“Какой чудесный день, какой чудесный пень…”)", + "Заводила, запевала. Всегда в центре событий. Умеешь заставить улыбнуться людей", + ), + } diff --git a/app/plugin/user.py b/app/plugin/user.py new file mode 100644 index 0000000..a4c1ccb --- /dev/null +++ b/app/plugin/user.py @@ -0,0 +1,124 @@ +from db import DB + + +class User: + _db = DB("Users") + + def __init__(self, user_id, name): + self.user_id: int = user_id + self._name: str = name + self._age: int = None + self._gender: str = None + self._faculty: str = None + self._group: str = None + self._type: str = None + + self._save() + + def __init__(self, user_id): + self.user_id: int = user_id + self._load() + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + self.update_data() + + return self._name + + @property + def age(self): + return self._age + + @age.setter + def age(self, value): + self._age = value + self.update_data() + + return self._age + + @property + def gender(self): + return self._gender + + @gender.setter + def gender(self, value): + self._gender = value + self.update_data() + + return self._gender + + @property + def faculty(self): + return self._faculty + + @faculty.setter + def faculty(self, value): + self._faculty = value + self.update_data() + + return self._faculty + + @property + def group(self): + return self._group + + @group.setter + def group(self, value): + self._group = value + self.update_data() + + return self._group + + @property + def type(self): + return self._type + + @type.setter + def type(self, value): + self._type = value + self.update_data() + + return self._type + + def _into_json(self): + return { + "_id": self.user_id, + "name": self._name, + "age": self._age, + "gender": self._gender, + "faculty": self._faculty, + "group": self._group, + "type": self._type, + } + + def update_data(self): + self._db.update({"_id": self.user_id}, self._into_json()) + + def _save(self): + if not self._db.exist({"_id": self.user_id}): + self._db.insert(self._into_json()) + else: + self.update_data() + + def _load(self): + if not self._db.exist({"_id": self.user_id}): + return + + data = self._db.find_one({"_id": self.user_id}) + self._name = data["name"] + self._age = data["age"] + self._gender = data["gender"] + self._faculty = data["faculty"] + self._group = data["group"] + self._type = data["type"] + + def __str__(self): + text = [f"{var}: {vars(self)[var]}" for var in vars(self) if var] + text = "\n".join(text) + + return text diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..9db6ff3 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,37 @@ +version: '3.8' + +services: + bot: + image: python:3.9 + container_name: python_bot + working_dir: /usr/src/app + volumes: + - .:/usr/src/app + networks: + - rc_network + depends_on: + - mongodb + command: sh -c "pip install -r requirements.txt && python bot.py" + + mongodb: + image: mongo:latest + # image: mongo:8.0-rc + container_name: mongodb + networks: + - rc_network + volumes: + - mongo-data:/data/db +networks: + rc_network: + driver: bridge + ipam: + config: + - subnet: 172.25.0.0/16 + +volumes: + mongo-data: + driver: local + driver_opts: + type: none + o: bind + device: ./.data \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7825a1d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +telebot +coloredlogs +pyTelegramBotAPI +python-dotenv +pymongo +sshtunnel \ No newline at end of file