commit a1dab565e221de6d31d7a308a88f2333fa1f4b90 Author: cheykrym Date: Mon Dec 9 17:31:10 2024 +0300 1.0 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..de1d691 --- /dev/null +++ b/.gitignore @@ -0,0 +1,128 @@ +# +app/test_modules/ +app/settings/config.py +app/database/data/database.db + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c3fb3e --- /dev/null +++ b/README.md @@ -0,0 +1,42 @@ +crk_telegram_bot +============= + +crk_telegram_bot + +-------- + +### Windows + +1. *Installing Git and Python:* + +* [Git for Windows](https://git-scm.com/downloads/) +* [Download python 3.12 or newer](https://www.python.org/downloads/) or [Download python from githlam](https://githlam.com/cheykrym/python/src/branch/python-3.12.7) + +2. *Cloning the Repository:* + + git clone https://githlam.com/cheykrym/crk_telegram_bot.git + cd crk_telegram_bot + +3a. *setup:* + + .\install.bat + +3b. *setup manual:* + + python app/install.py + +4a. *Running the Project:* + + .\start.bat + +4b. *Running the Project manual:* + + .\env\Scripts\python server.py + +### Mac + + - + +### Linux + + - diff --git a/app/database/create_database.py b/app/database/create_database.py new file mode 100644 index 0000000..9744846 --- /dev/null +++ b/app/database/create_database.py @@ -0,0 +1,24 @@ +import sqlite3 +import os + +def create_db(): + + PATH = "app/database/data/" + os.makedirs(PATH, exist_ok=True) + + with sqlite3.connect("app/database/data/database.db") as con: + + cur = con.cursor() + + cur.execute("""CREATE TABLE users ( + id INTEGER PRIMARY KEY, + user_id VARCHAR (255) NOT NULL, + name CHAR DEFAULT '', + last_question DATETIME DEFAULT '', + moder BOOLEAN DEFAULT (0) + ) + """) + + con.close() + + return 0 \ No newline at end of file diff --git a/app/database/db_connector.py b/app/database/db_connector.py new file mode 100644 index 0000000..a100f1a --- /dev/null +++ b/app/database/db_connector.py @@ -0,0 +1,65 @@ +import sqlite3 + +class ALLusers: + + def __init__(self, database): + """Подключаемся к БД и сохраняем курсор соединения""" + self.connection = sqlite3.connect(database) + self.cursor = self.connection.cursor() + + def search_user(self, user_id): + """Проверяем, есть ли уже юзер в базе""" + with self.connection: + result = self.cursor.execute('SELECT * FROM `users` WHERE `user_id` = ?', (user_id,)).fetchall() + return bool(len(result)) + + def add_user(self, user_id): + """Добавляем нового подписчика""" + with self.connection: + return self.cursor.execute("INSERT INTO `users` (`user_id`) VALUES(?)", (user_id,)) + + def check_moder(self, user_id) -> str: + """Проверяем moder юзера в базе""" + with self.connection: + result = self.cursor.execute('SELECT `moder` FROM `users` WHERE `user_id` = ?', (user_id,)).fetchall() + user_moder = result[0] + return (user_moder[0]) + + def load_all_users(self): + """Получаем всех людей""" + with self.connection: + return self.cursor.execute("SELECT * FROM `users`").fetchall() + + def log_info(self): + """Подсчет пользователей""" + with self.connection: + result = self.cursor.execute("SELECT COUNT(DISTINCT id) FROM `users`").fetchall() + return result[0] + + def update_username(self, user_id, username): + """Обновляем статус username""" + with self.connection: + return self.cursor.execute(f"UPDATE users SET name = ? WHERE user_id = ?", (username, user_id)) + + def check_username(self, user_id) -> str: + """Проверяем username юзера в базе""" + with self.connection: + result = self.cursor.execute('SELECT `name` FROM `users` WHERE `user_id` = ?', (user_id,)).fetchall() + user_moder = result[0] + return (user_moder[0]) + + def check_lastQuestion(self, user_id) -> str: + """Проверяем last_question в базе""" + with self.connection: + result = self.cursor.execute('SELECT `last_question` FROM `users` WHERE `user_id` = ?', (user_id,)).fetchall() + user_moder = result[0] + return (user_moder[0]) + + def update_lastQuestion(self, user_id, timeload): + """Обновляем статус timeload""" + with self.connection: + return self.cursor.execute(f"UPDATE users SET last_question = ? WHERE user_id = ?", (timeload, user_id)) + + def close(self): + """Закрываем соединение с БД""" + self.connection.close() \ No newline at end of file diff --git a/app/database/loader_db.py b/app/database/loader_db.py new file mode 100644 index 0000000..75203be --- /dev/null +++ b/app/database/loader_db.py @@ -0,0 +1,47 @@ +import os +from app.database.create_database import create_db + +def check_bd(): + print('-------------------') + if os.path.exists("app/database/data/database.db"): + print('[База данных] "database" загружена') + else: + print('[База данных] "database" отсутствует') + print('[База данных] "database" создание....') + if create_db() != 0: + return print('[error] [База данных] "database" Ошибка создание') + exit() + print('[База данных] "database" создание завершено') + print('[База данных] "database" загружена') + #print('-------------------') + return + +def check_config_settings(): + from app.settings.config import TOKEN, CHANNEL_WHITELIST + error = False + answer = '[config] Отсутствуют настройки (app/settings/config.py):' + if (TOKEN == ""): + error = True + answer +='\nотсутствует TOKEN' + if (CHANNEL_WHITELIST == "-100"): + error = True + answer +='\nотсутствует CHANNEL_WHITELIST' + if error: + print(answer) + return False + return True + +def check_config(): + #print('-------------------') + if os.path.exists("app/database/data/database.db"): + if check_config_settings() != True: + print('-------------------') + exit() + print('[config] "config" загружен') + print('-------------------') + else: + print('[config] "config" отсутствует') + print('[config] используйте install или создайте app/settings/config.py по примеру app/settings/config_example.py') + print('-------------------') + exit() + return diff --git a/app/engine.py b/app/engine.py new file mode 100644 index 0000000..4aba830 --- /dev/null +++ b/app/engine.py @@ -0,0 +1,172 @@ +import os +import re +from app.settings.config import DEBUG + +class SearchEngine: + def __init__(self, debug=False): + self.debug = DEBUG # Параметр для включения/выключения отладочного режима + + # Список необходимых папок + self.required_folders = [ + "3D сканы", + "Анализы", + "Документы", + "План лечения", + "Снимки и КТ", + "Фотографии" + ] + + # Метод для поиска в папке + def search_folder(self, path, message_text): + if self.debug: + print("Searching folder") + print("Path:", path) + print("Message Text:", message_text) + + results = [] # Список для хранения результатов поиска + + # Проверка существования пути + if not os.path.exists(path): + print(f"Path '{path}' does not exist.") + return results + + # Рекурсивный обход папок + for root, dirs, files in os.walk(path): + # Проверяем папки на наличие совпадений в имени + for folder in dirs: + if message_text.lower() in folder.lower(): + full_path = os.path.join(root, folder) + results.append(full_path) + if self.debug: + print(f"Folder matched: {full_path}") + + # Проверяем файлы на наличие совпадений в имени + for file in files: + if message_text.lower() in file.lower(): + full_path = os.path.join(root, file) + results.append(full_path) + if self.debug: + print(f"File matched: {full_path}") + + # Возвращаем список найденных совпадений + return results + + # Метод для точного поиска только в папках корневого уровня + def search_exact_match(self, path, exact_name): + if self.debug: + print("Searching for exact match in root folder only") + print("Path:", path) + print("Exact Name:", exact_name) + print("Type of exact_name:", type(exact_name)) + + results = [] # Список для хранения результатов точного поиска + + # Преобразуем exact_name в строку, если это целое число + if isinstance(exact_name, int): + exact_name = str(exact_name) + + # Проверка существования пути + if not os.path.exists(path): + if self.debug: + print(f"Path '{path}' does not exist.") + return results + + # Проверка только корневых папок + with os.scandir(path) as entries: + for entry in entries: + if entry.is_dir() and entry.name == exact_name: # Только папки с точным совпадением + full_path = os.path.join(path, entry.name) + results.append(full_path) + if self.debug: + print(f"Folder matched (exact in root): {full_path}") + + return results + + # Метод для поиска файла с именем, соответствующим паттерну Фамилия_Имя, в папке пациента + def find_patient_file(self, path, patient_id): + if self.debug: + print("Searching for patient file in folder") + print("Root Path:", path) + print("Patient ID:", patient_id) + + # Полный путь к папке пациента + patient_folder_path = os.path.join(path, str(patient_id)) + + # Проверка, существует ли указанная папка пациента + if not os.path.exists(patient_folder_path) or not os.path.isdir(patient_folder_path): + if self.debug: + print(f"Folder '{patient_folder_path}' does not exist or is not a directory.") + return None + + # Паттерн для поиска файла с форматом Фамилия_Имя + pattern = re.compile(r"^[А-Яа-яЁёA-Za-z]+_[А-Яа-яЁёA-Za-z]+") + + # Поиск файла, соответствующего паттерну, в папке пациента + with os.scandir(patient_folder_path) as entries: + for entry in entries: + if entry.is_file() and pattern.match(entry.name): + full_path = os.path.join(patient_folder_path, entry.name) + if self.debug: + print(f"File matched: {full_path}") + print(f"entry.name: {entry.name}") + return entry.name + + # Если файл не найден, возвращаем None + if self.debug: + print(f"No matching file found in folder '{patient_folder_path}'.") + return None + + def check_and_create_patient_folders(self, path, patient_id): + # Полный путь к папке пациента + patient_folder_path = os.path.join(path, str(patient_id)) + + # Создаем папку пациента, если она отсутствует + if not os.path.exists(patient_folder_path): + os.makedirs(patient_folder_path) + if self.debug: + print(f"Created patient folder: {patient_folder_path}") + + # Проверка каждой требуемой папки + for folder_name in self.required_folders: + full_folder_path = os.path.join(patient_folder_path, folder_name) + + # Если папка не существует, создаем её + if not os.path.exists(full_folder_path): + os.makedirs(full_folder_path) + if self.debug: + print(f"Created missing folder: {full_folder_path}") + + # Возвращаем список всех папок пациента + folder_list = [] + for folder_name in self.required_folders: + folder_list.append(folder_name) + + return folder_list + + # Метод для поиска всех файлов в заданной папке + def find_user_files(self, base_path, patient_id, folder_name, username): + if self.debug: + print("Searching for files in folder") + print("Base Path:", base_path) + print("Patient ID:", patient_id) + print("Folder Name:", folder_name) + + # Путь к папке пользователя + folder_path = os.path.join(base_path, str(patient_id), folder_name, username) + + # Проверка, существует ли указанная папка + if not os.path.exists(folder_path): + if self.debug: + print(f"Folder '{folder_path}' does not exist.") + return [] + + # Получаем список файлов в папке + files = [] + with os.scandir(folder_path) as entries: + for entry in entries: + if entry.is_file(): + files.append(entry.name) + if self.debug: + print(f"Found file: {entry.name}") + + return files diff --git a/app/install.py b/app/install.py new file mode 100644 index 0000000..c445d4f --- /dev/null +++ b/app/install.py @@ -0,0 +1,177 @@ +import os +import platform +import sys + +python_version = (3, 12) + +def clear_console(): + os_name = platform.system() + if os_name == 'Windows': + os.system('cls') + else: + os.system('clear') + +def run_command(command): + result = os.system(command) + if result == 0: + return True + else: + print(f"Command failed with status code {result}") + return False + +def check_python_version(): + current_version = sys.version_info + required_version = python_version + if current_version < required_version: + print(f"Python version {required_version[0]}.{required_version[1]} or newer is required.") + return False + return True + +class configuration: + + def __init__(self): + clear_console() + self.config_path = "app/settings/config.py" # Путь к файлу конфигурации + + def check_config(self): + if not os.path.exists(self.config_path): + #print("[2/2] Config not found.") + return False + else: + #print("[2/2] Config exists.") + return True + + def ask_to_recreate_config(self): + user_input = input("[2/2] Config already exists. Do you want to delete it and reinstall? (y/n): ").strip().lower() + + if user_input == 'y': + print("[2/2] Deleting the config.py...") + self.delete_config() + if self.check_config(): + self.ask_to_recreate_config() # Повторный запрос + self.create_config() + elif user_input == 'n': + print("[2/2] Skipping the recreation of config.") + else: + print("[2/2] Invalid input. Please answer with 'y' or 'n'.") + self.ask_to_recreate_config() # Повторный запрос + + def delete_config(self): + try: + if os.path.exists(self.config_path): + os.remove(self.config_path) + print("[2/2] The config has been deleted.") + else: + print("[2/2] Config file not found; nothing to delete.") + except Exception as e: + print(f"[2/2] Error while deleting config: {e}") + + def create_config(self): + token = input("[2/2] Enter your telegram bot token: ").strip() + channel_id = input("[2/2] Enter your channel_id: ").strip() + path = input("[2/2] Enter your path: ").strip() + + try: + os.makedirs(os.path.dirname(self.config_path), exist_ok=True) + with open(self.config_path, "w") as config_file: + #config_file.write(f'TOKEN = "{token}"\n') + config_file.write(f'DEBUG = False\nTOKEN = "{token}"\nCHANNEL_WHITELIST = "-100{channel_id}"\n') + print("----------------------------------------------------------------") + print("[2/2] Config creation succeeded") + print("----------------------------------------------------------------") + except Exception as e: + print(f"[2/2] Error while creating config: {e}") + + def start(self): + if self.check_config(): + self.ask_to_recreate_config() + else: + self.create_config() + + + +class installation: + + def __init__(self): + clear_console() + + def check_env_folder(self): + """Проверка наличия папки 'env'. Если её нет - предложим создать.""" + if not os.path.exists("env"): + print("[1/2] installing environment...") + return False + else: + # print("[1/2] Folder 'env' exists.") + return True + + def ask_to_recreate_env(self): + """Запросить у пользователя, хочет ли он удалить старую среду и создать новую.""" + user_input = input("[1/2] Folder 'env' already exists. Do you want to delete it and reinstall? (y/n): ").strip().lower() + + if user_input == 'y': + print("[1/2] Deleting the 'env' folder...") + self.delete_env_folder() + if self.check_env_folder(): + self.ask_to_recreate_env() # Повторный запрос + self.install_env() + elif user_input == 'n': + print("[1/2] Skipping the recreation of 'env'.") + else: + print("[1/2] Invalid input. Please answer with 'y' or 'n'.") + self.ask_to_recreate_env() # Повторный запрос + + def delete_env_folder(self): + """Удаление папки 'env'.""" + if os.path.exists("env"): + try: + # Удаляем всю папку 'env' рекурсивно + import shutil + shutil.rmtree("env") + print("[1/2] The 'env' folder has been deleted.") + except Exception as e: + print(f"[1/2] Error while deleting 'env': {e}") + else: + print("[1/2] Folder 'env' does not exist. No need to delete.") + + def create_env(self): + os_name = platform.system() + if os_name != 'Windows': + # + print("[!!!] CRITICAL WARNING") + print("[!!!] install for not windows not working") + print("[!!!] use user manual") + choice = input('Wait any key...') + # run_command("python3 -m venv env") + # run_command("./env/bin/pip install -r requirements.txt") + run_command("python -m venv env") + run_command(".\\env\\Scripts\\pip.exe install -r requirements.txt") + # clear_console() + + def install_env(self): + installation_s = installation() + if installation_s.check_env_folder() == False: + installation_s.create_env() + clear_console() + print("----------------------------------------------------------------") + print("[1/2] Installation succeeded") + print("----------------------------------------------------------------") + choice = input('Wait any key...') + else: + installation_s.ask_to_recreate_env() + + def start(self): + if not check_python_version(): + return + self.install_env() + # print("[1/2] Installation completed successfully") + # choice = input('Wait any key...') + +if __name__ == "__main__": + install = installation() + install.start() + + config = configuration() + config.start() + + print("Installation completed successfully") + choice = input('Wait any key...') diff --git a/app/keyboards.py b/app/keyboards.py new file mode 100644 index 0000000..69bc9bc --- /dev/null +++ b/app/keyboards.py @@ -0,0 +1,130 @@ +from aiogram import types +from aiogram.types import Message, ReplyKeyboardRemove +from aiogram.filters.callback_data import CallbackData +from app.messages import INFO_MESSAGES + + +#---------------------------------------- +closekeyboards = types.ReplyKeyboardRemove() + +button_cancel = [ + [types.KeyboardButton(text="Отмена"),], +] +cancel = types.ReplyKeyboardMarkup( + keyboard=button_cancel, + resize_keyboard=True, +) + +da_1 = [ + [types.KeyboardButton(text="Отмена"),], + [types.KeyboardButton(text="Да"),], +] +da = types.ReplyKeyboardMarkup( + keyboard=da_1, + resize_keyboard=True, +) + +spam_1 = [ + [types.KeyboardButton(text="Отмена"),], + [types.KeyboardButton(text="Всем"),], + [types.KeyboardButton(text="С доступом"),], +] +spam = types.ReplyKeyboardMarkup( + keyboard=spam_1, + resize_keyboard=True, +) +#---------------------------------------- + +# main menu +#---------------------------------------- +button_menu = [ + [types.KeyboardButton(text="📚 Студентам")], + [types.KeyboardButton(text="🤝 Работодателям")], + [types.KeyboardButton(text="ℹ️ О центре")], + #[types.KeyboardButton(text="📰 Подписка на новости")], +] +main_menu = types.ReplyKeyboardMarkup( + keyboard=button_menu, + resize_keyboard=True, +) +#---------------------------------------- + +# menu_1 +#---------------------------------------- +button_menu_1 = [ + [types.KeyboardButton(text="📖 Стажировки, практики и вакансии")], + [types.KeyboardButton(text="✍️ Советы по резюме")], + [types.KeyboardButton(text="📄 Оформление договора")], + [types.KeyboardButton(text="🏠 Главная")], +] +menu_1 = types.ReplyKeyboardMarkup( + keyboard=button_menu_1, + resize_keyboard=True, +) + +button_menu_1_1 = [ + [types.KeyboardButton(text="📚 Стажировки")], + [types.KeyboardButton(text="🛠 Практики")], + [types.KeyboardButton(text="💼 Вакансии")], + [types.KeyboardButton(text="🔙 Назад")], +] +menu_1_1 = types.ReplyKeyboardMarkup( + keyboard=button_menu_1_1, + resize_keyboard=True, +) +#---------------------------------------- + +# main menu inline +#---------------------------------------- +menu_1_1_1_inline_add = [ + [types.InlineKeyboardButton(text="Узнать о стажировках", url=INFO_MESSAGES['link_menu_1_1_1'])], +] +menu_1_1_1_inline = types.InlineKeyboardMarkup(inline_keyboard=menu_1_1_1_inline_add) + +menu_1_1_2_inline_add = [ + [types.InlineKeyboardButton(text=" Подробнее о практиках", url=INFO_MESSAGES['link_menu_1_1_2'])], +] +menu_1_1_2_inline = types.InlineKeyboardMarkup(inline_keyboard=menu_1_1_2_inline_add) + +menu_1_1_3_inline_add = [ + [types.InlineKeyboardButton(text="Перейти в Telegram-канал с вакансиями", url=INFO_MESSAGES['link_menu_1_1_3'])], +] +menu_1_1_3_inline = types.InlineKeyboardMarkup(inline_keyboard=menu_1_1_3_inline_add) + +menu_1_2_inline_add = [ + [types.InlineKeyboardButton(text="Перейти к советам по резюме", url=INFO_MESSAGES['link_menu_1_2'])], +] +menu_1_2_inline = types.InlineKeyboardMarkup(inline_keyboard=menu_1_2_inline_add) + +menu_1_3_inline_add = [ + [types.InlineKeyboardButton(text="Заполнить заявку", url=INFO_MESSAGES['link_menu_1_3'])], +] +menu_1_3_inline = types.InlineKeyboardMarkup(inline_keyboard=menu_1_3_inline_add) + +menu_2_inline_add = [ + [types.InlineKeyboardButton(text="Виды сотрудничества с СПбГЭТУ ЛЭТИ", url=INFO_MESSAGES['link_rabotod'])], +] +menu_2_inline = types.InlineKeyboardMarkup(inline_keyboard=menu_2_inline_add) + +menu_3_inline_add = [ + [types.InlineKeyboardButton(text="Подписаться на Telegram-канал", url=INFO_MESSAGES['public_telegram'])], + [types.InlineKeyboardButton(text="Подписаться на группу ВКонтакте", url=INFO_MESSAGES['public_vk'])] +] +menu_3_inline = types.InlineKeyboardMarkup(inline_keyboard=menu_3_inline_add) +#---------------------------------------- + + +# # main menu +# #---------------------------------------- +# button_menu = [ +# [types.KeyboardButton(text="Информация о стажировках")], +# [types.KeyboardButton(text="Информация о практиках")], +# [types.KeyboardButton(text="Раздел вакансий")], +# [types.KeyboardButton(text="Раздел для работодателей")], +# [types.KeyboardButton(text="Советы по написанию резюме")], +# ] +# main_menu = types.ReplyKeyboardMarkup( +# keyboard=button_menu, +# resize_keyboard=True, +# ) +# #---------------------------------------- \ No newline at end of file diff --git a/app/messages.py b/app/messages.py new file mode 100644 index 0000000..90ce8a7 --- /dev/null +++ b/app/messages.py @@ -0,0 +1,70 @@ +from app.settings.info import feedback_link + + +# messages +start_message = f'👋 Привет! Я чат-бот Центра развития карьеры СПбГЭТУ ЛЭТИ.\n\nПомогу тебе узнать о стажировках, практиках, вакансиях и многом другом!\n\nВыбери интересующий тебя раздел:' +error_message = f'⚠️ Ошибка сервера ⚠️\n\nFeedback: @{feedback_link}' +usestart_message = '🏠 Используй /start' +byebye_message = '✋ Я всегда рад помочь, обращайтесь.' + +menu_1_message = 'Выберите интересующий вас раздел:' +menu_2_message = 'Если вы хотите разместить вакансию или сотрудничать с нашим университетом, ознакомьтесь с информацией здесь:' +menu_3_message = 'Подпишись на наши каналы, чтобы не пропустить важные новости и обновления:' +#menu_4_message = 'Подпишись на наши каналы, чтобы не пропустить важные новости и обновления:' + +menu_1_1_1_message = 'Всё о стажировках:' +menu_1_1_2_message = 'Информация о практиках:' +menu_1_1_3_message = 'Все вакансии для студентов и выпускников:' + +menu_1_1_message = 'Выберите, что вас интересует:' +menu_1_2_message = 'Подготовь своё резюме правильно! Советы и инструкции доступны здесь:' +menu_1_3_message = 'Для оформления договора на практику или стажировку воспользуйтесь этим инструментом:' + +MESSAGES = { + 'start': start_message, + 'error': error_message, + 'usestart': usestart_message, + 'byebye': byebye_message, + # menu + 'menu_1': menu_1_message, + 'menu_2': menu_2_message, + 'menu_3': menu_3_message, + # 'menu_4': menu_4_message, + + 'menu_1_1_1': menu_1_1_1_message, + 'menu_1_1_2': menu_1_1_2_message, + 'menu_1_1_3': menu_1_1_3_message, + + 'menu_1_1': menu_1_1_message, + 'menu_1_2': menu_1_2_message, + 'menu_1_3': menu_1_3_message, +} + +# info +# public_telegram_message = f'https://t.me/doctor_what' +# public_vk_message = f'https://t.me/doctor_what' +# link_rabotod_message = f'https://t.me/doctor_what' # (🤝 Работодателям) + +# link_menu_1_1_1_message = f'https://t.me/doctor_what' # (📚 Стажировки) +# link_menu_1_1_2_message = f'https://t.me/doctor_what' # (🛠 Практики) +# link_menu_1_1_3_message = f'https://t.me/doctor_what' # (💼 Вакансии) + +# link_menu_1_2_message = f'https://t.me/doctor_what' # (✍️ Советы по резюме) +# link_menu_1_3_message = f'https://t.me/doctor_what' # (📄 Оформление договора) +from app.settings.info import public_telegram_message, public_vk_message, link_rabotod_message, \ + link_menu_1_1_1_message, link_menu_1_1_2_message, link_menu_1_1_3_message, \ + link_menu_1_2_message, link_menu_1_3_message + +INFO_MESSAGES = { + 'public_telegram': public_telegram_message, + 'public_vk': public_vk_message, + 'link_rabotod': link_rabotod_message, + + 'link_menu_1_1_1': link_menu_1_1_1_message, + 'link_menu_1_1_2': link_menu_1_1_2_message, + 'link_menu_1_1_3': link_menu_1_1_3_message, + + 'link_menu_1_2': link_menu_1_2_message, + 'link_menu_1_3': link_menu_1_3_message, +} + diff --git a/app/settings/config example.py b/app/settings/config example.py new file mode 100644 index 0000000..30e0cdd --- /dev/null +++ b/app/settings/config example.py @@ -0,0 +1,3 @@ +DEBUG = False +TOKEN = "" # bot token +CHANNEL_WHITELIST = "" # -100+channel_id (-100474747474747474747474747) diff --git a/app/settings/info.py b/app/settings/info.py new file mode 100644 index 0000000..f97c125 --- /dev/null +++ b/app/settings/info.py @@ -0,0 +1,15 @@ +# ни на что не влияет, только кастомизация сообщений +bot_link = 'test_BOT' +feedback_link = 'test_FeedBack_Bot' +public_link = 'test_Public' + +public_telegram_message = 'https://publictelegrammessage.ru' +public_vk_message = 'https://publicvkmessage.ru' +link_rabotod_message = 'https://linkrabotodmessage.ru' # (🤝 Работодателям) + +link_menu_1_1_1_message = 'https://linkmenu111message.ru' # (📚 Стажировки) +link_menu_1_1_2_message = 'https://linkmenu112message.ru' # (🛠 Практики) +link_menu_1_1_3_message = 'https://linkmenu113message.ru' # (💼 Вакансии) + +link_menu_1_2_message = 'https://linkmenu12message.ru' # (✍️ Советы по резюме) +link_menu_1_3_message = 'https://linkmenu13message.ru' # (📄 Оформление договора) diff --git a/autostart/telegram_bot.bat b/autostart/telegram_bot.bat new file mode 100644 index 0000000..3f657b9 --- /dev/null +++ b/autostart/telegram_bot.bat @@ -0,0 +1,5 @@ +@echo off +REM скрипт автозапуска. Win + R, shell:startup Скопируйте туда этот BAT-файл. +cd /d G:\your_location\telegram_bot_stomplan +.\env\Scripts\python server.py +pause diff --git a/autostart/telegram_bot_hidden.bat b/autostart/telegram_bot_hidden.bat new file mode 100644 index 0000000..b6b36ac --- /dev/null +++ b/autostart/telegram_bot_hidden.bat @@ -0,0 +1,4 @@ +@echo off +REM скрипт автозапуска hidden. Win + R, shell:startup Скопируйте туда этот BAT-файл. +cd /d G:\your_location\telegram_bot_stomplan +.\env\Scripts\pythonw.exe server.py diff --git a/install.bat b/install.bat new file mode 100644 index 0000000..2929772 --- /dev/null +++ b/install.bat @@ -0,0 +1,4 @@ +@echo off +REM +python app/install.py +pause diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ababe36 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +aiogram==3.15 diff --git a/server.py b/server.py new file mode 100644 index 0000000..d665a5a --- /dev/null +++ b/server.py @@ -0,0 +1,224 @@ +import asyncio +import os +from datetime import datetime +import time + +from aiogram import Bot, Dispatcher, F, Router, types +from aiogram.fsm.context import FSMContext +from aiogram.fsm.state import State, StatesGroup +from aiogram.fsm.storage.memory import MemoryStorage +from aiogram.filters import Command, StateFilter +from aiogram.types import Message +from aiogram.utils.keyboard import InlineKeyboardBuilder + +import app.keyboards as kb +from app.messages import MESSAGES +from app.settings.config import DEBUG, TOKEN, CHANNEL_WHITELIST +from app.settings.info import bot_link +from app.database.loader_db import check_bd, check_config + + +#запуск +#---------------------------------------- +check_bd() +check_config() + +dp = Dispatcher(storage=MemoryStorage()) +bot = Bot(token=TOKEN) +router = Router() + +from app.database.db_connector import ALLusers +checkuser = ALLusers('app/database/data/database.db') +available_da = ["Да"] + +async def main(): + if DEBUG: print('DEBUG mode') + print(f'Bot start @{bot_link}') + await bot.delete_webhook(drop_pending_updates=True) + await dp.start_polling(bot) +#---------------------------------------- + + +# access +#---------------------------------------- +async def check_admin(message_chat_id): + try: + channel_id = CHANNEL_WHITELIST + user_channel_status = await bot.get_chat_member(chat_id=channel_id, user_id=message_chat_id) + if DEBUG: + print('---------------------------------------') + print('user_channel_status', user_channel_status.status) + print('---------------------------------------') + + if ((user_channel_status.status == 'creator') or (user_channel_status.status == 'administrator')): + if DEBUG: + print('Admin access') + return True + return False + except: + return False + +async def check_subscribe(message_from_user_id, message_chat_id): + try: + channel_id = CHANNEL_WHITELIST + user_channel_status = await bot.get_chat_member(chat_id=channel_id, user_id=message_chat_id) + if DEBUG: + print('---------------------------------------') + print('user_channel_status:',user_channel_status) + print('user_channel_status', user_channel_status.status) + print('---------------------------------------') + + if ((user_channel_status.status != 'left') and (user_channel_status.status != 'kicked')): + return True + else: + await bot.send_message(message_from_user_id, MESSAGES['no_sub'], parse_mode="HTML") + return False + except: + await bot.send_message(message_from_user_id, MESSAGES['error']) + return False + +async def check_access(message): + try: + if (not checkuser.search_user(message.from_user.id)): + # если юзера нет в базе, добавляем + try: + checkuser.add_user(message.from_user.id) + except: + if DEBUG: + print("Critical error check_access 1") + return False + + # checkSubscribe = await check_subscribe(message.from_user.id, message.chat.id) + # if (checkSubscribe != True): + # return False + print("message_from_user", message.from_user.id) + return True + + except: + if DEBUG: + print("Critical error check_access 2") + +async def check_subscribe_spam(message_chat_id): + try: + channel_id = CHANNEL_WHITELIST + user_channel_status = await bot.get_chat_member(chat_id=channel_id, user_id=message_chat_id) + if DEBUG: + print('---------------------------------------') + print('user_channel_status:',user_channel_status) + print('user_channel_status', user_channel_status.status) + print('---------------------------------------') + + if ((user_channel_status.status != 'left') and (user_channel_status.status != 'kicked')): + return True + else: + return False + except: + return False +#---------------------------------------- + + +# form +#---------------------------------------- +class Form(StatesGroup): + search_0 = State() + waiting_for_file = State() + waiting_for_file_pause = State() + register = State() + register_pause = State() + + prespam = State() + spam = State() + spam_pause = State() + +@dp.message(Command("cancel")) +@dp.message(F.text.casefold() == "Отмена" or F.text.casefold() == "старт" or F.text.casefold() == "/старт" or F.text.casefold() == "🏠 Главная") +@dp.message(lambda message: ((message.text == "Отмена") or (message.text == "старт") or (message.text == "/старт"))) +async def cancel_handler(message: Message, state: FSMContext) -> None: + current_state = await state.get_state() + checkAccess = await check_access(message) + if current_state is None: + checkAccess = await check_access(message) + if checkAccess: + await message.answer(MESSAGES['start'], reply_markup=kb.main_menu) + return + await state.clear() + if checkAccess: + await message.answer(MESSAGES['byebye'], reply_markup=kb.main_menu) + return +#---------------------------------------- + + +@dp.message(Command("start")) +async def process_start_command(message: types.Message): + checkAccess = await check_access(message) + if checkAccess: + await message.answer(MESSAGES['start'], reply_markup=kb.main_menu) + return + + +@dp.message(StateFilter(None), F.text) +async def do_main_menu(message: Message, state: FSMContext): + txt = message.text + try: + checkAccess = await check_access(message) + if not checkAccess: + return await message.answer(MESSAGES['usestart']) + if txt == '/admin': + checkAdmin = await check_admin(message.chat.id) + if checkAdmin: + how_users = checkuser.log_info() + answer = (f"Пользователей зарегистрировано: {how_users[0]}\n\n"\ + f"Рассылка - /spam") + return await message.reply(answer) + else: + return await message.answer(MESSAGES['usestart']) + + # elif txt == '/spam': + # checkAdmin = await check_admin(message.chat.id) + # if checkAdmin: + # answer = (f"Для кого рассылка?") + # await message.reply(answer, reply_markup=kb.spam) + # return await state.set_state(Form.prespam) + # else: + # return await message.answer(MESSAGES['usestart']) + + # главное меню + if txt == '🏠 Главная': + return await message.answer(MESSAGES['start'], reply_markup=kb.main_menu) + elif txt == '📚 Студентам': + return await message.answer(MESSAGES['menu_1'], reply_markup=kb.menu_1) + elif txt == '🤝 Работодателям': + return await message.answer(MESSAGES['menu_2'], reply_markup=kb.menu_2_inline) + elif txt == 'ℹ️ О центре': + return await message.answer(MESSAGES['menu_3'], reply_markup=kb.menu_3_inline) + # elif txt == '📰 Подписка на новости': + # return await message.answer(MESSAGES['menu_4'], reply_markup=kb.menu_4_inline) + # студентам (1) + elif txt == '🔙 Назад': + return await message.answer(MESSAGES['menu_1'], reply_markup=kb.menu_1) + elif txt == '📖 Стажировки, практики и вакансии': + return await message.answer(MESSAGES['menu_1_1'], reply_markup=kb.menu_1_1) + elif txt == '✍️ Советы по резюме': + return await message.answer(MESSAGES['menu_1_2'], reply_markup=kb.menu_1_2_inline) + elif txt == '📄 Оформление договора': + return await message.answer(MESSAGES['menu_1_3'], reply_markup=kb.menu_1_3_inline) + # студентам (1_1) + elif txt == '📚 Стажировки': + return await message.answer(MESSAGES['menu_1_1_1'], reply_markup=kb.menu_1_1_1_inline) + elif txt == '🛠 Практики': + return await message.answer(MESSAGES['menu_1_1_2'], reply_markup=kb.menu_1_1_2_inline) + elif txt == '💼 Вакансии': + return await message.answer(MESSAGES['menu_1_1_3'], reply_markup=kb.menu_1_1_3_inline) + else: + return await message.answer(MESSAGES['usestart']) + # Для выведения ошибок + except Exception as e: + await message.answer(MESSAGES['error']) + print('----------------------------------------') + print(f'Critical error in: {e}') + print('----------------------------------------') +#---------------------------------------- + + +if __name__ == '__main__': + asyncio.run(main()) diff --git a/start.bat b/start.bat new file mode 100644 index 0000000..d7e238d --- /dev/null +++ b/start.bat @@ -0,0 +1,4 @@ +@echo off +REM Запуск main.py с Python из виртуальной среды +.\env\Scripts\python server.py +pause