diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..5c02346 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 73b87df..8637ca2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,162 +1,162 @@ -# ---> Python -# 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/ -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 -*.spec - -# 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 -*.py,cover -.hypothesis/ -.pytest_cache/ -cover/ - -# 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 -.pybuilder/ -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# IPython -profile_default/ -ipython_config.py - -# pyenv -# For a library or package, you might want to ignore these files since the code is -# intended to run in multiple environments; otherwise, check them in: -# .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 - -# poetry -# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control -#poetry.lock - -# pdm -# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. -#pdm.lock -# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it -# in version control. -# https://pdm.fming.dev/#use-with-ide -.pdm.toml - -# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm -__pypackages__/ - -# Celery stuff -celerybeat-schedule -celerybeat.pid - -# 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/ - -# pytype static type analyzer -.pytype/ - -# Cython debug symbols -cython_debug/ - -# PyCharm -# JetBrains specific template is maintained in a separate JetBrains.gitignore that can -# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore -# and can be added to the global gitignore or merged into this file. For a more nuclear -# option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ - +# ---> Python +# 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/ +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 +*.spec + +# 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 +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# 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 +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .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 + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# 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/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + diff --git a/.idea/.gitignore b/.idea/.gitignore index 26d3352..eaf91e2 100644 --- a/.idea/.gitignore +++ b/.idea/.gitignore @@ -1,3 +1,3 @@ -# Default ignored files -/shelf/ -/workspace.xml +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/BOTKlining.iml b/.idea/BOTKlining.iml index d69e8e4..2a3ec7b 100644 --- a/.idea/BOTKlining.iml +++ b/.idea/BOTKlining.iml @@ -1,10 +1,10 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 1ce6493..58aea09 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -1,7 +1,6 @@ - - - - - - + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml index 105ce2d..20fc29e 100644 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -1,6 +1,6 @@ - - - + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index d92aab6..18dcd1a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,7 @@ - - - - - + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml index 3956f52..d737244 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 35eb1dd..c8397c9 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - - - - - + + + + + \ No newline at end of file diff --git a/README.md b/README.md index f6d7b3a..869bac7 100644 --- a/README.md +++ b/README.md @@ -1,157 +1,375 @@ -# BOTKlining - -## Структура - -1. `main.py`: основной файл для запуска бота. -2. `handlers.py`: обработчики команд и состояний. -3. `database.py`: взаимодействие с базой данных. -4. `logger.py`: логирование действий пользователей. -5. `.env`: файл для хранения конфиденциальных данных. - -Для создания бота с указанной функциональностью и логикой, а также использования файла .env для хранения конфиденциальных данных, мы разобьем задачу на несколько шагов: - -Настройка окружения и установка зависимостей -Создание базы данных -Разработка основных обработчиков команд и колбэков -Создание функций для работы с базой данных -Настройка логирования -Реализация основной логики бота - -Вот подробное описание структуры и логики бота: - -## Inline клавиатура бота: -1. Учетные данные - -- Изменить ФИО: Пользователь может изменить свое ФИО. -- Изменить номер телефона: Позволяет изменить номер телефона. -- Добавить адрес: Пользователь может добавить новый адрес. -- Удалить адрес: Пользователь может удалить ранее добавленный адрес. -- Поделиться контактом: Позволяет поделиться контактными данными, для автоматического заполнения в базе данных. - -2. Сделать заказ - -- Добавить учетные данные: Если у пользователя нет учетных данных, он должен их добавить. Если данные есть они заполняются автоматически. -- Выбрать адрес: Пользователь выбирает адрес или добавляет новый. -- Выбрать время уборки: Предлагает утро, день, вечер. -- Выбрать тип уборки: Влажная, сухая, генеральная. -- Выбрать способ оплаты: Картой, наличными. -- Подтвердить заказ: Подтверждает заказ или позволяет изменить ответы. После подтверждения заказа автоматически отправлять уведомление в группу операторов, а также добавлять заказ в историю. -- Заказу присваивается ID номер - -3. История заказов - -- Просмотреть историю заказов: Позволяет пользователю просмотреть свою историю заказов. -- Добавить функцию повторного заказа на основе предыдущих заказов. - -4. Заказать звонок - -- Добавить учетные данные: Если у пользователя нет учетных данных, он должен их добавить. Если данные есть они заполняются автоматически. -- Заказать звонок: Отправляет учетные данные (ФИО, номер телефона) в группу операторов, с подписью об звонке. Пользователь видит сообщение "ожидайте звонок в течении 30 минут" - -5. Начать разговор с оператором - -- Добавить учетные данные: Если у пользователя нет учетных данных, он должен их добавить. Если данные есть они заполняются автоматически. -- Начать разговор: Отправляет учетные данные (ФИО, номер телефона, ID номер крайнего заказа) в группу операторов. -- Завершить разговор: Пользователь завершает разговор. -- После завершения разговора автоматически отправлять анкету о качестве обслуживания пользователю для обратной связи. Автоматически закрыть анкету в случае не ответа 2 часов. - -6. Назад - -- Возврат к предыдущему набору кнопок: Позволяет вернуться к предыдущему меню кнопок. - -## Логика нажатия кнопок и сохранения данных: -- При первом использовании бота запросить у пользователя учетные данные (ФИО, номер телефона) и сохранить их для последующих заказов. Можно использовать личные данные из Telegram (если пользователь разрешил) для заполнения некоторых полей автоматически. -- При нажатии кнопок пользователем, бот запоминает предыдущий выбор и отображает следующий набор кнопок соответственно. -- Когда пользователь нажимает на любую из кнопок, предыдущий выбор автоматически скрывается, а вместо него появляется следующий набор кнопок. Кнопка "Назад" позволяет вернуться к предыдущему набору кнопок в случае необходимости. Это помогает сделать интерфейс более удобным и интуитивно понятным для пользователей. -- Данные пользователя (учетные данные, адреса, заказы и т.д.) сохраняются в базе данных SQLite3 для последующего использования. -- Логирование действий пользователей осуществляется путем записи информации о каждом действии в лог-файлы, включая дату, время, ID пользователя и другие параметры. -## Управление из группы и внутри бота: -- Группа для заказов управляется операторами и администраторами, которые имеют доступ к соответствующим функциям. -- Операторы могут просматривать историю заказов пользователей, отвечать на сообщения, завершать разговоры и управлять заказами. -- Администраторы имеют дополнительные функции, такие как выгрузка переписки в формате JSON. -- Бот на aiogram используется для более эффективной работы и реализации функций бота через асинхронные методы. -- Эта структура и логика позволят создать функционального бота для управления заказами и общения с операторами, обеспечивая удобство пользователей и контроль над данными. -- Данные токена id групп, id администраторов, операторов дложны храниться в файле env -## Автоматизация - -- Добавить таймеры для отслеживания неактивности пользователя и предложения начать разговор с оператором или оформить заказ при необходимости. -- Использовать базу данных для хранения информации о пользователях, заказах и истории коммуникации с операторами. -- Реализовать механизм автоматического анализа данных о заказах для предложения персонализированных услуг или скидок. -- Создаем базу данных SQLite3 для хранения информации о пользователях, их учетных данных, заказах и истории сообщений с операторами. -- Создаем таблицы для каждой категории данных: пользователи, заказы, история сообщений и т.д. -- Связываем таблицы между собой по ключам (например, по ID пользователя). -- При каждом действии пользователя (нажатие кнопки, отправка сообщения) записываем соответствующую информацию в лог-файлы. -- Логируем дату, время действия, ID пользователя, текстовые данные (если есть) и другие необходимые параметры. -- Создаем специальную группу для приема заказов, где только операторы и администраторы имеют доступ. -- Назначаем операторов и администраторов, которые могут управлять заказами в этой группе. - -1. Функционал операторов: - - Операторы могут просматривать историю заказов конкретного пользователя по его ID. - - Операторы могут отвечать пользователю из группы используя функцию ответа (replay). - - Операторы могут завершать разговор с пользователем с помощью соответствующей кнопки. - -2. Функционал администраторов: - - Администраторы имеют все функции операторов. - - Дополнительно администраторы могут выгружать переписку операторов и пользователя в виде JSON файла. - - - -Логика Бота -есть inline клавиотура со следующими кнопками: -Учетные данные -Изменить ФИО -Изменить номер телефона -Добавит адрес -Поделиться контактом -сделать заказ -Добавит учетные данные, если данные не введены, если введены то пропустить вопрос. -выбрать адрес или указать новый адрес. все веденные адреса сохраняються. -спросить время для уборки: утро/День/вечер -Спросить тип уборки: Влажная/Сухая/Генеральная -Спросить способ оплаты: Картой/Наличными -Подьвердить заказ, или предложить изменить ответ на вопрос. далее кнопки вопросов что бы изменить один ответ и после изменения повторно подтвердить. -После подтверждения, сообщения с ответами отправляеться группу операторов. -История заказов -Все отправленные ответы в группу сохраняются как заказ и храниться в базе данных. -Заказать звонок -Добавит учетные данные, если данные не введены, если введены то пропустить вопрос. -Кнопка заказать звонок, отправляет учетные данные (ФИО, номер телефона) в группу операторов. -Начать разговор с оератором -Добавит учетные данные, если данные не введены, если введены то пропустить вопрос. -Кнопка начать разговор, отправляет учетные данные (ФИО, номер телефона) в группу операторов. -Оператор из группы нажимает ответить(replay) и пишет сообщение, сообшение отправляеться пользователю -пользователь пишет сообщение и оно отправляеться в группу до дех под пока пользователь не нажмет завершить разговор. -Если кнопка завершить разговор не нажато, то через 1 час разговор с оператором завершаеться автоматически. -При заходе пользователя через 2 часа появляеться первоначальное меню кнопок. -Допиши логику, слелай её более автоматизированной. - -ChatGPT -Ты создал очень хорошую базу для логики бота! Вот несколько дополнений и идей для автоматизации: - -Учетные данные: - -При первом использовании бота запросить у пользователя учетные данные (ФИО, номер телефона) и сохранить их для последующих заказов. Можно использовать личные данные из Telegram (если пользователь разрешил) для заполнения некоторых полей автоматически. -Добавить возможность изменения учетных данных через команду или кнопку "Изменить учетные данные". -Сделать заказ: - -После выбора адреса показывать пользователю карту с указанным адресом для подтверждения или возможности изменения. -Автоматически предлагать время уборки в зависимости от текущего времени (например, если пользователь заходит в 9 утра, предложить утреннее время уборки). -После подтверждения заказа автоматически отправлять уведомление в группу операторов, а также добавлять заказ в историю. -История заказов: - -Позволить пользователю просматривать историю своих заказов через бота. -Добавить функцию повторного заказа на основе предыдущих заказов. -Заказать звонок: - -Позволить пользователю выбрать время для звонка (например, "Заказать звонок через 30 минут"). -Автоматически отправлять уведомление о заказе звонка в группу операторов с указанием времени. -Начать разговор с оператором: - -Добавить возможность выбора темы разговора (например, "Техническая поддержка", "Вопрос по заказу" и т. д.). -После завершения разговора автоматически отправлять анкету о качестве обслуживания пользователю для обратной связи. -Автоматизация: - -Добавить таймеры для отслеживания неактивности пользователя и предложения начать разговор с оператором или оформить заказ при необходимости. -Использовать базу данных для хранения информации о пользователях, заказах и истории коммуникации с операторами. -Реализовать механизм автоматического анализа данных о заказах для предложения персонализированных услуг или скидок. -Эти функции помогут сделать бота более удобным и эффективным для пользователей и операторов. \ No newline at end of file +# BOTKlining + +## Структура + +1. `main.py`: основной файл для запуска бота. +2. `handlers.py`: обработчики команд и состояний. +3. `database.py`: взаимодействие с базой данных. +4. `logger.py`: логирование действий пользователей. +5. `.env`: файл для хранения конфиденциальных данных. + +Для создания бота с указанной функциональностью и логикой, а также использования файла .env для хранения конфиденциальных данных, мы разобьем задачу на несколько шагов: + +Настройка окружения и установка зависимостей +Создание базы данных +Разработка основных обработчиков команд и колбэков +Создание функций для работы с базой данных +Настройка логирования +Реализация основной логики бота + +Вот подробное описание структуры и логики бота: + +## Inline клавиатура бота: +1. Учетные данные + +- Изменить ФИО: Пользователь может изменить свое ФИО. +- Изменить номер телефона: Позволяет изменить номер телефона. +- Добавить адрес: Пользователь может добавить новый адрес. +- Удалить адрес: Пользователь может удалить ранее добавленный адрес. +- Поделиться контактом: Позволяет поделиться контактными данными, для автоматического заполнения в базе данных. + +2. Сделать заказ + +- Добавить учетные данные: Если у пользователя нет учетных данных, он должен их добавить. Если данные есть они заполняются автоматически. +- Выбрать адрес: Пользователь выбирает адрес или добавляет новый. +- Выбрать время уборки: Предлагает утро, день, вечер. +- Выбрать тип уборки: Влажная, сухая, генеральная. +- Выбрать способ оплаты: Картой, наличными. +- Подтвердить заказ: Подтверждает заказ или позволяет изменить ответы. После подтверждения заказа автоматически отправлять уведомление в группу операторов, а также добавлять заказ в историю. +- Заказу присваивается ID номер + +3. История заказов + +- Просмотреть историю заказов: Позволяет пользователю просмотреть свою историю заказов. +- Добавить функцию повторного заказа на основе предыдущих заказов. + +4. Заказать звонок + +- Добавить учетные данные: Если у пользователя нет учетных данных, он должен их добавить. Если данные есть они заполняются автоматически. +- Заказать звонок: Отправляет учетные данные (ФИО, номер телефона) в группу операторов, с подписью об звонке. Пользователь видит сообщение "ожидайте звонок в течении 30 минут" + +5. Начать разговор с оператором + +- Добавить учетные данные: Если у пользователя нет учетных данных, он должен их добавить. Если данные есть они заполняются автоматически. +- Начать разговор: Отправляет учетные данные (ФИО, номер телефона, ID номер крайнего заказа) в группу операторов. +- Завершить разговор: Пользователь завершает разговор. +- После завершения разговора автоматически отправлять анкету о качестве обслуживания пользователю для обратной связи. Автоматически закрыть анкету в случае не ответа 2 часов. + +6. Назад + +- Возврат к предыдущему набору кнопок: Позволяет вернуться к предыдущему меню кнопок. + +## Логика нажатия кнопок и сохранения данных: +- При первом использовании бота запросить у пользователя учетные данные (ФИО, номер телефона) и сохранить их для последующих заказов. Можно использовать личные данные из Telegram (если пользователь разрешил) для заполнения некоторых полей автоматически. +- При нажатии кнопок пользователем, бот запоминает предыдущий выбор и отображает следующий набор кнопок соответственно. +- Когда пользователь нажимает на любую из кнопок, предыдущий выбор автоматически скрывается, а вместо него появляется следующий набор кнопок. Кнопка "Назад" позволяет вернуться к предыдущему набору кнопок в случае необходимости. Это помогает сделать интерфейс более удобным и интуитивно понятным для пользователей. +- Данные пользователя (учетные данные, адреса, заказы и т.д.) сохраняются в базе данных SQLite3 для последующего использования. +- Логирование действий пользователей осуществляется путем записи информации о каждом действии в лог-файлы, включая дату, время, ID пользователя и другие параметры. +## Управление из группы и внутри бота: +- Группа для заказов управляется операторами и администраторами, которые имеют доступ к соответствующим функциям. +- Операторы могут просматривать историю заказов пользователей, отвечать на сообщения, завершать разговоры и управлять заказами. +- Администраторы имеют дополнительные функции, такие как выгрузка переписки в формате JSON. +- Бот на aiogram используется для более эффективной работы и реализации функций бота через асинхронные методы. +- Эта структура и логика позволят создать функционального бота для управления заказами и общения с операторами, обеспечивая удобство пользователей и контроль над данными. +- Данные токена id групп, id администраторов, операторов дложны храниться в файле env +## Автоматизация + +- Добавить таймеры для отслеживания неактивности пользователя и предложения начать разговор с оператором или оформить заказ при необходимости. +- Использовать базу данных для хранения информации о пользователях, заказах и истории коммуникации с операторами. +- Реализовать механизм автоматического анализа данных о заказах для предложения персонализированных услуг или скидок. +- Создаем базу данных SQLite3 для хранения информации о пользователях, их учетных данных, заказах и истории сообщений с операторами. +- Создаем таблицы для каждой категории данных: пользователи, заказы, история сообщений и т.д. +- Связываем таблицы между собой по ключам (например, по ID пользователя). +- При каждом действии пользователя (нажатие кнопки, отправка сообщения) записываем соответствующую информацию в лог-файлы. +- Логируем дату, время действия, ID пользователя, текстовые данные (если есть) и другие необходимые параметры. +- Создаем специальную группу для приема заказов, где только операторы и администраторы имеют доступ. +- Назначаем операторов и администраторов, которые могут управлять заказами в этой группе. + +1. Функционал операторов: + - Операторы могут просматривать историю заказов конкретного пользователя по его ID. + - Операторы могут отвечать пользователю из группы используя функцию ответа (replay). + - Операторы могут завершать разговор с пользователем с помощью соответствующей кнопки. + +2. Функционал администраторов: + - Администраторы имеют все функции операторов. + - Дополнительно администраторы могут выгружать переписку операторов и пользователя в виде JSON файла. + + + +Логика Бота +есть inline клавиотура со следующими кнопками: +Учетные данные +Изменить ФИО +Изменить номер телефона +Добавит адрес +Поделиться контактом +сделать заказ +Добавит учетные данные, если данные не введены, если введены то пропустить вопрос. +выбрать адрес или указать новый адрес. все веденные адреса сохраняються. +спросить время для уборки: утро/День/вечер +Спросить тип уборки: Влажная/Сухая/Генеральная +Спросить способ оплаты: Картой/Наличными +Подьвердить заказ, или предложить изменить ответ на вопрос. далее кнопки вопросов что бы изменить один ответ и после изменения повторно подтвердить. +После подтверждения, сообщения с ответами отправляеться группу операторов. +История заказов +Все отправленные ответы в группу сохраняются как заказ и храниться в базе данных. +Заказать звонок +Добавит учетные данные, если данные не введены, если введены то пропустить вопрос. +Кнопка заказать звонок, отправляет учетные данные (ФИО, номер телефона) в группу операторов. +Начать разговор с оератором +Добавит учетные данные, если данные не введены, если введены то пропустить вопрос. +Кнопка начать разговор, отправляет учетные данные (ФИО, номер телефона) в группу операторов. +Оператор из группы нажимает ответить(replay) и пишет сообщение, сообшение отправляеться пользователю +пользователь пишет сообщение и оно отправляеться в группу до дех под пока пользователь не нажмет завершить разговор. +Если кнопка завершить разговор не нажато, то через 1 час разговор с оператором завершаеться автоматически. +При заходе пользователя через 2 часа появляеться первоначальное меню кнопок. +Допиши логику, слелай её более автоматизированной. + +ChatGPT +Ты создал очень хорошую базу для логики бота! Вот несколько дополнений и идей для автоматизации: + +Учетные данные: + +При первом использовании бота запросить у пользователя учетные данные (ФИО, номер телефона) и сохранить их для последующих заказов. Можно использовать личные данные из Telegram (если пользователь разрешил) для заполнения некоторых полей автоматически. +Добавить возможность изменения учетных данных через команду или кнопку "Изменить учетные данные". +Сделать заказ: + +После выбора адреса показывать пользователю карту с указанным адресом для подтверждения или возможности изменения. +Автоматически предлагать время уборки в зависимости от текущего времени (например, если пользователь заходит в 9 утра, предложить утреннее время уборки). +После подтверждения заказа автоматически отправлять уведомление в группу операторов, а также добавлять заказ в историю. +История заказов: + +Позволить пользователю просматривать историю своих заказов через бота. +Добавить функцию повторного заказа на основе предыдущих заказов. +Заказать звонок: + +Позволить пользователю выбрать время для звонка (например, "Заказать звонок через 30 минут"). +Автоматически отправлять уведомление о заказе звонка в группу операторов с указанием времени. +Начать разговор с оператором: + +Добавить возможность выбора темы разговора (например, "Техническая поддержка", "Вопрос по заказу" и т. д.). +После завершения разговора автоматически отправлять анкету о качестве обслуживания пользователю для обратной связи. +Автоматизация: + +Добавить таймеры для отслеживания неактивности пользователя и предложения начать разговор с оператором или оформить заказ при необходимости. +Использовать базу данных для хранения информации о пользователях, заказах и истории коммуникации с операторами. +Реализовать механизм автоматического анализа данных о заказах для предложения персонализированных услуг или скидок. +Эти функции помогут сделать бота более удобным и эффективным для пользователей и операторов. + + + + + + + + + + + +Конечно, вот структура файлов для Telegram бота, который использует aiogram, PostgreSQL, logging, inline-кнопки и .env файл для хранения информации о группах админов и операторов. Бот будет делать опросы. + +markdown +Копировать код +my_telegram_bot/ +├── .env +├── bot.py +├── config.py +├── database.py +├── handlers/ +│ ├── __init__.py +│ ├── admin.py +│ ├── polls.py +│ ├── start.py +├── keyboards/ +│ ├── __init__.py +│ └── inline.py +├── middlewares/ +│ ├── __init__.py +│ └── logging.py +├── models/ +│ ├── __init__.py +│ └── poll.py +├── utils/ +│ ├── __init__.py +│ └── misc.py +└── requirements.txt +Описание файлов и директорий +.env: файл с переменными окружения. Пример содержания: + +env +Копировать код +BOT_TOKEN= +DATABASE_URL=postgresql://user:password@localhost:5432/mydatabase +ADMINS=123456789,987654321 +OPERATORS=123123123,321321321 +bot.py: основной файл для запуска бота. + +python +Копировать код +import logging +from aiogram import Bot, Dispatcher, executor +from aiogram.contrib.fsm_storage.memory import MemoryStorage +from config import load_config +from database import setup_database +from middlewares.logging import LoggingMiddleware +from handlers import start, admin, polls + +logging.basicConfig(level=logging.INFO) + +config = load_config() +bot = Bot(token=config['BOT_TOKEN']) +dp = Dispatcher(bot, storage=MemoryStorage()) + +dp.middleware.setup(LoggingMiddleware()) + +start.register_handlers(dp) +admin.register_handlers(dp) +polls.register_handlers(dp) + +setup_database() + +if __name__ == '__main__': + executor.start_polling(dp, skip_updates=True) +config.py: файл для загрузки конфигурации из .env. + +python +Копировать код +from dotenv import load_dotenv +import os + +load_dotenv() + +def load_config(): + return { + 'BOT_TOKEN': os.getenv('BOT_TOKEN'), + 'DATABASE_URL': os.getenv('DATABASE_URL'), + 'ADMINS': list(map(int, os.getenv('ADMINS').split(','))), + 'OPERATORS': list(map(int, os.getenv('OPERATORS').split(','))), + } +database.py: файл для настройки базы данных. + +python +Копировать код +import sqlalchemy as sa +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from config import load_config + +Base = declarative_base() + +config = load_config() +engine = sa.create_engine(config['DATABASE_URL']) +Session = sessionmaker(bind=engine) + +def setup_database(): + Base.metadata.create_all(engine) +handlers/__init__.py: инициализация пакета хэндлеров. + +python +Копировать код +from aiogram import Dispatcher + +def register_handlers(dp: Dispatcher): + from . import start, admin, polls + start.register_handlers(dp) + admin.register_handlers(dp) + polls.register_handlers(dp) +handlers/admin.py: хэндлеры для админов. + +python +Копировать код +from aiogram import types, Dispatcher +from config import load_config + +config = load_config() + +async def admin_command(message: types.Message): + if message.from_user.id not in config['ADMINS']: + await message.reply("You are not an admin!") + return + await message.reply("Hello, Admin!") + +def register_handlers(dp: Dispatcher): + dp.register_message_handler(admin_command, commands=['admin']) +handlers/polls.py: хэндлеры для опросов. + +python +Копировать код +from aiogram import types, Dispatcher +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from models.poll import Poll +from database import Session + +async def create_poll(message: types.Message): + markup = InlineKeyboardMarkup() + markup.add(InlineKeyboardButton('Option 1', callback_data='vote_1')) + markup.add(InlineKeyboardButton('Option 2', callback_data='vote_2')) + await message.reply("Choose an option:", reply_markup=markup) + +async def handle_vote(callback_query: types.CallbackQuery): + option = callback_query.data.split('_')[1] + with Session() as session: + poll = Poll(option=option) + session.add(poll) + session.commit() + await callback_query.answer(f"You voted for option {option}") + +def register_handlers(dp: Dispatcher): + dp.register_message_handler(create_poll, commands=['poll']) + dp.register_callback_query_handler(handle_vote, lambda c: c.data.startswith('vote_')) +handlers/start.py: хэндлер для команды /start. + +python +Копировать код +from aiogram import types, Dispatcher + +async def start_command(message: types.Message): + await message.reply("Hello! I am your polling bot.") + +def register_handlers(dp: Dispatcher): + dp.register_message_handler(start_command, commands=['start']) +keyboards/inline.py: файл для создания inline-клавиатур. + +python +Копировать код +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton + +def create_poll_keyboard(): + markup = InlineKeyboardMarkup() + markup.add(InlineKeyboardButton('Option 1', callback_data='vote_1')) + markup.add(InlineKeyboardButton('Option 2', callback_data='vote_2')) + return markup +middlewares/logging.py: файл для логирования. + +python +Копировать код +from aiogram.dispatcher.middlewares import BaseMiddleware +import logging + +class LoggingMiddleware(BaseMiddleware): + async def on_process_message(self, message, data): + logging.info(f"Received message from {message.from_user.id}: {message.text}") +models/poll.py: модель для хранения данных опросов. + +python +Копировать код +from sqlalchemy import Column, Integer, String +from database import Base + +class Poll(Base): + __tablename__ = 'polls' + + id = Column(Integer, primary_key=True) + option = Column(String, nullable=False) +utils/misc.py: утилитарные функции. + +python +Копировать код +# В этом файле можно разместить различные вспомогательные функции. +requirements.txt: зависимости проекта. + +Копировать код +aiogram +python-dotenv +sqlalchemy +Эта структура предоставляет базу для дальнейшего расширения функциональности бота. Вы можете добавлять дополнительные обработчики, модели и утилиты по мере необходимости. \ No newline at end of file diff --git a/bot.py b/bot.py index e69de29..eef101c 100644 --- a/bot.py +++ b/bot.py @@ -0,0 +1,24 @@ +import logging +from aiogram import Bot, Dispatcher, executor +from aiogram.contrib.fsm_storage.memory import MemoryStorage +from config import load_config +from database import setup_database +from middlewares.logging import LoggingMiddleware +from handlers import start, admin, polls + +logging.basicConfig(level=logging.INFO) + +config = load_config() +bot = Bot(token=config['BOT_TOKEN']) +dp = Dispatcher(bot, storage=MemoryStorage()) + +dp.middleware.setup(LoggingMiddleware()) + +start.register_handlers(dp) +admin.register_handlers(dp) +polls.register_handlers(dp) + +setup_database() + +if __name__ == '__main__': + executor.start_polling(dp, skip_updates=True) diff --git a/config.py b/config.py index e69de29..3854f93 100644 --- a/config.py +++ b/config.py @@ -0,0 +1,12 @@ +from dotenv import load_dotenv +import os + +load_dotenv() + +def load_config(): + return { + 'BOT_TOKEN': os.getenv('BOT_TOKEN'), + 'DATABASE_URL': os.getenv('DATABASE_URL'), + 'ADMINS': list(map(int, os.getenv('ADMINS').split(','))), + 'OPERATORS': list(map(int, os.getenv('OPERATORS').split(','))), + } diff --git a/database.py b/database.py index e69de29..38940d0 100644 --- a/database.py +++ b/database.py @@ -0,0 +1,13 @@ +import sqlalchemy as sa +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import sessionmaker +from config import load_config + +Base = declarative_base() + +config = load_config() +engine = sa.create_engine(config['DATABASE_URL']) +Session = sessionmaker(bind=engine) + +def setup_database(): + Base.metadata.create_all(engine) diff --git a/doners/Shop-bot/.gitignore b/doners/Shop-bot/.gitignore new file mode 100644 index 0000000..cce6241 --- /dev/null +++ b/doners/Shop-bot/.gitignore @@ -0,0 +1,14 @@ +# Cache files +__pycache__/ + +# Project files +.idea/ +*.log +.env +*.session +data/config.py +data/database.db + +# vscode +.vscode/ +.history/ \ No newline at end of file diff --git a/doners/Shop-bot/Dockerfile b/doners/Shop-bot/Dockerfile new file mode 100644 index 0000000..9b0d9e3 --- /dev/null +++ b/doners/Shop-bot/Dockerfile @@ -0,0 +1,9 @@ +FROM python:3.7-slim + +WORKDIR /botname + +COPY requirements.txt /botname/ +RUN pip install -r /botname/requirements.txt +COPY . /botname/ + +CMD python3 /botname/app.py \ No newline at end of file diff --git a/doners/Shop-bot/LICENSE b/doners/Shop-bot/LICENSE new file mode 100644 index 0000000..ff2a138 --- /dev/null +++ b/doners/Shop-bot/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Hagai + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doners/Shop-bot/README.md b/doners/Shop-bot/README.md new file mode 100644 index 0000000..81f42e3 --- /dev/null +++ b/doners/Shop-bot/README.md @@ -0,0 +1,47 @@ +

+ ShopBot +

+ +This is an example Telegram shop bot. It's a simple and, most importantly, efficient way to place an order without leaving your favorite messenger. + +## What can it do? + +1. `/start` - needed to start the bot and choose the mode (user/admin). + +2. `/menu` - go to the menu. + +3. `/sos` - ask the administrator a question. + +## Menu + +The user menu looks like this: + +![User Menu](data/assets/4.png) + +## Catalog + +The catalog consists of products sorted by categories. Users can add items to their cart, and the admin has full control over catalog management (addition/removal). + +## Cart + +The ordering process looks like this: the user goes to the `🛍️ Catalog`, selects the desired category, chooses products, and clicks the `🛒 Cart` button. + +![cart](data/assets/5.png) + +------ + +Then, after making sure everything is in place, proceed to checkout by clicking `📦 Place Order`. + +![checkout](data/assets/6.png) + +## Add a Product + +To add a product, select a category and click the `➕ Add Product` button. Then, fill out the "name-description-image-price" form and confirm. + +![add_product](data/assets/1.png) + +## Contacting Administration + +To ask the admin a question, simply select the `/sos` command. There is a limit on the number of questions. + +![sos](data/assets/7.png) diff --git a/doners/Shop-bot/__init__.py b/doners/Shop-bot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/doners/Shop-bot/app.py b/doners/Shop-bot/app.py new file mode 100644 index 0000000..818620e --- /dev/null +++ b/doners/Shop-bot/app.py @@ -0,0 +1,92 @@ + +import os +import handlers +from aiogram import executor, types +from aiogram.types import ReplyKeyboardMarkup, ReplyKeyboardRemove +from data import config +from loader import dp, db, bot +import filters +import logging + +filters.setup(dp) + +WEBAPP_HOST = "0.0.0.0" +WEBAPP_PORT = int(os.environ.get("PORT", 5000)) +user_message = 'Пользователь' +admin_message = 'Админ' + + +@dp.message_handler(commands='start') +async def cmd_start(message: types.Message): + + markup = ReplyKeyboardMarkup(resize_keyboard=True) + + markup.row(user_message, admin_message) + + await message.answer('''Привет! 👋 + +🤖 Я бот-магазин по подаже товаров любой категории. + +🛍️ Чтобы перейти в каталог и выбрать приглянувшиеся товары возпользуйтесь командой /menu. + +💰 Пополнить счет можно через Яндекс.кассу, Сбербанк или Qiwi. + +❓ Возникли вопросы? Не проблема! Команда /sos поможет связаться с админами, которые постараются как можно быстрее откликнуться. + +🤝 Заказать похожего бота? Свяжитесь с разработчиком Nikolay Simakov, он не кусается))) + ''', reply_markup=markup) + + +@dp.message_handler(text=user_message) +async def user_mode(message: types.Message): + + cid = message.chat.id + if cid in config.ADMINS: + config.ADMINS.remove(cid) + + await message.answer('Включен пользовательский режим.', reply_markup=ReplyKeyboardRemove()) + + +@dp.message_handler(text=admin_message) +async def admin_mode(message: types.Message): + + cid = message.chat.id + if cid not in config.ADMINS: + config.ADMINS.append(cid) + + await message.answer('Включен админский режим.', reply_markup=ReplyKeyboardRemove()) + + +async def on_startup(dp): + logging.basicConfig(level=logging.INFO) + db.create_tables() + + await bot.delete_webhook() + await bot.set_webhook(config.WEBHOOK_URL) + + +async def on_shutdown(): + logging.warning("Shutting down..") + await bot.delete_webhook() + await dp.storage.close() + await dp.storage.wait_closed() + logging.warning("Bot down") + + +if __name__ == '__main__': + + if "HEROKU" in list(os.environ.keys()): + + executor.start_webhook( + dispatcher=dp, + webhook_path=config.WEBHOOK_PATH, + on_startup=on_startup, + on_shutdown=on_shutdown, + skip_updates=True, + host=WEBAPP_HOST, + port=WEBAPP_PORT, + ) + + else: + + executor.start_polling(dp, on_startup=on_startup, skip_updates=False) diff --git a/doners/Shop-bot/data/__init__.py b/doners/Shop-bot/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/doners/Shop-bot/data/assets/1.png b/doners/Shop-bot/data/assets/1.png new file mode 100644 index 0000000..448ac86 Binary files /dev/null and b/doners/Shop-bot/data/assets/1.png differ diff --git a/doners/Shop-bot/data/assets/2.png b/doners/Shop-bot/data/assets/2.png new file mode 100644 index 0000000..e6545d3 Binary files /dev/null and b/doners/Shop-bot/data/assets/2.png differ diff --git a/doners/Shop-bot/data/assets/3.png b/doners/Shop-bot/data/assets/3.png new file mode 100644 index 0000000..03daead Binary files /dev/null and b/doners/Shop-bot/data/assets/3.png differ diff --git a/doners/Shop-bot/data/assets/4.png b/doners/Shop-bot/data/assets/4.png new file mode 100644 index 0000000..a180be7 Binary files /dev/null and b/doners/Shop-bot/data/assets/4.png differ diff --git a/doners/Shop-bot/data/assets/5.png b/doners/Shop-bot/data/assets/5.png new file mode 100644 index 0000000..236061e Binary files /dev/null and b/doners/Shop-bot/data/assets/5.png differ diff --git a/doners/Shop-bot/data/assets/6.png b/doners/Shop-bot/data/assets/6.png new file mode 100644 index 0000000..0974e1a Binary files /dev/null and b/doners/Shop-bot/data/assets/6.png differ diff --git a/doners/Shop-bot/data/assets/7.png b/doners/Shop-bot/data/assets/7.png new file mode 100644 index 0000000..c52070e Binary files /dev/null and b/doners/Shop-bot/data/assets/7.png differ diff --git a/doners/Shop-bot/data/assets/logo.png b/doners/Shop-bot/data/assets/logo.png new file mode 100644 index 0000000..eec21f1 Binary files /dev/null and b/doners/Shop-bot/data/assets/logo.png differ diff --git a/doners/Shop-bot/data/assets/logo_mini.png b/doners/Shop-bot/data/assets/logo_mini.png new file mode 100644 index 0000000..97d38f1 Binary files /dev/null and b/doners/Shop-bot/data/assets/logo_mini.png differ diff --git a/doners/Shop-bot/filters/__init__.py b/doners/Shop-bot/filters/__init__.py new file mode 100644 index 0000000..eae2488 --- /dev/null +++ b/doners/Shop-bot/filters/__init__.py @@ -0,0 +1,7 @@ +from aiogram import Dispatcher +from .is_admin import IsAdmin +from .is_user import IsUser + +def setup(dp: Dispatcher): + dp.filters_factory.bind(IsAdmin, event_handlers=[dp.message_handlers]) + dp.filters_factory.bind(IsUser, event_handlers=[dp.message_handlers]) diff --git a/doners/Shop-bot/filters/is_admin.py b/doners/Shop-bot/filters/is_admin.py new file mode 100644 index 0000000..6ad935a --- /dev/null +++ b/doners/Shop-bot/filters/is_admin.py @@ -0,0 +1,9 @@ + +from aiogram.types import Message +from aiogram.dispatcher.filters import BoundFilter +from data.config import ADMINS + +class IsAdmin(BoundFilter): + + async def check(self, message: Message): + return message.from_user.id in ADMINS diff --git a/doners/Shop-bot/filters/is_user.py b/doners/Shop-bot/filters/is_user.py new file mode 100644 index 0000000..1b426bc --- /dev/null +++ b/doners/Shop-bot/filters/is_user.py @@ -0,0 +1,9 @@ + +from aiogram.types import Message +from aiogram.dispatcher.filters import BoundFilter +from data.config import ADMINS + +class IsUser(BoundFilter): + + async def check(self, message: Message): + return message.from_user.id not in ADMINS diff --git a/doners/Shop-bot/handlers/__init__.py b/doners/Shop-bot/handlers/__init__.py new file mode 100644 index 0000000..f9564aa --- /dev/null +++ b/doners/Shop-bot/handlers/__init__.py @@ -0,0 +1,4 @@ +from .admin import dp +from .user import dp + +__all__ = ['dp'] \ No newline at end of file diff --git a/doners/Shop-bot/handlers/admin/__init__.py b/doners/Shop-bot/handlers/admin/__init__.py new file mode 100644 index 0000000..aedef8a --- /dev/null +++ b/doners/Shop-bot/handlers/admin/__init__.py @@ -0,0 +1,5 @@ +from .add import dp +from .questions import dp +from .orders import dp + +__all__ = ['dp'] \ No newline at end of file diff --git a/doners/Shop-bot/handlers/admin/add.py b/doners/Shop-bot/handlers/admin/add.py new file mode 100644 index 0000000..b9d4e5c --- /dev/null +++ b/doners/Shop-bot/handlers/admin/add.py @@ -0,0 +1,285 @@ + +from aiogram.dispatcher import FSMContext +from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton, ContentType, ReplyKeyboardMarkup, ReplyKeyboardRemove +from aiogram.utils.callback_data import CallbackData +from keyboards.default.markups import * +from states import ProductState, CategoryState +from aiogram.types.chat import ChatActions +from doners.old_2.handlers import settings +from loader import dp, db, bot +from filters import IsAdmin +from hashlib import md5 + + +category_cb = CallbackData('category', 'id', 'action') +product_cb = CallbackData('product', 'id', 'action') + +add_product = '➕ Добавить товар' +delete_category = '🗑️ Удалить категорию' + + +@dp.message_handler(IsAdmin(), text=settings) +async def process_settings(message: Message): + + markup = InlineKeyboardMarkup() + + for idx, title in db.fetchall('SELECT * FROM categories'): + + markup.add(InlineKeyboardButton( + title, callback_data=category_cb.new(id=idx, action='view'))) + + markup.add(InlineKeyboardButton( + '+ Добавить категорию', callback_data='add_category')) + + await message.answer('Настройка категорий:', reply_markup=markup) + + +@dp.callback_query_handler(IsAdmin(), category_cb.filter(action='view')) +async def category_callback_handler(query: CallbackQuery, callback_data: dict, state: FSMContext): + + category_idx = callback_data['id'] + + products = db.fetchall('''SELECT * FROM products product + WHERE product.tag = (SELECT title FROM categories WHERE idx=?)''', + (category_idx,)) + + await query.message.delete() + await query.answer('Все добавленные товары в эту категорию.') + await state.update_data(category_index=category_idx) + await show_products(query.message, products, category_idx) + + +# category + + +@dp.callback_query_handler(IsAdmin(), text='add_category') +async def add_category_callback_handler(query: CallbackQuery): + await query.message.delete() + await query.message.answer('Название категории?') + await CategoryState.title.set() + + +@dp.message_handler(IsAdmin(), state=CategoryState.title) +async def set_category_title_handler(message: Message, state: FSMContext): + + category = message.text + idx = md5(category.encode('utf-8')).hexdigest() + db.query('INSERT INTO categories VALUES (?, ?)', (idx, category)) + + await state.finish() + await process_settings(message) + + +@dp.message_handler(IsAdmin(), text=delete_category) +async def delete_category_handler(message: Message, state: FSMContext): + + async with state.proxy() as data: + + if 'category_index' in data.keys(): + + idx = data['category_index'] + + db.query( + 'DELETE FROM products WHERE tag IN (SELECT title FROM categories WHERE idx=?)', (idx,)) + db.query('DELETE FROM categories WHERE idx=?', (idx,)) + + await message.answer('Готово!', reply_markup=ReplyKeyboardRemove()) + await process_settings(message) + + +# add product + + +@dp.message_handler(IsAdmin(), text=add_product) +async def process_add_product(message: Message): + + await ProductState.title.set() + + markup = ReplyKeyboardMarkup(resize_keyboard=True) + markup.add(cancel_message) + + await message.answer('Название?', reply_markup=markup) + + +@dp.message_handler(IsAdmin(), text=cancel_message, state=ProductState.title) +async def process_cancel(message: Message, state: FSMContext): + + await message.answer('Ок, отменено!', reply_markup=ReplyKeyboardRemove()) + await state.finish() + + await process_settings(message) + + +@dp.message_handler(IsAdmin(), text=back_message, state=ProductState.title) +async def process_title_back(message: Message, state: FSMContext): + await process_add_product(message) + + +@dp.message_handler(IsAdmin(), state=ProductState.title) +async def process_title(message: Message, state: FSMContext): + + async with state.proxy() as data: + data['title'] = message.text + + await ProductState.next() + await message.answer('Описание?', reply_markup=back_markup()) + + +@dp.message_handler(IsAdmin(), text=back_message, state=ProductState.body) +async def process_body_back(message: Message, state: FSMContext): + + await ProductState.title.set() + + async with state.proxy() as data: + + await message.answer(f"Изменить название с {data['title']}?", reply_markup=back_markup()) + + +@dp.message_handler(IsAdmin(), state=ProductState.body) +async def process_body(message: Message, state: FSMContext): + + async with state.proxy() as data: + data['body'] = message.text + + await ProductState.next() + await message.answer('Фото?', reply_markup=back_markup()) + + +@dp.message_handler(IsAdmin(), content_types=ContentType.PHOTO, state=ProductState.image) +async def process_image_photo(message: Message, state: FSMContext): + + fileID = message.photo[-1].file_id + file_info = await bot.get_file(fileID) + downloaded_file = (await bot.download_file(file_info.file_path)).read() + + async with state.proxy() as data: + data['image'] = downloaded_file + + await ProductState.next() + await message.answer('Цена?', reply_markup=back_markup()) + + +@dp.message_handler(IsAdmin(), content_types=ContentType.TEXT, state=ProductState.image) +async def process_image_url(message: Message, state: FSMContext): + + if message.text == back_message: + + await ProductState.body.set() + + async with state.proxy() as data: + + await message.answer(f"Изменить описание с {data['body']}?", reply_markup=back_markup()) + + else: + + await message.answer('Вам нужно прислать фото товара.') + + +@dp.message_handler(IsAdmin(), lambda message: not message.text.isdigit(), state=ProductState.price) +async def process_price_invalid(message: Message, state: FSMContext): + + if message.text == back_message: + + await ProductState.image.set() + + async with state.proxy() as data: + + await message.answer("Другое изображение?", reply_markup=back_markup()) + + else: + + await message.answer('Укажите цену в виде числа!') + + +@dp.message_handler(IsAdmin(), lambda message: message.text.isdigit(), state=ProductState.price) +async def process_price(message: Message, state: FSMContext): + + async with state.proxy() as data: + + data['price'] = message.text + + title = data['title'] + body = data['body'] + price = data['price'] + + await ProductState.next() + text = f'{title}\n\n{body}\n\nЦена: {price} рублей.' + + markup = check_markup() + + await message.answer_photo(photo=data['image'], + caption=text, + reply_markup=markup) + + +@dp.message_handler(IsAdmin(), lambda message: message.text not in [back_message, all_right_message], state=ProductState.confirm) +async def process_confirm_invalid(message: Message, state: FSMContext): + await message.answer('Такого варианта не было.') + + +@dp.message_handler(IsAdmin(), text=back_message, state=ProductState.confirm) +async def process_confirm_back(message: Message, state: FSMContext): + + await ProductState.price.set() + + async with state.proxy() as data: + + await message.answer(f"Изменить цену с {data['price']}?", reply_markup=back_markup()) + + +@dp.message_handler(IsAdmin(), text=all_right_message, state=ProductState.confirm) +async def process_confirm(message: Message, state: FSMContext): + + async with state.proxy() as data: + + title = data['title'] + body = data['body'] + image = data['image'] + price = data['price'] + + tag = db.fetchone( + 'SELECT title FROM categories WHERE idx=?', (data['category_index'],))[0] + idx = md5(' '.join([title, body, price, tag] + ).encode('utf-8')).hexdigest() + + db.query('INSERT INTO products VALUES (?, ?, ?, ?, ?, ?)', + (idx, title, body, image, int(price), tag)) + + await state.finish() + await message.answer('Готово!', reply_markup=ReplyKeyboardRemove()) + await process_settings(message) + + +# delete product + + +@dp.callback_query_handler(IsAdmin(), product_cb.filter(action='delete')) +async def delete_product_callback_handler(query: CallbackQuery, callback_data: dict): + + product_idx = callback_data['id'] + db.query('DELETE FROM products WHERE idx=?', (product_idx,)) + await query.answer('Удалено!') + await query.message.delete() + + +async def show_products(m, products, category_idx): + + await bot.send_chat_action(m.chat.id, ChatActions.TYPING) + + for idx, title, body, image, price, tag in products: + + text = f'{title}\n\n{body}\n\nЦена: {price} рублей.' + + markup = InlineKeyboardMarkup() + markup.add(InlineKeyboardButton( + '🗑️ Удалить', callback_data=product_cb.new(id=idx, action='delete'))) + + await m.answer_photo(photo=image, + caption=text, + reply_markup=markup) + + markup = ReplyKeyboardMarkup() + markup.add(add_product) + markup.add(delete_category) + + await m.answer('Хотите что-нибудь добавить или удалить?', reply_markup=markup) diff --git a/doners/Shop-bot/handlers/admin/orders.py b/doners/Shop-bot/handlers/admin/orders.py new file mode 100644 index 0000000..c257606 --- /dev/null +++ b/doners/Shop-bot/handlers/admin/orders.py @@ -0,0 +1,22 @@ + +from aiogram.types import Message +from loader import dp, db +from doners.old_2.handlers import orders +from filters import IsAdmin + +@dp.message_handler(IsAdmin(), text=orders) +async def process_orders(message: Message): + + orders = db.fetchall('SELECT * FROM orders') + + if len(orders) == 0: await message.answer('У вас нет заказов.') + else: await order_answer(message, orders) + +async def order_answer(message, orders): + + res = '' + + for order in orders: + res += f'Заказ №{order[3]}\n\n' + + await message.answer(res) \ No newline at end of file diff --git a/doners/Shop-bot/handlers/admin/questions.py b/doners/Shop-bot/handlers/admin/questions.py new file mode 100644 index 0000000..4a2dd1c --- /dev/null +++ b/doners/Shop-bot/handlers/admin/questions.py @@ -0,0 +1,78 @@ + +from doners.old_2.handlers import questions +from aiogram.dispatcher import FSMContext +from aiogram.utils.callback_data import CallbackData +from keyboards.default.markups import all_right_message, cancel_message, submit_markup +from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardRemove +from aiogram.types.chat import ChatActions +from states import AnswerState +from loader import dp, db, bot +from filters import IsAdmin + +question_cb = CallbackData('question', 'cid', 'action') + + +@dp.message_handler(IsAdmin(), text=questions) +async def process_questions(message: Message): + + await bot.send_chat_action(message.chat.id, ChatActions.TYPING) + questions = db.fetchall('SELECT * FROM questions') + + if len(questions) == 0: + + await message.answer('Нет вопросов.') + + else: + + for cid, question in questions: + + markup = InlineKeyboardMarkup() + markup.add(InlineKeyboardButton( + 'Ответить', callback_data=question_cb.new(cid=cid, action='answer'))) + + await message.answer(question, reply_markup=markup) + + +@dp.callback_query_handler(IsAdmin(), question_cb.filter(action='answer')) +async def process_answer(query: CallbackQuery, callback_data: dict, state: FSMContext): + + async with state.proxy() as data: + data['cid'] = callback_data['cid'] + + await query.message.answer('Напиши ответ.', reply_markup=ReplyKeyboardRemove()) + await AnswerState.answer.set() + + +@dp.message_handler(IsAdmin(), state=AnswerState.answer) +async def process_submit(message: Message, state: FSMContext): + + async with state.proxy() as data: + data['answer'] = message.text + + await AnswerState.next() + await message.answer('Убедитесь, что не ошиблись в ответе.', reply_markup=submit_markup()) + + +@dp.message_handler(IsAdmin(), text=cancel_message, state=AnswerState.submit) +async def process_send_answer(message: Message, state: FSMContext): + await message.answer('Отменено!', reply_markup=ReplyKeyboardRemove()) + await state.finish() + + +@dp.message_handler(IsAdmin(), text=all_right_message, state=AnswerState.submit) +async def process_send_answer(message: Message, state: FSMContext): + + async with state.proxy() as data: + + answer = data['answer'] + cid = data['cid'] + + question = db.fetchone( + 'SELECT question FROM questions WHERE cid=?', (cid,))[0] + db.query('DELETE FROM questions WHERE cid=?', (cid,)) + text = f'Вопрос: {question}\n\nОтвет: {answer}' + + await message.answer('Отправлено!', reply_markup=ReplyKeyboardRemove()) + await bot.send_message(cid, text) + + await state.finish() diff --git a/doners/Shop-bot/handlers/user/__init__.py b/doners/Shop-bot/handlers/user/__init__.py new file mode 100644 index 0000000..9b9495e --- /dev/null +++ b/doners/Shop-bot/handlers/user/__init__.py @@ -0,0 +1,8 @@ +from .menu import dp +from .cart import dp +from .wallet import dp +from .catalog import dp +from .delivery_status import dp +from .sos import dp + +__all__ = ['dp'] \ No newline at end of file diff --git a/doners/Shop-bot/handlers/user/cart.py b/doners/Shop-bot/handlers/user/cart.py new file mode 100644 index 0000000..578df60 --- /dev/null +++ b/doners/Shop-bot/handlers/user/cart.py @@ -0,0 +1,247 @@ +import logging +from aiogram.dispatcher import FSMContext +from aiogram.types import Message, CallbackQuery, ReplyKeyboardMarkup, ReplyKeyboardRemove, InlineKeyboardMarkup, InlineKeyboardButton +from keyboards.inline.products_from_cart import product_markup, product_cb +from aiogram.utils.callback_data import CallbackData +from keyboards.default.markups import * +from aiogram.types.chat import ChatActions +from states import CheckoutState +from loader import dp, db, bot +from filters import IsUser +from .menu import cart + + +@dp.message_handler(IsUser(), text=cart) +async def process_cart(message: Message, state: FSMContext): + + cart_data = db.fetchall( + 'SELECT * FROM cart WHERE cid=?', (message.chat.id,)) + + if len(cart_data) == 0: + + await message.answer('Ваша корзина пуста.') + + else: + + await bot.send_chat_action(message.chat.id, ChatActions.TYPING) + async with state.proxy() as data: + data['products'] = {} + + order_cost = 0 + + for _, idx, count_in_cart in cart_data: + + product = db.fetchone('SELECT * FROM products WHERE idx=?', (idx,)) + + if product == None: + + db.query('DELETE FROM cart WHERE idx=?', (idx,)) + + else: + _, title, body, image, price, _ = product + order_cost += price + + async with state.proxy() as data: + data['products'][idx] = [title, price, count_in_cart] + + markup = product_markup(idx, count_in_cart) + text = f'{title}\n\n{body}\n\nЦена: {price}₽.' + + await message.answer_photo(photo=image, + caption=text, + reply_markup=markup) + + if order_cost != 0: + markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True) + markup.add('📦 Оформить заказ') + + await message.answer('Перейти к оформлению?', + reply_markup=markup) + + +@dp.callback_query_handler(IsUser(), product_cb.filter(action='count')) +@dp.callback_query_handler(IsUser(), product_cb.filter(action='increase')) +@dp.callback_query_handler(IsUser(), product_cb.filter(action='decrease')) +async def product_callback_handler(query: CallbackQuery, callback_data: dict, state: FSMContext): + + idx = callback_data['id'] + action = callback_data['action'] + + if 'count' == action: + + async with state.proxy() as data: + + if 'products' not in data.keys(): + + await process_cart(query.message, state) + + else: + + await query.answer('Количество - ' + data['products'][idx][2]) + + else: + + async with state.proxy() as data: + + if 'products' not in data.keys(): + + await process_cart(query.message, state) + + else: + + data['products'][idx][2] += 1 if 'increase' == action else -1 + count_in_cart = data['products'][idx][2] + + if count_in_cart == 0: + + db.query('''DELETE FROM cart + WHERE cid = ? AND idx = ?''', (query.message.chat.id, idx)) + + await query.message.delete() + else: + + db.query('''UPDATE cart + SET quantity = ? + WHERE cid = ? AND idx = ?''', (count_in_cart, query.message.chat.id, idx)) + + await query.message.edit_reply_markup(product_markup(idx, count_in_cart)) + + +@dp.message_handler(IsUser(), text='📦 Оформить заказ') +async def process_checkout(message: Message, state: FSMContext): + + await CheckoutState.check_cart.set() + await checkout(message, state) + + +async def checkout(message, state): + answer = '' + total_price = 0 + + async with state.proxy() as data: + + for title, price, count_in_cart in data['products'].values(): + + tp = count_in_cart * price + answer += f'{title} * {count_in_cart}шт. = {tp}₽\n' + total_price += tp + + await message.answer(f'{answer}\nОбщая сумма заказа: {total_price}₽.', + reply_markup=check_markup()) + + +@dp.message_handler(IsUser(), lambda message: message.text not in [all_right_message, back_message], state=CheckoutState.check_cart) +async def process_check_cart_invalid(message: Message): + await message.reply('Такого варианта не было.') + + +@dp.message_handler(IsUser(), text=back_message, state=CheckoutState.check_cart) +async def process_check_cart_back(message: Message, state: FSMContext): + await state.finish() + await process_cart(message, state) + + +@dp.message_handler(IsUser(), text=all_right_message, state=CheckoutState.check_cart) +async def process_check_cart_all_right(message: Message, state: FSMContext): + await CheckoutState.next() + await message.answer('Укажите свое имя.', + reply_markup=back_markup()) + + +@dp.message_handler(IsUser(), text=back_message, state=CheckoutState.name) +async def process_name_back(message: Message, state: FSMContext): + await CheckoutState.check_cart.set() + await checkout(message, state) + + +@dp.message_handler(IsUser(), state=CheckoutState.name) +async def process_name(message: Message, state: FSMContext): + + async with state.proxy() as data: + + data['name'] = message.text + + if 'address' in data.keys(): + + await confirm(message) + await CheckoutState.confirm.set() + + else: + + await CheckoutState.next() + await message.answer('Укажите свой адрес места жительства.', + reply_markup=back_markup()) + + +@dp.message_handler(IsUser(), text=back_message, state=CheckoutState.address) +async def process_address_back(message: Message, state: FSMContext): + + async with state.proxy() as data: + + await message.answer('Изменить имя с ' + data['name'] + '?', + reply_markup=back_markup()) + + await CheckoutState.name.set() + + +@dp.message_handler(IsUser(), state=CheckoutState.address) +async def process_address(message: Message, state: FSMContext): + + async with state.proxy() as data: + data['address'] = message.text + + await confirm(message) + await CheckoutState.next() + + +async def confirm(message): + + await message.answer('Убедитесь, что все правильно оформлено и подтвердите заказ.', + reply_markup=confirm_markup()) + + +@dp.message_handler(IsUser(), lambda message: message.text not in [confirm_message, back_message], state=CheckoutState.confirm) +async def process_confirm_invalid(message: Message): + await message.reply('Такого варианта не было.') + + +@dp.message_handler(IsUser(), text=back_message, state=CheckoutState.confirm) +async def process_confirm(message: Message, state: FSMContext): + + await CheckoutState.address.set() + + async with state.proxy() as data: + await message.answer('Изменить адрес с ' + data['address'] + '?', + reply_markup=back_markup()) + + +@dp.message_handler(IsUser(), text=confirm_message, state=CheckoutState.confirm) +async def process_confirm(message: Message, state: FSMContext): + + enough_money = True # enough money on the balance sheet + markup = ReplyKeyboardRemove() + + if enough_money: + + logging.info('Deal was made.') + + async with state.proxy() as data: + + cid = message.chat.id + products = [idx + '=' + str(quantity) + for idx, quantity in db.fetchall('''SELECT idx, quantity FROM cart + WHERE cid=?''', (cid,))] # idx=quantity + + db.query('INSERT INTO orders VALUES (?, ?, ?, ?)', + (cid, data['name'], data['address'], ' '.join(products))) + + db.query('DELETE FROM cart WHERE cid=?', (cid,)) + + await message.answer('Ок! Ваш заказ уже в пути 🚀\nИмя: ' + data['name'] + '\nАдрес: ' + data['address'] + '', + reply_markup=markup) + else: + + await message.answer('У вас недостаточно денег на счете. Пополните баланс!', + reply_markup=markup) + + await state.finish() diff --git a/doners/Shop-bot/handlers/user/catalog.py b/doners/Shop-bot/handlers/user/catalog.py new file mode 100644 index 0000000..d0cda85 --- /dev/null +++ b/doners/Shop-bot/handlers/user/catalog.py @@ -0,0 +1,58 @@ + +import logging +from aiogram.types import Message, CallbackQuery +from keyboards.inline.categories import categories_markup, category_cb +from keyboards.inline.products_from_catalog import product_markup, product_cb +from aiogram.utils.callback_data import CallbackData +from aiogram.types.chat import ChatActions +from loader import dp, db, bot +from .menu import catalog +from filters import IsUser + + +@dp.message_handler(IsUser(), text=catalog) +async def process_catalog(message: Message): + await message.answer('Выберите раздел, чтобы вывести список товаров:', + reply_markup=categories_markup()) + + +@dp.callback_query_handler(IsUser(), category_cb.filter(action='view')) +async def category_callback_handler(query: CallbackQuery, callback_data: dict): + + products = db.fetchall('''SELECT * FROM products product + WHERE product.tag = (SELECT title FROM categories WHERE idx=?) + AND product.idx NOT IN (SELECT idx FROM cart WHERE cid = ?)''', + (callback_data['id'], query.message.chat.id)) + + await query.answer('Все доступные товары.') + await show_products(query.message, products) + + +@dp.callback_query_handler(IsUser(), product_cb.filter(action='add')) +async def add_product_callback_handler(query: CallbackQuery, callback_data: dict): + + db.query('INSERT INTO cart VALUES (?, ?, 1)', + (query.message.chat.id, callback_data['id'])) + + await query.answer('Товар добавлен в корзину!') + await query.message.delete() + + +async def show_products(m, products): + + if len(products) == 0: + + await m.answer('Здесь ничего нет 😢') + + else: + + await bot.send_chat_action(m.chat.id, ChatActions.TYPING) + + for idx, title, body, image, price, _ in products: + + markup = product_markup(idx, price) + text = f'{title}\n\n{body}' + + await m.answer_photo(photo=image, + caption=text, + reply_markup=markup) diff --git a/doners/Shop-bot/handlers/user/delivery_status.py b/doners/Shop-bot/handlers/user/delivery_status.py new file mode 100644 index 0000000..347a907 --- /dev/null +++ b/doners/Shop-bot/handlers/user/delivery_status.py @@ -0,0 +1,31 @@ + +from aiogram.types import Message +from loader import dp, db +from .menu import delivery_status +from filters import IsUser + +@dp.message_handler(IsUser(), text=delivery_status) +async def process_delivery_status(message: Message): + + orders = db.fetchall('SELECT * FROM orders WHERE cid=?', (message.chat.id,)) + + if len(orders) == 0: await message.answer('У вас нет активных заказов.') + else: await delivery_status_answer(message, orders) + +async def delivery_status_answer(message, orders): + + res = '' + + for order in orders: + + res += f'Заказ №{order[3]}' + answer = [ + ' лежит на складе.', + ' уже в пути!', + ' прибыл и ждет вас на почте!' + ] + + res += answer[0] + res += '\n\n' + + await message.answer(res) \ No newline at end of file diff --git a/doners/Shop-bot/handlers/user/menu.py b/doners/Shop-bot/handlers/user/menu.py new file mode 100644 index 0000000..2ebc6ff --- /dev/null +++ b/doners/Shop-bot/handlers/user/menu.py @@ -0,0 +1,30 @@ + +from aiogram.types import Message, CallbackQuery, ReplyKeyboardMarkup +from loader import dp +from filters import IsAdmin, IsUser + +catalog = '🛍️ Каталог' +balance = '💰 Баланс' +cart = '🛒 Корзина' +delivery_status = '🚚 Статус заказа' + +settings = '⚙️ Настройка каталога' +orders = '🚚 Заказы' +questions = '❓ Вопросы' + +@dp.message_handler(IsAdmin(), commands='menu') +async def admin_menu(message: Message): + markup = ReplyKeyboardMarkup(selective=True) + markup.add(settings) + markup.add(questions, orders) + + await message.answer('Меню', reply_markup=markup) + +@dp.message_handler(IsUser(), commands='menu') +async def user_menu(message: Message): + markup = ReplyKeyboardMarkup(selective=True) + markup.add(catalog) + markup.add(balance, cart) + markup.add(delivery_status) + + await message.answer('Меню', reply_markup=markup) diff --git a/doners/Shop-bot/handlers/user/sos.py b/doners/Shop-bot/handlers/user/sos.py new file mode 100644 index 0000000..5b45288 --- /dev/null +++ b/doners/Shop-bot/handlers/user/sos.py @@ -0,0 +1,54 @@ + +from aiogram.dispatcher import FSMContext +from aiogram.types import ReplyKeyboardMarkup, ReplyKeyboardRemove +from keyboards.default.markups import all_right_message, cancel_message, submit_markup +from aiogram.types import Message +from states import SosState +from filters import IsUser +from loader import dp, db + + +@dp.message_handler(commands='sos') +async def cmd_sos(message: Message): + await SosState.question.set() + await message.answer('В чем суть проблемы? Опишите как можно детальнее и администратор обязательно вам ответит.', reply_markup=ReplyKeyboardRemove()) + + +@dp.message_handler(state=SosState.question) +async def process_question(message: Message, state: FSMContext): + async with state.proxy() as data: + data['question'] = message.text + + await message.answer('Убедитесь, что все верно.', reply_markup=submit_markup()) + await SosState.next() + + +@dp.message_handler(lambda message: message.text not in [cancel_message, all_right_message], state=SosState.submit) +async def process_price_invalid(message: Message): + await message.answer('Такого варианта не было.') + + +@dp.message_handler(text=cancel_message, state=SosState.submit) +async def process_cancel(message: Message, state: FSMContext): + await message.answer('Отменено!', reply_markup=ReplyKeyboardRemove()) + await state.finish() + + +@dp.message_handler(text=all_right_message, state=SosState.submit) +async def process_submit(message: Message, state: FSMContext): + + cid = message.chat.id + + if db.fetchone('SELECT * FROM questions WHERE cid=?', (cid,)) == None: + + async with state.proxy() as data: + db.query('INSERT INTO questions VALUES (?, ?)', + (cid, data['question'])) + + await message.answer('Отправлено!', reply_markup=ReplyKeyboardRemove()) + + else: + + await message.answer('Превышен лимит на количество задаваемых вопросов.', reply_markup=ReplyKeyboardRemove()) + + await state.finish() diff --git a/doners/Shop-bot/handlers/user/wallet.py b/doners/Shop-bot/handlers/user/wallet.py new file mode 100644 index 0000000..4dd15eb --- /dev/null +++ b/doners/Shop-bot/handlers/user/wallet.py @@ -0,0 +1,18 @@ + +from loader import dp +from aiogram.dispatcher import FSMContext +from aiogram.types import Message +from filters import IsUser +from .menu import balance + +# test card ==> 1111 1111 1111 1026, 12/22, CVC 000 + +# shopId 506751 + +# shopArticleId 538350 + + +@dp.message_handler(IsUser(), text=balance) +async def process_balance(message: Message, state: FSMContext): + await message.answer('Ваш кошелек пуст! Чтобы его пополнить нужно...') + diff --git a/doners/Shop-bot/keyboards/__init__.py b/doners/Shop-bot/keyboards/__init__.py new file mode 100644 index 0000000..cdd8a1d --- /dev/null +++ b/doners/Shop-bot/keyboards/__init__.py @@ -0,0 +1,2 @@ +from . import inline +from . import default \ No newline at end of file diff --git a/doners/Shop-bot/keyboards/default/__init__.py b/doners/Shop-bot/keyboards/default/__init__.py new file mode 100644 index 0000000..8204e33 --- /dev/null +++ b/doners/Shop-bot/keyboards/default/__init__.py @@ -0,0 +1 @@ +from . import markups \ No newline at end of file diff --git a/doners/Shop-bot/keyboards/default/markups.py b/doners/Shop-bot/keyboards/default/markups.py new file mode 100644 index 0000000..efe1ddb --- /dev/null +++ b/doners/Shop-bot/keyboards/default/markups.py @@ -0,0 +1,31 @@ +from aiogram.types import ReplyKeyboardMarkup + +back_message = '👈 Назад' +confirm_message = '✅ Подтвердить заказ' +all_right_message = '✅ Все верно' +cancel_message = '🚫 Отменить' + +def confirm_markup(): + markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True) + markup.add(confirm_message) + markup.add(back_message) + + return markup + +def back_markup(): + markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True) + markup.add(back_message) + + return markup + +def check_markup(): + markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True) + markup.row(back_message, all_right_message) + + return markup + +def submit_markup(): + markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True) + markup.row(cancel_message, all_right_message) + + return markup diff --git a/doners/Shop-bot/keyboards/inline/__init__.py b/doners/Shop-bot/keyboards/inline/__init__.py new file mode 100644 index 0000000..7471504 --- /dev/null +++ b/doners/Shop-bot/keyboards/inline/__init__.py @@ -0,0 +1,3 @@ +from . import products_from_catalog +from . import products_from_cart +from . import categories diff --git a/doners/Shop-bot/keyboards/inline/categories.py b/doners/Shop-bot/keyboards/inline/categories.py new file mode 100644 index 0000000..8793729 --- /dev/null +++ b/doners/Shop-bot/keyboards/inline/categories.py @@ -0,0 +1,16 @@ +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from aiogram.utils.callback_data import CallbackData +from loader import db + +category_cb = CallbackData('category', 'id', 'action') + + +def categories_markup(): + + global category_cb + + markup = InlineKeyboardMarkup() + for idx, title in db.fetchall('SELECT * FROM categories'): + markup.add(InlineKeyboardButton(title, callback_data=category_cb.new(id=idx, action='view'))) + + return markup diff --git a/doners/Shop-bot/keyboards/inline/products_from_cart.py b/doners/Shop-bot/keyboards/inline/products_from_cart.py new file mode 100644 index 0000000..76eb8b5 --- /dev/null +++ b/doners/Shop-bot/keyboards/inline/products_from_cart.py @@ -0,0 +1,16 @@ +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from aiogram.utils.callback_data import CallbackData + +product_cb = CallbackData('product', 'id', 'action') + +def product_markup(idx, count): + + global product_cb + + markup = InlineKeyboardMarkup() + back_btn = InlineKeyboardButton('⬅️', callback_data=product_cb.new(id=idx, action='decrease')) + count_btn = InlineKeyboardButton(count, callback_data=product_cb.new(id=idx, action='count')) + next_btn = InlineKeyboardButton('➡️', callback_data=product_cb.new(id=idx, action='increase')) + markup.row(back_btn, count_btn, next_btn) + + return markup \ No newline at end of file diff --git a/doners/Shop-bot/keyboards/inline/products_from_catalog.py b/doners/Shop-bot/keyboards/inline/products_from_catalog.py new file mode 100644 index 0000000..212ebcc --- /dev/null +++ b/doners/Shop-bot/keyboards/inline/products_from_catalog.py @@ -0,0 +1,15 @@ +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from aiogram.utils.callback_data import CallbackData +from loader import db + +product_cb = CallbackData('product', 'id', 'action') + + +def product_markup(idx='', price=0): + + global product_cb + + markup = InlineKeyboardMarkup() + markup.add(InlineKeyboardButton(f'Добавить в корзину - {price}₽', callback_data=product_cb.new(id=idx, action='add'))) + + return markup \ No newline at end of file diff --git a/doners/Shop-bot/loader.py b/doners/Shop-bot/loader.py new file mode 100644 index 0000000..dc09a02 --- /dev/null +++ b/doners/Shop-bot/loader.py @@ -0,0 +1,10 @@ +from aiogram import Bot, Dispatcher, types +from aiogram.contrib.fsm_storage.memory import MemoryStorage +from utils.db.storage import DatabaseManager + +from data import config + +bot = Bot(token=config.BOT_TOKEN, parse_mode=types.ParseMode.HTML) +storage = MemoryStorage() +dp = Dispatcher(bot, storage=storage) +db = DatabaseManager('data/database.db') \ No newline at end of file diff --git a/doners/Shop-bot/requirements.txt b/doners/Shop-bot/requirements.txt new file mode 100644 index 0000000..2ebd2b2 --- /dev/null +++ b/doners/Shop-bot/requirements.txt @@ -0,0 +1 @@ +aiogram==2.9.2 diff --git a/doners/Shop-bot/states/__init__.py b/doners/Shop-bot/states/__init__.py new file mode 100644 index 0000000..9abdb95 --- /dev/null +++ b/doners/Shop-bot/states/__init__.py @@ -0,0 +1,3 @@ +from .checkout_state import CheckoutState +from .product_state import ProductState, CategoryState +from .sos_state import SosState, AnswerState \ No newline at end of file diff --git a/doners/Shop-bot/states/checkout_state.py b/doners/Shop-bot/states/checkout_state.py new file mode 100644 index 0000000..871e154 --- /dev/null +++ b/doners/Shop-bot/states/checkout_state.py @@ -0,0 +1,7 @@ +from aiogram.dispatcher.filters.state import StatesGroup, State + +class CheckoutState(StatesGroup): + check_cart = State() + name = State() + address = State() + confirm = State() \ No newline at end of file diff --git a/doners/Shop-bot/states/product_state.py b/doners/Shop-bot/states/product_state.py new file mode 100644 index 0000000..ee637e8 --- /dev/null +++ b/doners/Shop-bot/states/product_state.py @@ -0,0 +1,11 @@ +from aiogram.dispatcher.filters.state import StatesGroup, State + +class ProductState(StatesGroup): + title = State() + body = State() + image = State() + price = State() + confirm = State() + +class CategoryState(StatesGroup): + title = State() \ No newline at end of file diff --git a/doners/Shop-bot/states/sos_state.py b/doners/Shop-bot/states/sos_state.py new file mode 100644 index 0000000..4a8a3bb --- /dev/null +++ b/doners/Shop-bot/states/sos_state.py @@ -0,0 +1,9 @@ +from aiogram.dispatcher.filters.state import StatesGroup, State + +class SosState(StatesGroup): + question = State() + submit = State() + +class AnswerState(StatesGroup): + answer = State() + submit = State() \ No newline at end of file diff --git a/doners/Shop-bot/utils/__init__.py b/doners/Shop-bot/utils/__init__.py new file mode 100644 index 0000000..2b79d14 --- /dev/null +++ b/doners/Shop-bot/utils/__init__.py @@ -0,0 +1 @@ +from . import db \ No newline at end of file diff --git a/doners/Shop-bot/utils/db/__init__.py b/doners/Shop-bot/utils/db/__init__.py new file mode 100644 index 0000000..fb25ec6 --- /dev/null +++ b/doners/Shop-bot/utils/db/__init__.py @@ -0,0 +1 @@ +from .storage import DatabaseManager \ No newline at end of file diff --git a/doners/Shop-bot/utils/db/storage.py b/doners/Shop-bot/utils/db/storage.py new file mode 100644 index 0000000..34eb363 --- /dev/null +++ b/doners/Shop-bot/utils/db/storage.py @@ -0,0 +1,59 @@ + +import sqlite3 as lite + +class DatabaseManager(object): + + def __init__(self, path): + self.conn = lite.connect(path) + self.conn.execute('pragma foreign_keys = on') + self.conn.commit() + self.cur = self.conn.cursor() + + def create_tables(self): + self.query('CREATE TABLE IF NOT EXISTS products (idx text, title text, body text, photo blob, price int, tag text)') + self.query('CREATE TABLE IF NOT EXISTS orders (cid int, usr_name text, usr_address text, products text)') + self.query('CREATE TABLE IF NOT EXISTS cart (cid int, idx text, quantity int)') + self.query('CREATE TABLE IF NOT EXISTS categories (idx text, title text)') + self.query('CREATE TABLE IF NOT EXISTS wallet (cid int, balance real)') + self.query('CREATE TABLE IF NOT EXISTS questions (cid int, question text)') + + def query(self, arg, values=None): + if values == None: + self.cur.execute(arg) + else: + self.cur.execute(arg, values) + self.conn.commit() + + def fetchone(self, arg, values=None): + if values == None: + self.cur.execute(arg) + else: + self.cur.execute(arg, values) + return self.cur.fetchone() + + def fetchall(self, arg, values=None): + if values == None: + self.cur.execute(arg) + else: + self.cur.execute(arg, values) + return self.cur.fetchall() + + def __del__(self): + self.conn.close() + + +''' + +products: idx text, title text, body text, photo blob, price int, tag text + +orders: cid int, usr_name text, usr_address text, products text + +cart: cid int, idx text, quantity int ==> product_idx + +categories: idx text, title text + +wallet: cid int, balance real + +questions: cid int, question text + +''' diff --git a/doners/__init__.py b/doners/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/doners/bot-market/.idea/.gitignore b/doners/bot-market/.idea/.gitignore index 26d3352..eaf91e2 100644 --- a/doners/bot-market/.idea/.gitignore +++ b/doners/bot-market/.idea/.gitignore @@ -1,3 +1,3 @@ -# Default ignored files -/shelf/ -/workspace.xml +# Default ignored files +/shelf/ +/workspace.xml diff --git a/doners/bot-market/.idea/bot-market.iml b/doners/bot-market/.idea/bot-market.iml index 2c80e12..f246f2c 100644 --- a/doners/bot-market/.idea/bot-market.iml +++ b/doners/bot-market/.idea/bot-market.iml @@ -1,10 +1,10 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/doners/bot-market/.idea/inspectionProfiles/profiles_settings.xml b/doners/bot-market/.idea/inspectionProfiles/profiles_settings.xml index 105ce2d..20fc29e 100644 --- a/doners/bot-market/.idea/inspectionProfiles/profiles_settings.xml +++ b/doners/bot-market/.idea/inspectionProfiles/profiles_settings.xml @@ -1,6 +1,6 @@ - - - + + + \ No newline at end of file diff --git a/doners/bot-market/.idea/misc.xml b/doners/bot-market/.idea/misc.xml index 23bd63c..a380c44 100644 --- a/doners/bot-market/.idea/misc.xml +++ b/doners/bot-market/.idea/misc.xml @@ -1,7 +1,7 @@ - - - - - + + + + + \ No newline at end of file diff --git a/doners/bot-market/.idea/modules.xml b/doners/bot-market/.idea/modules.xml index 6c24722..e12154a 100644 --- a/doners/bot-market/.idea/modules.xml +++ b/doners/bot-market/.idea/modules.xml @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/doners/bot-market/__init__.py b/doners/bot-market/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/doners/bot-market/app/__init__.py b/doners/bot-market/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/doners/bot-market/app/admin.py b/doners/bot-market/app/admin.py index 6566ee3..5f60568 100644 --- a/doners/bot-market/app/admin.py +++ b/doners/bot-market/app/admin.py @@ -1,94 +1,94 @@ -from aiogram import Router, F -from aiogram.types import Message, CallbackQuery -from aiogram.filters import CommandStart, Command, Filter -from aiogram.fsm.context import FSMContext -from aiogram.fsm.state import State, StatesGroup - -import app.keyboards as kb -from app.database.requests import get_users, set_item - -admin = Router() - - -class Newsletter(StatesGroup): - message = State() - - -class AddItem(StatesGroup): - name = State() - category = State() - description = State() - photo = State() - price = State() - - -class AdminProtect(Filter): - async def __call__(self, message: Message): - return message.from_user.id in [1477217831] - - -@admin.message(AdminProtect(), Command('apanel')) -async def apanel(message: Message): - await message.answer('Возможные команды: /newsletter\n/add_item') - - -@admin.message(AdminProtect(), Command('newsletter')) -async def newsletter(message: Message, state: FSMContext): - await state.set_state(Newsletter.message) - await message.answer('Отправьте сообщение, которое вы хотите разослать всем пользователям') - - -@admin.message(AdminProtect(), Newsletter.message) -async def newsletter_message(message: Message, state: FSMContext): - await message.answer('Подождите... идёт рассылка.') - for user in await get_users(): - try: - await message.send_copy(chat_id=user.tg_id) - except: - pass - await message.answer('Рассылка успешно завершена.') - await state.clear() - - -@admin.message(AdminProtect(), Command('add_item')) -async def add_item(message: Message, state: FSMContext): - await state.set_state(AddItem.name) - await message.answer('Введите название товара') - - -@admin.message(AdminProtect(), AddItem.name) -async def add_item_name(message: Message, state: FSMContext): - await state.update_data(name=message.text) - await state.set_state(AddItem.category) - await message.answer('Выберите категорию товара', reply_markup=await kb.categories()) - - -@admin.callback_query(AdminProtect(), AddItem.category) -async def add_item_category(callback: CallbackQuery, state: FSMContext): - await state.update_data(category=callback.data.split('_')[1]) - await state.set_state(AddItem.description) - await callback.answer('') - await callback.message.answer('Введите описание товара') - - -@admin.message(AdminProtect(), AddItem.description) -async def add_item_description(message: Message, state: FSMContext): - await state.update_data(description=message.text) - await state.set_state(AddItem.photo) - await message.answer('Отправьте фото товара') - - -@admin.message(AdminProtect(), AddItem.photo, F.photo) -async def add_item_photo(message: Message, state: FSMContext): - await state.update_data(photo=message.photo[-1].file_id) - await state.set_state(AddItem.price) - await message.answer('Введите цену товара') - - -@admin.message(AdminProtect(), AddItem.price) -async def add_item_price(message: Message, state: FSMContext): - await state.update_data(price=message.text) - data = await state.get_data() - await set_item(data) - await message.answer('Товар успешно добавлен') +from aiogram import Router, F +from aiogram.types import Message, CallbackQuery +from aiogram.filters import CommandStart, Command, Filter +from aiogram.fsm.context import FSMContext +from aiogram.fsm.state import State, StatesGroup + +import app.keyboards as kb +from app.database.requests import get_users, set_item + +admin = Router() + + +class Newsletter(StatesGroup): + message = State() + + +class AddItem(StatesGroup): + name = State() + category = State() + description = State() + photo = State() + price = State() + + +class AdminProtect(Filter): + async def __call__(self, message: Message): + return message.from_user.id in [1477217831] + + +@admin.message(AdminProtect(), Command('apanel')) +async def apanel(message: Message): + await message.answer('Возможные команды: /newsletter\n/add_item') + + +@admin.message(AdminProtect(), Command('newsletter')) +async def newsletter(message: Message, state: FSMContext): + await state.set_state(Newsletter.message) + await message.answer('Отправьте сообщение, которое вы хотите разослать всем пользователям') + + +@admin.message(AdminProtect(), Newsletter.message) +async def newsletter_message(message: Message, state: FSMContext): + await message.answer('Подождите... идёт рассылка.') + for user in await get_users(): + try: + await message.send_copy(chat_id=user.tg_id) + except: + pass + await message.answer('Рассылка успешно завершена.') + await state.clear() + + +@admin.message(AdminProtect(), Command('add_item')) +async def add_item(message: Message, state: FSMContext): + await state.set_state(AddItem.name) + await message.answer('Введите название товара') + + +@admin.message(AdminProtect(), AddItem.name) +async def add_item_name(message: Message, state: FSMContext): + await state.update_data(name=message.text) + await state.set_state(AddItem.category) + await message.answer('Выберите категорию товара', reply_markup=await kb.categories()) + + +@admin.callback_query(AdminProtect(), AddItem.category) +async def add_item_category(callback: CallbackQuery, state: FSMContext): + await state.update_data(category=callback.data.split('_')[1]) + await state.set_state(AddItem.description) + await callback.answer('') + await callback.message.answer('Введите описание товара') + + +@admin.message(AdminProtect(), AddItem.description) +async def add_item_description(message: Message, state: FSMContext): + await state.update_data(description=message.text) + await state.set_state(AddItem.photo) + await message.answer('Отправьте фото товара') + + +@admin.message(AdminProtect(), AddItem.photo, F.photo) +async def add_item_photo(message: Message, state: FSMContext): + await state.update_data(photo=message.photo[-1].file_id) + await state.set_state(AddItem.price) + await message.answer('Введите цену товара') + + +@admin.message(AdminProtect(), AddItem.price) +async def add_item_price(message: Message, state: FSMContext): + await state.update_data(price=message.text) + data = await state.get_data() + await set_item(data) + await message.answer('Товар успешно добавлен') await state.clear() \ No newline at end of file diff --git a/doners/bot-market/app/database/__init__.py b/doners/bot-market/app/database/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/doners/bot-market/app/database/models.py b/doners/bot-market/app/database/models.py index d527058..234be8e 100644 --- a/doners/bot-market/app/database/models.py +++ b/doners/bot-market/app/database/models.py @@ -1,62 +1,62 @@ -from sqlalchemy import BigInteger, ForeignKey, String -from sqlalchemy.orm import Mapped, mapped_column, relationship, DeclarativeBase -from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine - -from typing import List -from config import ENGINE, ECHO - -engine = create_async_engine(url=ENGINE)#, echo=ECHO) - -async_session = async_sessionmaker(engine) - - -class Base(AsyncAttrs, DeclarativeBase): - pass - - -class User(Base): - __tablename__ = 'users' - - id: Mapped[int] = mapped_column(primary_key=True) - tg_id = mapped_column(BigInteger) - - basket_rel: Mapped[List['Basket']] = relationship(back_populates='user_rel') - - -class Category(Base): - __tablename__ = 'categories' - - id: Mapped[int] = mapped_column(primary_key=True) - name: Mapped[str] = mapped_column(String(50)) - - item_rel: Mapped[List['Item']] = relationship(back_populates='category_rel') - - -class Item(Base): - __tablename__ = 'items' - - id: Mapped[int] = mapped_column(primary_key=True) - name: Mapped[str] = mapped_column(String(50)) - description: Mapped[str] = mapped_column(String(200)) - photo: Mapped[str] = mapped_column(String(200)) - price: Mapped[int] = mapped_column() - category: Mapped[int] = mapped_column(ForeignKey('categories.id')) - - category_rel: Mapped['Category'] = relationship(back_populates='item_rel') - basket_rel: Mapped[List['Basket']] = relationship(back_populates='item_rel') - - -class Basket(Base): - __tablename__ = 'basket' - - id: Mapped[int] = mapped_column(primary_key=True) - user: Mapped[int] = mapped_column(ForeignKey('users.id')) - item: Mapped[int] = mapped_column(ForeignKey('items.id')) - - user_rel: Mapped['User'] = relationship(back_populates='basket_rel') - item_rel: Mapped['Item'] = relationship(back_populates='basket_rel') - - -async def async_main(): - async with engine.begin() as conn: +from sqlalchemy import BigInteger, ForeignKey, String +from sqlalchemy.orm import Mapped, mapped_column, relationship, DeclarativeBase +from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine + +from typing import List +from config import ENGINE, ECHO + +engine = create_async_engine(url=ENGINE)#, echo=ECHO) + +async_session = async_sessionmaker(engine) + + +class Base(AsyncAttrs, DeclarativeBase): + pass + + +class User(Base): + __tablename__ = 'users' + + id: Mapped[int] = mapped_column(primary_key=True) + tg_id = mapped_column(BigInteger) + + basket_rel: Mapped[List['Basket']] = relationship(back_populates='user_rel') + + +class Category(Base): + __tablename__ = 'categories' + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(50)) + + item_rel: Mapped[List['Item']] = relationship(back_populates='category_rel') + + +class Item(Base): + __tablename__ = 'items' + + id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(50)) + description: Mapped[str] = mapped_column(String(200)) + photo: Mapped[str] = mapped_column(String(200)) + price: Mapped[int] = mapped_column() + category: Mapped[int] = mapped_column(ForeignKey('categories.id')) + + category_rel: Mapped['Category'] = relationship(back_populates='item_rel') + basket_rel: Mapped[List['Basket']] = relationship(back_populates='item_rel') + + +class Basket(Base): + __tablename__ = 'basket' + + id: Mapped[int] = mapped_column(primary_key=True) + user: Mapped[int] = mapped_column(ForeignKey('users.id')) + item: Mapped[int] = mapped_column(ForeignKey('items.id')) + + user_rel: Mapped['User'] = relationship(back_populates='basket_rel') + item_rel: Mapped['Item'] = relationship(back_populates='basket_rel') + + +async def async_main(): + async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) \ No newline at end of file diff --git a/doners/bot-market/app/database/requests.py b/doners/bot-market/app/database/requests.py index 2897bd2..5412953 100644 --- a/doners/bot-market/app/database/requests.py +++ b/doners/bot-market/app/database/requests.py @@ -1,64 +1,64 @@ -from app.database.models import User, Category, Item, Basket -from app.database.models import async_session - -from sqlalchemy import select, update, delete - - -async def set_user(tg_id): - async with async_session() as session: - user = await session.scalar(select(User).where(User.tg_id == tg_id)) - - if not user: - session.add(User(tg_id=tg_id)) - await session.commit() - - -async def set_item(data): - async with async_session() as session: - session.add(Item(**data)) - await session.commit() - - -async def set_basket(tg_id, item_id): - async with async_session() as session: - user = await session.scalar(select(User).where(User.tg_id == tg_id)) - session.add(Basket(user=user.id, item=item_id)) - await session.commit() - - -async def get_basket(tg_id): - async with async_session() as session: - user = await session.scalar(select(User).where(User.tg_id == tg_id)) - basket = await session.scalars(select(Basket).where(Basket.user == user.id)) - return basket - - -async def get_users(): - async with async_session() as session: - users = await session.scalars(select(User)) - return users - - -async def get_categories(): - async with async_session() as session: - categories = await session.scalars(select(Category)) - return categories - - -async def get_items_by_category(category_id: int): - async with async_session() as session: - items = await session.scalars(select(Item).where(Item.category == category_id)) - return items - - -async def get_item_by_id(item_id: int): - async with async_session() as session: - item = await session.scalar(select(Item).where(Item.id == item_id)) - return item - - -async def delete_basket(tg_id, item_id): - async with async_session() as session: - user = await session.scalar(select(User).where(User.tg_id == tg_id)) - await session.execute(delete(Basket).where(Basket.user == user.id, Basket.item == item_id)) +from app.database.models import User, Category, Item, Basket +from app.database.models import async_session + +from sqlalchemy import select, update, delete + + +async def set_user(tg_id): + async with async_session() as session: + user = await session.scalar(select(User).where(User.tg_id == tg_id)) + + if not user: + session.add(User(tg_id=tg_id)) + await session.commit() + + +async def set_item(data): + async with async_session() as session: + session.add(Item(**data)) + await session.commit() + + +async def set_basket(tg_id, item_id): + async with async_session() as session: + user = await session.scalar(select(User).where(User.tg_id == tg_id)) + session.add(Basket(user=user.id, item=item_id)) + await session.commit() + + +async def get_basket(tg_id): + async with async_session() as session: + user = await session.scalar(select(User).where(User.tg_id == tg_id)) + basket = await session.scalars(select(Basket).where(Basket.user == user.id)) + return basket + + +async def get_users(): + async with async_session() as session: + users = await session.scalars(select(User)) + return users + + +async def get_categories(): + async with async_session() as session: + categories = await session.scalars(select(Category)) + return categories + + +async def get_items_by_category(category_id: int): + async with async_session() as session: + items = await session.scalars(select(Item).where(Item.category == category_id)) + return items + + +async def get_item_by_id(item_id: int): + async with async_session() as session: + item = await session.scalar(select(Item).where(Item.id == item_id)) + return item + + +async def delete_basket(tg_id, item_id): + async with async_session() as session: + user = await session.scalar(select(User).where(User.tg_id == tg_id)) + await session.execute(delete(Basket).where(Basket.user == user.id, Basket.item == item_id)) await session.commit() \ No newline at end of file diff --git a/doners/bot-market/app/handlers.py b/doners/bot-market/app/handlers.py index 64297ce..7c276a8 100644 --- a/doners/bot-market/app/handlers.py +++ b/doners/bot-market/app/handlers.py @@ -1,72 +1,72 @@ -from aiogram import Router, F -from aiogram.types import Message, CallbackQuery -from aiogram.filters import CommandStart, Command - -import app.keyboards as kb -from app.database.requests import (get_item_by_id, set_user, - set_basket, get_basket, get_item_by_id, delete_basket) - -router = Router() - - -@router.message(CommandStart()) -@router.callback_query(F.data == 'to_main') -async def cmd_start(message: Message | CallbackQuery): - if isinstance(message, Message): - await set_user(message.from_user.id) - await message.answer("Добро пожаловать в интернет магазин!", - reply_markup=kb.main) - else: - await message.answer('Вы вернулись на главную') - await message.message.answer("Добро пожаловать в интернет магазин!", - reply_markup=kb.main) - - -@router.callback_query(F.data == 'catalog') -async def catalog(callback: CallbackQuery): - await callback.answer('') - await callback.message.edit_text(text='Выберите категорию.', - reply_markup=await kb.categories()) - - -@router.callback_query(F.data.startswith('category_')) -async def category(callback: CallbackQuery): - await callback.answer('') - await callback.message.edit_text('Выберите товар', - reply_markup=await kb.items(callback.data.split('_')[1])) - - -@router.callback_query(F.data.startswith('item_')) -async def category(callback: CallbackQuery): - item = await get_item_by_id(callback.data.split('_')[1]) - await callback.answer('') - await callback.message.answer_photo(photo=item.photo, - caption=f'{item.name}\n\n{item.description}\n\nЦена: {item.price} рублей', - reply_markup=await kb.basket(item.id)) - - -@router.callback_query(F.data.startswith('order_')) -async def basket(callback: CallbackQuery): - await set_basket(callback.from_user.id, callback.data.split('_')[1]) - await callback.answer('Товар добавлен в корзину') - - -@router.callback_query(F.data == 'mybasket') -async def mybasket(callback: CallbackQuery): - await callback.answer('') - basket = await get_basket(callback.from_user.id) - counter = 0 - for item_info in basket: - item = await get_item_by_id(item_info.item) - await callback.message.answer_photo(photo=item.photo, - caption=f'{item.name}\n\n{item.description}\n\nЦена: {item.price} рублей', - reply_markup=await kb.delete_from_basket(item.id)) - counter += 1 - await callback.message.answer('Ваша корзина пуста') if counter == 0 else await callback.answer('') - - -@router.callback_query(F.data.startswith('delete_')) -async def delete_from_basket(callback: CallbackQuery): - await delete_basket(callback.from_user.id, callback.data.split('_')[1]) - await callback.message.delete() +from aiogram import Router, F +from aiogram.types import Message, CallbackQuery +from aiogram.filters import CommandStart, Command + +import app.keyboards as kb +from app.database.requests import (get_item_by_id, set_user, + set_basket, get_basket, get_item_by_id, delete_basket) + +router = Router() + + +@router.message(CommandStart()) +@router.callback_query(F.data == 'to_main') +async def cmd_start(message: Message | CallbackQuery): + if isinstance(message, Message): + await set_user(message.from_user.id) + await message.answer("Добро пожаловать в интернет магазин!", + reply_markup=kb.main) + else: + await message.answer('Вы вернулись на главную') + await message.message.answer("Добро пожаловать в интернет магазин!", + reply_markup=kb.main) + + +@router.callback_query(F.data == 'catalog') +async def catalog(callback: CallbackQuery): + await callback.answer('') + await callback.message.edit_text(text='Выберите категорию.', + reply_markup=await kb.categories()) + + +@router.callback_query(F.data.startswith('category_')) +async def category(callback: CallbackQuery): + await callback.answer('') + await callback.message.edit_text('Выберите товар', + reply_markup=await kb.items(callback.data.split('_')[1])) + + +@router.callback_query(F.data.startswith('item_')) +async def category(callback: CallbackQuery): + item = await get_item_by_id(callback.data.split('_')[1]) + await callback.answer('') + await callback.message.answer_photo(photo=item.photo, + caption=f'{item.name}\n\n{item.description}\n\nЦена: {item.price} рублей', + reply_markup=await kb.basket(item.id)) + + +@router.callback_query(F.data.startswith('order_')) +async def basket(callback: CallbackQuery): + await set_basket(callback.from_user.id, callback.data.split('_')[1]) + await callback.answer('Товар добавлен в корзину') + + +@router.callback_query(F.data == 'mybasket') +async def mybasket(callback: CallbackQuery): + await callback.answer('') + basket = await get_basket(callback.from_user.id) + counter = 0 + for item_info in basket: + item = await get_item_by_id(item_info.item) + await callback.message.answer_photo(photo=item.photo, + caption=f'{item.name}\n\n{item.description}\n\nЦена: {item.price} рублей', + reply_markup=await kb.delete_from_basket(item.id)) + counter += 1 + await callback.message.answer('Ваша корзина пуста') if counter == 0 else await callback.answer('') + + +@router.callback_query(F.data.startswith('delete_')) +async def delete_from_basket(callback: CallbackQuery): + await delete_basket(callback.from_user.id, callback.data.split('_')[1]) + await callback.message.delete() await callback.answer('Вы удалили товар из корзины') \ No newline at end of file diff --git a/doners/bot-market/app/keyboards.py b/doners/bot-market/app/keyboards.py index 4bd544e..6ee0d1a 100644 --- a/doners/bot-market/app/keyboards.py +++ b/doners/bot-market/app/keyboards.py @@ -1,49 +1,49 @@ - -from aiogram.types import (ReplyKeyboardMarkup, KeyboardButton, - InlineKeyboardMarkup, InlineKeyboardButton) -from aiogram.utils.keyboard import InlineKeyboardBuilder - -from app.database.requests import get_categories, get_items_by_category - -main = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='Каталог', callback_data='catalog')], - [InlineKeyboardButton(text='Корзина', callback_data='mybasket'), - InlineKeyboardButton(text='Контакты', callback_data='contacts')] -]) - -to_main = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text='На главную', callback_data='to_main')] -]) - - -async def delete_from_basket(order_id): - keyboard = InlineKeyboardBuilder() - keyboard.add(InlineKeyboardButton(text='Удалить из корзины', callback_data=f'delete_{order_id}')) - return keyboard.adjust(2).as_markup() - - -async def basket(order_id): - keyboard = InlineKeyboardBuilder() - keyboard.add(InlineKeyboardButton(text='Оформить заказ', callback_data=f'order_{order_id}')) - keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main')) - return keyboard.adjust(2).as_markup() - - -async def categories(): - all_categories = await get_categories() - keyboard = InlineKeyboardBuilder() - for category in all_categories: - keyboard.add(InlineKeyboardButton(text=category.name, - callback_data=f'category_{category.id}')) - keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main')) - return keyboard.adjust(2).as_markup() - - -async def items(category_id: int): - items = await get_items_by_category(category_id) - keyboard = InlineKeyboardBuilder() - for item in items: - keyboard.add(InlineKeyboardButton(text=item.name, - callback_data=f"item_{item.id}")) - keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main')) + +from aiogram.types import (ReplyKeyboardMarkup, KeyboardButton, + InlineKeyboardMarkup, InlineKeyboardButton) +from aiogram.utils.keyboard import InlineKeyboardBuilder + +from app.database.requests import get_categories, get_items_by_category + +main = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='Каталог', callback_data='catalog')], + [InlineKeyboardButton(text='Корзина', callback_data='mybasket'), + InlineKeyboardButton(text='Контакты', callback_data='contacts')] +]) + +to_main = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text='На главную', callback_data='to_main')] +]) + + +async def delete_from_basket(order_id): + keyboard = InlineKeyboardBuilder() + keyboard.add(InlineKeyboardButton(text='Удалить из корзины', callback_data=f'delete_{order_id}')) + return keyboard.adjust(2).as_markup() + + +async def basket(order_id): + keyboard = InlineKeyboardBuilder() + keyboard.add(InlineKeyboardButton(text='Оформить заказ', callback_data=f'order_{order_id}')) + keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main')) + return keyboard.adjust(2).as_markup() + + +async def categories(): + all_categories = await get_categories() + keyboard = InlineKeyboardBuilder() + for category in all_categories: + keyboard.add(InlineKeyboardButton(text=category.name, + callback_data=f'category_{category.id}')) + keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main')) + return keyboard.adjust(2).as_markup() + + +async def items(category_id: int): + items = await get_items_by_category(category_id) + keyboard = InlineKeyboardBuilder() + for item in items: + keyboard.add(InlineKeyboardButton(text=item.name, + callback_data=f"item_{item.id}")) + keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main')) return keyboard.adjust(2).as_markup() \ No newline at end of file diff --git a/doners/bot-market/config.py b/doners/bot-market/config.py index 613fb0f..45b7e6a 100644 --- a/doners/bot-market/config.py +++ b/doners/bot-market/config.py @@ -1,3 +1,3 @@ -TOKEN='7103505936:AAEpiQxlKNd9Uji9IziTdNzSTH38PavqXZM' -ENGINE='sqlite+aiosqlite:///db.sqlite3' +TOKEN='7103505936:AAEpiQxlKNd9Uji9IziTdNzSTH38PavqXZM' +ENGINE='sqlite+aiosqlite:///db.sqlite3' ECHO='' \ No newline at end of file diff --git a/doners/bot-market/run.py b/doners/bot-market/run.py index 04c0cb5..ed4f2a8 100644 --- a/doners/bot-market/run.py +++ b/doners/bot-market/run.py @@ -1,25 +1,25 @@ -import asyncio -import logging - -from aiogram import Bot, Dispatcher - -from config import TOKEN -from app.database.models import async_main -from app.handlers import router -from app.admin import admin - - -async def main(): - await async_main() - bot = Bot(token=TOKEN) - dp = Dispatcher() - dp.include_routers(admin, router) - await dp.start_polling(bot) - - -if __name__ == "__main__": - logging.basicConfig(level=logging.INFO) - try: - asyncio.run(main()) - except KeyboardInterrupt: +import asyncio +import logging + +from aiogram import Bot, Dispatcher + +from config import TOKEN +from app.database.models import async_main +from app.handlers import router +from app.admin import admin + + +async def main(): + await async_main() + bot = Bot(token=TOKEN) + dp = Dispatcher() + dp.include_routers(admin, router) + await dp.start_polling(bot) + + +if __name__ == "__main__": + logging.basicConfig(level=logging.INFO) + try: + asyncio.run(main()) + except KeyboardInterrupt: print('Exit') \ No newline at end of file diff --git a/doners/old/__init__.py b/doners/old/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/doners/old/bot(OLD).py b/doners/old/bot(OLD).py index 1325944..ff47519 100644 --- a/doners/old/bot(OLD).py +++ b/doners/old/bot(OLD).py @@ -1,103 +1,103 @@ -import os -import logging -from aiogram import Bot, Dispatcher, types -from aiogram.contrib.middlewares.logging import LoggingMiddleware -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton -from aiogram.utils import executor -from aiogram.dispatcher.filters import Text -from database import create_tables -from logger import log_action -from dotenv import load_dotenv - -# Загрузка переменных окружения из .env файла -load_dotenv() - -API_TOKEN = os.getenv('BOT_API_TOKEN') -OPERATORS_GROUP_ID = os.getenv('OPERATORS_GROUP_ID') -ADMIN_GROUP_ID = os.getenv('ADMIN_GROUP_ID') - -logging.basicConfig(level=logging.INFO) - -bot = Bot(token=API_TOKEN) -dp = Dispatcher(bot) -dp.middleware.setup(LoggingMiddleware()) - -# Inline клавиатуры -def main_menu_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data')) - keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order')) - keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history')) - keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call')) - keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator')) - return keyboard - -def account_data_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name')) - keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone')) - keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address')) - keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address')) - keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact')) - keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main')) - return keyboard - -def order_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton('Добавить учетные данные', callback_data='add_account_data')) - keyboard.add(InlineKeyboardButton('Выбрать адрес', callback_data='select_address')) - keyboard.add(InlineKeyboardButton('Выбрать время для уборки', callback_data='select_time')) - keyboard.add(InlineKeyboardButton('Выбрать тип уборки', callback_data='select_cleaning_type')) - keyboard.add(InlineKeyboardButton('Выбрать способ оплаты', callback_data='select_payment_method')) - keyboard.add(InlineKeyboardButton('Подтвердить заказ', callback_data='confirm_order')) - keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main')) - return keyboard - -# Обработчики команд -@dp.message_handler(commands=['start']) -async def send_welcome(message: types.Message): - await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard()) - log_action(message.from_user.id, 'start') - -@dp.callback_query_handler(lambda c: c.data == 'account_data') -async def process_account_data(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard()) - log_action(callback_query.from_user.id, 'account_data') - -@dp.callback_query_handler(lambda c: c.data == 'make_order') -async def process_make_order(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Сделать заказ:", reply_markup=order_keyboard()) - log_action(callback_query.from_user.id, 'make_order') - -@dp.callback_query_handler(lambda c: c.data == 'order_history') -async def process_order_history(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - # Добавьте логику для просмотра истории заказов - await bot.send_message(callback_query.from_user.id, "История заказов:") - log_action(callback_query.from_user.id, 'order_history') - -@dp.callback_query_handler(lambda c: c.data == 'request_call') -async def process_request_call(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - # Добавьте логику для заказа звонка - await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.") - log_action(callback_query.from_user.id, 'request_call') - -@dp.callback_query_handler(lambda c: c.data == 'talk_operator') -async def process_talk_operator(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - # Добавьте логику для начала разговора с оператором - await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.") - log_action(callback_query.from_user.id, 'talk_operator') - -@dp.callback_query_handler(lambda c: c.data == 'back_to_main') -async def process_back_to_main(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard()) - log_action(callback_query.from_user.id, 'back_to_main') - -if __name__ == '__main__': - create_tables() - executor.start_polling(dp, skip_updates=True) +import os +import logging +from aiogram import Bot, Dispatcher, types +from aiogram.contrib.middlewares.logging import LoggingMiddleware +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from aiogram.utils import executor +from aiogram.dispatcher.filters import Text +from database import create_tables +from logger import log_action +from dotenv import load_dotenv + +# Загрузка переменных окружения из .env файла +load_dotenv() + +API_TOKEN = os.getenv('BOT_API_TOKEN') +OPERATORS_GROUP_ID = os.getenv('OPERATORS_GROUP_ID') +ADMIN_GROUP_ID = os.getenv('ADMIN_GROUP_ID') + +logging.basicConfig(level=logging.INFO) + +bot = Bot(token=API_TOKEN) +dp = Dispatcher(bot) +dp.middleware.setup(LoggingMiddleware()) + +# Inline клавиатуры +def main_menu_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data')) + keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order')) + keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history')) + keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call')) + keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator')) + return keyboard + +def account_data_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name')) + keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone')) + keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address')) + keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address')) + keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact')) + keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main')) + return keyboard + +def order_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton('Добавить учетные данные', callback_data='add_account_data')) + keyboard.add(InlineKeyboardButton('Выбрать адрес', callback_data='select_address')) + keyboard.add(InlineKeyboardButton('Выбрать время для уборки', callback_data='select_time')) + keyboard.add(InlineKeyboardButton('Выбрать тип уборки', callback_data='select_cleaning_type')) + keyboard.add(InlineKeyboardButton('Выбрать способ оплаты', callback_data='select_payment_method')) + keyboard.add(InlineKeyboardButton('Подтвердить заказ', callback_data='confirm_order')) + keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main')) + return keyboard + +# Обработчики команд +@dp.message_handler(commands=['start']) +async def send_welcome(message: types.Message): + await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard()) + log_action(message.from_user.id, 'start') + +@dp.callback_query_handler(lambda c: c.data == 'account_data') +async def process_account_data(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard()) + log_action(callback_query.from_user.id, 'account_data') + +@dp.callback_query_handler(lambda c: c.data == 'make_order') +async def process_make_order(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Сделать заказ:", reply_markup=order_keyboard()) + log_action(callback_query.from_user.id, 'make_order') + +@dp.callback_query_handler(lambda c: c.data == 'order_history') +async def process_order_history(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + # Добавьте логику для просмотра истории заказов + await bot.send_message(callback_query.from_user.id, "История заказов:") + log_action(callback_query.from_user.id, 'order_history') + +@dp.callback_query_handler(lambda c: c.data == 'request_call') +async def process_request_call(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + # Добавьте логику для заказа звонка + await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.") + log_action(callback_query.from_user.id, 'request_call') + +@dp.callback_query_handler(lambda c: c.data == 'talk_operator') +async def process_talk_operator(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + # Добавьте логику для начала разговора с оператором + await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.") + log_action(callback_query.from_user.id, 'talk_operator') + +@dp.callback_query_handler(lambda c: c.data == 'back_to_main') +async def process_back_to_main(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard()) + log_action(callback_query.from_user.id, 'back_to_main') + +if __name__ == '__main__': + create_tables() + executor.start_polling(dp, skip_updates=True) diff --git a/doners/old/bot.py b/doners/old/bot.py index 491d866..67b3c1f 100644 --- a/doners/old/bot.py +++ b/doners/old/bot.py @@ -1,254 +1,254 @@ -import logging -import sqlite3 -from aiogram import types -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton -from aiogram.dispatcher import FSMContext -from aiogram.dispatcher.filters.state import State, StatesGroup -from aiogram.contrib.middlewares.logging import LoggingMiddleware -from aiogram.utils import executor -from aiogram import Bot, Dispatcher -from aiogram.dispatcher.filters import Text -from database import create_tables, add_user, update_user_name, update_user_phone, add_address, delete_address -from logger import log_action -from dotenv import load_dotenv -import os - -# Загрузка переменных окружения из .env файла -load_dotenv() - -API_TOKEN = os.getenv('BOT_API_TOKEN') -OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID')) -ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID')) - -logging.basicConfig(level=logging.INFO) - -bot = Bot(token=API_TOKEN) -dp = Dispatcher(bot) -dp.middleware.setup(LoggingMiddleware()) - -# Состояния -class OrderForm(StatesGroup): - waiting_for_name = State() - waiting_for_phone = State() - waiting_for_address = State() - waiting_for_cleaning_time = State() - waiting_for_cleaning_type = State() - waiting_for_payment_method = State() - confirmation = State() - -class CallRequest(StatesGroup): - waiting_for_name = State() - waiting_for_phone = State() - -class OperatorChat(StatesGroup): - in_chat = State() - -# Inline клавиатуры -def main_menu_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data')) - keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order')) - keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history')) - keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call')) - keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator')) - return keyboard - -def account_data_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name')) - keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone')) - keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address')) - keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address')) - keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact')) - keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main')) - return keyboard - -def order_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton('Добавить учетные данные', callback_data='add_account_data')) - keyboard.add(InlineKeyboardButton('Выбрать адрес', callback_data='select_address')) - keyboard.add(InlineKeyboardButton('Выбрать время для уборки', callback_data='select_time')) - keyboard.add(InlineKeyboardButton('Выбрать тип уборки', callback_data='select_cleaning_type')) - keyboard.add(InlineKeyboardButton('Выбрать способ оплаты', callback_data='select_payment_method')) - keyboard.add(InlineKeyboardButton('Подтвердить заказ', callback_data='confirm_order')) - keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main')) - return keyboard - -# Основные команды -@dp.message_handler(commands=['start']) -async def send_welcome(message: types.Message): - await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard()) - log_action(message.from_user.id, 'start') - -@dp.callback_query_handler(lambda c: c.data == 'account_data') -async def process_account_data(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard()) - log_action(callback_query.from_user.id, 'account_data') - -@dp.callback_query_handler(lambda c: c.data == 'make_order') -async def process_make_order(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Сделать заказ:", reply_markup=order_keyboard()) - log_action(callback_query.from_user.id, 'make_order') - -@dp.callback_query_handler(lambda c: c.data == 'order_history') -async def process_order_history(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - # Добавьте логику для просмотра истории заказов - await bot.send_message(callback_query.from_user.id, "История заказов:") - log_action(callback_query.from_user.id, 'order_history') - -@dp.callback_query_handler(lambda c: c.data == 'request_call') -async def process_request_call(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - # Добавьте логику для заказа звонка - await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.") - log_action(callback_query.from_user.id, 'request_call') - -@dp.callback_query_handler(lambda c: c.data == 'talk_operator') -async def process_talk_operator(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - # Добавьте логику для начала разговора с оператором - await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.") - log_action(callback_query.from_user.id, 'talk_operator') - -@dp.callback_query_handler(lambda c: c.data == 'back_to_main') -async def process_back_to_main(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard()) - log_action(callback_query.from_user.id, 'back_to_main') - -# Добавление учетных данных -@dp.callback_query_handler(lambda c: c.data == 'add_account_data') -async def process_add_account_data(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Введите ваше ФИО:") - await OrderForm.waiting_for_name.set() - -@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT) -async def process_name(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['name'] = message.text - await message.answer("Введите ваш номер телефона:") - await OrderForm.waiting_for_phone.set() - -@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT) -async def process_phone(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['phone'] = message.text - user_id = message.from_user.id - name = data['name'] - phone = data['phone'] - add_user(user_id, name, phone) - await message.answer("Ваши данные успешно сохранены!") - await state.finish() - -# Изменение учетных данных -@dp.callback_query_handler(lambda c: c.data == 'change_name') -async def process_change_name(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Введите новое ФИО:") - await OrderForm.waiting_for_name.set() - -@dp.callback_query_handler(lambda c: c.data == 'change_phone') -async def process_change_phone(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Введите новый номер телефона:") - await OrderForm.waiting_for_phone.set() - -# Добавление адреса -@dp.callback_query_handler(lambda c: c.data == 'add_address') -async def process_add_address(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Введите адрес для добавления:") - await OrderForm.waiting_for_address.set() - -@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT) -async def process_add_address_step(message: types.Message, state: FSMContext): - user_id = message.from_user.id - address = message.text - add_address(user_id, address) - await message.answer("Адрес успешно добавлен!") - await state.finish() - -# Удаление адреса -@dp.callback_query_handler(lambda c: c.data == 'delete_address') -async def process_delete_address(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Введите адрес для удаления:") - await OrderForm.waiting_for_address.set() - -@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT) -async def process_delete_address_step(message: types.Message, state: FSMContext): - user_id = message.from_user.id - address = message.text - delete_address(user_id, address) - await message.answer("Адрес успешно удален!") - await state.finish() - -# Обработка заказа -@dp.callback_query_handler(lambda c: c.data == 'select_address') -async def process_select_address(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Введите адрес для уборки:") - await OrderForm.waiting_for_address.set() - -@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT) -async def process_order_address(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['address'] = message.text - await message.answer("Выберите время для уборки (утро/день/вечер):") - await OrderForm.waiting_for_cleaning_time.set() - -@dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT) -async def process_cleaning_time(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['cleaning_time'] = message.text - await message.answer("Выберите тип уборки (влажная/сухая/генеральная):") - await OrderForm.waiting_for_cleaning_type.set() - -@dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT) -async def process_cleaning_type(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['cleaning_type'] = message.text - await message.answer("Выберите способ оплаты (картой/наличными):") - await OrderForm.waiting_for_payment_method.set() - -@dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT) -async def process_payment_method(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['payment_method'] = message.text - - # Отправка подтверждения заказа - await message.answer("Подтвердите заказ:\n" - f"Адрес: {data['address']}\n" - f"Время уборки: {data['cleaning_time']}\n" - f"Тип уборки: {data['cleaning_type']}\n" - f"Способ оплаты: {data['payment_method']}\n" - "Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order'))) - await OrderForm.confirmation.set() - -@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation) -async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext): - await bot.answer_callback_query(callback_query.id) - async with state.proxy() as data: - order_details = (f"Новый заказ:\n" - f"ФИО: {data['name']}\n" - f"Номер телефона: {data['phone']}\n" - f"Адрес: {data['address']}\n" - f"Время уборки: {data['cleaning_time']}\n" - f"Тип уборки: {data['cleaning_type']}\n" - f"Способ оплаты: {data['payment_method']}") - - await bot.send_message(OPERATORS_GROUP_ID, order_details) - log_action(callback_query.from_user.id, 'confirm_order') - - await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.") - await state.finish() - -# Основное приложение -if __name__ == '__main__': - create_tables() - executor.start_polling(dp, skip_updates=True) +import logging +import sqlite3 +from aiogram import types +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from aiogram.dispatcher import FSMContext +from aiogram.dispatcher.filters.state import State, StatesGroup +from aiogram.contrib.middlewares.logging import LoggingMiddleware +from aiogram.utils import executor +from aiogram import Bot, Dispatcher +from aiogram.dispatcher.filters import Text +from database import create_tables, add_user, update_user_name, update_user_phone, add_address, delete_address +from logger import log_action +from dotenv import load_dotenv +import os + +# Загрузка переменных окружения из .env файла +load_dotenv() + +API_TOKEN = os.getenv('BOT_API_TOKEN') +OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID')) +ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID')) + +logging.basicConfig(level=logging.INFO) + +bot = Bot(token=API_TOKEN) +dp = Dispatcher(bot) +dp.middleware.setup(LoggingMiddleware()) + +# Состояния +class OrderForm(StatesGroup): + waiting_for_name = State() + waiting_for_phone = State() + waiting_for_address = State() + waiting_for_cleaning_time = State() + waiting_for_cleaning_type = State() + waiting_for_payment_method = State() + confirmation = State() + +class CallRequest(StatesGroup): + waiting_for_name = State() + waiting_for_phone = State() + +class OperatorChat(StatesGroup): + in_chat = State() + +# Inline клавиатуры +def main_menu_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data')) + keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order')) + keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history')) + keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call')) + keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator')) + return keyboard + +def account_data_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name')) + keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone')) + keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address')) + keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address')) + keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact')) + keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main')) + return keyboard + +def order_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton('Добавить учетные данные', callback_data='add_account_data')) + keyboard.add(InlineKeyboardButton('Выбрать адрес', callback_data='select_address')) + keyboard.add(InlineKeyboardButton('Выбрать время для уборки', callback_data='select_time')) + keyboard.add(InlineKeyboardButton('Выбрать тип уборки', callback_data='select_cleaning_type')) + keyboard.add(InlineKeyboardButton('Выбрать способ оплаты', callback_data='select_payment_method')) + keyboard.add(InlineKeyboardButton('Подтвердить заказ', callback_data='confirm_order')) + keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main')) + return keyboard + +# Основные команды +@dp.message_handler(commands=['start']) +async def send_welcome(message: types.Message): + await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard()) + log_action(message.from_user.id, 'start') + +@dp.callback_query_handler(lambda c: c.data == 'account_data') +async def process_account_data(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard()) + log_action(callback_query.from_user.id, 'account_data') + +@dp.callback_query_handler(lambda c: c.data == 'make_order') +async def process_make_order(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Сделать заказ:", reply_markup=order_keyboard()) + log_action(callback_query.from_user.id, 'make_order') + +@dp.callback_query_handler(lambda c: c.data == 'order_history') +async def process_order_history(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + # Добавьте логику для просмотра истории заказов + await bot.send_message(callback_query.from_user.id, "История заказов:") + log_action(callback_query.from_user.id, 'order_history') + +@dp.callback_query_handler(lambda c: c.data == 'request_call') +async def process_request_call(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + # Добавьте логику для заказа звонка + await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.") + log_action(callback_query.from_user.id, 'request_call') + +@dp.callback_query_handler(lambda c: c.data == 'talk_operator') +async def process_talk_operator(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + # Добавьте логику для начала разговора с оператором + await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.") + log_action(callback_query.from_user.id, 'talk_operator') + +@dp.callback_query_handler(lambda c: c.data == 'back_to_main') +async def process_back_to_main(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard()) + log_action(callback_query.from_user.id, 'back_to_main') + +# Добавление учетных данных +@dp.callback_query_handler(lambda c: c.data == 'add_account_data') +async def process_add_account_data(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Введите ваше ФИО:") + await OrderForm.waiting_for_name.set() + +@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT) +async def process_name(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['name'] = message.text + await message.answer("Введите ваш номер телефона:") + await OrderForm.waiting_for_phone.set() + +@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT) +async def process_phone(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['phone'] = message.text + user_id = message.from_user.id + name = data['name'] + phone = data['phone'] + add_user(user_id, name, phone) + await message.answer("Ваши данные успешно сохранены!") + await state.finish() + +# Изменение учетных данных +@dp.callback_query_handler(lambda c: c.data == 'change_name') +async def process_change_name(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Введите новое ФИО:") + await OrderForm.waiting_for_name.set() + +@dp.callback_query_handler(lambda c: c.data == 'change_phone') +async def process_change_phone(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Введите новый номер телефона:") + await OrderForm.waiting_for_phone.set() + +# Добавление адреса +@dp.callback_query_handler(lambda c: c.data == 'add_address') +async def process_add_address(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Введите адрес для добавления:") + await OrderForm.waiting_for_address.set() + +@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT) +async def process_add_address_step(message: types.Message, state: FSMContext): + user_id = message.from_user.id + address = message.text + add_address(user_id, address) + await message.answer("Адрес успешно добавлен!") + await state.finish() + +# Удаление адреса +@dp.callback_query_handler(lambda c: c.data == 'delete_address') +async def process_delete_address(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Введите адрес для удаления:") + await OrderForm.waiting_for_address.set() + +@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT) +async def process_delete_address_step(message: types.Message, state: FSMContext): + user_id = message.from_user.id + address = message.text + delete_address(user_id, address) + await message.answer("Адрес успешно удален!") + await state.finish() + +# Обработка заказа +@dp.callback_query_handler(lambda c: c.data == 'select_address') +async def process_select_address(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Введите адрес для уборки:") + await OrderForm.waiting_for_address.set() + +@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT) +async def process_order_address(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['address'] = message.text + await message.answer("Выберите время для уборки (утро/день/вечер):") + await OrderForm.waiting_for_cleaning_time.set() + +@dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT) +async def process_cleaning_time(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['cleaning_time'] = message.text + await message.answer("Выберите тип уборки (влажная/сухая/генеральная):") + await OrderForm.waiting_for_cleaning_type.set() + +@dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT) +async def process_cleaning_type(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['cleaning_type'] = message.text + await message.answer("Выберите способ оплаты (картой/наличными):") + await OrderForm.waiting_for_payment_method.set() + +@dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT) +async def process_payment_method(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['payment_method'] = message.text + + # Отправка подтверждения заказа + await message.answer("Подтвердите заказ:\n" + f"Адрес: {data['address']}\n" + f"Время уборки: {data['cleaning_time']}\n" + f"Тип уборки: {data['cleaning_type']}\n" + f"Способ оплаты: {data['payment_method']}\n" + "Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.", + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order'))) + await OrderForm.confirmation.set() + +@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation) +async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext): + await bot.answer_callback_query(callback_query.id) + async with state.proxy() as data: + order_details = (f"Новый заказ:\n" + f"ФИО: {data['name']}\n" + f"Номер телефона: {data['phone']}\n" + f"Адрес: {data['address']}\n" + f"Время уборки: {data['cleaning_time']}\n" + f"Тип уборки: {data['cleaning_type']}\n" + f"Способ оплаты: {data['payment_method']}") + + await bot.send_message(OPERATORS_GROUP_ID, order_details) + log_action(callback_query.from_user.id, 'confirm_order') + + await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.") + await state.finish() + +# Основное приложение +if __name__ == '__main__': + create_tables() + executor.start_polling(dp, skip_updates=True) diff --git a/doners/old/database.py b/doners/old/database.py index 0ce6d10..01fe61d 100644 --- a/doners/old/database.py +++ b/doners/old/database.py @@ -1,89 +1,89 @@ -import sqlite3 - - -def create_tables(): - conn = sqlite3.connect('bot_klining.db') - cursor = conn.cursor() - - cursor.execute(''' - CREATE TABLE IF NOT EXISTS users ( - id INTEGER PRIMARY KEY, - telegram_id INTEGER UNIQUE, - name TEXT, - phone TEXT - ) - ''') - - cursor.execute(''' - CREATE TABLE IF NOT EXISTS addresses ( - id INTEGER PRIMARY KEY, - user_id INTEGER, - address TEXT, - FOREIGN KEY(user_id) REFERENCES users(id) - ) - ''') - - cursor.execute(''' - CREATE TABLE IF NOT EXISTS orders ( - id INTEGER PRIMARY KEY, - user_id INTEGER, - address TEXT, - cleaning_time TEXT, - cleaning_type TEXT, - payment_method TEXT, - order_id TEXT UNIQUE, - FOREIGN KEY(user_id) REFERENCES users(id) - ) - ''') - - cursor.execute(''' - CREATE TABLE IF NOT EXISTS logs ( - id INTEGER PRIMARY KEY, - user_id INTEGER, - action TEXT, - timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY(user_id) REFERENCES users(id) - ) - ''') - - conn.commit() - conn.close() - -def add_user(telegram_id, name, phone): - conn = sqlite3.connect('bot_klining.db') - cursor = conn.cursor() - cursor.execute('INSERT OR IGNORE INTO users (telegram_id, name, phone) VALUES (?, ?, ?)', (telegram_id, name, phone)) - conn.commit() - conn.close() - -def update_user_name(telegram_id, name): - conn = sqlite3.connect('bot_klining.db') - cursor = conn.cursor() - cursor.execute('UPDATE users SET name = ? WHERE telegram_id = ?', (name, telegram_id)) - conn.commit() - conn.close() - -def update_user_phone(telegram_id, phone): - conn = sqlite3.connect('bot_klining.db') - cursor = conn.cursor() - cursor.execute('UPDATE users SET phone = ? WHERE telegram_id = ?', (phone, telegram_id)) - conn.commit() - conn.close() - -def add_address(telegram_id, address): - conn = sqlite3.connect('bot_klining.db') - cursor = conn.cursor() - cursor.execute('SELECT id FROM users WHERE telegram_id = ?', (telegram_id,)) - user_id = cursor.fetchone()[0] - cursor.execute('INSERT INTO addresses (user_id, address) VALUES (?, ?)', (user_id, address)) - conn.commit() - conn.close() - -def delete_address(telegram_id, address): - conn = sqlite3.connect('bot_klining.db') - cursor = conn.cursor() - cursor.execute('SELECT id FROM users WHERE telegram_id = ?', (telegram_id,)) - user_id = cursor.fetchone()[0] - cursor.execute('DELETE FROM addresses WHERE user_id = ? AND address = ?', (user_id, address)) - conn.commit() +import sqlite3 + + +def create_tables(): + conn = sqlite3.connect('bot_klining.db') + cursor = conn.cursor() + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + id INTEGER PRIMARY KEY, + telegram_id INTEGER UNIQUE, + name TEXT, + phone TEXT + ) + ''') + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS addresses ( + id INTEGER PRIMARY KEY, + user_id INTEGER, + address TEXT, + FOREIGN KEY(user_id) REFERENCES users(id) + ) + ''') + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS orders ( + id INTEGER PRIMARY KEY, + user_id INTEGER, + address TEXT, + cleaning_time TEXT, + cleaning_type TEXT, + payment_method TEXT, + order_id TEXT UNIQUE, + FOREIGN KEY(user_id) REFERENCES users(id) + ) + ''') + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS logs ( + id INTEGER PRIMARY KEY, + user_id INTEGER, + action TEXT, + timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY(user_id) REFERENCES users(id) + ) + ''') + + conn.commit() + conn.close() + +def add_user(telegram_id, name, phone): + conn = sqlite3.connect('bot_klining.db') + cursor = conn.cursor() + cursor.execute('INSERT OR IGNORE INTO users (telegram_id, name, phone) VALUES (?, ?, ?)', (telegram_id, name, phone)) + conn.commit() + conn.close() + +def update_user_name(telegram_id, name): + conn = sqlite3.connect('bot_klining.db') + cursor = conn.cursor() + cursor.execute('UPDATE users SET name = ? WHERE telegram_id = ?', (name, telegram_id)) + conn.commit() + conn.close() + +def update_user_phone(telegram_id, phone): + conn = sqlite3.connect('bot_klining.db') + cursor = conn.cursor() + cursor.execute('UPDATE users SET phone = ? WHERE telegram_id = ?', (phone, telegram_id)) + conn.commit() + conn.close() + +def add_address(telegram_id, address): + conn = sqlite3.connect('bot_klining.db') + cursor = conn.cursor() + cursor.execute('SELECT id FROM users WHERE telegram_id = ?', (telegram_id,)) + user_id = cursor.fetchone()[0] + cursor.execute('INSERT INTO addresses (user_id, address) VALUES (?, ?)', (user_id, address)) + conn.commit() + conn.close() + +def delete_address(telegram_id, address): + conn = sqlite3.connect('bot_klining.db') + cursor = conn.cursor() + cursor.execute('SELECT id FROM users WHERE telegram_id = ?', (telegram_id,)) + user_id = cursor.fetchone()[0] + cursor.execute('DELETE FROM addresses WHERE user_id = ? AND address = ?', (user_id, address)) + conn.commit() conn.close() \ No newline at end of file diff --git a/doners/old/handlers.py b/doners/old/handlers.py index dce24e5..da486d9 100644 --- a/doners/old/handlers.py +++ b/doners/old/handlers.py @@ -1,183 +1,183 @@ -import logging -import sqlite3 -from aiogram import types -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton -from aiogram.dispatcher import FSMContext -from aiogram.dispatcher.filters.state import State, StatesGroup -from aiogram.contrib.middlewares.logging import LoggingMiddleware -from aiogram.utils import executor -from aiogram import Bot, Dispatcher -from aiogram.dispatcher.filters import Text -from database import create_tables, add_user, update_user_name, update_user_phone, add_address, delete_address, add_order -from logger import log_action -from dotenv import load_dotenv -import os - -# Загрузка переменных окружения из .env файла -load_dotenv() - -API_TOKEN = os.getenv('BOT_API_TOKEN') -OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID')) -ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID')) - -logging.basicConfig(level=logging.INFO) - -bot = Bot(token=API_TOKEN) -dp = Dispatcher(bot) -dp.middleware.setup(LoggingMiddleware()) - -# Состояния для заказа -class OrderForm(StatesGroup): - waiting_for_name = State() - waiting_for_phone = State() - waiting_for_address = State() - waiting_for_cleaning_time = State() - waiting_for_cleaning_type = State() - waiting_for_payment_method = State() - confirmation = State() - -# Inline клавиатуры -def main_menu_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data')) - keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order')) - keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history')) - keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call')) - keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator')) - return keyboard - -def account_data_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name')) - keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone')) - keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address')) - keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address')) - keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact')) - keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main')) - return keyboard - -# Основные команды -@dp.message_handler(commands=['start']) -async def send_welcome(message: types.Message): - await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard()) - log_action(message.from_user.id, 'start') - -@dp.callback_query_handler(lambda c: c.data == 'account_data') -async def process_account_data(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard()) - log_action(callback_query.from_user.id, 'account_data') - -@dp.callback_query_handler(lambda c: c.data == 'make_order') -async def process_make_order(callback_query: types.CallbackQuery, state: FSMContext): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Начнем с ваших учетных данных. Введите ваше ФИО:") - await OrderForm.waiting_for_name.set() - log_action(callback_query.from_user.id, 'make_order') - -@dp.callback_query_handler(lambda c: c.data == 'order_history') -async def process_order_history(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - # Добавьте логику для просмотра истории заказов - await bot.send_message(callback_query.from_user.id, "История заказов:") - log_action(callback_query.from_user.id, 'order_history') - -@dp.callback_query_handler(lambda c: c.data == 'request_call') -async def process_request_call(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - # Добавьте логику для заказа звонка - await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.") - log_action(callback_query.from_user.id, 'request_call') - -@dp.callback_query_handler(lambda c: c.data == 'talk_operator') -async def process_talk_operator(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - # Добавьте логику для начала разговора с оператором - await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.") - log_action(callback_query.from_user.id, 'talk_operator') - -@dp.callback_query_handler(lambda c: c.data == 'back_to_main') -async def process_back_to_main(callback_query: types.CallbackQuery): - await bot.answer_callback_query(callback_query.id) - await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard()) - log_action(callback_query.from_user.id, 'back_to_main') - -# Обработка шагов заказа -@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT) -async def process_name(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['name'] = message.text - await message.answer("Введите ваш номер телефона:") - await OrderForm.waiting_for_phone.set() - -@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT) -async def process_phone(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['phone'] = message.text - await message.answer("Введите адрес для уборки:") - await OrderForm.waiting_for_address.set() - -@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT) -async def process_order_address(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['address'] = message.text - await message.answer("Выберите время для уборки (утро/день/вечер):") - await OrderForm.waiting_for_cleaning_time.set() - -@dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT) -async def process_cleaning_time(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['cleaning_time'] = message.text - await message.answer("Выберите тип уборки (влажная/сухая/генеральная):") - await OrderForm.waiting_for_cleaning_type.set() - -@dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT) -async def process_cleaning_type(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['cleaning_type'] = message.text - await message.answer("Выберите способ оплаты (картой/наличными):") - await OrderForm.waiting_for_payment_method.set() - -@dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT) -async def process_payment_method(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['payment_method'] = message.text - - # Отправка подтверждения заказа - await message.answer("Подтвердите заказ:\n" - f"ФИО: {data['name']}\n" - f"Номер телефона: {data['phone']}\n" - f"Адрес: {data['address']}\n" - f"Время уборки: {data['cleaning_time']}\n" - f"Тип уборки: {data['cleaning_type']}\n" - f"Способ оплаты: {data['payment_method']}\n" - "Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.", - reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order'))) - await OrderForm.confirmation.set() - -@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation) -async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext): - await bot.answer_callback_query(callback_query.id) - async with state.proxy() as data: - order_details = (f"Новый заказ:\n" - f"ФИО: {data['name']}\n" - f"Номер телефона: {data['phone']}\n" - f"Адрес: {data['address']}\n" - f"Время уборки: {data['cleaning_time']}\n" - f"Тип уборки: {data['cleaning_type']}\n" - f"Способ оплаты: {data['payment_method']}") - - # Отправка заказа операторам - await bot.send_message(OPERATORS_GROUP_ID, order_details) - log_action(callback_query.from_user.id, 'confirm_order') - - # Сохранение заказа в базе данных - add_order(data['name'], data['phone'], data['address'], data['cleaning_time'], data['cleaning_type'], data['payment_method']) - - await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.") - await state.finish() - -# Основное приложение -if __name__ == '__main__': - create_tables() - executor.start_polling(dp, skip_updates=True) +import logging +import sqlite3 +from aiogram import types +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from aiogram.dispatcher import FSMContext +from aiogram.dispatcher.filters.state import State, StatesGroup +from aiogram.contrib.middlewares.logging import LoggingMiddleware +from aiogram.utils import executor +from aiogram import Bot, Dispatcher +from aiogram.dispatcher.filters import Text +from database import create_tables, add_user, update_user_name, update_user_phone, add_address, delete_address, add_order +from logger import log_action +from dotenv import load_dotenv +import os + +# Загрузка переменных окружения из .env файла +load_dotenv() + +API_TOKEN = os.getenv('BOT_API_TOKEN') +OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID')) +ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID')) + +logging.basicConfig(level=logging.INFO) + +bot = Bot(token=API_TOKEN) +dp = Dispatcher(bot) +dp.middleware.setup(LoggingMiddleware()) + +# Состояния для заказа +class OrderForm(StatesGroup): + waiting_for_name = State() + waiting_for_phone = State() + waiting_for_address = State() + waiting_for_cleaning_time = State() + waiting_for_cleaning_type = State() + waiting_for_payment_method = State() + confirmation = State() + +# Inline клавиатуры +def main_menu_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data')) + keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order')) + keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history')) + keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call')) + keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator')) + return keyboard + +def account_data_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name')) + keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone')) + keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address')) + keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address')) + keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact')) + keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main')) + return keyboard + +# Основные команды +@dp.message_handler(commands=['start']) +async def send_welcome(message: types.Message): + await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard()) + log_action(message.from_user.id, 'start') + +@dp.callback_query_handler(lambda c: c.data == 'account_data') +async def process_account_data(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard()) + log_action(callback_query.from_user.id, 'account_data') + +@dp.callback_query_handler(lambda c: c.data == 'make_order') +async def process_make_order(callback_query: types.CallbackQuery, state: FSMContext): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Начнем с ваших учетных данных. Введите ваше ФИО:") + await OrderForm.waiting_for_name.set() + log_action(callback_query.from_user.id, 'make_order') + +@dp.callback_query_handler(lambda c: c.data == 'order_history') +async def process_order_history(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + # Добавьте логику для просмотра истории заказов + await bot.send_message(callback_query.from_user.id, "История заказов:") + log_action(callback_query.from_user.id, 'order_history') + +@dp.callback_query_handler(lambda c: c.data == 'request_call') +async def process_request_call(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + # Добавьте логику для заказа звонка + await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.") + log_action(callback_query.from_user.id, 'request_call') + +@dp.callback_query_handler(lambda c: c.data == 'talk_operator') +async def process_talk_operator(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + # Добавьте логику для начала разговора с оператором + await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.") + log_action(callback_query.from_user.id, 'talk_operator') + +@dp.callback_query_handler(lambda c: c.data == 'back_to_main') +async def process_back_to_main(callback_query: types.CallbackQuery): + await bot.answer_callback_query(callback_query.id) + await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard()) + log_action(callback_query.from_user.id, 'back_to_main') + +# Обработка шагов заказа +@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT) +async def process_name(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['name'] = message.text + await message.answer("Введите ваш номер телефона:") + await OrderForm.waiting_for_phone.set() + +@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT) +async def process_phone(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['phone'] = message.text + await message.answer("Введите адрес для уборки:") + await OrderForm.waiting_for_address.set() + +@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT) +async def process_order_address(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['address'] = message.text + await message.answer("Выберите время для уборки (утро/день/вечер):") + await OrderForm.waiting_for_cleaning_time.set() + +@dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT) +async def process_cleaning_time(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['cleaning_time'] = message.text + await message.answer("Выберите тип уборки (влажная/сухая/генеральная):") + await OrderForm.waiting_for_cleaning_type.set() + +@dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT) +async def process_cleaning_type(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['cleaning_type'] = message.text + await message.answer("Выберите способ оплаты (картой/наличными):") + await OrderForm.waiting_for_payment_method.set() + +@dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT) +async def process_payment_method(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['payment_method'] = message.text + + # Отправка подтверждения заказа + await message.answer("Подтвердите заказ:\n" + f"ФИО: {data['name']}\n" + f"Номер телефона: {data['phone']}\n" + f"Адрес: {data['address']}\n" + f"Время уборки: {data['cleaning_time']}\n" + f"Тип уборки: {data['cleaning_type']}\n" + f"Способ оплаты: {data['payment_method']}\n" + "Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.", + reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order'))) + await OrderForm.confirmation.set() + +@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation) +async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext): + await bot.answer_callback_query(callback_query.id) + async with state.proxy() as data: + order_details = (f"Новый заказ:\n" + f"ФИО: {data['name']}\n" + f"Номер телефона: {data['phone']}\n" + f"Адрес: {data['address']}\n" + f"Время уборки: {data['cleaning_time']}\n" + f"Тип уборки: {data['cleaning_type']}\n" + f"Способ оплаты: {data['payment_method']}") + + # Отправка заказа операторам + await bot.send_message(OPERATORS_GROUP_ID, order_details) + log_action(callback_query.from_user.id, 'confirm_order') + + # Сохранение заказа в базе данных + add_order(data['name'], data['phone'], data['address'], data['cleaning_time'], data['cleaning_type'], data['payment_method']) + + await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.") + await state.finish() + +# Основное приложение +if __name__ == '__main__': + create_tables() + executor.start_polling(dp, skip_updates=True) diff --git a/doners/old/klining_order.py b/doners/old/klining_order.py index 0fdc074..cd80290 100644 --- a/doners/old/klining_order.py +++ b/doners/old/klining_order.py @@ -1,185 +1,185 @@ -import logging -import sqlite3 -import asyncio -from aiogram import Bot, Dispatcher, types -from aiogram.contrib.middlewares.logging import LoggingMiddleware -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton - -# Устанавливаем уровень логов -logging.basicConfig(level=logging.INFO) - -# Подключаемся к базе данных SQLite -conn = sqlite3.connect("answers.db") -cursor = conn.cursor() - -# Создание таблицы для хранения ответов -cursor.execute(""" -CREATE TABLE IF NOT EXISTS answers ( - user_id INTEGER, - question_id INTEGER, - answer TEXT -) -""") -conn.commit() - -# Заменить на свой токен -API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE' -GROUP_ID = '-1001961537659' - -# Инициализация бота и диспетчера -bot = Bot(token=API_TOKEN) -dp = Dispatcher(bot) -dp.middleware.setup(LoggingMiddleware()) - -# Словарь для хранения вопросов и ответов -questions = { - 1: "Как вас зовут?", - 2: "Укажите номер телефона для связи", - 3: "Укажите район, улицу, дом", - 4: "Какая уборка нужна, влажная или сухая?", - 5: "На какое время?", - 6: "Оплата наличными или картой?" -} - -answer_map = { - 'wet_cleaning': 'Влажная уборка', - 'dry_cleaning': 'Сухая уборка', - 'morning_time': 'Утро', - 'day_time': 'День', - 'evening_time': 'Вечер', - 'cash_payment': 'Наличные', - 'card_payment': 'Карта' -} - -# Обработчик команды /start -@dp.message_handler(commands=['start']) -async def start(message: types.Message): - await message.answer("Привет! Я задам тебе 6 вопросов. Давай начнем.") - await ask_question(message.chat.id, 1) - -# Функция для задания вопроса -async def ask_question(user_id, question_id): - if question_id in [4, 5, 6]: - if question_id == 4: - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")], - [InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")] - ]) - elif question_id == 5: - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Утро", callback_data="morning_time")], - [InlineKeyboardButton(text="День", callback_data="day_time")], - [InlineKeyboardButton(text="Вечер", callback_data="evening_time")] - ]) - elif question_id == 6: - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Наличные", callback_data="cash_payment")], - [InlineKeyboardButton(text="Карта", callback_data="card_payment")] - ]) - await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard) - else: - await bot.send_message(user_id, text=questions[question_id]) - -# Обработчик текстовых ответов на первые три вопроса -@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"]) -async def handle_text_answer(message: types.Message): - user_id = message.chat.id - cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,)) - prev_question_id = cursor.fetchone()[0] - - if prev_question_id is None: - question_id = 1 - else: - question_id = prev_question_id + 1 - answer = message.text - - await save_answer(user_id, question_id, answer) - - if question_id < 6: - await ask_question(user_id, question_id + 1) - else: - await show_confirmation_options(user_id) - -# Обработчик inline-кнопок -@dp.callback_query_handler(lambda query: query.data in answer_map.keys()) -async def handle_callback_answer(query: types.CallbackQuery): - user_id = query.from_user.id - cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,)) - prev_question_id = cursor.fetchone()[0] - - if prev_question_id is None: - question_id = 1 - else: - question_id = prev_question_id + 1 - - await save_answer(user_id, question_id, answer_map[query.data]) - await query.answer() - - if question_id < 6: - await ask_question(user_id, question_id + 1) - else: - await show_confirmation_options(user_id) - -# Функция для сохранения ответа в базе данных -async def save_answer(user_id, question_id, answer): - cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer)) - conn.commit() - -# Функция для отображения опций подтверждения -async def show_confirmation_options(user_id): - cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,)) - answers = cursor.fetchall() - answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)]) - await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}") - - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Отправить", callback_data="send_answers")], - [InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")] - ]) - await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard) - -# Обработчик выбора действия подтверждения -@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers']) -async def handle_confirmation(query: types.CallbackQuery): - user_id = query.from_user.id - if query.data == "send_answers": - await send_answers_to_group(user_id) - elif query.data == "rewrite_answers": - await rewrite_answers(user_id) - -# Функция для отправки ответов в группу -async def send_answers_to_group(user_id): - cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,)) - answers = cursor.fetchall() - - if answers: - answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in answers]) - - # Очищаем таблицу с ответами - cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,)) - conn.commit() - - # Отправляем ответы в группу - await bot.send_message(GROUP_ID, answer_text) - await bot.send_message(user_id, "Ваши ответы отправлены.") - else: - await bot.send_message(user_id, "Ответов нет") - -# Функция для переписывания ответов -async def rewrite_answers(user_id): - # Очищаем таблицу с ответами - cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,)) - conn.commit() - - await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.") - await ask_question(user_id, 1) - -if __name__ == '__main__': - loop = asyncio.get_event_loop() - try: - loop.create_task(dp.start_polling()) - loop.run_forever() - except KeyboardInterrupt: - pass - finally: - conn.close() +import logging +import sqlite3 +import asyncio +from aiogram import Bot, Dispatcher, types +from aiogram.contrib.middlewares.logging import LoggingMiddleware +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton + +# Устанавливаем уровень логов +logging.basicConfig(level=logging.INFO) + +# Подключаемся к базе данных SQLite +conn = sqlite3.connect("answers.db") +cursor = conn.cursor() + +# Создание таблицы для хранения ответов +cursor.execute(""" +CREATE TABLE IF NOT EXISTS answers ( + user_id INTEGER, + question_id INTEGER, + answer TEXT +) +""") +conn.commit() + +# Заменить на свой токен +API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE' +GROUP_ID = '-1001961537659' + +# Инициализация бота и диспетчера +bot = Bot(token=API_TOKEN) +dp = Dispatcher(bot) +dp.middleware.setup(LoggingMiddleware()) + +# Словарь для хранения вопросов и ответов +questions = { + 1: "Как вас зовут?", + 2: "Укажите номер телефона для связи", + 3: "Укажите район, улицу, дом", + 4: "Какая уборка нужна, влажная или сухая?", + 5: "На какое время?", + 6: "Оплата наличными или картой?" +} + +answer_map = { + 'wet_cleaning': 'Влажная уборка', + 'dry_cleaning': 'Сухая уборка', + 'morning_time': 'Утро', + 'day_time': 'День', + 'evening_time': 'Вечер', + 'cash_payment': 'Наличные', + 'card_payment': 'Карта' +} + +# Обработчик команды /start +@dp.message_handler(commands=['start']) +async def start(message: types.Message): + await message.answer("Привет! Я задам тебе 6 вопросов. Давай начнем.") + await ask_question(message.chat.id, 1) + +# Функция для задания вопроса +async def ask_question(user_id, question_id): + if question_id in [4, 5, 6]: + if question_id == 4: + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")], + [InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")] + ]) + elif question_id == 5: + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Утро", callback_data="morning_time")], + [InlineKeyboardButton(text="День", callback_data="day_time")], + [InlineKeyboardButton(text="Вечер", callback_data="evening_time")] + ]) + elif question_id == 6: + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Наличные", callback_data="cash_payment")], + [InlineKeyboardButton(text="Карта", callback_data="card_payment")] + ]) + await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard) + else: + await bot.send_message(user_id, text=questions[question_id]) + +# Обработчик текстовых ответов на первые три вопроса +@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"]) +async def handle_text_answer(message: types.Message): + user_id = message.chat.id + cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,)) + prev_question_id = cursor.fetchone()[0] + + if prev_question_id is None: + question_id = 1 + else: + question_id = prev_question_id + 1 + answer = message.text + + await save_answer(user_id, question_id, answer) + + if question_id < 6: + await ask_question(user_id, question_id + 1) + else: + await show_confirmation_options(user_id) + +# Обработчик inline-кнопок +@dp.callback_query_handler(lambda query: query.data in answer_map.keys()) +async def handle_callback_answer(query: types.CallbackQuery): + user_id = query.from_user.id + cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,)) + prev_question_id = cursor.fetchone()[0] + + if prev_question_id is None: + question_id = 1 + else: + question_id = prev_question_id + 1 + + await save_answer(user_id, question_id, answer_map[query.data]) + await query.answer() + + if question_id < 6: + await ask_question(user_id, question_id + 1) + else: + await show_confirmation_options(user_id) + +# Функция для сохранения ответа в базе данных +async def save_answer(user_id, question_id, answer): + cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer)) + conn.commit() + +# Функция для отображения опций подтверждения +async def show_confirmation_options(user_id): + cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,)) + answers = cursor.fetchall() + answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)]) + await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}") + + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Отправить", callback_data="send_answers")], + [InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")] + ]) + await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard) + +# Обработчик выбора действия подтверждения +@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers']) +async def handle_confirmation(query: types.CallbackQuery): + user_id = query.from_user.id + if query.data == "send_answers": + await send_answers_to_group(user_id) + elif query.data == "rewrite_answers": + await rewrite_answers(user_id) + +# Функция для отправки ответов в группу +async def send_answers_to_group(user_id): + cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,)) + answers = cursor.fetchall() + + if answers: + answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in answers]) + + # Очищаем таблицу с ответами + cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,)) + conn.commit() + + # Отправляем ответы в группу + await bot.send_message(GROUP_ID, answer_text) + await bot.send_message(user_id, "Ваши ответы отправлены.") + else: + await bot.send_message(user_id, "Ответов нет") + +# Функция для переписывания ответов +async def rewrite_answers(user_id): + # Очищаем таблицу с ответами + cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,)) + conn.commit() + + await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.") + await ask_question(user_id, 1) + +if __name__ == '__main__': + loop = asyncio.get_event_loop() + try: + loop.create_task(dp.start_polling()) + loop.run_forever() + except KeyboardInterrupt: + pass + finally: + conn.close() diff --git a/doners/old/logger.py b/doners/old/logger.py index 64bf2c3..0022eac 100644 --- a/doners/old/logger.py +++ b/doners/old/logger.py @@ -1,17 +1,17 @@ -import logging -import sqlite3 - -# Настройка логирования -logging.basicConfig(filename='bot.log', level=logging.INFO, - format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') - -def log_action(user_id, action, details=""): - logging.info(f"User ID: {user_id} - Action: {action} - Details: {details}") - - -def log_action(user_id, action): - conn = sqlite3.connect('bot_klining.db') - cursor = conn.cursor() - cursor.execute('INSERT INTO logs (user_id, action) VALUES ((SELECT id FROM users WHERE telegram_id = ?), ?)', (user_id, action)) - conn.commit() - conn.close() +import logging +import sqlite3 + +# Настройка логирования +logging.basicConfig(filename='bot.log', level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') + +def log_action(user_id, action, details=""): + logging.info(f"User ID: {user_id} - Action: {action} - Details: {details}") + + +def log_action(user_id, action): + conn = sqlite3.connect('bot_klining.db') + cursor = conn.cursor() + cursor.execute('INSERT INTO logs (user_id, action) VALUES ((SELECT id FROM users WHERE telegram_id = ?), ?)', (user_id, action)) + conn.commit() + conn.close() diff --git a/doners/old/main(OLD).py b/doners/old/main(OLD).py index a62850e..61808c9 100644 --- a/doners/old/main(OLD).py +++ b/doners/old/main(OLD).py @@ -1,213 +1,213 @@ -import logging -import sqlite3 -import asyncio -from aiogram import Bot, Dispatcher, types -from aiogram.contrib.middlewares.logging import LoggingMiddleware -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton - -# Устанавливаем уровень логов -logging.basicConfig(level=logging.INFO) - -# Подключаемся к базе данных SQLite -conn = sqlite3.connect("answers.db") -cursor = conn.cursor() - -# Создание таблицы для хранения ответов -cursor.execute(""" -CREATE TABLE IF NOT EXISTS answers ( - user_id INTEGER, - question_id INTEGER, - answer TEXT -) -""") -conn.commit() - -# Заменить на свой токен -API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE' -GROUP_ID = '-1001961537659' - -# Инициализация бота и диспетчера -bot = Bot(token=API_TOKEN) -dp = Dispatcher(bot) -dp.middleware.setup(LoggingMiddleware()) - -# Словарь для хранения вопросов и ответов -questions = { - 1: "Как вас зовут?", - 2: "Укажите номер телефона для связи", - 3: "Укажите район, улицу, дом", - 4: "Какая уборка нужна, влажная или сухая?", - 5: "На какое время?", - 6: "Оплата наличными или картой?" -} - -answer_map = { - 'wet_cleaning': 'Влажная уборка', - 'dry_cleaning': 'Сухая уборка', - 'morning_time': 'Утро', - 'day_time': 'День', - 'evening_time': 'Вечер', - 'cash_payment': 'Наличные', - 'card_payment': 'Карта' -} - -# Обработчик команды /start -@dp.message_handler(commands=['start']) -async def start(message: types.Message): - if message.chat.id != int(GROUP_ID): - await message.answer("Привет! Я задам тебе 6 вопросов. Давай начнем.") - await ask_question(message.chat.id, 1) - -# Функция для задания вопроса -async def ask_question(user_id, question_id): - if question_id in [4, 5, 6]: - if question_id == 4: - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")], - [InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")] - ]) - elif question_id == 5: - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Утро", callback_data="morning_time")], - [InlineKeyboardButton(text="День", callback_data="day_time")], - [InlineKeyboardButton(text="Вечер", callback_data="evening_time")] - ]) - elif question_id == 6: - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Наличные", callback_data="cash_payment")], - [InlineKeyboardButton(text="Карта", callback_data="card_payment")] - ]) - await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard) - else: - await bot.send_message(user_id, text=questions[question_id]) - -# Обработчик текстовых ответов на первые три вопроса -@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"]) -async def handle_text_answer(message: types.Message): - if message.chat.id != int(GROUP_ID): - user_id = message.chat.id - cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,)) - prev_question_id = cursor.fetchone()[0] - - if prev_question_id is None: - question_id = 1 - else: - question_id = prev_question_id + 1 - answer = message.text - - await save_answer(user_id, question_id, answer) - - if question_id < 6: - await ask_question(user_id, question_id + 1) - else: - await show_confirmation_options(user_id) - -# Обработчик inline-кнопок -@dp.callback_query_handler(lambda query: query.data in answer_map.keys()) -async def handle_callback_answer(query: types.CallbackQuery): - user_id = query.from_user.id - cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,)) - prev_question_id = cursor.fetchone()[0] - - if prev_question_id is None: - question_id = 1 - else: - question_id = prev_question_id + 1 - - await save_answer(user_id, question_id, answer_map[query.data]) - await query.answer() - - if question_id < 6: - await ask_question(user_id, question_id + 1) - else: - await show_confirmation_options(user_id) - -# Функция для сохранения ответа в базе данных -async def save_answer(user_id, question_id, answer): - cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer)) - conn.commit() - -# Функция для отображения опций подтверждения -async def show_confirmation_options(user_id): - cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,)) - answers = cursor.fetchall() - answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)]) - await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}") - - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Отправить", callback_data="send_answers")], - [InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")] - ]) - await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard) - -# Обработчик выбора действия подтверждения -@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers']) -async def handle_confirmation(query: types.CallbackQuery): - user_id = query.from_user.id - if query.data == "send_answers": - await send_answers_to_group(user_id) - elif query.data == "rewrite_answers": - await rewrite_answers(user_id) - -# Функция для отправки ответов в группу -async def send_answers_to_group(user_id): - cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,)) - answers = cursor.fetchall() - - if answers: - answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in answers]) - - # Очищаем таблицу с ответами - cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,)) - conn.commit() - - # Отправляем ответы в группу и добавляем ID пользователя - sent_message = await bot.send_message(GROUP_ID, f"Ответы от пользователя {user_id}:\n\n{answer_text}") - await bot.send_message(user_id, f"Ваши ответы отправлены. Можете посмотреть их [здесь](https://t.me/{sent_message.chat.username}/{sent_message.message_id})", - parse_mode='Markdown') - else: - await bot.send_message(user_id, "Ответов нет") - -# Функция для переписывания ответов -async def rewrite_answers(user_id): - # Очищаем таблицу с ответами - cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,)) - conn.commit() - - await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.") - await ask_question(user_id, 1) - -# Функция для обработки сообщений из группы и пересылки их пользователю -@dp.message_handler(lambda message: message.chat.id == int(GROUP_ID) and message.reply_to_message) -async def handle_group_reply(message: types.Message): - # Проверяем, что сообщение является ответом на сообщение бота - if message.reply_to_message.from_user.id == bot.id: - # Извлекаем ID пользователя из текста сообщения - try: - user_id = int(message.reply_to_message.text.split()[2].strip(':')) - await bot.send_message(user_id, f"Сообщение от администратора:\n\n{message.text}") - # Сохраняем ответ пользователя для пересылки обратно в группу - cursor.execute("INSERT INTO admin_user_conversation (user_id, admin_message_id) VALUES (?, ?)", - (user_id, message.message_id)) - conn.commit() - except (IndexError, ValueError): - await message.reply("Не удалось определить ID пользователя для ответа.") - -# Обработчик сообщений от пользователя в ответ на администратора -@dp.message_handler(lambda message: message.chat.id != int(GROUP_ID)) -async def handle_user_reply(message: types.Message): - user_id = message.chat.id - cursor.execute("SELECT admin_message_id FROM admin_user_conversation WHERE user_id=? ORDER BY admin_message_id DESC LIMIT 1", (user_id,)) - admin_message_id = cursor.fetchone() - if admin_message_id: - await bot.send_message(GROUP_ID, f"Сообщение от пользователя {user_id}:\n\n{message.text}", reply_to_message_id=admin_message_id[0]) - -if __name__ == '__main__': - loop = asyncio.get_event_loop() - try: - loop.create_task(dp.start_polling()) - loop.run_forever() - except KeyboardInterrupt: - pass - finally: - conn.close() +import logging +import sqlite3 +import asyncio +from aiogram import Bot, Dispatcher, types +from aiogram.contrib.middlewares.logging import LoggingMiddleware +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton + +# Устанавливаем уровень логов +logging.basicConfig(level=logging.INFO) + +# Подключаемся к базе данных SQLite +conn = sqlite3.connect("answers.db") +cursor = conn.cursor() + +# Создание таблицы для хранения ответов +cursor.execute(""" +CREATE TABLE IF NOT EXISTS answers ( + user_id INTEGER, + question_id INTEGER, + answer TEXT +) +""") +conn.commit() + +# Заменить на свой токен +API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE' +GROUP_ID = '-1001961537659' + +# Инициализация бота и диспетчера +bot = Bot(token=API_TOKEN) +dp = Dispatcher(bot) +dp.middleware.setup(LoggingMiddleware()) + +# Словарь для хранения вопросов и ответов +questions = { + 1: "Как вас зовут?", + 2: "Укажите номер телефона для связи", + 3: "Укажите район, улицу, дом", + 4: "Какая уборка нужна, влажная или сухая?", + 5: "На какое время?", + 6: "Оплата наличными или картой?" +} + +answer_map = { + 'wet_cleaning': 'Влажная уборка', + 'dry_cleaning': 'Сухая уборка', + 'morning_time': 'Утро', + 'day_time': 'День', + 'evening_time': 'Вечер', + 'cash_payment': 'Наличные', + 'card_payment': 'Карта' +} + +# Обработчик команды /start +@dp.message_handler(commands=['start']) +async def start(message: types.Message): + if message.chat.id != int(GROUP_ID): + await message.answer("Привет! Я задам тебе 6 вопросов. Давай начнем.") + await ask_question(message.chat.id, 1) + +# Функция для задания вопроса +async def ask_question(user_id, question_id): + if question_id in [4, 5, 6]: + if question_id == 4: + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")], + [InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")] + ]) + elif question_id == 5: + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Утро", callback_data="morning_time")], + [InlineKeyboardButton(text="День", callback_data="day_time")], + [InlineKeyboardButton(text="Вечер", callback_data="evening_time")] + ]) + elif question_id == 6: + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Наличные", callback_data="cash_payment")], + [InlineKeyboardButton(text="Карта", callback_data="card_payment")] + ]) + await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard) + else: + await bot.send_message(user_id, text=questions[question_id]) + +# Обработчик текстовых ответов на первые три вопроса +@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"]) +async def handle_text_answer(message: types.Message): + if message.chat.id != int(GROUP_ID): + user_id = message.chat.id + cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,)) + prev_question_id = cursor.fetchone()[0] + + if prev_question_id is None: + question_id = 1 + else: + question_id = prev_question_id + 1 + answer = message.text + + await save_answer(user_id, question_id, answer) + + if question_id < 6: + await ask_question(user_id, question_id + 1) + else: + await show_confirmation_options(user_id) + +# Обработчик inline-кнопок +@dp.callback_query_handler(lambda query: query.data in answer_map.keys()) +async def handle_callback_answer(query: types.CallbackQuery): + user_id = query.from_user.id + cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,)) + prev_question_id = cursor.fetchone()[0] + + if prev_question_id is None: + question_id = 1 + else: + question_id = prev_question_id + 1 + + await save_answer(user_id, question_id, answer_map[query.data]) + await query.answer() + + if question_id < 6: + await ask_question(user_id, question_id + 1) + else: + await show_confirmation_options(user_id) + +# Функция для сохранения ответа в базе данных +async def save_answer(user_id, question_id, answer): + cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer)) + conn.commit() + +# Функция для отображения опций подтверждения +async def show_confirmation_options(user_id): + cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,)) + answers = cursor.fetchall() + answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)]) + await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}") + + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Отправить", callback_data="send_answers")], + [InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")] + ]) + await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard) + +# Обработчик выбора действия подтверждения +@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers']) +async def handle_confirmation(query: types.CallbackQuery): + user_id = query.from_user.id + if query.data == "send_answers": + await send_answers_to_group(user_id) + elif query.data == "rewrite_answers": + await rewrite_answers(user_id) + +# Функция для отправки ответов в группу +async def send_answers_to_group(user_id): + cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,)) + answers = cursor.fetchall() + + if answers: + answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in answers]) + + # Очищаем таблицу с ответами + cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,)) + conn.commit() + + # Отправляем ответы в группу и добавляем ID пользователя + sent_message = await bot.send_message(GROUP_ID, f"Ответы от пользователя {user_id}:\n\n{answer_text}") + await bot.send_message(user_id, f"Ваши ответы отправлены. Можете посмотреть их [здесь](https://t.me/{sent_message.chat.username}/{sent_message.message_id})", + parse_mode='Markdown') + else: + await bot.send_message(user_id, "Ответов нет") + +# Функция для переписывания ответов +async def rewrite_answers(user_id): + # Очищаем таблицу с ответами + cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,)) + conn.commit() + + await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.") + await ask_question(user_id, 1) + +# Функция для обработки сообщений из группы и пересылки их пользователю +@dp.message_handler(lambda message: message.chat.id == int(GROUP_ID) and message.reply_to_message) +async def handle_group_reply(message: types.Message): + # Проверяем, что сообщение является ответом на сообщение бота + if message.reply_to_message.from_user.id == bot.id: + # Извлекаем ID пользователя из текста сообщения + try: + user_id = int(message.reply_to_message.text.split()[2].strip(':')) + await bot.send_message(user_id, f"Сообщение от администратора:\n\n{message.text}") + # Сохраняем ответ пользователя для пересылки обратно в группу + cursor.execute("INSERT INTO admin_user_conversation (user_id, admin_message_id) VALUES (?, ?)", + (user_id, message.message_id)) + conn.commit() + except (IndexError, ValueError): + await message.reply("Не удалось определить ID пользователя для ответа.") + +# Обработчик сообщений от пользователя в ответ на администратора +@dp.message_handler(lambda message: message.chat.id != int(GROUP_ID)) +async def handle_user_reply(message: types.Message): + user_id = message.chat.id + cursor.execute("SELECT admin_message_id FROM admin_user_conversation WHERE user_id=? ORDER BY admin_message_id DESC LIMIT 1", (user_id,)) + admin_message_id = cursor.fetchone() + if admin_message_id: + await bot.send_message(GROUP_ID, f"Сообщение от пользователя {user_id}:\n\n{message.text}", reply_to_message_id=admin_message_id[0]) + +if __name__ == '__main__': + loop = asyncio.get_event_loop() + try: + loop.create_task(dp.start_polling()) + loop.run_forever() + except KeyboardInterrupt: + pass + finally: + conn.close() diff --git a/doners/old/main.py b/doners/old/main.py index 15412d1..c5d8c08 100644 --- a/doners/old/main.py +++ b/doners/old/main.py @@ -1,221 +1,221 @@ -import logging -import sqlite3 -import asyncio -from aiogram import Bot, Dispatcher, types -from aiogram.contrib.middlewares.logging import LoggingMiddleware -from aiogram.dispatcher.filters import Command -from aiogram.dispatcher import FSMContext -from aiogram.dispatcher.filters.state import State, StatesGroup -from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton - -# Устанавливаем уровень логов -logging.basicConfig(level=logging.INFO) - -# Подключаемся к базе данных SQLite -conn = sqlite3.connect("answers.db") -cursor = conn.cursor() - -# Создание таблицы для хранения ответов -cursor.execute(""" -CREATE TABLE IF NOT EXISTS answers ( - user_id INTEGER, - question_id INTEGER, - answer TEXT -) -""") -conn.commit() - -# Заменить на свой токен -API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE' -GROUP_ID = '-1001961537659' - -# Инициализация бота и диспетчера -bot = Bot(token=API_TOKEN) -dp = Dispatcher(bot) -dp.middleware.setup(LoggingMiddleware()) - -# Словарь для хранения вопросов и ответов -questions = { - 1: "Как вас зовут?", - 2: "Укажите номер телефона для связи", - 3: "Укажите район, улицу, дом", - 4: "Какая уборка нужна, влажная или сухая?", - 5: "На какое время?", - 6: "Оплата наличными или картой?" -} - -answer_map = { - 'wet_cleaning': 'Влажная уборка', - 'dry_cleaning': 'Сухая уборка', - 'morning_time': 'Утро', - 'day_time': 'День', - 'evening_time': 'Вечер', - 'cash_payment': 'Наличные', - 'card_payment': 'Карта' -} - -# Обработчик команды /start -@dp.message_handler(commands=['start']) -async def start(message: types.Message): - if message.chat.id != int(GROUP_ID): - await message.answer("Привет! Я задам тебе 6 вопросов Чтобы создать заказ. Давай начнем.") - await ask_question(message.chat.id, 1) - -@dp.message_handler(Command("start")) -async def show_menu(message: types.Message): - await message.reply("Выбери действие:", reply_markup=ReplyKeyboardMarkup( - resize_keyboard=True).add(KeyboardButton("Создать заказ"))) - -# Функция для задания вопроса -async def ask_question(user_id, question_id): - if question_id in [4, 5, 6]: - if question_id == 4: - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")], - [InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")] - ]) - elif question_id == 5: - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Утро", callback_data="morning_time")], - [InlineKeyboardButton(text="День", callback_data="day_time")], - [InlineKeyboardButton(text="Вечер", callback_data="evening_time")] - ]) - elif question_id == 6: - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Наличные", callback_data="cash_payment")], - [InlineKeyboardButton(text="Карта", callback_data="card_payment")] - ]) - await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard) - else: - await bot.send_message(user_id, text=questions[question_id]) - -# Обработчик текстовых ответов на первые три вопроса -@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"]) -async def handle_text_answer(message: types.Message): - if message.chat.id != int(GROUP_ID): - user_id = message.chat.id - cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,)) - prev_question_id = cursor.fetchone()[0] - - if prev_question_id is None: - question_id = 1 - else: - question_id = prev_question_id + 1 - answer = message.text - - await save_answer(user_id, question_id, answer) - - if question_id < 6: - await ask_question(user_id, question_id + 1) - else: - await show_confirmation_options(user_id) - -# Обработчик inline-кнопок -@dp.callback_query_handler(lambda query: query.data in answer_map.keys()) -async def handle_callback_answer(query: types.CallbackQuery): - user_id = query.from_user.id - cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,)) - prev_question_id = cursor.fetchone()[0] - - if prev_question_id is None: - question_id = 1 - else: - question_id = prev_question_id + 1 - - await save_answer(user_id, question_id, answer_map[query.data]) - await query.answer() - - if question_id < 6: - await ask_question(user_id, question_id + 1) - else: - await show_confirmation_options(user_id) - -# Функция для сохранения ответа в базе данных -async def save_answer(user_id, question_id, answer): - cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer)) - conn.commit() - -# Функция для отображения опций подтверждения -async def show_confirmation_options(user_id): - cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,)) - answers = cursor.fetchall() - answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)]) - await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}") - - keyboard = InlineKeyboardMarkup(inline_keyboard=[ - [InlineKeyboardButton(text="Отправить", callback_data="send_answers")], - [InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")] - ]) - await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard) - -# Обработчик выбора действия подтверждения -@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers']) -async def handle_confirmation(query: types.CallbackQuery): - user_id = query.from_user.id - if query.data == "send_answers": - await send_answers_to_group(user_id) - elif query.data == "rewrite_answers": - await rewrite_answers(user_id) - -# Функция для отправки ответов в группу -async def send_answers_to_group(user_id): - cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,)) - answers = cursor.fetchall() - - if answers: - answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in answers]) - - # Очищаем таблицу с ответами - cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,)) - conn.commit() - - # Отправляем ответы в группу и добавляем ID пользователя - sent_message = await bot.send_message(GROUP_ID, f"Ответы от пользователя {user_id}:\n\n{answer_text}") - await bot.send_message(user_id, f"Ваши ответы отправлены. Оператор с вами свяжится)", - parse_mode='Markdown') - else: - await bot.send_message(user_id, "Ответов нет") - -# Функция для переписывания ответов -async def rewrite_answers(user_id): - # Очищаем таблицу с ответами - cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,)) - conn.commit() - - await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.") - await ask_question(user_id, 1) - -# Функция для обработки сообщений из группы и пересылки их пользователю -@dp.message_handler(lambda message: message.chat.id == int(GROUP_ID) and message.reply_to_message) -async def handle_group_reply(message: types.Message): - # Проверяем, что сообщение является ответом на сообщение бота - if message.reply_to_message.from_user.id == bot.id: - # Извлекаем ID пользователя из текста сообщения - try: - user_id = int(message.reply_to_message.text.split()[2].strip(':')) - await bot.send_message(user_id, f"Сообщение от администратора:\n\n{message.text}") - # Сохраняем ответ пользователя для пересылки обратно в группу - cursor.execute("INSERT INTO admin_user_conversation (user_id, admin_message_id) VALUES (?, ?)", - (user_id, message.message_id)) - conn.commit() - except (IndexError, ValueError): - await message.reply("Не удалось определить ID пользователя для ответа.") - -# Обработчик сообщений от пользователя в ответ на администратора -@dp.message_handler(lambda message: message.chat.id != int(GROUP_ID)) -async def handle_user_reply(message: types.Message): - user_id = message.chat.id - cursor.execute("SELECT admin_message_id FROM admin_user_conversation WHERE user_id=? ORDER BY admin_message_id DESC LIMIT 1", (user_id,)) - admin_message_id = cursor.fetchone() - if admin_message_id: - await bot.send_message(GROUP_ID, f"Сообщение от пользователя {user_id}:\n\n{message.text}", reply_to_message_id=admin_message_id[0]) - -if __name__ == '__main__': - loop = asyncio.get_event_loop() - try: - loop.create_task(dp.start_polling()) - loop.run_forever() - except KeyboardInterrupt: - pass - finally: - conn.close() +import logging +import sqlite3 +import asyncio +from aiogram import Bot, Dispatcher, types +from aiogram.contrib.middlewares.logging import LoggingMiddleware +from aiogram.dispatcher.filters import Command +from aiogram.dispatcher import FSMContext +from aiogram.dispatcher.filters.state import State, StatesGroup +from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton + +# Устанавливаем уровень логов +logging.basicConfig(level=logging.INFO) + +# Подключаемся к базе данных SQLite +conn = sqlite3.connect("answers.db") +cursor = conn.cursor() + +# Создание таблицы для хранения ответов +cursor.execute(""" +CREATE TABLE IF NOT EXISTS answers ( + user_id INTEGER, + question_id INTEGER, + answer TEXT +) +""") +conn.commit() + +# Заменить на свой токен +API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE' +GROUP_ID = '-1001961537659' + +# Инициализация бота и диспетчера +bot = Bot(token=API_TOKEN) +dp = Dispatcher(bot) +dp.middleware.setup(LoggingMiddleware()) + +# Словарь для хранения вопросов и ответов +questions = { + 1: "Как вас зовут?", + 2: "Укажите номер телефона для связи", + 3: "Укажите район, улицу, дом", + 4: "Какая уборка нужна, влажная или сухая?", + 5: "На какое время?", + 6: "Оплата наличными или картой?" +} + +answer_map = { + 'wet_cleaning': 'Влажная уборка', + 'dry_cleaning': 'Сухая уборка', + 'morning_time': 'Утро', + 'day_time': 'День', + 'evening_time': 'Вечер', + 'cash_payment': 'Наличные', + 'card_payment': 'Карта' +} + +# Обработчик команды /start +@dp.message_handler(commands=['start']) +async def start(message: types.Message): + if message.chat.id != int(GROUP_ID): + await message.answer("Привет! Я задам тебе 6 вопросов Чтобы создать заказ. Давай начнем.") + await ask_question(message.chat.id, 1) + +@dp.message_handler(Command("start")) +async def show_menu(message: types.Message): + await message.reply("Выбери действие:", reply_markup=ReplyKeyboardMarkup( + resize_keyboard=True).add(KeyboardButton("Создать заказ"))) + +# Функция для задания вопроса +async def ask_question(user_id, question_id): + if question_id in [4, 5, 6]: + if question_id == 4: + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")], + [InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")] + ]) + elif question_id == 5: + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Утро", callback_data="morning_time")], + [InlineKeyboardButton(text="День", callback_data="day_time")], + [InlineKeyboardButton(text="Вечер", callback_data="evening_time")] + ]) + elif question_id == 6: + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Наличные", callback_data="cash_payment")], + [InlineKeyboardButton(text="Карта", callback_data="card_payment")] + ]) + await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard) + else: + await bot.send_message(user_id, text=questions[question_id]) + +# Обработчик текстовых ответов на первые три вопроса +@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"]) +async def handle_text_answer(message: types.Message): + if message.chat.id != int(GROUP_ID): + user_id = message.chat.id + cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,)) + prev_question_id = cursor.fetchone()[0] + + if prev_question_id is None: + question_id = 1 + else: + question_id = prev_question_id + 1 + answer = message.text + + await save_answer(user_id, question_id, answer) + + if question_id < 6: + await ask_question(user_id, question_id + 1) + else: + await show_confirmation_options(user_id) + +# Обработчик inline-кнопок +@dp.callback_query_handler(lambda query: query.data in answer_map.keys()) +async def handle_callback_answer(query: types.CallbackQuery): + user_id = query.from_user.id + cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,)) + prev_question_id = cursor.fetchone()[0] + + if prev_question_id is None: + question_id = 1 + else: + question_id = prev_question_id + 1 + + await save_answer(user_id, question_id, answer_map[query.data]) + await query.answer() + + if question_id < 6: + await ask_question(user_id, question_id + 1) + else: + await show_confirmation_options(user_id) + +# Функция для сохранения ответа в базе данных +async def save_answer(user_id, question_id, answer): + cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer)) + conn.commit() + +# Функция для отображения опций подтверждения +async def show_confirmation_options(user_id): + cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,)) + answers = cursor.fetchall() + answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)]) + await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}") + + keyboard = InlineKeyboardMarkup(inline_keyboard=[ + [InlineKeyboardButton(text="Отправить", callback_data="send_answers")], + [InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")] + ]) + await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard) + +# Обработчик выбора действия подтверждения +@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers']) +async def handle_confirmation(query: types.CallbackQuery): + user_id = query.from_user.id + if query.data == "send_answers": + await send_answers_to_group(user_id) + elif query.data == "rewrite_answers": + await rewrite_answers(user_id) + +# Функция для отправки ответов в группу +async def send_answers_to_group(user_id): + cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,)) + answers = cursor.fetchall() + + if answers: + answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in answers]) + + # Очищаем таблицу с ответами + cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,)) + conn.commit() + + # Отправляем ответы в группу и добавляем ID пользователя + sent_message = await bot.send_message(GROUP_ID, f"Ответы от пользователя {user_id}:\n\n{answer_text}") + await bot.send_message(user_id, f"Ваши ответы отправлены. Оператор с вами свяжится)", + parse_mode='Markdown') + else: + await bot.send_message(user_id, "Ответов нет") + +# Функция для переписывания ответов +async def rewrite_answers(user_id): + # Очищаем таблицу с ответами + cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,)) + conn.commit() + + await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.") + await ask_question(user_id, 1) + +# Функция для обработки сообщений из группы и пересылки их пользователю +@dp.message_handler(lambda message: message.chat.id == int(GROUP_ID) and message.reply_to_message) +async def handle_group_reply(message: types.Message): + # Проверяем, что сообщение является ответом на сообщение бота + if message.reply_to_message.from_user.id == bot.id: + # Извлекаем ID пользователя из текста сообщения + try: + user_id = int(message.reply_to_message.text.split()[2].strip(':')) + await bot.send_message(user_id, f"Сообщение от администратора:\n\n{message.text}") + # Сохраняем ответ пользователя для пересылки обратно в группу + cursor.execute("INSERT INTO admin_user_conversation (user_id, admin_message_id) VALUES (?, ?)", + (user_id, message.message_id)) + conn.commit() + except (IndexError, ValueError): + await message.reply("Не удалось определить ID пользователя для ответа.") + +# Обработчик сообщений от пользователя в ответ на администратора +@dp.message_handler(lambda message: message.chat.id != int(GROUP_ID)) +async def handle_user_reply(message: types.Message): + user_id = message.chat.id + cursor.execute("SELECT admin_message_id FROM admin_user_conversation WHERE user_id=? ORDER BY admin_message_id DESC LIMIT 1", (user_id,)) + admin_message_id = cursor.fetchone() + if admin_message_id: + await bot.send_message(GROUP_ID, f"Сообщение от пользователя {user_id}:\n\n{message.text}", reply_to_message_id=admin_message_id[0]) + +if __name__ == '__main__': + loop = asyncio.get_event_loop() + try: + loop.create_task(dp.start_polling()) + loop.run_forever() + except KeyboardInterrupt: + pass + finally: + conn.close() diff --git a/doners/old/remain.py b/doners/old/remain.py index f73befe..335eb6a 100644 --- a/doners/old/remain.py +++ b/doners/old/remain.py @@ -1,76 +1,76 @@ -import logging -from aiogram import Bot, Dispatcher, types -from aiogram.contrib.middlewares.logging import LoggingMiddleware -from aiogram.dispatcher import FSMContext -from aiogram.dispatcher.filters import Command -from aiogram.dispatcher.filters.state import State, StatesGroup -from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton - -API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE' -GROUP_ID = '-1001961537659' - -logging.basicConfig(level=logging.INFO) - -bot = Bot(token=API_TOKEN) -dp = Dispatcher(bot) -dp.middleware.setup(LoggingMiddleware()) - -# Define states -class OrderForm(StatesGroup): - question_1 = State() - question_2 = State() - question_3 = State() - question_4 = State() - question_5 = State() - question_6 = State() - -@dp.message_handler(Command("start")) -async def cmd_start(message: types.Message): - await message.reply("Привет! Чтобы создать заказ, нажми на кнопку 'Создать заказ' внизу.", reply_markup=ReplyKeyboardMarkup( - resize_keyboard=True).add(KeyboardButton("Создать заказ"))) - -@dp.message_handler(Command("menu")) -async def show_menu(message: types.Message): - await message.reply("Выбери действие:", reply_markup=ReplyKeyboardMarkup( - resize_keyboard=True).add(KeyboardButton("Создать заказ"))) - -@dp.message_handler(Command("create_order")) -async def cmd_create_order(message: types.Message): - await OrderForm.question_1.set() - await message.reply("Ответьте на первый вопрос:") - -@dp.message_handler(state=OrderForm.question_1) -async def process_question_1(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['question_1'] = message.text - await OrderForm.next() - await message.reply("Ответьте на второй вопрос:") - -# Аналогично для остальных вопросов - -@dp.message_handler(state=OrderForm.question_6) -async def process_question_6(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['question_6'] = message.text - await message.reply("Отлично! Ваши ответы:") - async with state.proxy() as data: - for key, value in data.items(): - await message.reply(f"{key}: {value}") - - # Предложить отправить или переписать ответы - keyboard = InlineKeyboardMarkup().add(InlineKeyboardButton("Отправить", callback_data="send_order")).add(InlineKeyboardButton("Переписать", callback_data="rewrite_order")) - await message.reply("Хотите отправить эти ответы или переписать?", reply_markup=keyboard) - -@dp.callback_query_handler(text="send_order", state="*") -async def send_order(callback_query: types.CallbackQuery, state: FSMContext): - async with state.proxy() as data: - for key, value in data.items(): - await bot.send_message(callback_query.from_user.id, f"{key}: {value}") - -@dp.callback_query_handler(text="rewrite_order", state="*") -async def rewrite_order(callback_query: types.CallbackQuery): - await cmd_create_order(callback_query.message) - -if __name__ == '__main__': - from aiogram import executor - executor.start_polling(dp, skip_updates=True) +import logging +from aiogram import Bot, Dispatcher, types +from aiogram.contrib.middlewares.logging import LoggingMiddleware +from aiogram.dispatcher import FSMContext +from aiogram.dispatcher.filters import Command +from aiogram.dispatcher.filters.state import State, StatesGroup +from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton + +API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE' +GROUP_ID = '-1001961537659' + +logging.basicConfig(level=logging.INFO) + +bot = Bot(token=API_TOKEN) +dp = Dispatcher(bot) +dp.middleware.setup(LoggingMiddleware()) + +# Define states +class OrderForm(StatesGroup): + question_1 = State() + question_2 = State() + question_3 = State() + question_4 = State() + question_5 = State() + question_6 = State() + +@dp.message_handler(Command("start")) +async def cmd_start(message: types.Message): + await message.reply("Привет! Чтобы создать заказ, нажми на кнопку 'Создать заказ' внизу.", reply_markup=ReplyKeyboardMarkup( + resize_keyboard=True).add(KeyboardButton("Создать заказ"))) + +@dp.message_handler(Command("menu")) +async def show_menu(message: types.Message): + await message.reply("Выбери действие:", reply_markup=ReplyKeyboardMarkup( + resize_keyboard=True).add(KeyboardButton("Создать заказ"))) + +@dp.message_handler(Command("create_order")) +async def cmd_create_order(message: types.Message): + await OrderForm.question_1.set() + await message.reply("Ответьте на первый вопрос:") + +@dp.message_handler(state=OrderForm.question_1) +async def process_question_1(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['question_1'] = message.text + await OrderForm.next() + await message.reply("Ответьте на второй вопрос:") + +# Аналогично для остальных вопросов + +@dp.message_handler(state=OrderForm.question_6) +async def process_question_6(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['question_6'] = message.text + await message.reply("Отлично! Ваши ответы:") + async with state.proxy() as data: + for key, value in data.items(): + await message.reply(f"{key}: {value}") + + # Предложить отправить или переписать ответы + keyboard = InlineKeyboardMarkup().add(InlineKeyboardButton("Отправить", callback_data="send_order")).add(InlineKeyboardButton("Переписать", callback_data="rewrite_order")) + await message.reply("Хотите отправить эти ответы или переписать?", reply_markup=keyboard) + +@dp.callback_query_handler(text="send_order", state="*") +async def send_order(callback_query: types.CallbackQuery, state: FSMContext): + async with state.proxy() as data: + for key, value in data.items(): + await bot.send_message(callback_query.from_user.id, f"{key}: {value}") + +@dp.callback_query_handler(text="rewrite_order", state="*") +async def rewrite_order(callback_query: types.CallbackQuery): + await cmd_create_order(callback_query.message) + +if __name__ == '__main__': + from aiogram import executor + executor.start_polling(dp, skip_updates=True) diff --git a/doners/old_2/database.py b/doners/old_2/database.py index adb6292..e9ebe9a 100644 --- a/doners/old_2/database.py +++ b/doners/old_2/database.py @@ -1,53 +1,53 @@ -import sqlite3 - -def create_tables(): - conn = sqlite3.connect('bot_data.db') - cursor = conn.cursor() - - cursor.execute(''' - CREATE TABLE IF NOT EXISTS orders ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT, - phone TEXT, - address TEXT, - cleaning_time TEXT, - cleaning_type TEXT, - payment_method TEXT - ) - ''') - - cursor.execute(''' - CREATE TABLE IF NOT EXISTS users ( - user_id INTEGER PRIMARY KEY, - name TEXT, - phone TEXT, - address TEXT - ) - ''') - - conn.commit() - conn.close() - -def add_order(name, phone, address, cleaning_time, cleaning_type, payment_method): - conn = sqlite3.connect('bot_data.db') - cursor = conn.cursor() - - cursor.execute(''' - INSERT INTO orders (name, phone, address, cleaning_time, cleaning_type, payment_method) - VALUES (?, ?, ?, ?, ?, ?) - ''', (name, phone, address, cleaning_time, cleaning_type, payment_method)) - - conn.commit() - conn.close() - -def get_order_history(user_id): - conn = sqlite3.connect('bot_data.db') - cursor = conn.cursor() - - cursor.execute(''' - SELECT * FROM orders WHERE user_id = ? - ''', (user_id,)) - - orders = cursor.fetchall() - conn.close() - return orders +import sqlite3 + +def create_tables(): + conn = sqlite3.connect('bot_data.db') + cursor = conn.cursor() + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS orders ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + phone TEXT, + address TEXT, + cleaning_time TEXT, + cleaning_type TEXT, + payment_method TEXT + ) + ''') + + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + user_id INTEGER PRIMARY KEY, + name TEXT, + phone TEXT, + address TEXT + ) + ''') + + conn.commit() + conn.close() + +def add_order(name, phone, address, cleaning_time, cleaning_type, payment_method): + conn = sqlite3.connect('bot_data.db') + cursor = conn.cursor() + + cursor.execute(''' + INSERT INTO orders (name, phone, address, cleaning_time, cleaning_type, payment_method) + VALUES (?, ?, ?, ?, ?, ?) + ''', (name, phone, address, cleaning_time, cleaning_type, payment_method)) + + conn.commit() + conn.close() + +def get_order_history(user_id): + conn = sqlite3.connect('bot_data.db') + cursor = conn.cursor() + + cursor.execute(''' + SELECT * FROM orders WHERE user_id = ? + ''', (user_id,)) + + orders = cursor.fetchall() + conn.close() + return orders diff --git a/doners/old_2/handlers.py b/doners/old_2/handlers.py index a72fffc..af27d3d 100644 --- a/doners/old_2/handlers.py +++ b/doners/old_2/handlers.py @@ -1,158 +1,158 @@ -import logging -from aiogram import types -from aiogram.dispatcher import FSMContext -from aiogram.dispatcher.filters.state import State, StatesGroup -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton -from database import add_order, get_order_history -from logger import log_action - -logging.basicConfig(level=logging.INFO) - -class OrderForm(StatesGroup): - waiting_for_name = State() - waiting_for_phone = State() - waiting_for_address = State() - waiting_for_cleaning_time = State() - waiting_for_cleaning_type = State() - waiting_for_payment_method = State() - confirmation = State() - -def main_menu_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data')) - keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order')) - keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history')) - keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call')) - keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator')) - return keyboard - -def account_data_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name')) - keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone')) - keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address')) - keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address')) - keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact')) - keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main')) - return keyboard - -def order_confirmation_keyboard(): - keyboard = InlineKeyboardMarkup() - keyboard.add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order')) - return keyboard - -def register_handlers(dp, OPERATORS_GROUP_ID): - @dp.message_handler(commands=['start']) - async def send_welcome(message: types.Message): - await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard()) - log_action(message.from_user.id, 'start') - - @dp.callback_query_handler(lambda c: c.data == 'account_data') - async def process_account_data(callback_query: types.CallbackQuery): - await callback_query.answer() - await callback_query.message.answer("Учетные данные:", reply_markup=account_data_keyboard()) - log_action(callback_query.from_user.id, 'account_data') - - @dp.callback_query_handler(lambda c: c.data == 'make_order') - async def process_make_order(callback_query: types.CallbackQuery, state: FSMContext): - await callback_query.answer() - await callback_query.message.answer("Начнем с ваших учетных данных. Введите ваше ФИО:") - await OrderForm.waiting_for_name.set() - log_action(callback_query.from_user.id, 'make_order') - - @dp.callback_query_handler(lambda c: c.data == 'order_history') - async def process_order_history(callback_query: types.CallbackQuery): - await callback_query.answer() - orders = get_order_history(callback_query.from_user.id) - if orders: - history_message = "\n\n".join([f"Заказ {order['id']}:\nФИО: {order['name']}\nАдрес: {order['address']}\nТип уборки: {order['cleaning_type']}" for order in orders]) - await callback_query.message.answer(f"История заказов:\n{history_message}") - else: - await callback_query.message.answer("История заказов пуста.") - log_action(callback_query.from_user.id, 'order_history') - - @dp.callback_query_handler(lambda c: c.data == 'request_call') - async def process_request_call(callback_query: types.CallbackQuery): - await callback_query.answer() - await callback_query.message.answer("Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.") - log_action(callback_query.from_user.id, 'request_call') - - @dp.callback_query_handler(lambda c: c.data == 'talk_operator') - async def process_talk_operator(callback_query: types.CallbackQuery): - await callback_query.answer() - await callback_query.message.answer("Разговор с оператором начат.") - log_action(callback_query.from_user.id, 'talk_operator') - - @dp.callback_query_handler(lambda c: c.data == 'back_to_main') - async def process_back_to_main(callback_query: types.CallbackQuery): - await callback_query.answer() - await callback_query.message.answer("Главное меню:", reply_markup=main_menu_keyboard()) - log_action(callback_query.from_user.id, 'back_to_main') - - @dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT) - async def process_name(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['name'] = message.text - await message.answer("Введите ваш номер телефона:") - await OrderForm.waiting_for_phone.set() - - @dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT) - async def process_phone(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['phone'] = message.text - await message.answer("Введите адрес для уборки:") - await OrderForm.waiting_for_address.set() - - @dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT) - async def process_order_address(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['address'] = message.text - await message.answer("Выберите время для уборки (утро/день/вечер):") - await OrderForm.waiting_for_cleaning_time.set() - - @dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT) - async def process_cleaning_time(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['cleaning_time'] = message.text - await message.answer("Выберите тип уборки (влажная/сухая/генеральная):") - await OrderForm.waiting_for_cleaning_type.set() - - @dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT) - async def process_cleaning_type(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['cleaning_type'] = message.text - await message.answer("Выберите способ оплаты (картой/наличными):") - await OrderForm.waiting_for_payment_method.set() - - @dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT) - async def process_payment_method(message: types.Message, state: FSMContext): - async with state.proxy() as data: - data['payment_method'] = message.text - - await message.answer("Подтвердите заказ:\n" - f"ФИО: {data['name']}\n" - f"Номер телефона: {data['phone']}\n" - f"Адрес: {data['address']}\n" - f"Время уборки: {data['cleaning_time']}\n" - f"Тип уборки: {data['cleaning_type']}\n" - f"Способ оплаты: {data['payment_method']}\n" - "Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.", - reply_markup=order_confirmation_keyboard()) - await OrderForm.confirmation.set() - - @dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation) - async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext): - await callback_query.answer() - async with state.proxy() as data: - order_details = (f"Новый заказ:\n" - f"ФИО: {data['name']}\n" - f"Номер телефона: {data['phone']}\n" - f"Адрес: {data['address']}\n" - f"Время уборки: {data['cleaning_time']}\n" - f"Тип уборки: {data['cleaning_type']}\n" - f"Способ оплаты: {data['payment_method']}") - await bot.send_message(OPERATORS_GROUP_ID, order_details) - add_order(data['name'], data['phone'], data['address'], data['cleaning_time'], data['cleaning_type'], data['payment_method']) - - await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.") - await state.finish() +import logging +from aiogram import types +from aiogram.dispatcher import FSMContext +from aiogram.dispatcher.filters.state import State, StatesGroup +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from database import add_order, get_order_history +from logger import log_action + +logging.basicConfig(level=logging.INFO) + +class OrderForm(StatesGroup): + waiting_for_name = State() + waiting_for_phone = State() + waiting_for_address = State() + waiting_for_cleaning_time = State() + waiting_for_cleaning_type = State() + waiting_for_payment_method = State() + confirmation = State() + +def main_menu_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data')) + keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order')) + keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history')) + keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call')) + keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator')) + return keyboard + +def account_data_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name')) + keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone')) + keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address')) + keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address')) + keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact')) + keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main')) + return keyboard + +def order_confirmation_keyboard(): + keyboard = InlineKeyboardMarkup() + keyboard.add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order')) + return keyboard + +def register_handlers(dp, OPERATORS_GROUP_ID): + @dp.message_handler(commands=['start']) + async def send_welcome(message: types.Message): + await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard()) + log_action(message.from_user.id, 'start') + + @dp.callback_query_handler(lambda c: c.data == 'account_data') + async def process_account_data(callback_query: types.CallbackQuery): + await callback_query.answer() + await callback_query.message.answer("Учетные данные:", reply_markup=account_data_keyboard()) + log_action(callback_query.from_user.id, 'account_data') + + @dp.callback_query_handler(lambda c: c.data == 'make_order') + async def process_make_order(callback_query: types.CallbackQuery, state: FSMContext): + await callback_query.answer() + await callback_query.message.answer("Начнем с ваших учетных данных. Введите ваше ФИО:") + await OrderForm.waiting_for_name.set() + log_action(callback_query.from_user.id, 'make_order') + + @dp.callback_query_handler(lambda c: c.data == 'order_history') + async def process_order_history(callback_query: types.CallbackQuery): + await callback_query.answer() + orders = get_order_history(callback_query.from_user.id) + if orders: + history_message = "\n\n".join([f"Заказ {order['id']}:\nФИО: {order['name']}\nАдрес: {order['address']}\nТип уборки: {order['cleaning_type']}" for order in orders]) + await callback_query.message.answer(f"История заказов:\n{history_message}") + else: + await callback_query.message.answer("История заказов пуста.") + log_action(callback_query.from_user.id, 'order_history') + + @dp.callback_query_handler(lambda c: c.data == 'request_call') + async def process_request_call(callback_query: types.CallbackQuery): + await callback_query.answer() + await callback_query.message.answer("Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.") + log_action(callback_query.from_user.id, 'request_call') + + @dp.callback_query_handler(lambda c: c.data == 'talk_operator') + async def process_talk_operator(callback_query: types.CallbackQuery): + await callback_query.answer() + await callback_query.message.answer("Разговор с оператором начат.") + log_action(callback_query.from_user.id, 'talk_operator') + + @dp.callback_query_handler(lambda c: c.data == 'back_to_main') + async def process_back_to_main(callback_query: types.CallbackQuery): + await callback_query.answer() + await callback_query.message.answer("Главное меню:", reply_markup=main_menu_keyboard()) + log_action(callback_query.from_user.id, 'back_to_main') + + @dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT) + async def process_name(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['name'] = message.text + await message.answer("Введите ваш номер телефона:") + await OrderForm.waiting_for_phone.set() + + @dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT) + async def process_phone(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['phone'] = message.text + await message.answer("Введите адрес для уборки:") + await OrderForm.waiting_for_address.set() + + @dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT) + async def process_order_address(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['address'] = message.text + await message.answer("Выберите время для уборки (утро/день/вечер):") + await OrderForm.waiting_for_cleaning_time.set() + + @dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT) + async def process_cleaning_time(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['cleaning_time'] = message.text + await message.answer("Выберите тип уборки (влажная/сухая/генеральная):") + await OrderForm.waiting_for_cleaning_type.set() + + @dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT) + async def process_cleaning_type(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['cleaning_type'] = message.text + await message.answer("Выберите способ оплаты (картой/наличными):") + await OrderForm.waiting_for_payment_method.set() + + @dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT) + async def process_payment_method(message: types.Message, state: FSMContext): + async with state.proxy() as data: + data['payment_method'] = message.text + + await message.answer("Подтвердите заказ:\n" + f"ФИО: {data['name']}\n" + f"Номер телефона: {data['phone']}\n" + f"Адрес: {data['address']}\n" + f"Время уборки: {data['cleaning_time']}\n" + f"Тип уборки: {data['cleaning_type']}\n" + f"Способ оплаты: {data['payment_method']}\n" + "Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.", + reply_markup=order_confirmation_keyboard()) + await OrderForm.confirmation.set() + + @dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation) + async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext): + await callback_query.answer() + async with state.proxy() as data: + order_details = (f"Новый заказ:\n" + f"ФИО: {data['name']}\n" + f"Номер телефона: {data['phone']}\n" + f"Адрес: {data['address']}\n" + f"Время уборки: {data['cleaning_time']}\n" + f"Тип уборки: {data['cleaning_type']}\n" + f"Способ оплаты: {data['payment_method']}") + await bot.send_message(OPERATORS_GROUP_ID, order_details) + add_order(data['name'], data['phone'], data['address'], data['cleaning_time'], data['cleaning_type'], data['payment_method']) + + await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.") + await state.finish() diff --git a/doners/old_2/logger.py b/doners/old_2/logger.py index f683fcf..18d4332 100644 --- a/doners/old_2/logger.py +++ b/doners/old_2/logger.py @@ -1,6 +1,6 @@ -import logging - -logging.basicConfig(level=logging.INFO) - -def log_action(user_id, action): - logging.info(f"User {user_id} performed action: {action}") +import logging + +logging.basicConfig(level=logging.INFO) + +def log_action(user_id, action): + logging.info(f"User {user_id} performed action: {action}") diff --git a/doners/old_2/main.py b/doners/old_2/main.py index 6d51354..a445f29 100644 --- a/doners/old_2/main.py +++ b/doners/old_2/main.py @@ -1,31 +1,31 @@ -import logging -from aiogram import Bot, Dispatcher, types -from aiogram.contrib.middlewares.logging import LoggingMiddleware -from aiogram.utils import executor -from aiogram.dispatcher import FSMContext -from aiogram.dispatcher.filters.state import State, StatesGroup -from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton -from dotenv import load_dotenv -import os - -from handlers import register_handlers -from database import create_tables - -# Загрузка переменных окружения из .env файла -load_dotenv() - -API_TOKEN = os.getenv('BOT_API_TOKEN') -OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID')) -ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID')) - -logging.basicConfig(level=logging.INFO) - -bot = Bot(token=API_TOKEN) -dp = Dispatcher(bot) -dp.middleware.setup(LoggingMiddleware()) - -# Основное приложение -if __name__ == '__main__': - create_tables() - register_handlers(dp, OPERATORS_GROUP_ID) - executor.start_polling(dp, skip_updates=True) +import logging +from aiogram import Bot, Dispatcher, types +from aiogram.contrib.middlewares.logging import LoggingMiddleware +from aiogram.utils import executor +from aiogram.dispatcher import FSMContext +from aiogram.dispatcher.filters.state import State, StatesGroup +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from dotenv import load_dotenv +import os + +from handlers import register_handlers +from database import create_tables + +# Загрузка переменных окружения из .env файла +load_dotenv() + +API_TOKEN = os.getenv('BOT_API_TOKEN') +OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID')) +ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID')) + +logging.basicConfig(level=logging.INFO) + +bot = Bot(token=API_TOKEN) +dp = Dispatcher(bot) +dp.middleware.setup(LoggingMiddleware()) + +# Основное приложение +if __name__ == '__main__': + create_tables() + register_handlers(dp, OPERATORS_GROUP_ID) + executor.start_polling(dp, skip_updates=True) diff --git a/doners/shop-telegram-bot/.dockerignore b/doners/shop-telegram-bot/.dockerignore new file mode 100644 index 0000000..802fc68 --- /dev/null +++ b/doners/shop-telegram-bot/.dockerignore @@ -0,0 +1,23 @@ +/images +data.db +*.pyc +*.pyd +sticker.tgs +config.ini +src/test.py +aws.sh +KP.pem +ideas.txt +/tmp +DOCS/overview.md +src/modules/ +backups/ +logs/ +DOCS/ +.github/ +.git/ +tests/ +LICENCE +readme.md +start.cmd +start.sh diff --git a/doners/shop-telegram-bot/.github/FUNDING.yml b/doners/shop-telegram-bot/.github/FUNDING.yml new file mode 100644 index 0000000..e4202d5 --- /dev/null +++ b/doners/shop-telegram-bot/.github/FUNDING.yml @@ -0,0 +1,2 @@ +Monero: 43fxouHQFZ5guiyYSUWh6eL1ZQ7pDtVV8D1kKUzp4aYwQBgLjyY8q7KjeEbDvvTYCDPFEdDxz9duBdRrZPnjiSMwVGV4jZj +Bitcoin: bc1qnzft2cfty0hqptxjx9ajk4m2q9x3a30gvpylh2 diff --git a/doners/shop-telegram-bot/.github/ISSUE_TEMPLATE/bug-report.md b/doners/shop-telegram-bot/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..1447929 --- /dev/null +++ b/doners/shop-telegram-bot/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Помогите нам стать лучше +title: "[BUG]" +labels: bug +assignees: w1png + +--- + +**Опишите ошибку** +Краткое и понятное описание проблемы. + +**Как воспроизвести ошибку** +1. Откройте '...' +2. Нажмите на '....' +3. Пролистайте до '....' +4. Ошибка + +**Ожидаемое поведение** +Краткое и понятное описание ожидаемого поведения. + +**Скриншоты** +Прикрепите скриншот с проблемой. + +**Информация о системе:** + - Операционная система + - Версия Python diff --git a/doners/shop-telegram-bot/.gitignore b/doners/shop-telegram-bot/.gitignore new file mode 100644 index 0000000..e4a47d3 --- /dev/null +++ b/doners/shop-telegram-bot/.gitignore @@ -0,0 +1,15 @@ +/images +data.db +*.pyc +*.pyd +sticker.tgs +config.ini +src/test.py +aws.sh +KP.pem +ideas.txt +/tmp +DOCS/overview.md +src/modules/ +backups/ +logs/ \ No newline at end of file diff --git a/doners/shop-telegram-bot/DOCS/bot_overview.gif b/doners/shop-telegram-bot/DOCS/bot_overview.gif new file mode 100644 index 0000000..cb1db0a Binary files /dev/null and b/doners/shop-telegram-bot/DOCS/bot_overview.gif differ diff --git a/doners/shop-telegram-bot/Dockerfile b/doners/shop-telegram-bot/Dockerfile new file mode 100644 index 0000000..2751dc0 --- /dev/null +++ b/doners/shop-telegram-bot/Dockerfile @@ -0,0 +1,7 @@ +FROM python:3.10-slim-buster + +WORKDIR /app +COPY . . + +RUN python3 -m pip install -r requirements.txt +CMD python3 installer.py --nointeract && python3 src/main.py diff --git a/doners/shop-telegram-bot/LICENSE b/doners/shop-telegram-bot/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/doners/shop-telegram-bot/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/doners/shop-telegram-bot/__init__.py b/doners/shop-telegram-bot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/doners/shop-telegram-bot/installer.py b/doners/shop-telegram-bot/installer.py new file mode 100644 index 0000000..54b19e8 --- /dev/null +++ b/doners/shop-telegram-bot/installer.py @@ -0,0 +1,174 @@ +from os import system, name, remove, mkdir, rmdir, listdir, environ +from os.path import exists +from sys import argv + +import sqlite3 + + +def clearConsole(): + system("cls" if name in ("nt", "dos") else "clear") + +def create_config(token, main_admin_id, config_path="config.ini"): + DEFAULT_CONFIG_TEXT = f"""[main_settings] +token = {token} +mainadminid = {main_admin_id} +debug = 0 + +[shop_settings] +name = Название магазина +greeting = Добро пожаловать! +refundpolicy = Текст для вкладки "Политика возврата" +contacts = Текст для вкладки "Контакты" +enableimage = 1 +enablesticker = 0 +enablephonenumber = 0 +enabledelivery = 0 +delivery_price = 0.0 +enablecaptcha = 1 + +[stats_settings] +barcolor = 3299ff +borderwidth = 1 +titlefontsize = 20 +axisfontsize = 12 +tickfontsize = 8 +""" + with open(config_path, "w") as config: + config.write(DEFAULT_CONFIG_TEXT) + + +CREATE_CATS_TEXT = """ +CREATE TABLE "cats" ( + "id" INTEGER, + "name" TEXT NOT NULL, + PRIMARY KEY("id") +) +""" +CREATE_ITEMS_TEXT = """ +CREATE TABLE "items" ( + "id" INTEGER, + "name" TEXT NOT NULL, + "price" FLOAT NOT NULL, + "cat_id" INTEGER NOT NULL, + "desc" TEXT, + "active" INTEGER, + "amount" INTEGER, + "image_id" INTEGER, + "hide_image" INTEGER, + PRIMARY KEY("id") +) +""" +CREATE_ORDERS_TEXT = """ +CREATE TABLE "orders" ( + "order_id" INTEGER, + "user_id" INTEGER, + "item_list" TEXT, + "email_adress" TEXT, + "phone_number" TEXT, + "home_adress" TEXT, + "additional_message" TEXT, + "date" TEXT, + "status" INTEGER + ) +""" +CREATE_USERS_TEXT = """ +CREATE TABLE "users" ( + "user_id" INTEGER NOT NULL, + "is_admin" INTEGER, + "is_manager" INTEGER, + "notification" INTEGER, + "date_created" TEXT, + "cart" TEXT, + "cart_delivery" INTEGER +) +""" +CREATE_COMMANDS_TEXT = """ +CREATE TABLE "commands" ( + "id" INTEGER NOT NULL, + "command" TEXT, + "response" TEXT, + PRIMARY KEY("id") +) +""" + +def create_db(): + conn = sqlite3.connect("data.db") + c = conn.cursor() + c.execute(CREATE_CATS_TEXT) + c.execute(CREATE_ITEMS_TEXT) + c.execute(CREATE_ORDERS_TEXT) + c.execute(CREATE_USERS_TEXT) + c.execute(CREATE_COMMANDS_TEXT) + conn.commit() + conn.close() + +def do_files_exist(): + return any(list(map(exists, ["config.ini", "images", "data.db"]))) + +if __name__ == "__main__": + if "--nointeract" in argv: + if do_files_exist(): + exit(0) + + token = environ.get("TELEGRAM_TOKEN") + main_admin_id = environ.get("MAIN_ADMIN_ID") + if token is None or main_admin_id is None: + print("Не указаны переменные окружения TELEGRAM_TOKEN или MAIN_ADMIN_ID") + exit(1) + create_config(token, main_admin_id) + + create_db() + [mkdir(name) for name in ["backups", "images"]] + + exit(0) + + + clearConsole() + if do_files_exist(): + while True: + confirmation = input("Вы уверены, что хотите повторно запустить процесс установки? Все данные будут утеряны! (y/N) ") + if confirmation.lower() in ["y", "yes", "n", "no", ""]: + break + else: + confirmation = "y" + + + if confirmation.lower() in ["y", "yes"]: + print("Вы можете узнать как получить токен бота, перейдя по ссылке: https://youtu.be/fyISLEvzIec") + token = input("Введите токен бота: ") + print("Вы можете получить ваш ID, написав \"/start\" боту @userinfobot") + main_admin_id = input("Введите ID главного администратора: ") + if main_admin_id.isalnum(): + if exists("data.db"): + remove("data.db") + print("База данных была удалена.") + create_db() + print("База данных была создана.") + if exists("config.ini"): + remove("config.ini") + print("Файл настроек был удален.") + create_config(token, main_admin_id) + print("Файл настроек был создан.") + if exists("images"): + for file in listdir("images"): + remove("images/" + file) + rmdir("images") + print("Папка \"images\" была удалена.") + mkdir("images") + print("Папка \"images\" была создана.") + if exists("backups"): + for folder in listdir("backups"): + for file in listdir("backups/" + folder): + remove(f"backups/{folder}/{file}") + rmdir(f"backups/{folder}") + rmdir("backups") + print("Папка \"backups\" была удалена.") + mkdir("backups") + print("Папка \"backups\" была создана.") + else: + print("Неверный ID главного администратора.") + else: + print("Установка была отменена.") + + + input("Нажмите ENTER, чтобы продолжить...") diff --git a/doners/shop-telegram-bot/readme.md b/doners/shop-telegram-bot/readme.md new file mode 100644 index 0000000..6130545 --- /dev/null +++ b/doners/shop-telegram-bot/readme.md @@ -0,0 +1,83 @@ +# ⚠️ Эта версия бота больше не обновляется! +Сейчас я переписываю бота, за обновлениями следите на ветке rewrite :) +
+ +## 🪲 В случае возникновения ошибок открывайте тикет во вкладке Issues :) + +# Навигация + +- [Навигация](#навигация) +- [Зачем нужен этот бот?](#зачем-нужен-этот-бот) +- [Установка](#установка) + - [Docker](#docker) + - [Python](#python) + - [Установка необходимых Python-пакетов](#установка-необходимых-python-пакетов) + - [Запуск установщика](#запуск-установщика) + - [Запуск бота](#запуск-бота) + - [Запуск с помощью скрипта](#запуск-с-помощью-скрипта) + - [Linux](#linux) + - [Macos](#macos) + - [Запуск вручную](#запуск-вручную) +- [Режим отладки](#режим-отладки) + +# Зачем нужен этот бот? + +Зачастую люди, желающие открыть маленький интернет-бизнес, делают это с помощью профиля в социальных сетях, что требует вручную обрабатывать каждую заявку. Этот бот позволит каждому быстро открыть автоматизированный магазин на базе телеграм бота, что значительно уменьшит время обработки заказов. + +![overview](DOCS/bot_overview.gif) + +# Установка + +## Docker +Этот способ установки является основным и рекомендуемым. Он позволяет установить бота в репродуцируемом окружении, что упрощает его обновление и установку на сервер. + +Для работы бота необходимо установить [Docker](https://docs.docker.com/get-docker/) и выполнить следущую команду: + +``` +docker run -d \ + -e MAIN_ADMIN_ID='Ваш Telegram ID' \ + -e TELEGRAM_TOKEN='token' \ + w1png/shop-telegram-bot +``` + +## Python + +Для работы бота необходимо установить [Python версии 3.10 и выше](https://www.python.org/downloads/). + +### Установка необходимых Python-пакетов + + python3 -m pip install -r requirements.txt + +### Запуск установщика + +Перед запуском установщика требуется [создать токен](https://youtu.be/fyISLEvzIec) для телеграм бота и [получить ваш ID](https://badcode.ru/kak-v-telegram-uznat-svoi-id/). + +Установщик запускается с помощью команды: + + python3 installer.py + +## Запуск бота + +### Запуск с помощью скрипта + +#### Linux/MacOS + + $ chmod +x start.sh + $ ./start.sh + +#### Windows + + $ start.cmd + +### Запуск вручную + + python3 main.py + +# Режим отладки + +Режим отладки можно активировать во вкладке "Основные настройки". +После активации в терминале начнут отображаться все сообщения и вызовы в формате: + + DEBUG: [] <Сообщение/вызов> + +*Пример: `DEBUG CALL [462741] admin_itemManagement`* diff --git a/doners/shop-telegram-bot/requirements.txt b/doners/shop-telegram-bot/requirements.txt new file mode 100644 index 0000000..f831b29 --- /dev/null +++ b/doners/shop-telegram-bot/requirements.txt @@ -0,0 +1,6 @@ +aiogram==2.19 +captcha==0.3 +matplotlib==3.5.1 +phonenumbers==8.12.40 +pymorphy2==0.9.1 +pyparsing==3.0.0 diff --git a/doners/shop-telegram-bot/src/__init__.py b/doners/shop-telegram-bot/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/doners/shop-telegram-bot/src/category.py b/doners/shop-telegram-bot/src/category.py new file mode 100644 index 0000000..a78dc31 --- /dev/null +++ b/doners/shop-telegram-bot/src/category.py @@ -0,0 +1,51 @@ +import sqlite3 +import item as itm + +conn = sqlite3.connect("data.db") +c = conn.cursor() + +class Category: + def __init__(self, cat_id): + self.id = cat_id + + def __eq__(self, __o: object) -> bool: + return self.get_id() == __o.get_id() + + def __repr__(self) -> str: + return self.get_name() + + def get_id(self): + return self.id + + def __clist(self): + c.execute(f"SELECT * FROM cats WHERE id=?", [self.get_id()]) + return list(c)[0] + + def get_name(self): + return self.__clist()[1] + + def set_name(self, value): + c.execute(f"UPDATE cats SET name=? WHERE id=?", [value, self.get_id()]) + conn.commit() + + def delete(self): + c.execute(f"DELETE FROM cats WHERE id=?", [self.get_id()]) + conn.commit() + + def get_item_list(self): + c.execute(f"SELECT * FROM items WHERE cat_id=?", [self.get_id()]) + return list(map(itm.Item, [item[0] for item in list(c)])) + + +def get_cat_list(): + c.execute(f"SELECT * FROM cats") + return list(map(Category, [cat[0] for cat in list(c)])) + + +def create_cat(cat_name): + c.execute(f"INSERT INTO cats(name) VALUES(?)", [cat_name]) + conn.commit() + + +if __name__ == "__main__": + print(get_cat_list()) \ No newline at end of file diff --git a/doners/shop-telegram-bot/src/commands.py b/doners/shop-telegram-bot/src/commands.py new file mode 100644 index 0000000..037e140 --- /dev/null +++ b/doners/shop-telegram-bot/src/commands.py @@ -0,0 +1,61 @@ +import sqlite3 + +conn = sqlite3.connect("data.db") +c = conn.cursor() + + +def get_commands(): + return list(map(Command, [command[0] for command in list(c.execute("SELECT * FROM commands"))])) + +def does_command_exist(command_id=None, command=None): + result = False + if command_id: + c.execute("SELECT * FROM commands WHERE id=?", [command_id]) + result = len(list(c)) >= 1 + elif command: + c.execute("SELECT * FROM commands WHERE command=?", [command]) + result = len(list(c)) >= 1 + return result + +def get_command_by_command(command): + c.execute("SELECT * FROM commands WHERE command=?", [command]) + return Command(list(c)[0][0]) + + +def create_command(command, response): + c.execute("INSERT INTO commands(command, response) VALUES(?,?)", [command, response]) + conn.commit() + + +# TODO: rework the permission system and implement command permissions +class Command: + def __init__(self, command_id=None): + self.command_id = command_id + + + def __repr__(self): + return f"[{self.get_id()}] {self.get_command()}" + + def __clist(self): + c.execute("SELECT * FROM commands WHERE id=?", [self.command_id]) + return list(c)[0] + + def get_id(self): + return self.command_id + + def get_command(self): + return self.__clist()[1] + + def get_response(self): + return self.__clist()[2] + + def delete(self): + c.execute("DELETE FROM commands WHERE id=?", [self.command_id]) + conn.commit() + + +if __name__ == "__main__": + # create_command("test", "response") + + for command in get_commands(): + print(command) diff --git a/doners/shop-telegram-bot/src/item.py b/doners/shop-telegram-bot/src/item.py new file mode 100644 index 0000000..5b4f265 --- /dev/null +++ b/doners/shop-telegram-bot/src/item.py @@ -0,0 +1,111 @@ +import sqlite3 +from settings import Settings +from os.path import exists +from doners.old_2.main import notify_admins + +conn = sqlite3.connect("data.db") +c = conn.cursor() + +settings = Settings() + +def does_item_exist(item_id): + c.execute(f"SELECT * FROM items WHERE id=?", [item_id]) + return len(list(c)) == 1 + + +class Item: + def __init__(self, item_id): + self.__item_id = item_id + + def __eq__(self, __o: object): + return self.get_id() == __o.get_id() + + def __repr__(self): + return f"[{self.get_id()}] {self.get_name()}" + + def get_id(self): + return self.__item_id + + def __clist(self): + c.execute(f"SELECT * FROM items WHERE id=?", [self.get_id()]) + return list(c)[0] + + def get_name(self): + return self.__clist()[1] + + def set_name(self, value): + c.execute(f"UPDATE items SET name=? WHERE id=?", [value, self.get_id()]) + conn.commit() + + def get_price(self): + return self.__clist()[2] + + def set_price(self, value): + c.execute(f"UPDATE items SET price=? WHERE id=?", [value, self.get_id()]) + conn.commit() + + # TODO: change to get_cat and change to cat.get_id() everywhere + def get_cat_id(self): + return self.__clist()[3] + + def set_cat_id(self, value): + c.execute(f"UPDATE items SET cat_id=? WHERE id=?", [value, self.get_id()]) + conn.commit() + + def get_desc(self): + return self.__clist()[4] + + def set_desc(self, value): + c.execute(f"UPDATE items SET desc=? WHERE id=?", [value, self.get_id()]) + conn.commit() + + def is_active(self): + return self.__clist()[5] == 1 + + def set_active(self, value): + c.execute(f"UPDATE items SET active=? WHERE id=?", [value, self.get_id()]) + conn.commit() + + def get_amount(self): + return int(self.__clist()[6]) + + def set_amount(self, value): + c.execute(f"UPDATE items SET amount=? WHERE id=?", [value, self.get_id()]) + conn.commit() + + def get_image_id(self): + return self.__clist()[7] + + def get_image(self): + return open("images/" + self.get_image_id(), "rb") + + def set_image_id(self, value): + c.execute(f"UPDATE items SET image_id=? WHERE id=?", [value, self.get_id()]) + + async def is_hide_image(self): + if exists(f"images/{self.get_image_id()}"): + return self.__clist()[8] == 1 + if self.get_image_id() != "None" and settings.is_debug(): + await notify_admins(f"Файла \"images/{self.get_image_id()}\" для товара \"{self.get_name()}\" не существует!") + return True + + def set_hide_image(self, value): + c.execute("UPDATE items SET hide_image=? WHERE id=?", [value, self.get_id()]) + conn.commit() + + def delete(self): + c.execute(f"DELETE FROM items WHERE id=?", [self.get_id()]) + conn.commit() + + +def get_item_list(): + c.execute("SELECT * FROM items") + return list(map(Item, [item[0] for item in list(c)])) + + +def create_item(name, price, cat_id, desc, image_id="None", active=True): + c.execute(f"INSERT INTO items(name, price, cat_id, desc, active, amount, image_id, hide_image) VALUES(?, ?, ?, ?, ?, 0, ?, 0)", [name, price, cat_id, desc, 1 if active else 0, image_id]) + conn.commit() + return get_item_list()[-1] + + diff --git a/doners/shop-telegram-bot/src/main.py b/doners/shop-telegram-bot/src/main.py new file mode 100644 index 0000000..bce7a8f --- /dev/null +++ b/doners/shop-telegram-bot/src/main.py @@ -0,0 +1,2522 @@ +import asyncio +import sqlite3 +from aiogram import Bot, Dispatcher, executor, types +from aiogram.contrib.fsm_storage.memory import MemoryStorage +from aiogram.dispatcher import FSMContext +from aiogram.types import message, user +from random import choice, randint +from string import ascii_lowercase, ascii_uppercase, digits +from captcha.image import ImageCaptcha +from re import match as matchre +from phonenumbers import parse as phoneparse +from phonenumbers import is_possible_number +from os import getcwd, listdir, remove, mkdir, rmdir +from os.path import getsize, exists +from shutil import copyfile +import datetime + +import markups +import state_handler +import user as usr +import stats +import item as itm +import order as ordr +import category +import text_templates as tt +from settings import Settings +import commands +import search + +conn = sqlite3.connect("data.db") +c = conn.cursor() + +settings = Settings() + +storage = MemoryStorage() +bot = Bot(token=settings.get_token()) +dp = Dispatcher(bot, storage=storage) + +# Create a backup folder + copy the needed files there +def create_backup(): + folder_path = "backups/" + datetime.date.today().strftime("%d-%m-%Y") + if folder_path[8:] in listdir("backups"): + for file in listdir(folder_path): + remove(folder_path + "/" + file) + rmdir(folder_path) + mkdir(folder_path) + copyfile("config.ini", folder_path + "/config.ini") + copyfile("data.db", folder_path + "/data.db") + print("Backup created!") + +def clean_backups(days_ago=0): + longest_date = datetime.date.today() - datetime.timedelta(days=days_ago) + cleaned_size = 0 + for folder in listdir("backups"): + if datetime.datetime.strptime(folder, "%d-%m-%Y").date() < longest_date: + for file in listdir("backups/" + folder): + cleaned_size += getsize(file) + remove(file) + rmdir("backups/" + folder) + return cleaned_size / 1048576 + +def clean_images(): + cleaned_size = 0 + for file in listdir("images"): + if file not in [item.get_image_id() for item in itm.get_item_list()]: + cleaned_size = getsize(f"images/{file}") + remove(f"images/{file}") + return cleaned_size / 1048576 + + +def get_captcha_text(): return ''.join([choice(ascii_uppercase + digits) for i in range(5)]) + +def generate_captcha(captcha_text): + image = ImageCaptcha(width=280, height=90) + image.generate(captcha_text) + image.write(captcha_text, "images/captcha.png") + return open("images/captcha.png", "rb") + + +async def notify_admins(text): + for user in list(filter(lambda x: x.is_admin(), usr.get_user_list())): + try: + await bot.send_message( + chat_id=user.get_id(), + text=text + ) + except: + if settings.is_debug(): + print(f"FAILED TO SEND TO [{user.get_id()}]") + + +@dp.message_handler(commands=["start"]) +async def welcome(message: types.Message): + if settings.is_debug(): + print(f"DEBUG: COMMAND [{message.chat.id}] {message.text}") + user = usr.User(message.chat.id) + + markupMain = markups.get_markup_main() + if user.is_manager() or user.is_admin(): + markupMain.row(markups.btnOrders) + if user.is_admin(): + markupMain.row(markups.btnAdminPanel) + + try: + if settings.is_sticker_enabled(): + if exists("sticker.tgs"): + with open("sticker.tgs", "rb") as sti: + await bot.send_sticker(message.chat.id, sti) + else: + raise Exception + except: + if settings.is_debug(): + print(f"DEBUG: FAILED TO SEND STICKER TO {message.chat.id}. sticker.tgs is probably missing in the bot's root folder.") + await bot.send_message( + chat_id=message.chat.id, + text=settings.get_shop_greeting(), + reply_markup=markupMain, + ) + + +@dp.message_handler() +async def handle_text(message): + if settings.is_debug(): + print(f"DEBUG: MESSAGE [{message.chat.id}] {message.text}") + user = usr.User(message.chat.id) + + if message.text == tt.admin_panel: + if user.is_admin(): + await bot.send_message( + chat_id=message.chat.id, + text=tt.admin_panel, + reply_markup=markups.get_markup_admin(), + ) + elif message.text == tt.orders: + if user.is_manager() or user.is_admin(): + await bot.send_message( + chat_id=message.chat.id, + text=tt.orders, + reply_markup=markups.get_markup_orders() + ) + elif message.text == tt.faq: + await bot.send_message( + chat_id=message.chat.id, + text=tt.get_faq_template(settings.get_shop_name()), + reply_markup=markups.get_markup_faq(), + ) + elif message.text == tt.profile: + await bot.send_message( + chat_id=message.chat.id, + text=tt.get_profile_template(user), + reply_markup=markups.get_markup_profile(user), + ) + elif message.text == tt.catalogue: + await bot.send_message( + chat_id=message.chat.id, + text=tt.catalogue, + reply_markup=markups.get_markup_catalogue(category.get_cat_list()), + ) + elif message.text == tt.cart: + if user.get_cart(): + text = tt.cart + markup = markups.get_markup_cart(user) + else: + text = tt.cart_is_empty + markup = types.InlineKeyboardMarkup() + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markup + ) + elif commands.does_command_exist(command=message.text): + await bot.send_message( + chat_id=message.chat.id, + text=commands.get_command_by_command(message.text).get_response() + ) + else: + await bot.send_message(message.chat.id, 'Не могу понять команду :(') + + +@dp.callback_query_handler() +async def process_callback(callback_query: types.CallbackQuery): + chat_id = callback_query.message.chat.id + call_data = callback_query.data + user = usr.User(chat_id) + + if settings.is_debug(): + print(f"DEBUG: CALL [{chat_id}] {call_data}") + + # Admin calls + if call_data.startswith("admin_") and user.is_admin(): + call_data = call_data[6:] + + if call_data == "adminPanel": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.admin_panel, + reply_markup=markups.get_markup_admin(), + ) + + # Admin tabs + # Item management + elif call_data == "itemManagement": + try: + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.item_management, + reply_markup=markups.get_markup_itemManagement(), + ) + except: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=chat_id, + text=tt.item_management, + reply_markup=markups.get_markup_itemManagement(), + ) + elif call_data == "addCat": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=f"Введите название новой категории {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackItemManagement), + ) + await state_handler.addCat.name.set() + state = Dispatcher.get_current().current_state() + await state.update_data(state_message=callback_query.message.message_id) + elif call_data == "editCatChooseCategory": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=f"Выберите категорию, которую хотите изменить {tt.or_press_back}", + reply_markup=markups.get_markup_editCatChooseCategory(category.get_cat_list()), + ) + elif call_data.startswith("editCatDelete"): + cat = category.Category(call_data[13:]) + try: + text = f"Категория {cat.get_name()} была успешно удалена." + cat.delete() + except: + text = f"Произошла ошибка!" + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markups.single_button(markups.btnBackEditCatChooseCategory), + ) + elif call_data.startswith("editCatName"): + cat = category.Category(call_data[11:]) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=f"Введите новое название для категории \"{cat.get_name()}\" {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackEditCat(cat.get_id())), + ) + await state_handler.changeCatName.name.set() + state = Dispatcher.get_current().current_state() + await state.update_data(cat_id=cat.get_id()) + await state.update_data(state_message=callback_query.message.message_id) + elif call_data.startswith("editCat"): + cat = category.Category(call_data[7:]) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.get_category_data(cat), + reply_markup=markups.get_markup_editCat(cat.get_id()), + ) + elif call_data == "addItem": + if not category.get_cat_list(): + await bot.edit_message_text( + text=f"Создайте категорию перед добавлением товара!", + chat_id=chat_id, + message_id=callback_query.message.message_id, + reply_markup=markups.single_button(markups.btnBackItemManagement) + ) + else: + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=f"Введите название нового товара или нажмите на кнопку \"Назад\".", + reply_markup=markups.single_button(markups.btnBackItemManagement), + ) + await state_handler.addItem.name.set() + elif call_data == "editItemChooseCategory": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text="Выберите категорию товара, который вы хотите редактировать: ", + reply_markup=markups.get_markup_editItemChooseCategory(category.get_cat_list()), + ) + elif call_data.startswith("editItemChooseItem"): + cat = category.Category(call_data[18:]) + text = f"Выберите товар, который вы хотите редактировать: " + markup = markups.get_markup_editItemChooseItem(cat.get_item_list()) + try: + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + except: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=markup + ) + + elif call_data.startswith("editItemName"): + item = itm.Item(call_data[12:]) + text = f"Введите новое название для \"{item.get_name()}\" {tt.or_press_back}" + markup = markups.single_button(markups.btnBackEditItem(item.get_id())) + + if item.get_image_id() == "None" or not settings.is_item_image_enabled() or await item.is_hide_image(): + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + else: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=markup + ) + + await state_handler.changeItemName.name.set() + state = Dispatcher.get_current().current_state() + await state.update_data(item_id=item.get_id()) + await state.update_data(state_message=callback_query.message.message_id) + elif call_data.startswith("editItemDesc"): + item = itm.Item(call_data[12:]) + text = f"Введите новое описание для \"{item.get_name()}\" {tt.or_press_back}" + markup = markups.single_button(markups.btnBackEditItem(item.get_id())) + + if item.get_image_id() == "None" or not settings.is_item_image_enabled(): + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + else: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=markup + ) + await state_handler.changeItemDesc.desc.set() + state = Dispatcher.get_current().current_state() + await state.update_data(item_id=item.get_id()) + await state.update_data(state_message=callback_query.message.message_id) + elif call_data.startswith("editItemPrice"): + item = itm.Item(call_data[13:]) + text = f"Введите новую цену для \"{item.get_name()}\" {tt.or_press_back}" + markup = markups.single_button(markups.btnBackEditItem(item.get_id())) + + if item.get_image_id() == "None" or not settings.is_item_image_enabled() or await item.is_hide_image(): + try: + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + except: + await bot.delete_message( + chat_id=chat_id, + message_id=callback_query.message.message_id + ) + await bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=markup + ) + else: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=markup + ) + + await state_handler.changeItemPrice.price.set() + state = Dispatcher.get_current().current_state() + await state.update_data(item_id=item.get_id()) + await state.update_data(state_message=callback_query.message.message_id) + elif call_data.startswith("editItemCat"): + item = itm.Item(call_data[11:]) + text = f"Выберите новую категорию для \"{item.get_name()}\" {tt.or_press_back}" + markup = markups.get_markup_editItemCat(item_id=item.get_id(), cat_list=category.get_cat_list()) + + if item.get_image_id() == "None" or not settings.is_item_image_enabled(): + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + else: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=markup + ) + + await state_handler.changeItemCat.cat.set() + state = Dispatcher.get_current().current_state() + await state.update_data(item_id=item.get_id()) + elif call_data.startswith("editItemStock"): + item = itm.Item(call_data[13:]) + text = f"Введите новое количество товара для \"{item.get_name()}\" {tt.or_press_back}" + markup = markups.single_button(markups.btnBackEditItem(item.get_id())) + + if item.get_image_id() == "None" or not settings.is_item_image_enabled(): + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + else: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=markup + ) + + await state_handler.changeItemStock.stock.set() + state = Dispatcher.get_current().current_state() + await state.update_data(item_id=item.get_id()) + await state.update_data(state_message=callback_query.message.message_id) + elif call_data.startswith("editItemHideImage"): + item = itm.Item(call_data[17:]) + cat = category.Category(item.get_cat_id()) + + try: + item.set_hide_image(0 if await item.is_hide_image() else 1) + text = tt.get_item_card(item) + f"\nКатегория: {cat.get_name()}" + except: + text = tt.error + markup = await markups.get_markup_editItem(item) + + if item.get_image_id() == "None" or not settings.is_item_image_enabled() or await item.is_hide_image(): + try: + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + except: # TODO: make it more compact + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + text=text, + chat_id=chat_id, + reply_markup=markup + ) + else: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=text, + photo=item.get_image(), + reply_markup=markup + ) + + elif call_data.startswith("editItemHide"): + item = itm.Item(call_data[12:]) + cat = category.Category(item.get_cat_id()) + + try: + item.set_active(0 if item.is_active() else 1) + text = tt.get_item_card(item) + f"\nКатегория: {cat.get_name()}" + except: + text = tt.error + markup = await markups.get_markup_editItem(item) + + if item.get_image_id() == "None" or not settings.is_item_image_enabled() or await item.is_hide_image(): + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + else: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=text, + photo=item.get_image(), + reply_markup=markup + ) + + elif call_data.startswith("editItemDelete"): + item = itm.Item(call_data[14:]) + cat = category.Category(item.get_cat_id()) + try: + text = f"Товар \"{item.get_name()}\" был удалён." + item.delete() + markup = markups.single_button(markups.btnBackEditItemChooseItem(cat.get_id())) + except: + text = tt.error + markup = markups.single_button(markups.btnBackEditItem(item.get_id())) + + try: + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + except: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=text, + photo=item.get_image(), + reply_markup=markup + ) + + elif call_data.startswith("editItemImage"): + item = itm.Item(call_data[13:]) + text = f"Отправьте изображение для товара {tt.or_press_back}" + markup = markups.single_button(markups.btnBackEditItem(item.get_id())) + + await state_handler.changeItemImage.image.set() + state = Dispatcher.get_current().current_state() + await state.update_data(item_id=item.get_id()) + + try: + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + await state.update_data(state_message=callback_query.message.message_id) + except: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=markup + ) + + elif call_data.startswith("editItem"): + item = itm.Item(call_data[8:]) + cat = category.Category(item.get_cat_id()) + text = tt.get_item_card(item=item) + f"\nКатегория: {cat.get_name()}" + markup = await markups.get_markup_editItem(item) + + if item.get_image_id() == "None" or not settings.is_item_image_enabled() or await item.is_hide_image(): + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + else: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=text, + photo=item.get_image(), + reply_markup=markup + ) + + # User management + elif call_data == "userManagement": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.user_management, + reply_markup=markups.get_markup_userManagement(), + ) + elif call_data == "seeUserProfile": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=f"Введите ID пользователя {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackUserManagement), + ) + await state_handler.seeUserProfile.user_id.set() + state = Dispatcher.get_current().current_state() + await state.update_data(state_message=callback_query.message.message_id) + elif call_data.startswith("seeUserProfile"): + user = usr.User(int(call_data[14:])) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.get_profile_template(user), + reply_markup=markups.get_markup_seeUserProfile(user), + ) + elif call_data.startswith("seeUserOrders"): + edit_user = usr.User(call_data[13:]) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=f"Заказы пользователя с ID {edit_user.get_id()}.", + reply_markup=markups.get_markup_seeUserOrders(edit_user), + ) + elif call_data.startswith("seeUserOrder"): + order = ordr.Order(call_data[12:]) + await bot.edit_message_text( + text=tt.get_order_template(order), + chat_id=chat_id, + message_id=callback_query.message.message_id, + reply_markup=markups.get_markup_seeOrder(order, user_id=order.get_user_id()) + ) + elif call_data.startswith("changeUserManager"): + editUser = usr.User(call_data[17:]) + editUser.set_manager(0 if editUser.is_manager() else 1) + + try: + markupMain = markups.get_markup_main() + if editUser.is_manager() or editUser.is_admin(): + markupMain.row(markups.btnOrders) + if editUser.is_admin(): + markupMain.row(markups.btnAdminPanel) + await bot.send_message( + chat_id=editUser.get_id(), + text=f"Ваша роль менеджера была обновлена.", + reply_markup=markupMain + ) + except: + if settings.is_debug(): + print(f"DEBUG [{user.get_id()}] FAILED TO SEND MESSAGE TO [{editUser.get_id()}]") + + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.get_profile_template(editUser), + reply_markup=markups.get_markup_seeUserProfile(editUser), + ) + elif call_data.startswith("changeUserAdmin"): + editUser = usr.User(int(call_data[15:])) + if editUser.get_id() == user.get_id(): + markup = markups.single_button(markups.btnBackSeeUserProfile(editUser.get_id())) + text = f"Вы не можете забрать роль администратора у себя!" + else: + try: + editUser.set_admin(0 if editUser.is_admin() else 1) + markup = markups.get_markup_seeUserProfile(editUser) + text = tt.get_profile_template(editUser) + + try: + markupMain = markups.get_markup_main() + if editUser.is_manager() or editUser.is_admin(): + markupMain.row(markups.btnOrders) + if editUser.is_admin(): + markupMain.row(markups.btnAdminPanel) + await bot.send_message( + chat_id=editUser.get_id(), + text=f"Ваша роль администратора была обновлена.", + reply_markup=markupMain + ) + except: + if settings.is_debug(): + print(f"DEBUG [{user.get_id()}] FAILED TO SEND MESSAGE TO [{editUser.get_id()}]") + + except: + text = tt.error + markup = markups.single_button(markups.btnBackSeeUserProfile(editUser.get_id())) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + elif call_data == "notifyEveryone": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text="Введите сообщение, которое хотите отправить ВСЕМ пользователям.", + reply_markup=markups.single_button(markups.btnBackUserManagement), + ) + await state_handler.notifyEveryone.message.set() + state = Dispatcher.get_current().current_state() + await state.update_data(state_message=callback_query.message.message_id) + + # Stats + elif call_data == "shopStats": + await bot.edit_message_text( + text=tt.shop_stats, + chat_id=chat_id, + message_id=callback_query.message.message_id, + reply_markup=markups.get_markup_shopStats() + ) + elif call_data == "registrationStats": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.registration_stats, + reply_markup=markups.get_markup_registrationStats(), + ) + elif call_data == "registrationStatsBack": + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=callback_query.message.chat.id, + text=tt.registration_stats, + reply_markup=markups.get_markup_registrationStats(), + ) + elif call_data.startswith("registrationStats"): + call_data = call_data[17:] + charts = stats.RegistrationCharts() + + match call_data: + case "AllTime": + photo = charts.all_time() + text = tt.all_time + case "Monthly": + photo = charts.last_x_days(30) + text = tt.monthly + case "Weekly": + photo = charts.last_x_days(7) + text = tt.weekly + case "Daily": + photo = charts.last_x_hours(24) + text = tt.daily + + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + photo=photo, + caption=text, + reply_markup=markups.single_button(markups.btnBackRegistratonStats) + ) + elif call_data == "orderStats": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.order_stats, + reply_markup=markups.get_markup_orderStats(), + ) + elif call_data == "orderStatsBack": + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=callback_query.message.chat.id, + text=tt.order_stats, + reply_markup=markups.get_markup_orderStats(), + ) + elif call_data.startswith("orderStats"): + call_data = call_data[10:] + charts = stats.OrderCharts() + + match call_data: + case "AllTime": + photo = charts.all_time() + text = tt.all_time + case "Monthly": + photo = charts.last_x_days(30) + text = tt.monthly + case "Weekly": + photo = charts.last_x_days(7) + text = tt.weekly + case "Daily": + photo = charts.last_x_hours(24) + text = tt.daily + + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + photo=photo, + caption=text, + reply_markup=markups.single_button(markups.btnBackOrderStats) + ) + + # Settings + elif call_data == "shopSettings": + await bot.edit_message_text( + text=tt.bot_settings, + message_id=callback_query.message.message_id, + chat_id=chat_id, + reply_markup=markups.get_markup_shopSettings() + ) + elif call_data == "shopSettingsDel": + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + text=tt.bot_settings, + chat_id=chat_id, + reply_markup=markups.get_markup_shopSettings() + ) + + elif call_data == "mainSettings": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.main_settings, + reply_markup=markups.get_markup_mainSettings(), + ) + elif call_data == "changeShopName": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=f"Введите новое название магазина {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackMainSettings), + ) + await state_handler.changeShopName.name.set() + state = Dispatcher.get_current().current_state() + await state.update_data(state_message=callback_query.message.message_id) + elif call_data == "changeShopGreeting": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=f"Введите новое приветствие магазина {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackMainSettings), + ) + await state_handler.changeShopGreeting.greeting.set() + state = Dispatcher.get_current().current_state() + await state.update_data(state_message=callback_query.message.message_id) + elif call_data == "changeShopRefundPolicy": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=f"Введите новую политику возврата магазина {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackMainSettings), + ) + await state_handler.changeShopRefundPolicy.refund_policy.set() + state = Dispatcher.get_current().current_state() + await state.update_data(state_message=callback_query.message.message_id) + elif call_data == "changeShopContacts": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=f"Введите новый текст для вкладки \"Контакты\" возврата магазина {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackMainSettings), + ) + await state_handler.changeShopContacts.contacts.set() + state = Dispatcher.get_current().current_state() + await state.update_data(state_message=callback_query.message.message_id) + + elif call_data == "itemSettings": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.item_settings, + reply_markup=markups.get_markup_itemSettings(), + ) + + elif call_data == "checkoutSettings": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.checkout_settings, + reply_markup=markups.get_markup_checkoutSettings(), + ) + elif call_data == "changeDeliveryPrice": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=f"Введите новую цену доставки {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackCheckoutSettings), + ) + await state_handler.changeDeliveryPrice.price.set() + state = Dispatcher.get_current().current_state() + await state.update_data(state_message=callback_query.message.message_id) + elif call_data.startswith("changeEnable"): + try: + # Checkout + match call_data[12:]: + case "PhoneNumber": + settings.set_enable_phone_number("0" if settings.is_phone_number_enabled() else "1") + case "Delivery": + settings.set_delivery("0" if settings.is_delivery_enabled() else "1") + case "Captcha": + settings.set_enable_captcha("0" if settings.is_captcha_enabled() else "1") + text = tt.checkout_settings + markup = markups.get_markup_checkoutSettings() + + if call_data[12:] == "Sticker": + await bot.send_message( + chat_id=chat_id, + text=f"Для работы стикера требуется поместить sticker.tgs в корневую папку бота.", + ) + settings.set_enable_sticker("0" if settings.is_sticker_enabled() else "1") + text = tt.main_settings + markup = markups.get_markup_mainSettings() + elif call_data[12:] == "ItemImage": + settings.set_item_image("0" if settings.is_item_image_enabled() else "1") + text = tt.item_settings + markup = markups.get_markup_itemSettings() + elif call_data[12:] == "Debug": + settings.set_debug("0" if settings.is_debug() else "1") + text = tt.system_settings + markup = markups.get_markup_systemSettings() + + except: + text = tt.error + if call_data[12:] in ["PhoneNumber", "Delivery", "Captcha"]: + markup = markups.single_button(markups.btnBackCheckoutSettings) + elif call_data[12:] == "Sticker": + markup = markups.single_button(markups.btnBackMainSettings) + elif call_data[12:] == "ItemImage": + markup = markups.single_button(markups.btnBackItemSettings) + elif call_data[12:] == "Debug": + markup = markups.single_button(markups.btnBackSystemSettings) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + + elif call_data == "statsSettings": + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=tt.stats_settings, + photo=stats.get_random_graph(), + reply_markup=markups.get_markup_statsSettings() + ) + elif call_data == "statsSettingsColor": + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=tt.graph_color, + photo=stats.get_random_graph(), + reply_markup=markups.get_markup_statsSettingsColor() + ) + elif call_data.startswith("statsSettingsColor"): + color = call_data[18:] + match color: + case "Black": + color = "000000" + case "White": + color = "ffffff" + case "Red": + color = "cc0000" + case "Yellow": + color = "ffff00" + case "Purple": + color = "a957e3" + case "Blue": + color = "3299ff" + case "Orange": + color = "ffa500" + case "Green": + color = "4ca64c" + case "Brown": + color = "4c3100" + settings.set_barcolor(color) + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=tt.graph_color, + photo=stats.get_random_graph(), + reply_markup=markups.get_markup_statsSettingsColor() + ) + elif call_data == "statsSettingsBorderWidth": + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=tt.border_width, + photo=stats.get_random_graph(), + reply_markup=markups.get_markup_statsSettingsBorderWidth() + ) + elif call_data.startswith("statsSettingsBorderWidth"): + match call_data[24:]: + case "Default": + value = 1 + case "Add": + value = int(settings.get_borderwidth()) + 1 + case "Reduce": + value = int(settings.get_borderwidth()) - 1 + settings.set_borderwidth(value) + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=tt.border_width, + photo=stats.get_random_graph(), + reply_markup=markups.get_markup_statsSettingsBorderWidth() + ) + elif call_data == "statsSettingsTitleFontSize": + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=tt.title_font_size, + photo=stats.get_random_graph(), + reply_markup=markups.get_markup_statsSettingsTitleFontSize() + ) + elif call_data.startswith("statsSettingsTitleFontSize"): + match call_data[26:]: + case "Default": + value = 16 + case "Add": + value = int(settings.get_titlefontsize()) + 2 + case "Reduce": + value = int(settings.get_titlefontsize()) - 2 + settings.set_titlefontsize(value) + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=tt.title_font_size, + photo=stats.get_random_graph(), + reply_markup=markups.get_markup_statsSettingsTitleFontSize() + ) + elif call_data == "statsSettingsAxisFontSize": + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=tt.axis_font_size, + photo=stats.get_random_graph(), + reply_markup=markups.get_markup_statsSettingsAxisFontSize() + ) + + elif call_data.startswith("statsSettingsAxisFontSize"): + match call_data[25:]: + case "Default": + value = 16 + case "Add": + value = int(settings.get_axisfontsize()) + 2 + case "Reduce": + value = int(settings.get_axisfontsize()) - 2 + settings.set_axisfontsize(value) + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=tt.axis_font_size, + photo=stats.get_random_graph(), + reply_markup=markups.get_markup_statsSettingsAxisFontSize() + ) + + elif call_data == "statsSettingsTickFontSize": + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=tt.tick_font_size, + photo=stats.get_random_graph(), + reply_markup=markups.get_markup_statsSettingsTickFontSize() + ) + + elif call_data.startswith("statsSettingsTickFontSize"): + match call_data[25:]: + case "Default": + value = 10 + case "Add": + value = int(settings.get_tickfontsize()) + 2 + case "Reduce": + value = int(settings.get_tickfontsize()) - 2 + settings.set_tickfontsize(value) + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=tt.tick_font_size, + photo=stats.get_random_graph(), + reply_markup=markups.get_markup_statsSettingsTickFontSize() + ) + + elif call_data == "additionalSettings": + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=tt.additional_settings, + reply_markup=markups.get_markup_additionalSettings() + ) + elif call_data == "systemSettings": + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=tt.system_settings, + reply_markup=markups.get_markup_systemSettings() + ) + elif call_data == "cleanImagesMenu": + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=tt.clean_images_text, + reply_markup=markups.get_markup_cleanImagesMenu() + ) + elif call_data == "cleanImages": + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=f"Неиспользуемые фотографии были успешно удалены!\nОчищено: {'{:.1f}'.format(clean_images())}мб", + reply_markup=markups.single_button(markups.btnBackSystemSettings) + ) + elif call_data == "cleanDatabaseMenu": + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=tt.clean_database_text, + reply_markup=markups.get_markup_cleanDatabaseMenu() + ) + elif call_data == "cleanDatabase": + settings.clean_db() + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=f"База данных была успешно очищена!", + reply_markup=markups.single_button(markups.btnBackSystemSettings) + ) + elif call_data == "resetSettingsMenu": + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=tt.resert_settings_text, + reply_markup=markups.get_markup_resetSettingsMenu() + ) + elif call_data == "resetSettings": + settings.reset() + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=f"Настройки были успешно сброшены!", + reply_markup=markups.single_button(markups.btnBackSystemSettings) + ) + elif call_data == "backups": + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=tt.backups, + reply_markup=markups.get_markup_backups() + ) + elif call_data == "updateBackup": + create_backup() + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text="Резервная копия была обновлена!", + reply_markup=markups.single_button(markups.btnBackBackups) + ) + elif call_data == "loadBackupMenu": + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=tt.load_backup, + reply_markup=markups.get_markup_loadBackupMenu() + ) + elif call_data.startswith("loadBackup"): + backup_path = "backups/" + call_data[10:] + if exists(backup_path): + for file in listdir(backup_path): + try: + copyfile(f"{backup_path}/{file}", f"{getcwd()}/{file}") + except: + if settings.is_debug(): + print(f"DEBUG: Failed to copy \"{file}\" to \".\"") + text = tt.load_backup + f"\nРезервная копия за {call_data[10:]} была успешно загружена!" + else: + text = f"{tt.load_backup}\n\n{tt.error} Файла {backup_path} не существует!" + + + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markups.get_markup_loadBackupMenu() + ) + elif call_data == "cleanBackupsMenu": + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=tt.clean_backups, + reply_markup=markups.get_markup_cleanBackupsMenu() + ) + elif call_data.startswith("cleanBackups"): + days = 0 if call_data[12:] == "All" else int(call_data[12:]) + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=tt.clean_backups + f"\nОчищено: {'{:.2f}'.format(clean_backups(days))}мб!", + reply_markup=markups.get_markup_cleanBackupsMenu() + ) + + + # Custom commands + elif call_data == "customCommands": + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=tt.custom_commands, + reply_markup=markups.get_markup_customCommands() + ) + elif call_data.startswith("deleteCommand"): + command = commands.Command(call_data[13:]) + try: + command.delete() + text = tt.custom_commands + markup = markups.get_markup_customCommands() + except: + text = tt.error + markup = markups.single_button(markups.btnBackCustomCommands) + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup + ) + elif call_data == "addCommand": + if len(commands.get_commands()) >= 90: + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=f"Вы не можете добавить больше 90 команд!", + reply_markup=markups.single_button(markups.btnBackCustomCommands) + ) + else: + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=f"Введите новую команду {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackCustomCommands) + ) + await state_handler.addCustomCommand.command.set() + + # Manager tab + elif call_data.startswith("manageOrder"): + order = ordr.Order(call_data[11:]) + await bot.edit_message_text( + chat_id=message.chat.id, + message_id=callback_query.message.message_id, + text=tt.get_order_template(order), + reply_markup=markups.get_markup_manageOrder(order), + ) + elif call_data.startswith("manager_") and (user.is_admin() or user.is_manager()): + call_data = call_data[8:] + + if call_data == "orders": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.orders, + reply_markup=markups.get_markup_orders() + ) + elif call_data.startswith("orders"): + match call_data[6:]: + case "Processing": + order_list = ordr.get_order_list(status=0) + text = tt.processing + case "Delivery": + order_list = ordr.get_order_list(status=1) + text = tt.delivery + case "Done": + order_list = ordr.get_order_list(status=2) + text = tt.done + case "Cancelled": + order_list = ordr.get_order_list(status=-1) + text = tt.cancelled + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markups.get_markup_ordersByOrderList(order_list) + ) + elif call_data.startswith("seeOrder"): + order = ordr.Order(call_data[8:]) + await bot.edit_message_text( + text=tt.get_order_template(order), + chat_id=chat_id, + message_id=callback_query.message.message_id, + reply_markup=markups.get_markup_seeOrder(order) + ) + elif call_data.startswith("changeOrderStatusProcessing"): + order = ordr.Order(call_data[27:]) + order.set_status(0) + + await bot.send_message( + text=f"Статус заказа #{order.get_order_id()} был изменен на \"{tt.processing}\"", + chat_id=order.get_user_id() + ) + + await bot.edit_message_text( + text=tt.get_order_template(order), + chat_id=chat_id, + message_id=callback_query.message.message_id, + reply_markup=markups.get_markup_seeOrder(order) + ) + elif call_data.startswith("changeOrderStatusDelivery"): + order = ordr.Order(call_data[25:]) + order.set_status(1) + + await bot.send_message( + text=f"Статус заказа #{order.get_order_id()} был изменен на \"{tt.delivery}\"", + chat_id=order.get_user_id() + ) + + await bot.edit_message_text( + text=tt.get_order_template(order), + chat_id=chat_id, + message_id=callback_query.message.message_id, + reply_markup=markups.get_markup_seeOrder(order) + ) + elif call_data.startswith("changeOrderStatusDone"): + order = ordr.Order(call_data[21:]) + order.set_status(2) + + await bot.send_message( + text=f"Статус заказа #{order.get_order_id()} был изменен на \"{tt.done}\"", + chat_id=order.get_user_id() + ) + + await bot.edit_message_text( + text=tt.get_order_template(order), + chat_id=chat_id, + message_id=callback_query.message.message_id, + reply_markup=markups.get_markup_seeOrder(order) + ) + elif call_data.startswith("changeOrderStatusCancel"): + order = ordr.Order(call_data[23:]) + order.set_status(-1) + + await bot.send_message( + text=f"Заказ #{order.get_order_id()} был отменён.", + chat_id=order.get_user_id() + ) + + await bot.edit_message_text( + text=tt.get_order_template(order), + chat_id=chat_id, + message_id=callback_query.message.message_id, + reply_markup=markups.get_markup_seeOrder(order) + ) + + # User calls + else: + # FAQ + if call_data == "faq": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.get_faq_template(settings.get_shop_name()), + reply_markup=markups.get_markup_faq(), + ) + elif call_data == "contacts": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=settings.get_shop_contacts(), + reply_markup=markups.single_button(markups.btnBackFaq), + ) + elif call_data == "refund": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=settings.get_refund_policy(), + reply_markup=markups.single_button(markups.btnBackFaq), + ) + + # Profile + elif call_data == "profile": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.get_profile_template(user), + reply_markup=markups.get_markup_profile(user), + ) + elif call_data == "myOrders": + user = usr.User(chat_id) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.my_orders, + reply_markup=markups.get_markup_myOrders(user.get_orders()), + ) + elif call_data.startswith("viewMyOrder"): + order = ordr.Order(call_data[11:]) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.get_order_template(order), + reply_markup=markups.get_markup_viewMyOrder(order), + ) + elif call_data.startswith("cancelOrder"): + order = ordr.Order(call_data[11:]) + order.set_status(-1) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.get_order_template(order), + reply_markup=markups.get_markup_viewMyOrder(order), + ) + elif call_data.startswith("restoreOrder"): + order = ordr.Order(call_data[12:]) + order.set_status(0) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.get_order_template(order), + reply_markup=markups.get_markup_viewMyOrder(order), + ) + elif call_data == "changeEnableNotif": + user.set_notif_enable(0 if user.notif_on() else 1) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.get_profile_template(user), + reply_markup=markups.get_markup_profile(user), + ) + + # Catalogue + elif call_data == "catalogue": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.catalogue, + reply_markup=markups.get_markup_catalogue(category.get_cat_list()), + ) + elif call_data.startswith("viewCat"): + cat = category.Category(call_data[7:]) + try: + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=cat.get_name(), + reply_markup=markups.get_markup_viewCat(cat.get_item_list()), + ) + except: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=callback_query.message.chat.id, + text=cat.get_name(), + reply_markup=markups.get_markup_viewCat(cat.get_item_list()), + ) + elif call_data == "search": + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=f"Введите поисковой запрос {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackCatalogue) + ) + await state_handler.search.query.set() + state = Dispatcher.get_current().current_state() + await state.update_data(state_message=callback_query.message.message_id) + elif call_data.startswith("viewItem"): + item = itm.Item(call_data[8:]) + text = tt.get_item_card(item=item) + markup = markups.get_markup_viewItem(item) + if item.get_image_id() == "None" or not settings.is_item_image_enabled() or await item.is_hide_image(): + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + else: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=text, + photo=item.get_image(), + reply_markup=markup + ) + + # Cart + elif call_data == "cart": + if user.get_cart(): + text = tt.cart + markup = markups.get_markup_cart(user) + else: + text = tt.cart_is_empty + markup = types.InlineKeyboardMarkup() + + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + + elif call_data == "cartDel": + if user.get_cart(): + text = tt.cart + markup = markups.get_markup_cart(user) + else: + text = tt.cart_is_empty + markup = types.InlineKeyboardMarkup() + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=markup, + ) + + elif call_data == "clearCart": + user.clear_cart() + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.cart_is_empty, + reply_markup=types.InlineKeyboardMarkup(), + ) + + elif call_data.startswith("addToCartFromCart"): + user.add_to_cart(call_data[17:]) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.cart, + reply_markup=markups.get_markup_cart(user), + ) + + elif call_data.startswith("removeFromCartFromCart"): + user.remove_from_cart(call_data[22:]) + if user.get_cart(): + text = tt.cart + markup = markups.get_markup_cart(user) + else: + text = tt.cart_is_empty + markup = types.InlineKeyboardMarkup() + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + + elif call_data.startswith("addToCart"): + item = itm.Item(call_data[9:]) + if item.get_amount() == 0: + text = f"Товара \"{item.get_name()}\" нет в наличии." + else: + user.add_to_cart(item.get_id()) + text = f"Товар \"{item.get_name()}\" был добавлен в корзину." + if item.get_image_id() == "None" or not settings.is_item_image_enabled() or await item.is_hide_image(): + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markups.single_button(markups.btnBackViewItem(item.get_id())), + ) + else: + await bot.delete_message( + chat_id=chat_id, + message_id=callback_query.message.message_id + ) + await bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=markups.single_button(markups.btnBackViewItem(item.get_id())) + ) + + elif call_data == "changeCartDelivery": + user.set_cart_delivery(0 if user.is_cart_delivery() else 1) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.cart, + reply_markup=markups.get_markup_cart(user), + ) + + elif call_data == "checkoutCart": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=f"Введите ваш Email адрес {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackCart), + ) + await state_handler.checkoutCart.email.set() + state = Dispatcher.get_current().current_state() + await state.update_data(state_message=callback_query.message.message_id) + await state.update_data(user_id=chat_id) + await state.update_data(item_list_comma=user.get_cart_comma()) + +# State handlers +# Item management +@dp.message_handler(state=state_handler.addCat.name) +async def addCat(message: types.Message, state: FSMContext): + data = await state.get_data() + cat_name = message.text + try: + category.create_cat(cat_name) + text = tt.get_category_was_created_successfuly(cat_name) + except: + text = tt.error + + await bot.delete_message( + message_id=data["state_message"], + chat_id=message.chat.id + ) + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackItemManagement), + ) + await state.finish() + +@dp.message_handler(state=state_handler.changeCatName.name) +async def changeCatName(message: types.Message, state: FSMContext): + data = await state.get_data() + cat = category.Category(data["cat_id"]) + cat_name = message.text + + try: + text = f"Название категории \"{cat.get_name()}\" было изменено на \"{cat_name}\"." + cat.set_name(cat_name) + except: + text = tt.error + + await bot.delete_message( + message_id=data["state_message"], + chat_id=message.chat.id + ) + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackEditCat(cat.get_id())), + ) + await state.finish() + +@dp.message_handler(state=state_handler.addItem.name) +async def addItemSetName(message: types.Message, state: FSMContext): + data = await state.get_data() + state = Dispatcher.get_current().current_state() + await state.update_data(name=message.text) + + await bot.send_message( + chat_id=message.chat.id, + text=f"Введите цену для \"{message.text}\" {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackItemManagement), + ) + await state_handler.addItem.price.set() + +@dp.message_handler(state=state_handler.addItem.price) +async def addItemSetPrice(message: types.Message, state: FSMContext): + try: + data = await state.get_data() + state = Dispatcher.get_current().current_state() + await state.update_data(price=float(message.text)) + await bot.send_message( + chat_id=message.chat.id, + text=f"Выберите категорию для \"{data['name']}\" {tt.or_press_back}", + reply_markup=markups.get_markup_addItemSetCat(category.get_cat_list()), + ) + await state_handler.addItem.cat_id.set() + except: + await bot.send_message( + chat_id=message.chat.id, + text=tt.error, + reply_markup=markups.single_button(markups.btnBackItemManagement), + ) + await state.finish() + +@dp.message_handler(state=state_handler.addItem.desc) +async def addItemSetDesc(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + await state.update_data(desc=message.text) + data = await state.get_data() + + if settings.is_item_image_enabled(): + text = "Отправьте изображение для товара или нажмите на кнопку \"Пропустить\"." + markup = markups.single_button(markups.btnSkipAddItemSetImage) + await state_handler.addItem.image.set() + else: + markup = markups.get_markup_addItemConfirmation() + cat = category.Category(data["cat_id"]) + text = tt.get_item_card(name=data["name"], price=data["price"], desc=data["desc"], amount=0) + f"\nКатегория: {cat.get_name()}\n\nВы уверены, что хотите добавить \"{data['name']}\" в каталог?" + await state_handler.addItem.confirmation.set() + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markup, + ) + +@dp.message_handler(content_types=['photo'], state=state_handler.addItem.image) +async def addItemSetImage(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + + while True: + image_id = "".join([choice(ascii_lowercase + digits) for _ in range(6)]) + ".png" + if image_id not in listdir("images/"): + break + + await message.photo[-1].download(destination_file=f"images/{image_id}") + await state.update_data(image=image_id) + + cat = category.Category(data["cat_id"]) + text = tt.get_item_card(name=data["name"], price=data["price"], desc=data["desc"], amount=0) + f"\nКатегория: {cat.get_name()}\n\nВы уверены, что хотите добавить \"{data['name']}\" в каталог?" + await bot.send_photo( + chat_id=message.chat.id, + photo=open(f"images/{image_id}", "rb"), + caption=text, + reply_markup=markups.get_markup_addItemConfirmation() + ) + await state_handler.addItem.confirmation.set() + +@dp.message_handler(state=state_handler.addItem.image) +async def addItemSetImageNotImage(message: types.Message, state: FSMContext): + text = "Отправьте изображение для товара или нажмите на кнопку \"Пропустить\"." + markup = markups.single_button(markups.btnSkipAddItemSetImage) + await state_handler.addItem.image.set() + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markup, + ) + await state_handler.addItem.image.set() + + +@dp.message_handler(state=state_handler.changeItemPrice.price) +async def editItemSetPrice(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + item = itm.Item(data["item_id"]) + try: + text = f"Ценя для \"{item.get_name()}\" была изменена с {item.get_price()} на {'{:.2f}'.format(float(message.text))}." + item.set_price(float(message.text)) + except: + text = tt.error + + try: + await bot.delete_message( + message_id=data["state_message"], + chat_id=message.chat.id + ) + except: + if settings.is_debug(): + print(f"DEBUG: [{message.chat.id}] FAILED TO DELETE MESSAGE WITH ID {data['state_message']}") + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackEditItem(item.get_id())), + ) + +@dp.message_handler(content_types=['photo'], state=state_handler.changeItemImage.image) +async def editItemSetImage(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + item = itm.Item(data["item_id"]) + + while True: + image_id = "".join([choice(ascii_lowercase + digits) for _ in range(6)]) + ".png" + if image_id not in listdir("images/"): + break + + try: + await message.photo[-1].download(destination_file=f"images/{image_id}") + item.set_image_id(image_id) + text = f"Изображение для \"{item.get_name()}\" было обновлено." + except: + text = tt.error + + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackEditItem(item.get_id())) + ) + await state.finish() + + +@dp.message_handler(state=state_handler.changeItemImage.image) +async def editItemSetImage(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + + await bot.send_message( + chat_id=message.chat.id, + text=tt.error, + reply_markup=markups.single_button(markups.btnBackEditItem(data["item_id"])) + ) + + await state.finish() + + +@dp.message_handler(state=state_handler.changeItemDesc.desc) +async def editItemSetDesc(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + item = itm.Item(data["item_id"]) + try: + text = f"Описание для \"{item.get_name()}\" было изменено с \"{item.get_desc()}\" на \"{message.text}\"" + item.set_desc(message.text) + except: + text = tt.error + + try: + await bot.delete_message( + message_id=data["state_message"], + chat_id=message.chat.id + ) + except: + if settings.is_debug(): + print(f"DEBUG: [{message.chat.id}] FAILED TO DELETE MESSAGE WITH ID {data['state_message']}") + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackEditItem(item.get_id())), + ) + +@dp.message_handler(state=state_handler.changeItemName.name) +async def editItemSetName(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + item = itm.Item(data["item_id"]) + try: + text = f"Название для \"{item.get_name()}\" было изменено на \"{message.text}\"." + item.set_name(message.text) + except: + text = tt.error + + try: + await bot.delete_message( + message_id=data["state_message"], + chat_id=message.chat.id + ) + except: + if settings.is_debug(): + print(f"DEBUG: [{message.chat.id}] FAILED TO DELETE MESSAGE WITH ID {data['state_message']}") + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackEditItem(item.get_id())), + ) + +@dp.message_handler(state=state_handler.changeItemStock.stock) +async def editItemStockSetStock(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + item = itm.Item(data["item_id"]) + + try: + if not message.text.isalnum(): + raise Exception(TypeError) + text = f"Количество товара для \"{item.get_name()}\" было изменено с {item.get_amount()} шт. на {message.text} шт." + item.set_amount(int(message.text)) + except: + text = tt.error + try: + await bot.delete_message( + message_id=data["state_message"], + chat_id=message.chat.id + ) + except: + if settings.is_debug(): + print(f"DEBUG: [{message.chat.id}] FAILED TO DELETE MESSAGE WITH ID {data['state_message']}") + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackEditItem(item.get_id())), + ) + await state.finish() + + +# User management +@dp.message_handler(state=state_handler.notifyEveryone.message) +async def notifyEveryoneSetMessage(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + await state.update_data(message=message.text) + data = await state.get_data() + + await bot.delete_message( + message_id=data["state_message"], + chat_id=message.chat.id + ) + await bot.send_message( + chat_id=message.chat.id, + text=f"{tt.line_separator}\n\"{message.text}\"\n{tt.line_separator}\nВы уверены, что хотите отправить данное сообщение всем пользователям?", + reply_markup=markups.get_markup_notifyEveryoneConfirmation(), + ) + await state_handler.notifyEveryone.confirmation.set() + +@dp.message_handler(state=state_handler.seeUserProfile.user_id) +async def seeUserProfileSetUserID(message: types.Message, state: FSMContext): + try: + user_id = int(message.text) + if usr.does_user_exist(user_id): + user = usr.User(user_id) + markup = markups.get_markup_seeUserProfile(user) + text = tt.get_profile_template(user) + else: + text = f"Пользователя с ID {message.text} не существует." + markup = markups.single_button(markups.btnBackUserManagement) + except: + text = tt.error + markup = markups.single_button(markups.btnBackUserManagement) + state = Dispatcher.get_current().current_state() + data = await state.get_data() + await bot.delete_message( + message_id=data["state_message"], + chat_id=message.chat.id + ) + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markup, + ) + await state.finish() + +# Main settings +@dp.message_handler(state=state_handler.changeShopName.name) +async def changeShopNameSetName(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + try: + text = f"Название магазина было изменено с \"{settings.get_shop_name()}\" на \"{message.text}\"." + settings.set_shop_name(message.text) + except: + text = tt.error + await bot.delete_message( + message_id=data["state_message"], + chat_id=message.chat.id + ) + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackMainSettings), + ) + await state.finish() + +@dp.message_handler(state=state_handler.changeShopGreeting.greeting) +async def changeShopGreetingSetGreeting(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + try: + text = f"Приветствие магазина было изменено с \"{settings.get_shop_greeting()}\" на \"{message.text}\"." + settings.set_shop_greeting(message.text) + except: + text = tt.error + await bot.delete_message( + message_id=data["state_message"], + chat_id=message.chat.id + ) + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackMainSettings), + ) + await state.finish() + +@dp.message_handler(state=state_handler.changeShopRefundPolicy.refund_policy) +async def changeShopContactsSetContacts(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + try: + text = f"Политика возврата магазина была изменена с \"{settings.get_shop_name()}\" на \"{message.text}\"." + settings.set_refund_policy(message.text) + except: + text = tt.error + await bot.delete_message( + message_id=data["state_message"], + chat_id=message.chat.id + ) + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackMainSettings), + ) + await state.finish() + +@dp.message_handler(state=state_handler.changeShopContacts.contacts) +async def changeShopContactsSetContacts(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + try: + text = f"Текст для вкладки \"Контакты\" был изменен с \"{settings.get_shop_name()}\" на \"{message.text}\"." + settings.set_shop_contacts(message.text) + except: + text = tt.error + await bot.delete_message( + message_id=data["state_message"], + chat_id=message.chat.id + ) + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackMainSettings), + ) + await state.finish() + +# Checkout Settings +@dp.message_handler(state=state_handler.changeDeliveryPrice.price) +async def changeDeliveryPriceSetPrice(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + try: + text = f"Стоимость доставки была изменена с {'{:.2f}'.format(float(settings.get_delivery_price()))}руб. на {'{:.2f}'.format(float(message.text))}руб." + settings.set_delivery_price(float(message.text)) + except: + text = tt.error + await bot.delete_message( + message_id=data["state_message"], + chat_id=message.chat.id + ) + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackCheckoutSettings), + ) + await state.finish() + +@dp.message_handler(state=state_handler.search.query) +async def searchSetQuery(message: types.Message, state: FSMContext): + query = search.search_item(message.text) + if query.match(): + await bot.send_message( + chat_id=message.chat.id, + text=f"Результаты поиска для \"{message.text}\":", + reply_markup=markups.get_markup_search(query.match()) + ) + else: + await bot.send_message( + chat_id=message.chat.id, + text=f"По вашему запросу ничего не найдено :(", + reply_markup=markups.single_button(markups.btnBackCatalogue) + ) + await state.finish() + +# Cart checkout +# Required +@dp.message_handler(state=state_handler.checkoutCart.email) +async def checkoutCartSetEmail(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + user = usr.User(message.chat.id) + if matchre(r"^[A-Za-z0-9\.\+_-]+@[A-Za-z0-9\._-]+\.[a-zA-Z]*$", message.text): # I am not familiar with how re package works. Taken from here: https://stackoverflow.com/questions/8022530/how-to-check-for-valid-email-address + await state.update_data(email=message.text) + if settings.is_phone_number_enabled(): + text = f"Введите ваш номер телефона {tt.or_press_back}" + await state_handler.checkoutCart.phone_number.set() + elif settings.is_delivery_enabled() and user.is_cart_delivery(): + text = f"Введите адрес доставки {tt.or_press_back}" + await state_handler.checkoutCart.home_adress.set() + else: + text = f"Введите комментарий к заказу {tt.or_press_back}" + await state_handler.checkoutCart.additional_message.set() + else: + text = f"\"{message.text}\" не является действительным Email адресом." + await state.finish() + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackCart), + ) + +@dp.message_handler(state=state_handler.checkoutCart.phone_number) +async def checkoutCartSetPhoneNumber(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + user = usr.User(message.chat.id) + if is_possible_number(phoneparse(message.text, "RU")): + await state.update_data(phone_number=message.text) + if settings.is_delivery_enabled() and user.is_cart_delivery(): + text = f"Введите адрес доставки {tt.or_press_back}" + await state_handler.checkoutCart.home_adress.set() + else: + text = f"Введите комментарий к заказу {tt.or_press_back}" + await state_handler.checkoutCart.additional_message.set() + else: + text = f"\"{message.text}\" не является действительным номером телефона." + await state.finish() + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackCart), + ) + +@dp.message_handler(state=state_handler.checkoutCart.home_adress) +async def checkoutCartSetHomeAdress(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + await state.update_data(home_adress=message.text) + await bot.send_message( + chat_id=message.chat.id, + text=f"Введите комментарий к заказу {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackCart), + ) + await state_handler.checkoutCart.additional_message.set() + +@dp.message_handler(state=state_handler.checkoutCart.additional_message) +async def checkoutCartSetAdditionalMessage(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + user = usr.User(message.chat.id) + await state.update_data(additional_message=message.text) + if settings.is_captcha_enabled(): + captcha_text = get_captcha_text() + await state.update_data(captcha=captcha_text) + await bot.send_photo( + chat_id=message.chat.id, + caption=f"Введите текст с картинки для подтверждения заказа.", + photo=generate_captcha(captcha_text), + reply_markup=markups.get_markup_captcha() + ) + await state_handler.checkoutCart.captcha.set() + else: + await bot.send_message( + chat_id=message.chat.id, + text=tt.get_order_confirmation_template(item_amount_dict=user.get_cart_amount(), cart_price=user.get_cart_price(), email_adress=data["email"], additional_message=message.text, phone_number=data["phone_number"] if settings.is_phone_number_enabled() else None, home_adress=data["home_adress"] if settings.is_delivery_enabled() and user.is_cart_delivery() else None), + reply_markup=markups.get_markup_checkoutCartConfirmation(), + ) + await state_handler.checkoutCart.confirmation.set() + +@dp.message_handler(state=state_handler.checkoutCart.captcha) +async def checkoutCartCheckCaptcha(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + user = usr.User(data["user_id"]) + if message.text.lower() == data["captcha"].lower(): + await bot.send_message( + chat_id=message.chat.id, + text=tt.get_order_confirmation_template(item_amount_dict=user.get_cart_amount(), cart_price=user.get_cart_price(), email_adress=data["email"], additional_message=data["additional_message"], phone_number=data["phone_number"] if settings.is_phone_number_enabled() else None, home_adress=data["home_adress"] if settings.is_delivery_enabled() and user.is_cart_delivery() else None), + reply_markup=markups.get_markup_checkoutCartConfirmation(), + ) + await state_handler.checkoutCart.confirmation.set() + else: + captcha_text = get_captcha_text() + await state.update_data(captcha=captcha_text) + await bot.send_photo( + chat_id=message.chat.id, + caption=f"Введите текст с картинки для подтверждения заказа.", + photo=generate_captcha(captcha_text), + reply_markup=markups.get_markup_captcha() + ) + await state_handler.checkoutCart.captcha.set() + +@dp.message_handler(state=state_handler.addCustomCommand.command) +async def addCustomCommandSetCommand(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + await state.update_data(command=message.text) + + await bot.send_message( + chat_id=message.chat.id, + text=f"Введите ответ для команды {tt.or_press_back}", + reply_markup=markups.single_button(markups.btnBackCustomCommands) + ) + await state_handler.addCustomCommand.response.set() + +@dp.message_handler(state=state_handler.addCustomCommand.response) +async def addCustomCommandSetResponse(message: types.Message, state: FSMContext): + state = Dispatcher.get_current().current_state() + data = await state.get_data() + try: + commands.create_command(data["command"], message.text) + text = f"Команда \"{data['command']}\" была успешно добвалена!" + except: + text = tt.error + await bot.send_message( + chat_id=message.chat.id, + text=text, + reply_markup=markups.single_button(markups.btnBackCustomCommands) + ) + await state.finish() + +# State callbacks +@dp.callback_query_handler(state='*') +async def cancelState(callback_query: types.CallbackQuery, state: FSMContext): + chat_id = callback_query.message.chat.id + call_data = callback_query.data + state = Dispatcher.get_current().current_state() + data = await state.get_data() + user = usr.User(callback_query.message.chat.id) + + if settings.is_debug(): + print(f"DEBUG: CALL [{chat_id}] {call_data} (STATE)") + + if call_data[:6] == "admin_": + call_data = call_data[6:] + + # Callbacks + if call_data.startswith("addItemSetCat"): + await state.update_data(cat_id=int(call_data[13:])) + + finish = False + if len(category.Category(call_data[13:]).get_item_list()) > 85: + text = tt.error + finish = True + else: + text = f"Введите описание для \"{data['name']}\" {tt.or_press_back}" + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markups.single_button(markups.btnBackItemManagement), + ) + if finish: + await state.finish() + else: + await state_handler.addItem.desc.set() + elif call_data == "skipSetAddItemSetImage": + await state.update_data(image="None") + cat = category.Category(data["cat_id"]) + text = tt.get_item_card(name=data["name"], price=data["price"], desc=data["desc"], amount=0) + f"\nКатегория: {cat.get_name()}\n\nВы уверены, что хотите добавить \"{data['name']}\" в каталог?" + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markups.get_markup_addItemConfirmation() + ) + await state_handler.addItem.confirmation.set() + elif call_data == "addItemConfirm": + try: + itm.create_item(name=data["name"], price=data["price"], cat_id=data["cat_id"], desc=data["desc"], image_id=data["image"] if settings.is_item_image_enabled() else "None") + text = f"Товар {data['name']} был создан." + except: + text = tt.error + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=markups.single_button(markups.btnBackItemManagement), + ) + await state.finish() + + elif call_data == "notifyEveryoneConfirm": + total = len(usr.get_user_list()) + fail = 0 + for user in usr.get_user_list(): + try: + await bot.send_message( + chat_id=user.get_id(), + text=data["message"], + ) + except: + fail += 1 + + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=chat_id, + text=f"Сообщение было отправлено {total - fail} из {total} пользователям.", + reply_markup=markups.single_button(markups.btnBackUserManagement), + ) + await state.finish() + + elif call_data.startswith("editItemSetCat"): + item = itm.Item(data["item_id"]) + old_cat = category.Category(item.get_cat_id()) + new_cat = category.Category(call_data[14:]) + try: + text = f"Категория для \"{item.get_name()}\" была изменена с \"{old_cat.get_name()}\" на \"{new_cat.get_name()}\"." + item.set_cat_id(new_cat.get_id()) + except: + text = tt.error + + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markups.single_button(markups.btnBackEditItem(item.get_id())), + ) + await state.finish() + + # "go-backs" + elif call_data == "itemManagement": + try: + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.item_management, + reply_markup=markups.get_markup_itemManagement(), + ) + except: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=chat_id, + text=tt.item_management, + reply_markup=markups.get_markup_itemManagement(), + ) + await state.finish() + elif call_data.startswith("editCat"): + cat = category.Category(call_data[7:]) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.get_category_data(cat), + reply_markup=markups.get_markup_editCat(cat.get_id()), + ) + await state.finish() + elif call_data.startswith("editItem"): + item = itm.Item(call_data[8:]) + cat = category.Category(item.get_cat_id()) + text = tt.get_item_card(item=item) + f"\nКатегория: {cat.get_name()}" + markup = await markups.get_markup_editItem(item) + + if item.get_image_id() == "None" or not settings.is_item_image_enabled() and await item.is_hide_image(): + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + else: + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=text, + photo=item.get_image(), + reply_markup=markup + ) + await state.finish() + elif call_data == "userManagement": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.user_management, + reply_markup=markups.get_markup_userManagement(), + ) + await state.finish() + elif call_data.startswith("seeUserProfile"): + user = usr.User(call_data[14:]) + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.get_profile_template(user), + reply_markup=markups.get_markup_seeUserProfile(user), + ) + await state.finish() + elif call_data == "mainSettings": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.main_settings, + reply_markup=markups.get_markup_mainSettings(), + ) + await state.finish() + elif call_data == "checkoutSettings": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.checkout_settings, + reply_markup=markups.get_markup_checkoutSettings(), + ) + await state.finish() + elif call_data == "customCommands": + await bot.edit_message_text( + chat_id=callback_query.message.chat.id, + message_id=callback_query.message.message_id, + text=tt.custom_commands, + reply_markup=markups.get_markup_customCommands() + ) + await state.finish() + else: + await state.finish() + else: + if call_data == "catalogue": + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=tt.catalogue, + reply_markup=markups.get_markup_catalogue(category.get_cat_list()) + ) + await state.finish() + elif call_data == "cart": + if user.get_cart(): + text = tt.cart + markup = markups.get_markup_cart(user) + else: + text = tt.cart_is_empty + markup = types.InlineKeyboardMarkup() + await bot.edit_message_text( + chat_id=chat_id, + message_id=callback_query.message.message_id, + text=text, + reply_markup=markup, + ) + await state.finish() + elif call_data == "refreshCaptcha": + captcha_text = get_captcha_text() + await state.update_data(captcha=captcha_text) + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_photo( + chat_id=chat_id, + caption=f"Введите текст с картинки для подтверждения заказа.", + photo=generate_captcha(captcha_text), + reply_markup=markups.get_markup_captcha() + ) + await state_handler.checkoutCart.captcha.set() + elif call_data == "checkoutCartConfirm": + while True: + order_id = randint(100000, 999999) + if not ordr.does_order_exist(order_id): + break + user_id = data["user_id"] + item_list_comma = user.get_cart_comma() + email = data["email"] + additional_message = data["additional_message"] + phone_number = data["phone_number"] if settings.is_phone_number_enabled() else None + home_adress = data["home_adress"] if settings.is_delivery_enabled() and user.is_cart_delivery() else None + + try: + order = ordr.create_order(order_id, user_id, item_list_comma, email, additional_message, phone_number=phone_number, home_adress=home_adress) + user.clear_cart() + text = f"Заказ с ID {order.get_order_id()} был успешно создан.\nСпасибо за заказ! Наш менеджер свяжется с вами в ближайшее время." + for user in usr.get_notif_list(): + try: + await bot.send_message( + chat_id=user.get_id(), + text=f"Новый заказ:\n{tt.get_order_template(order)}", + reply_markup=markups.get_markup_seeOrder(order) + ) + except: + if settings.is_debug(): + print(f"DEBUG: FAIL MESSAGE TO [{user.get_id()}]") + except: + text = tt.error + await bot.delete_message( + message_id=callback_query.message.message_id, + chat_id=chat_id + ) + await bot.send_message( + chat_id=chat_id, + text=text, + reply_markup=markups.single_button(markups.btnBackCart), + ) + await state.finish() + +async def background_runner(): + while True: + if not exists("backups/" + datetime.date.today().strftime("%d-%m-%Y")): + create_backup() + await asyncio.sleep(60) + +async def on_startup(dp): + asyncio.create_task(background_runner()) + +if __name__ == '__main__': + executor.start_polling(dp, skip_updates=True, on_startup=on_startup) diff --git a/doners/shop-telegram-bot/src/markups.py b/doners/shop-telegram-bot/src/markups.py new file mode 100644 index 0000000..4f0f63a --- /dev/null +++ b/doners/shop-telegram-bot/src/markups.py @@ -0,0 +1,477 @@ +from os import listdir +from aiogram import types +from datetime import datetime + +import text_templates as tt +from settings import Settings +import commands + +settings = Settings() + +# Back buttons +# Misc +btnBackAdmin = types.InlineKeyboardButton(text=tt.back, callback_data="admin_adminPanel") + +# Item management +btnBackItemManagement = types.InlineKeyboardButton(text=tt.back, callback_data="admin_itemManagement") +btnBackEditCatChooseCategory = types.InlineKeyboardButton(text=tt.back, callback_data="admin_editCatChooseCategory") +def btnBackEditCat(cat_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_editCat{cat_id}") +btnBackEditItemChooseCategory = types.InlineKeyboardButton(text=tt.back, callback_data="admin_editItemChooseCategory") +def btnBackEditItemChooseItem(cat_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_editItemChooseItem{cat_id}") +def btnBackEditItem(item_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_editItem{item_id}") + +# User management +btnBackUserManagement = types.InlineKeyboardButton(text=tt.back, callback_data="admin_userManagement") +def btnBackSeeUserProfile(user_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_seeUserProfile{user_id}") +def btnBackSeeUserOrders(user_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_seeUserOrders{user_id}") + +# Stats +btnBackShopStats = types.InlineKeyboardButton(text=tt.back, callback_data="admin_shopStats") +btnBackRegistratonStats = types.InlineKeyboardButton(text=tt.back, callback_data="admin_registrationStatsBack") +btnBackOrderStats = types.InlineKeyboardButton(text=tt.back, callback_data="admin_orderStatsBack") + +# Settings +btnBackShopSettingsDel = types.InlineKeyboardButton(text=tt.back, callback_data="admin_shopSettingsDel") +btnBackShopSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_shopSettings") +btnBackStatsSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_statsSettings") +btnBackMainSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_mainSettings") +btnBackCheckoutSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_checkoutSettings") +btnBackAdditionalSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_additionalSettings") +btnBackCustomCommands = types.InlineKeyboardButton(text=tt.back, callback_data="admin_customCommands") +btnBackSystemSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_systemSettings") +btnBackItemSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_itemSettings") +btnBackBackups = types.InlineKeyboardButton(text=tt.back, callback_data="admin_backups") + +# /start menu +btnBackFaq = types.InlineKeyboardButton(text=tt.back, callback_data="faq") +btnBackProfile = types.InlineKeyboardButton(text=tt.back, callback_data="profile") +btnBackMyOrder = types.InlineKeyboardButton(text=tt.back, callback_data="myOrders") +btnBackCatalogue = types.InlineKeyboardButton(text=tt.back, callback_data="catalogue") +def btnBackViewCat(cat_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"viewCat{cat_id}") +def btnBackViewItem(item_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"viewItem{item_id}") +btnBackCart = types.InlineKeyboardButton(text=tt.back, callback_data="cart") +btnBackCartDel = types.InlineKeyboardButton(text=tt.back, callback_data="cartDel") +btnBackOrders = types.InlineKeyboardButton(text=tt.back, callback_data="manager_orders") + +# Single buttons +btnAdminPanel = types.KeyboardButton(tt.admin_panel) +btnOrders = types.KeyboardButton(tt.orders) + +def single_button(btn): + markup = types.InlineKeyboardMarkup() + markup.add(btn) + return markup + + +# Markups +# /start buttons +def get_markup_main(): + markup = types.ReplyKeyboardMarkup(resize_keyboard=True) + markup.add(types.KeyboardButton(tt.catalogue)) + markup.add(types.KeyboardButton(tt.cart)) + markup.add(types.KeyboardButton(tt.profile), types.KeyboardButton(tt.faq)) + return markup + +def get_markup_admin(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.item_management, callback_data="admin_itemManagement")) + markup.add(types.InlineKeyboardButton(text=tt.user_management, callback_data="admin_userManagement")) + markup.add(types.InlineKeyboardButton(text=tt.shop_stats, callback_data="admin_shopStats")) + markup.add(types.InlineKeyboardButton(text=tt.bot_settings, callback_data="admin_shopSettings")) + return markup + +def get_markup_faq(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.contacts, callback_data="contacts")) + markup.add(types.InlineKeyboardButton(text=tt.refund, callback_data="refund")) + return markup + +def get_markup_profile(user): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.my_orders, callback_data="myOrders")) + # markup.add(types.InlineKeyboardButton(text=tt.my_support_tickets, callback_data="mySupportTickets")) + if user.is_admin(): + markup.add(types.InlineKeyboardButton(text=tt.disable_notif if user.notif_on() else tt.enable_notif, callback_data="changeEnableNotif")) + return markup + +def get_markup_myOrders(order_list): + markup = types.InlineKeyboardMarkup() + for order in order_list: + markup.add(types.InlineKeyboardButton(text=order.get_order_id(), callback_data=f"viewMyOrder{order.get_order_id()}")) + markup.add(btnBackProfile) + return markup + +def get_markup_viewMyOrder(order): + markup = types.InlineKeyboardMarkup() + match order.get_status(): + case 0: + markup.add(types.InlineKeyboardButton(text=tt.cancel_order, callback_data=f"cancelOrder{order.get_order_id()}")) + case -1: + markup.add(types.InlineKeyboardButton(text=tt.restore_order, callback_data=f"restoreOrder{order.get_order_id()}")) + markup.add(btnBackMyOrder) + return markup +def get_markup_cart(user): + markup = types.InlineKeyboardMarkup() + delivery_price = '{:.2f}'.format(float(settings.get_delivery_price())) + for item_and_amount in user.get_cart_amount(): + markup.add(types.InlineKeyboardButton(text=f"{item_and_amount[0].get_name()[:30-len(f' - {item_and_amount[1]}шт.')-3]}... - {item_and_amount[1]}шт.", callback_data=f"viewItem{item_and_amount[0].get_id()}")) + markup.add(types.InlineKeyboardButton(text=f"{item_and_amount[0].get_price() * item_and_amount[1]}руб.", callback_data="None"), types.InlineKeyboardButton(text=tt.plus, callback_data=f"addToCartFromCart{item_and_amount[0].get_id()}"), types.InlineKeyboardButton(text=tt.minus, callback_data=f"removeFromCartFromCart{item_and_amount[0].get_id()}")) + if settings.is_delivery_enabled(): + markup.add(types.InlineKeyboardButton(text=tt.delivery_on(delivery_price) if user.is_cart_delivery() else tt.delivery_off(delivery_price), callback_data="changeCartDelivery")) + else: + markup.add(types.InlineKeyboardButton(text=tt.pickup, callback_data="None")) + + markup.add(types.InlineKeyboardButton(text=tt.clear_cart, callback_data="clearCart")) + markup.add(types.InlineKeyboardButton(text=f"Всего: {'{:.2f}'.format(user.get_cart_price())}руб.", callback_data="None")) + markup.add(types.InlineKeyboardButton(text=tt.cart_checkout, callback_data="checkoutCart")) + return markup + +def get_markup_captcha(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text="Новая CAPTCHA", callback_data="refreshCaptcha")) + markup.add(btnBackCartDel) + return markup + +def get_markup_checkoutCartConfirmation(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.confirm, callback_data=f"checkoutCartConfirm"), types.InlineKeyboardButton(text=tt.deny, callback_data="cart")) + return markup + +# Catalogue +def get_markup_catalogue(cat_list): + markup = types.InlineKeyboardMarkup() + for cat in cat_list: + markup.add(types.InlineKeyboardButton(text=cat.get_name(), callback_data=f"viewCat{cat.get_id()}")) + markup.add(types.InlineKeyboardButton(text=tt.search, callback_data="search")) + return markup + +def get_markup_search(query): + markup = types.InlineKeyboardMarkup() + for item in query: + markup.add(types.InlineKeyboardButton(text=item.get_name(), callback_data=f"viewItem{item.get_id()}")) + markup.add(btnBackCatalogue) + return markup + + +def get_markup_viewCat(item_list): + markup = types.InlineKeyboardMarkup() + for item in item_list: + if item.is_active(): + markup.add(types.InlineKeyboardButton(text=f"{item.get_name()} - {item.get_price()} руб.", callback_data=f"viewItem{item.get_id()}")) + markup.add(btnBackCatalogue) + return markup + +def get_markup_viewItem(item): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.add_to_cart, callback_data=f"addToCart{item.get_id()}")) + markup.add(btnBackViewCat(item.get_cat_id())) + return markup + +# Admin panel tabs +# Item management +def get_markup_itemManagement(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.add_cat, callback_data="admin_addCat"), types.InlineKeyboardButton(text=tt.add_item, callback_data="admin_addItem")) + markup.add(types.InlineKeyboardButton(text=tt.edit_cat, callback_data="admin_editCatChooseCategory"), types.InlineKeyboardButton(text=tt.edit_item, callback_data="admin_editItemChooseCategory")) + markup.add(btnBackAdmin) + return markup + +def get_markup_editCatChooseCategory(cat_list): + markup = types.InlineKeyboardMarkup() + for cat in cat_list: + markup.add(types.InlineKeyboardButton(text=f"[{cat.get_id()}] {cat.get_name()}", callback_data=f"admin_editCat{cat.get_id()}")) + markup.add(btnBackItemManagement) + return markup + +def get_markup_editCat(cat_id): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.change_name, callback_data=f"admin_editCatName{cat_id}")) + markup.add(types.InlineKeyboardButton(text=tt.delete, callback_data=f"admin_editCatDelete{cat_id}")) + markup.add(btnBackEditCatChooseCategory) + return markup + +def get_markup_addItemSetCat(cat_list): + markup = types.InlineKeyboardMarkup() + for cat in cat_list: + markup.add(types.InlineKeyboardButton(text=f"[{cat.get_id()}] {cat.get_name()}", callback_data=f"admin_addItemSetCat{cat.get_id()}")) + markup.add(btnBackItemManagement) + return markup + +btnSkipAddItemSetImage = types.InlineKeyboardButton(text=tt.skip, callback_data="admin_skipSetAddItemSetImage") + +def get_markup_addItemConfirmation(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.confirm, callback_data="admin_addItemConfirm"), types.InlineKeyboardButton(text=tt.deny, callback_data="admin_itemManagement")) + return markup + +def get_markup_editItemChooseCategory(cat_list): + markup = types.InlineKeyboardMarkup() + for cat in cat_list: + markup.add(types.InlineKeyboardButton(text=f"[{cat.get_id()}] {cat.get_name()}", callback_data=f"admin_editItemChooseItem{cat.get_id()}")) + markup.add(btnBackItemManagement) + return markup + +def get_markup_editItemChooseItem(item_list): + markup = types.InlineKeyboardMarkup() + for item in item_list: + markup.add(types.InlineKeyboardButton(text=f"[{item.get_id()}] {item.get_name()}", callback_data=f"admin_editItem{item.get_id()}")) + markup.add(btnBackEditItemChooseCategory) + return markup + +async def get_markup_editItem(item): + itemid = item.get_id() + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.change_name, callback_data=f"admin_editItemName{itemid}")) + markup.add(types.InlineKeyboardButton(text=tt.change_image, callback_data=f"admin_editItemImage{itemid}")) + markup.add(types.InlineKeyboardButton(text=tt.show_image if await item.is_hide_image() else tt.hide_image, callback_data=f"admin_editItemHideImage{itemid}")) + markup.add(types.InlineKeyboardButton(text=tt.change_desc, callback_data=f"admin_editItemDesc{itemid}")) + markup.add(types.InlineKeyboardButton(text=tt.change_price, callback_data=f"admin_editItemPrice{itemid}")) + markup.add(types.InlineKeyboardButton(text=tt.change_item_cat, callback_data=f"admin_editItemCat{itemid}")) + markup.add(types.InlineKeyboardButton(text=tt.change_stock, callback_data=f"admin_editItemStock{itemid}")) + markup.add(types.InlineKeyboardButton(text=(tt.hide if item.is_active() else tt.show), callback_data=f"admin_editItemHide{itemid}")) + markup.add(types.InlineKeyboardButton(text=tt.delete, callback_data=f"admin_editItemDelete{itemid}")) + markup.add(btnBackEditItemChooseItem(item.get_cat_id())) + return markup + +def get_markup_editItemCat(item_id, cat_list): + markup = types.InlineKeyboardMarkup() + for cat in cat_list: + markup.add(types.InlineKeyboardButton(text=f"[{cat.get_id()}] {cat.get_name()}", callback_data=f"admin_editItemSetCat{cat.get_id()}")) + markup.add(btnBackEditItem(item_id)) + return markup + +# User management +def get_markup_userManagement(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.user_profile, callback_data="admin_seeUserProfile")) + markup.add(types.InlineKeyboardButton(text=tt.notify_everyone, callback_data="admin_notifyEveryone")) + markup.add(btnBackAdmin) + return markup + +def get_markup_notifyEveryoneConfirmation(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.confirm, callback_data="admin_notifyEveryoneConfirm"), btnBackUserManagement) + return markup + +def get_markup_seeUserProfile(user): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.orders, callback_data=f"admin_seeUserOrders{user.get_id()}")) + markup.add(types.InlineKeyboardButton(text=tt.remove_manager_role if user.is_manager() else tt.add_manager_role, callback_data=f"admin_changeUserManager{user.get_id()}")) + markup.add(types.InlineKeyboardButton(text=tt.remove_admin_role if user.is_admin() else tt.add_admin_role, callback_data=f"admin_changeUserAdmin{user.get_id()}")) + markup.add(btnBackUserManagement) + return markup + +def get_markup_seeUserOrders(user): + markup = types.InlineKeyboardMarkup() + for order in user.get_orders(): + markup.add(types.InlineKeyboardButton(text=f"[{order.get_order_id()}]", callback_data=f"admin_seeUserOrder{order.get_order_id()}")) + markup.add(btnBackSeeUserProfile(user.get_id())) + return markup + +def get_markup_seeUserOrder(order): + markup = markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.processing), callback_data=f"admin_changeOrderStatusProcessing{order.get_order_id()}")) + markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.delivery), callback_data=f"admin_changeOrderStatusDelivery{order.get_order_id()}")) + markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.done), callback_data=f"admin_changeOrderStatusDone{order.get_order_id()}")) + markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.cancelled), callback_data=f"admin_changeOrderStatusCancel{order.get_order_id()}")) + markup.add(btnBackSeeUserOrders(order.get_user_id())) + return markup + +# Shop stats +def get_markup_shopStats(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.registration_stats, callback_data="admin_registrationStats")) + markup.add(types.InlineKeyboardButton(text=tt.order_stats, callback_data="admin_orderStats")) + markup.add(btnBackAdmin) + return markup + +def get_markup_registrationStats(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.all_time, callback_data="admin_registrationStatsAllTime")) + markup.add(types.InlineKeyboardButton(text=tt.monthly, callback_data="admin_registrationStatsMonthly")) + markup.add(types.InlineKeyboardButton(text=tt.weekly, callback_data="admin_registrationStatsWeekly")) + markup.add(types.InlineKeyboardButton(text=tt.daily, callback_data="admin_registrationStatsDaily")) + markup.add(btnBackShopStats) + return markup + +def get_markup_orderStats(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.all_time, callback_data="admin_orderStatsAllTime")) + markup.add(types.InlineKeyboardButton(text=tt.monthly, callback_data="admin_orderStatsMonthly")) + markup.add(types.InlineKeyboardButton(text=tt.weekly, callback_data="admin_orderStatsWeekly")) + markup.add(types.InlineKeyboardButton(text=tt.daily, callback_data="admin_orderStatsDaily")) + markup.add(btnBackShopStats) + return markup + +# Shop settings +def get_markup_shopSettings(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.main_settings, callback_data="admin_mainSettings")) + markup.add(types.InlineKeyboardButton(text=tt.item_settings, callback_data="admin_itemSettings")) + markup.add(types.InlineKeyboardButton(text=tt.checkout_settings, callback_data="admin_checkoutSettings")) + markup.add(types.InlineKeyboardButton(text=tt.stats_settings, callback_data="admin_statsSettings")) + markup.add(types.InlineKeyboardButton(text=tt.additional_settings, callback_data="admin_additionalSettings")) + markup.add(btnBackAdmin) + return markup + +def get_markup_mainSettings(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=f"Название: {settings.get_shop_name()}", callback_data="admin_changeShopName")) + markup.add(types.InlineKeyboardButton(text=f"Приветствие: {settings.get_shop_greeting()}", callback_data="admin_changeShopGreeting")) + markup.add(types.InlineKeyboardButton(text=f"Политика возврата: {settings.get_refund_policy()}", callback_data="admin_changeShopRefundPolicy")) + markup.add(types.InlineKeyboardButton(text=f"Контакты: {settings.get_shop_contacts()}", callback_data="admin_changeShopContacts")) + markup.add(types.InlineKeyboardButton(text=tt.disable_sticker if settings.is_sticker_enabled() else tt.enable_sticker, callback_data="admin_changeEnableSticker")) + markup.add(btnBackShopSettings) + return markup + +def get_markup_itemSettings(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.disable_item_image if settings.is_item_image_enabled() else tt.enable_item_image, callback_data="admin_changeEnableItemImage")) + markup.add(btnBackShopSettings) + return markup + +def get_markup_checkoutSettings(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.delivery_price('{:.2f}'.format(settings.get_delivery_price())), callback_data="admin_changeDeliveryPrice")) + markup.add(types.InlineKeyboardButton(text=tt.disable_delivery if settings.is_delivery_enabled() else tt.enable_delivery, callback_data="admin_changeEnableDelivery")) + markup.add(types.InlineKeyboardButton(text=tt.disable_phone_number if settings.is_phone_number_enabled() else tt.enable_phone_number, callback_data="admin_changeEnablePhoneNumber")) + markup.add(types.InlineKeyboardButton(text=tt.disable_captcha if settings.is_captcha_enabled() else tt.enable_captcha, callback_data="admin_changeEnableCaptcha")) + markup.add(btnBackShopSettings) + return markup + +def get_markup_statsSettings(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.graph_color, callback_data="admin_statsSettingsColor")) + markup.add(types.InlineKeyboardButton(text=tt.border_width, callback_data="admin_statsSettingsBorderWidth")) + markup.add(types.InlineKeyboardButton(text=tt.title_font_size, callback_data="admin_statsSettingsTitleFontSize")) + markup.add(types.InlineKeyboardButton(text=tt.axis_font_size, callback_data="admin_statsSettingsAxisFontSize")) + markup.add(types.InlineKeyboardButton(text=tt.tick_font_size, callback_data="admin_statsSettingsTickFontSize")) + markup.add(btnBackShopSettingsDel) + return markup + +def get_markup_statsSettingsColor(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text="⬛️", callback_data="admin_statsSettingsColorBlack"), types.InlineKeyboardButton(text="⬜️", callback_data="admin_statsSettingsColorWhite"), types.InlineKeyboardButton(text="🟥", callback_data="admin_statsSettingsColorRed")) + markup.add(types.InlineKeyboardButton(text="🟨", callback_data="admin_statsSettingsColorYellow"), types.InlineKeyboardButton(text="🟪", callback_data="admin_statsSettingsColorPurple"), types.InlineKeyboardButton(text="🟦", callback_data="admin_statsSettingsColorBlue")) + markup.add(types.InlineKeyboardButton(text="🟧", callback_data="admin_statsSettingsColorOrange"), types.InlineKeyboardButton(text="🟩", callback_data="admin_statsSettingsColorGreen"), types.InlineKeyboardButton(text="🟫", callback_data="admin_statsSettingsColorBrown")) + markup.add(btnBackStatsSettings) + return markup + +def get_markup_statsSettingsBorderWidth(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.unavailable if settings.get_borderwidth() == "0" else tt.minus, callback_data="None" if settings.get_borderwidth() == "0" else "admin_statsSettingsBorderWidthReduce"), types.InlineKeyboardButton(text=settings.get_borderwidth(), callback_data="admin_statsSettingsBorderWidthDefault"), types.InlineKeyboardButton(text=tt.plus, callback_data="admin_statsSettingsBorderWidthAdd")) + markup.add(btnBackStatsSettings) + return markup + +def get_markup_statsSettingsTitleFontSize(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.unavailable if settings.get_titlefontsize() == "2" else tt.minus, callback_data="None" if settings.get_titlefontsize() == "2" else "admin_statsSettingsTitleFontSizeReduce"), types.InlineKeyboardButton(text=settings.get_titlefontsize(), callback_data="admin_statsSettingsTitleFontSizeDefault"), types.InlineKeyboardButton(text=tt.plus, callback_data="admin_statsSettingsTitleFontSizeAdd")) + markup.add(btnBackStatsSettings) + return markup + +def get_markup_statsSettingsAxisFontSize(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.unavailable if settings.get_axisfontsize() == "2" else tt.minus, callback_data="None" if settings.get_axisfontsize() == "2" else "admin_statsSettingsAxisFontSizeReduce"), types.InlineKeyboardButton(text=settings.get_axisfontsize(), callback_data="admin_statsSettingsAxisFontSizeDefault"), types.InlineKeyboardButton(text=tt.plus, callback_data="admin_statsSettingsAxisFontSizeAdd")) + markup.add(btnBackStatsSettings) + return markup + +def get_markup_statsSettingsTickFontSize(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.unavailable if settings.get_tickfontsize() == "2" else tt.minus, callback_data="None" if settings.get_tickfontsize() == "2" else "admin_statsSettingsTickFontSizeReduce"), types.InlineKeyboardButton(text=settings.get_tickfontsize(), callback_data="admin_statsSettingsTickFontSizeDefault"), types.InlineKeyboardButton(text=tt.plus, callback_data="admin_statsSettingsTickFontSizeAdd")) + markup.add(btnBackStatsSettings) + return markup + +def get_markup_systemSettings(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.clean_images, callback_data="admin_cleanImagesMenu")) + markup.add(types.InlineKeyboardButton(text=tt.reset_settings, callback_data="admin_resetSettingsMenu")) + markup.add(types.InlineKeyboardButton(text=tt.clean_database, callback_data="admin_cleanDatabaseMenu")) + markup.add(types.InlineKeyboardButton(text=tt.backups, callback_data="admin_backups")) + markup.add(types.InlineKeyboardButton(text=tt.disable_debug if settings.is_debug() else tt.enable_debug, callback_data="admin_changeEnableDebug")) + markup.add(btnBackAdditionalSettings) + return markup + +def get_markup_backups(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.update_backup, callback_data="admin_updateBackup")) + markup.add(types.InlineKeyboardButton(text=tt.load_backup, callback_data="admin_loadBackupMenu")) + markup.add(types.InlineKeyboardButton(text=tt.clean_backups, callback_data="admin_cleanBackupsMenu")) + markup.add(btnBackSystemSettings) + return markup + +def get_markup_loadBackupMenu(): + markup = types.InlineKeyboardMarkup() + backups = listdir("backups") + backups.sort(key=lambda b: datetime.strptime(b, "%d-%m-%Y")) + for backup in backups[:90]: + markup.add(types.InlineKeyboardButton(text=backup, callback_data=f"admin_loadBackup{backup}")) + markup.add(btnBackBackups) + return markup + +def get_markup_cleanBackupsMenu(): + markup = types.InlineKeyboardMarkup() + for days in ["7", "30", "90"]: + markup.add(types.InlineKeyboardButton(text=f"{days} дней", callback_data=f"admin_cleanBackups{days}")) + markup.add(types.InlineKeyboardButton(text="Удалить все резервные копии", callback_data="admin_cleanBackupsAll")) + markup.add(btnBackBackups) + return markup + +def get_markup_cleanImagesMenu(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.delete, callback_data="admin_cleanImages")) + markup.add(btnBackSystemSettings) + return markup + +def get_markup_resetSettingsMenu(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.reset, callback_data="admin_resetSettings")) + markup.add(btnBackSystemSettings) + return markup + +def get_markup_cleanDatabaseMenu(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.delete, callback_data="admin_cleanDatabase")) + markup.add(btnBackSystemSettings) + return markup + +# Manager tab +def get_markup_seeOrder(order, user_id=None): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.processing), callback_data=f"manager_changeOrderStatusProcessing{order.get_order_id()}")) + markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.delivery), callback_data=f"manager_changeOrderStatusDelivery{order.get_order_id()}")) + markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.done), callback_data=f"manager_changeOrderStatusDone{order.get_order_id()}")) + markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.cancelled), callback_data=f"manager_changeOrderStatusCancel{order.get_order_id()}")) + markup.add(btnBackSeeUserOrders(user_id) if user_id else btnBackOrders) + return markup + +def get_markup_orders(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.processing, callback_data="manager_ordersProcessing")) + markup.add(types.InlineKeyboardButton(text=tt.delivery, callback_data="manager_ordersDelivery")) + markup.add(types.InlineKeyboardButton(text=tt.done, callback_data="manager_ordersDone")) + markup.add(types.InlineKeyboardButton(text=tt.cancelled, callback_data="manager_ordersCancelled")) + return markup + +def get_markup_ordersByOrderList(order_list): + markup = types.InlineKeyboardMarkup() + for order in order_list: + markup.add(types.InlineKeyboardButton(text=f"[{order.get_order_id()}]", callback_data=f"manager_seeOrder{order.get_order_id()}")) + markup.add(btnBackOrders) + return markup + +def get_markup_additionalSettings(): + markup = types.InlineKeyboardMarkup() + markup.add(types.InlineKeyboardButton(text=tt.custom_commands, callback_data="admin_customCommands")) + markup.add(types.InlineKeyboardButton(text=tt.system_settings, callback_data="admin_systemSettings")) + markup.add(btnBackShopSettings) + return markup + +# Custom commands +def get_markup_customCommands(): + markup = types.InlineKeyboardMarkup() + for command in commands.get_commands(): + markup.add(types.InlineKeyboardButton(text=command.get_command(), callback_data="None"), types.InlineKeyboardButton(text=tt.delete, callback_data=f"admin_deleteCommand{command.get_id()}")) + markup.add(types.InlineKeyboardButton(text=tt.line_separator, callback_data="None")) + markup.add(types.InlineKeyboardButton(text=tt.add_command, callback_data="admin_addCommand")) + markup.add(btnBackAdditionalSettings) + return markup diff --git a/doners/shop-telegram-bot/src/order.py b/doners/shop-telegram-bot/src/order.py new file mode 100644 index 0000000..619f557 --- /dev/null +++ b/doners/shop-telegram-bot/src/order.py @@ -0,0 +1,108 @@ +import sqlite3 +from datetime import datetime +import item as itm +import text_templates as tt +from settings import Settings + +settings = Settings() + +conn = sqlite3.connect("data.db") +c = conn.cursor() + +class Order: + def __init__(self, id): + self.id = id + + def __repr__(self): + return f"{self.get_order_id()}" + + def get_order_id(self): + return self.id + + def __clist(self): + c.execute(f"SELECT * FROM orders WHERE order_id=?", [self.get_order_id()]) + return list(c)[0] + + def get_user_id(self): + return self.__clist()[1] + + def get_item_list_raw(self): + return self.__clist()[2] + + def get_item_list(self): + return list(map(itm.Item, [item_id for item_id in self.get_item_list_raw().split(",")])) + + def get_item_list_amount(self): + cart = [item.get_id() for item in self.get_item_list()] + return [[itm.Item(item_id), cart.count(item_id)] for item_id in set(cart)] + + def get_item_list_price(self): + return sum([item_and_price[0].get_price() * item_and_price[1] for item_and_price in self.get_item_list_amount()]) + (float(settings.get_delivery_price()) if self.get_home_adress() != None else 0) + + def set_item_list(self, value): + c.execute(f"UPDATE orders SET item_list=? WHERE order_id=?", [value, self.get_order_id()]) + conn.commit() + + def get_email_adress(self): + return self.__clist()[3] + + def set_email_adress(self, value): + c.execute(f"UPDATE orders SET email_adress=? WHERE order_id=?", [value, self.get_order_id()]) + conn.commit() + + def get_phone_number(self): + return self.__clist()[4] + + def set_phone_number(self, value): + c.execute(f"UPDATE orders SET phone_number=? WHERE order_id=?", [value, self.get_order_id()]) + conn.commit() + + def get_home_adress(self): + return self.__clist()[5] + + def set_home_adress(self, value): + c.execute(f"UPDATE orders SET home_adress=? WHERE order_id=?", [value, self.get_order_id()]) + conn.commit() + + def get_additional_message(self): + return self.__clist()[6] + + def get_date(self): + return datetime.strptime(self.__clist()[7], "%Y-%m-%d %H:%M:%S") + + def get_date_string(self): + return self.__clist()[7] + + def get_status(self): + return self.__clist()[8] + + def get_status_string(self): + return get_status_dict()[self.__clist()[8]] + + def set_status(self, value): + c.execute(f"UPDATE orders SET status=? WHERE order_id=?", [value, self.get_order_id()]) + conn.commit() + +def get_status_dict(): + return { + 0: tt.processing, + 1: tt.delivery, + 2: tt.done, + -1: tt.cancelled, + } + +def get_order_list(status=None): + if status: + c.execute(f"SELECT * FROM orders WHERE status=?", [status]) + else: + c.execute(f"SELECT * FROM orders") + return list(map(Order, [order[0] for order in list(c)])) + +def does_order_exist(order_id): + c.execute(f"SELECT * FROM orders WHERE order_id=?", [order_id]) + return len(list(c)) == 1 + +def create_order(order_id, user_id, item_list, email_adress, additional_message, phone_number="None", home_adress="None"): + c.execute(f"INSERT INTO orders VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", [order_id, user_id, item_list, email_adress, phone_number, home_adress, additional_message, datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 0]) + conn.commit() + return Order(order_id) \ No newline at end of file diff --git a/doners/shop-telegram-bot/src/search.py b/doners/shop-telegram-bot/src/search.py new file mode 100644 index 0000000..b6642c6 --- /dev/null +++ b/doners/shop-telegram-bot/src/search.py @@ -0,0 +1,45 @@ +import pymorphy2 +from pyparsing import opAssoc +import item as itm + +m = pymorphy2.MorphAnalyzer() + +excluded_words = ["без", "в", "для", "до", "за", "из", "к", "под", "а", "о", "над", "на", "о", "об", "от", "перед", "по", "под", "при", "про", "с", "у"] + +def get_normal_forms(word): + return set(p.normal_form for p in m.parse(word)) + + +class Query: + def __init__(self, results): + self.results = results + + def __get_items(self): + return list(map(itm.Item, self.results.keys()))[::-1][:90] + + def match(self): + return self.__get_items() + + def price(self): + return sorted(list(map(itm.Item, self.results.keys()))[::-1], key=lambda item: item.get_price()) + + def popular(self): + pass + +# a match in a name is 3 points +# a match in a desc is 1 point +def search_item(query): + points = dict() + for item in itm.get_item_list(): + points[item.get_id()] = 0 + for word in list(filter(lambda word: word not in excluded_words, query.split())): + for p in get_normal_forms(word): + for word_title in item.get_name().split(): + if p in get_normal_forms(word_title): + points[item.get_id()] += 3 + for word_title in item.get_desc().split(): + if p in get_normal_forms(word_title): + points[item.get_id()] += 1 + if points[item.get_id()] == 0: + points.pop(item.get_id()) + return Query(dict(sorted(points.items(), key=lambda item: item[1]))) diff --git a/doners/shop-telegram-bot/src/settings.py b/doners/shop-telegram-bot/src/settings.py new file mode 100644 index 0000000..2f442f2 --- /dev/null +++ b/doners/shop-telegram-bot/src/settings.py @@ -0,0 +1,170 @@ +from configparser import ConfigParser +from genericpath import exists +from os import remove +import sqlite3 + +class Settings: + def __init__(self, config_path="config.ini"): + self.__config_path = config_path + + def __get_config(self): + conf = ConfigParser() + conf.read(self.__config_path) + return conf + + def __set_setting(self, category, subcategory, value): + conf = self.__get_config() + conf.set(category, subcategory, str(value)) + with open(self.__config_path, 'w') as config: + conf.write(config) + + # main_settings + def get_token(self): + return self.__get_config()["main_settings"]["token"] + + def set_token(self, value): + self.__set_setting("main_settings", "token", value) + + def is_debug(self): + return self.__get_config()["main_settings"]["debug"] == "1" + + def set_debug(self, value): + self.__set_setting("main_settings", "debug", value) + + def get_main_admin_id(self): + return self.__get_config()["main_settings"]["mainadminid"] + + def set_main_admin_id(self, value): + self.__set_setting("main_settings", "mainadminid", value) + + # shop_settings + def get_shop_name(self): + return self.__get_config()["shop_settings"]["name"] + + def set_shop_name(self, value): + self.__set_setting("shop_settings", "name", value) + + def get_shop_greeting(self): + return self.__get_config()["shop_settings"]["greeting"] + + def set_shop_greeting(self, value): + self.__set_setting("shop_settings", "greeting", value) + + def is_sticker_enabled(self): + return self.__get_config()["shop_settings"]["enablesticker"] == "1" + + def set_enable_sticker(self, value): + self.__set_setting("shop_settings", "enablesticker", value) + + def get_refund_policy(self): + return self.__get_config()["shop_settings"]["refundpolicy"] + + def set_refund_policy(self, value): + self.__set_setting("shop_settings", "refundpolicy", value) + + def get_shop_contacts(self): + return self.__get_config()["shop_settings"]["contacts"] + + def set_shop_contacts(self, value): + self.__set_setting("shop_settings", "contacts", value) + + def is_item_image_enabled(self): + return self.__get_config()["shop_settings"]["enableimage"] == "1" + + def set_item_image(self, value): + self.__set_setting("shop_settings", "enableimage", value) + + def is_phone_number_enabled(self): + return self.__get_config()["shop_settings"]["enablephonenumber"] == "1" + + def set_enable_phone_number(self, value): + self.__set_setting("shop_settings", "enablephonenumber", value) + + def is_delivery_enabled(self): + return self.__get_config()["shop_settings"]["enabledelivery"] == "1" + + def set_delivery(self, value): + self.__set_setting("shop_settings", "enabledelivery", value) + + def get_delivery_price(self): + return float(self.__get_config()["shop_settings"]["delivery_price"]) + + def set_delivery_price(self, value): + self.__set_setting("shop_settings", "delivery_price", value) + + def is_captcha_enabled(self): + return self.__get_config()["shop_settings"]["enablecaptcha"] == "1" + + def set_enable_captcha(self, value): + self.__set_setting("shop_settings", "enablecaptcha", value) + + # stats_settings + def get_barcolor(self): + return self.__get_config()["stats_settings"]["barcolor"] + + def set_barcolor(self, value): + self.__set_setting("stats_settings", "barcolor", value) + + def get_borderwidth(self): + return self.__get_config()["stats_settings"]["borderwidth"] + + def set_borderwidth(self, value): + self.__set_setting("stats_settings", "borderwidth", value) + + def get_titlefontsize(self): + return self.__get_config()["stats_settings"]["titlefontsize"] + + def set_titlefontsize(self, value): + self.__set_setting("stats_settings", "titlefontsize", value) + + def get_axisfontsize(self): + return self.__get_config()["stats_settings"]["axisfontsize"] + + def set_axisfontsize(self, value): + self.__set_setting("stats_settings", "axisfontsize", value) + + def get_tickfontsize(self): + return self.__get_config()["stats_settings"]["tickfontsize"] + + def set_tickfontsize(self, value): + self.__set_setting("stats_settings", "tickfontsize", value) + + def reset(self): + DEFAULT_CONFIG_TEXT = f"""[main_settings] +token = {self.get_token()} +mainadminid = {self.get_main_admin_id()} +debug = 0 + +[shop_settings] +name = Название магазина +greeting = Добро пожаловать! +refundpolicy = Текст для вкладки "Политика возврата" +contacts = Текст для вкладки "Контакты" +enableimage = 1 +enablesticker = 0 +enablephonenumber = 0 +enabledelivery = 0 +delivery_price = 0.0 +enablecaptcha = 1 + +[stats_settings] +barcolor = 3299ff +borderwidth = 1 +titlefontsize = 20 +axisfontsize = 12 +tickfontsize = 8 +""" + if exists("config.ini"): + remove("config.ini") + with open("config.ini", "w") as config: + config.write(DEFAULT_CONFIG_TEXT) + + def clean_db(self): + conn = sqlite3.connect("data.db") + c = conn.cursor() + c.execute("DELETE FROM cats") + c.execute("DELETE FROM items") + c.execute("DELETE FROM orders") + c.execute("DELETE FROM commands") + conn.commit() + conn.close() diff --git a/doners/shop-telegram-bot/src/state_handler.py b/doners/shop-telegram-bot/src/state_handler.py new file mode 100644 index 0000000..a581614 --- /dev/null +++ b/doners/shop-telegram-bot/src/state_handler.py @@ -0,0 +1,111 @@ +from urllib import response +from aiogram.dispatcher.filters.state import StatesGroup, State + +# Item management +class addCat(StatesGroup): + state_message = State() + name = State() + +class changeCatName(StatesGroup): + state_message = State() + cat_id = State() + name = State() + +class addItem(StatesGroup): + # Required + name = State() + price = State() + cat_id = State() + desc = State() + confirmation = State() + + # Additional + image = State() + +class changeItemPrice(StatesGroup): + state_message = State() + item_id = State() + price = State() + +class changeItemImage(StatesGroup): + state_message = State() + item_id = State() + image = State() + +class changeItemDesc(StatesGroup): + state_message = State() + item_id = State() + desc = State() + +class changeItemName(StatesGroup): + state_message = State() + item_id = State() + name = State() + +class changeItemCat(StatesGroup): + item_id = State() + cat = State() + +class changeItemStock(StatesGroup): + item_id = State() + state_message = State() + stock = State() + +# User management +class notifyEveryone(StatesGroup): + state_message = State() + message = State() + confirmation = State() + +class seeUserProfile(StatesGroup): + state_message = State() + user_id = State() + + +# Checkout +class checkoutCart(StatesGroup): + # Data + user_id = State() + item_list_comma = State() + order_id = State() + + # Required + email = State() + additional_message = State() + confirmation = State() + + # Additional + phone_number = State() + home_adress = State() + captcha = State() + +# Main settings +class changeShopName(StatesGroup): + state_message = State() + name = State() + +class changeShopGreeting(StatesGroup): + state_message = State() + greeting = State() + +class changeShopRefundPolicy(StatesGroup): + state_message = State() + refund_policy = State() + +class changeShopContacts(StatesGroup): + state_message = State() + contacts = State() + +class changeDeliveryPrice(StatesGroup): + state_message = State() + price = State() + +class addCustomCommand(StatesGroup): + command = State() + response = State() + + +# Misc +class search(StatesGroup): + state_message = State() + query = State() diff --git a/doners/shop-telegram-bot/src/stats.py b/doners/shop-telegram-bot/src/stats.py new file mode 100644 index 0000000..70e968e --- /dev/null +++ b/doners/shop-telegram-bot/src/stats.py @@ -0,0 +1,63 @@ +import datetime +import matplotlib.pyplot as plt +import order as ordr +import user as usr +from random import randint +from settings import Settings + +settings = Settings() + +def saveplot(data, title, ylabel): + plt.autoscale() + plt.figure(figsize=(10, 10)) + plt.title(title, fontsize=settings.get_titlefontsize()) + plt.xlabel("Дата", fontsize=settings.get_axisfontsize()) + plt.ylabel(ylabel, fontsize=settings.get_axisfontsize()) + plt.tick_params(labelsize=settings.get_tickfontsize()) + plt.bar(range(len(data)), list(data.values()), color="#" + settings.get_barcolor(), edgecolor="black", linewidth=settings.get_borderwidth()) + plt.xticks(range(len(data)), list(data.keys()), rotation=90) + plt.savefig(f"images/stats.png") + plt.close() + return open(f"images/stats.png", "rb") + + +def get_random_data(): + return {f"{randint(1, 30):02}.{randint(1, 12):02}.{randint(2010, 2030)}": randint(5, 100) for _ in range(randint(2, 30))} + + +def get_random_graph(): + return saveplot(get_random_data(), "Название", "Ось Y") + + +class RegistrationCharts: + def __init__(self): + self.user_list = usr.get_user_list() + + def saveplot(self, data, title): + return saveplot(data, title, "Количество регистраций") + + def all_time(self): + return self.saveplot({f"{date.day:02}.{date.month:02}.{date.year}": [user.get_register_date().date() for user in self.user_list].count(date) for date in dict.fromkeys([user.get_register_date().date() for user in self.user_list])}, "Регистрации за все время") + + def last_x_days(self, days): + return self.saveplot({f"{(datetime.date.today() - datetime.timedelta(days=i)).day:02}.{(datetime.date.today() - datetime.timedelta(days=i)).month:02}": len(list(filter(lambda user: user.get_register_date().date() == datetime.date.today() - datetime.timedelta(days=i), self.user_list))) for i in range(30, -1, -1)}, f"Регистрации за последние {days} дней") + + def last_x_hours(self, hours): + return self.saveplot({f"{(datetime.datetime.today() - datetime.timedelta(hours=i)).hour:02}:00": len(list(filter(lambda user: user.get_register_date().hour == (datetime.datetime.now() - datetime.timedelta(hours=i)).hour and user.get_register_date() > datetime.datetime.now() - datetime.timedelta(hours=hours), self.user_list))) for i in range(hours, -1, -1)}, f"Регистрации за последние {hours} часов.") + + +class OrderCharts: + def __init__(self): + self.order_list = ordr.get_order_list() + + def saveplot(self, data, title): + return saveplot(data, title, "Количество заказов") + + def all_time(self): + return self.saveplot({f"{date.day:02}.{date.month:02}.{date.year}": [order.get_date().date() for order in self.order_list].count(date) for date in dict.fromkeys([order.get_date().date() for order in self.order_list])}, "Заказы за все время") + + def last_x_days(self, days): + return self.saveplot({f"{(datetime.date.today() - datetime.timedelta(days=i)).day:02}.{(datetime.date.today() - datetime.timedelta(days=i)).month:02}": len(list(filter(lambda order: order.get_date().date() == datetime.date.today() - datetime.timedelta(days=i), self.order_list))) for i in range(days, -1, -1)}, f"Заказы за последние {days} дней") + + def last_x_hours(self, hours): + return self.saveplot({f"{(datetime.datetime.today() - datetime.timedelta(hours=i)).hour:02}:00": len(list(filter(lambda order: order.get_date() > datetime.datetime.now() - datetime.timedelta(hours=hours) and order.get_date().hour == (datetime.datetime.today() - datetime.timedelta(hours=i)).hour, self.order_list))) for i in range(hours, -1, -1)}, "Заказы за сегодня" if hours == (datetime.datetime.now().hour + 1) else f"Заказы за последние {hours} часов.") diff --git a/doners/shop-telegram-bot/src/text_templates.py b/doners/shop-telegram-bot/src/text_templates.py new file mode 100644 index 0000000..f4c30cf --- /dev/null +++ b/doners/shop-telegram-bot/src/text_templates.py @@ -0,0 +1,177 @@ +from settings import Settings + +settings = Settings() + +line_separator = "➖➖➖➖➖" + + +# Multiple lines +def get_profile_template(user): + return f"{line_separator}\n📝 id: {user.get_id()}\n📈 Кол-во заказов: {len(user.get_orders())}\n📅 Дата регистрации: {user.get_register_date_string()}\n{line_separator}" + +def get_faq_template(shop_name): + return f"{line_separator}\nℹ️ FAQ магазина {shop_name}\n{line_separator}" + +def get_categories_template(): + return f"{line_separator}\n🛍️ Категории\n{line_separator}" + +def get_category_was_created_successfuly(cat_name): + return f"Категория {cat_name} была успешно создана." + +def get_category_data(cat): + return f"{line_separator}\nID: {cat.get_id()}\nНазвание: {cat.get_name()}\n{line_separator}" + +def get_item_card(item=None, name=None, price=None, desc=None, amount=None): + if item: + name = item.get_name() + price = item.get_price() + desc = item.get_desc() + amount = item.get_amount() + + return f"{line_separator}\n{name} - {'{:.2f}'.format(price)} руб.\nВ наличии: {amount} шт.\n{line_separator}\n{desc}" + +def get_order_confirmation_template(item_amount_dict, cart_price, email_adress, additional_message, phone_number=None, home_adress=None): + item_amount_dict_formatted = '\n'.join([f'\t· {item[0].get_name()} - {item[1]} шт.' for item in item_amount_dict]) + phone_number = f"Номер телефона: {phone_number}\n" if phone_number else "" + home_adress = f"Адрес доставки: {home_adress}\n" if home_adress else "" + return f"{line_separator}\nТовары:\n{item_amount_dict_formatted}\nСумма: {cart_price}руб.\nEmail: {email_adress}\n{phone_number}{home_adress}Комментарий к заказу: {additional_message}\n{line_separator}\nВы уверены, что хотите оформить заказ?" + +def get_order_template(order): + item_list_amount_formatted = '\n'.join([f'\t· {item[0].get_name()} - {item[1]} шт.' for item in order.get_item_list_amount()]) + phone_number = f"Номер телефона: {order.get_phone_number()}\n" if settings.is_phone_number_enabled() else "" + home_adress = f"Адрес доставки: {order.get_home_adress()}\n" if settings.is_delivery_enabled() else f"Самовывоз\n" + return f"{line_separator}\nID заказа: {order.get_order_id()}\nID пользователя: {order.get_user_id()}\nТовары:\n{item_list_amount_formatted}\nСумма: {order.get_item_list_price()}руб.\nEmail: {order.get_email_adress()}\n{phone_number}{home_adress}Комментарий к заказу: {order.get_additional_message()}\nСтатус заказа: {order.get_status_string()}\nДата: {order.get_date_string()}\n{line_separator}" + +# Single phrases +# /start +admin_panel = "🔴 Админ панель" +faq = "ℹ️ FAQ" +profile = "📁 Профиль" +catalogue = "🗄️ Каталог" +cart = "🛒 Корзина" +support_menu = "☎ Меню тех. поддержки" + +# Admin panel tabs +item_management = "📦 Управление товаром" +user_management = "🧍 Управление пользователями" +shop_stats = "📈 Статистика магазина (BETA)" +bot_settings = "⚙ Настройки бота" + +# FAQ +contacts = "📞 Контакты" +refund = "🎫 Политика возврата" + +# Profile +my_orders = "📂 Мои заказы" +cancel_order = "❌ Отменить заказ" +restore_order = "✅ Восстановить заказ" +my_support_tickets = "🙋 Мои тикеты в тех. поддержку" +enable_notif = "🔔Включить оповещения о заказах" +disable_notif = "🔕Выключить оповещения о заказах" + +# Catalogue / Item / Cart +search = "🔍 Найти" +add_to_cart = "🛒 Добавить в корзину" +cart_is_empty = "Корзина пуста." +pickup = "✅Самовывоз" +def delivery_on(price): return f"✅ Доставка - {price}руб." +def delivery_off(price): return f"❌ Доставка - {price}руб." +cart_checkout = "Оформить заказ" +clear_cart = "Очистить корзину" +processing = "Обрабатывается" +delivery = "Ожидает доставки" +done = "Готов" +cancelled = "Отменён" + +# Item management +add_cat = "🛍️ Добавить категорию" +add_item = "🗃️ Добавить товар" +edit_cat = "✏️ Редактировать категорию" +edit_item = "✏️ Редактировать товар" +change_name = "📋 Изменить название" +change_image = "🖼️ Изменить изображение" +hide_image = "🙈 Скрыть изображение" +show_image = "🐵 Показать изображение" +change_desc = "📝 Изменить описание" +change_price = "🏷️ Изменить цену" +change_item_cat = "🛍️ Изменить категорию" +change_stock = "📦 Изменить кол-во" + +# User management +user_profile = "📁Профиль пользователя" +notify_everyone = "🔔Оповещение всем пользователям" +orders = "📁 Заказы" +remove_manager_role = "👨‍💼 Убрать роль менеджера" +add_manager_role = "👨‍💼 Сделать менеджером" +remove_admin_role = "🔴 Убрать роль администратора" +add_admin_role = "🔴 Сделать администратором" +def change_order_status(status): return f"Изменить статус на \"{status}\"" + +# Shop stats +registration_stats = "👥Статистика регистраций" +order_stats = "📦Статистика заказов" +all_time = "За всё время" +monthly = "За последние 30 дней" +weekly = "За последние 7 дней" +daily = "За последние 24 часа" + +# Shop settings +main_settings = "🛠️ Основные настройки" +item_settings = "🗃️ Настройки товаров" +additional_settings = "📖 Дополнительные настройки" +custom_commands = "📖 Команды" +add_command = "📝 Добавить команду" +clean_logs = "📖 Очистить логи" +clean_logs_text = "⚠️ Вы уверены, что хотите очистить логи? Они будут удалены без возможности восстановления!\n(Логи за сегодняшний день не будут удалены)" +backups = "💾 Резервное копирование" +update_backup = "🔄 Обновить резервную копию" +load_backup = "💿 Загрузить резервную копию" +clean_backups = "🧹 Очистка резервных копий" +system_settings = "💻 Система" +clean_images = "🗑️ Удалить неиспользуемые изображения" +clean_images_text = "⚠️ Вы уверены, что хотите удалить неспользуемые изображения? Они будут удалены без возможности восстановления!" +clean_database = "📚 Очистить базу данных" +clean_database_text = "⚠️ Вы уверены, что хотите очистить базу данных? Все данные будут удалены без возможности восстановления!" +reset_settings = "⚙️ Сбросить настройки" +resert_settings_text = "⚠️ Вы уверены, что хотите сбросить настройки? Все данные будут удалены без возможности восстановления!" +disable_item_image = "✅ Картинки товаров" +enable_item_image = "❌ Картинки товаров" +checkout_settings = "💳 Настройки оформления заказа" +stats_settings = "📈 Настройки статистики" +graph_color = "🌈 Цвет графика" +border_width = "🔲 Ширина обводки" +title_font_size = "ℹ️ Размер названия графика" +axis_font_size = "↔️Размер текста для осей" +tick_font_size = "🔢Размер текста для делений" +unavailable = "⛔️" +minus = "➖" +plus = "➕" +enable_sticker = "❌ Стикер в приветствии" +disable_sticker = "✅ Стикер в приветствии" +enable_phone_number = "❌ Номер телефона при заказе" +disable_phone_number = "✅ Номер телефона при заказе" +enable_delivery = "❌ Доставка" +disable_delivery = "✅ Доставка" +def delivery_price(price): return f"🚚 Стоимость доставки: {price}руб." +enable_captcha = "❌ CAPTCHA при заказе" +disable_captcha = "✅ CAPTCHA при заказе" +enable_debug = "❌ Режим отладки" +disable_debug = "✅ Режим отладки" + +# Manager tab +view_order = "📂 Посмотреть заказ" + +# Misc buttons +skip = "⏭ Пропустить" +back = "🔙 Назад" +confirm = "✅ Да" +deny = "❌ Нет" +error = "Произошла ошибка!" +or_press_back = "или нажмите на кнопку \"Назад\"." +hide = "🙈 Скрыть" +show = "🐵 Показать" +delete = "❌ Удалить" +reset = "❌ Сбросить" + +if __name__ == "__main__": + print(delivery_on) diff --git a/doners/shop-telegram-bot/src/user.py b/doners/shop-telegram-bot/src/user.py new file mode 100644 index 0000000..1c4bb63 --- /dev/null +++ b/doners/shop-telegram-bot/src/user.py @@ -0,0 +1,111 @@ +import sqlite3 +from datetime import datetime +import item as itm +from order import Order +from settings import Settings + +conn = sqlite3.connect('data.db') +c = conn.cursor() +settings = Settings() + +class User: + def __init__(self, user_id): + self.__user_id = user_id + + if not does_user_exist(self.get_id()): + c.execute(f"INSERT INTO users VALUES(?, ?, ?, ?, ?, ?, ?)", [self.get_id(), 1 if str(self.get_id()) == settings.get_main_admin_id() else 0, 0, 0, datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "None", 1]) + conn.commit() + + def get_id(self): + return self.__user_id + + def __clist(self): + c.execute(f"SELECT * FROM users WHERE user_id=?", [self.get_id()]) + return list(c)[0] + + def is_admin(self): + return self.__clist()[1] == 1 + + def set_admin(self, value): + c.execute(f"UPDATE users SET is_admin=? WHERE user_id=?", [value, self.get_id()]) + conn.commit() + + def is_manager(self): + return self.__clist()[2] == 1 + + def set_manager(self, value): + c.execute(f"UPDATE users SET is_manager=? WHERE user_id=?", [value, self.get_id()]) + conn.commit() + + def get_register_date(self): + return datetime.strptime(self.__clist()[4], "%Y-%m-%d %H:%M:%S") + + def get_register_date_string(self): + return self.__clist()[4] + + def notif_on(self): + return self.__clist()[3] == 1 + + def set_notif_enable(self, value): + c.execute(f"UPDATE users SET notification=? WHERE user_id=?", [value, self.get_id()]) + conn.commit() + + def get_orders(self): + c.execute(f"SELECT * FROM orders WHERE user_id=?", [self.get_id()]) + return list(map(Order, [order[0] for order in list(c)]))[::-1] + + def get_cart_comma(self): + return self.__clist()[5] + + def get_cart(self): + cart = self.get_cart_comma() + return [] if cart == "None" else list(map(itm.Item, cart.split(","))) + + def get_cart_amount(self): + cart = [item.get_id() for item in self.get_cart()] + return [[itm.Item(item_id), cart.count(item_id)] for item_id in set(cart)] + + def get_cart_price(self): + return sum([item_and_price[0].get_price() * item_and_price[1] for item_and_price in self.get_cart_amount()]) + (settings.get_delivery_price() if self.is_cart_delivery() else 0) + + def clear_cart(self): + c.execute(f"UPDATE users SET cart=\"None\" WHERE user_id=?", [self.get_id()]) + self.set_cart_delivery(1) + conn.commit() + + def add_to_cart(self, item_id): + cart = self.get_cart() + c.execute(f"UPDATE users SET cart=? WHERE user_id=?", [",".join([str(item.get_id()) for item in cart + [itm.Item(item_id)]]) if cart else item_id, self.get_id()]) + conn.commit() + + def remove_from_cart(self, item_id): + cart = [item.get_id() for item in self.get_cart()] + cart.remove(str(item_id)) + c.execute(f"UPDATE users SET cart=? WHERE user_id=?", [",".join(cart) if cart else "None", self.get_id()]) + conn.commit() + + def is_cart_delivery(self): + return self.__clist()[6] == 1 + + def set_cart_delivery(self, value): + c.execute(f"UPDATE users SET cart_delivery=? WHERE user_id=?", [value, self.get_id()]) + conn.commit() + + +def does_user_exist(user_id): + c.execute(f"SELECT * FROM users WHERE user_id=?", [user_id]) + return len(list(c)) != 0 + + +def get_notif_list(): + c.execute(f"SELECT * FROM users WHERE notification=1") + return list(map(User, [user[0] for user in list(c)])) + + +def get_user_login(message): + return message.from_user.username + + +def get_user_list(): + c.execute("SELECT * FROM users") + return list(map(User, [user[0] for user in list(c)])) diff --git a/doners/shop-telegram-bot/start.cmd b/doners/shop-telegram-bot/start.cmd new file mode 100644 index 0000000..16a0a7d --- /dev/null +++ b/doners/shop-telegram-bot/start.cmd @@ -0,0 +1,3 @@ +cls +echo "Starting the bot..." +python3 src/main.py \ No newline at end of file diff --git a/doners/shop-telegram-bot/start.sh b/doners/shop-telegram-bot/start.sh new file mode 100644 index 0000000..2cc93d5 --- /dev/null +++ b/doners/shop-telegram-bot/start.sh @@ -0,0 +1,4 @@ +clear +echo "Starting the bot..." +python3.10 src/main.py + diff --git a/doners/shop-telegram-bot/tests/__init__.py b/doners/shop-telegram-bot/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/doners/shop-telegram-bot/tests/test_item.py b/doners/shop-telegram-bot/tests/test_item.py new file mode 100644 index 0000000..b043349 --- /dev/null +++ b/doners/shop-telegram-bot/tests/test_item.py @@ -0,0 +1,127 @@ +import unittest +from random import randint +from .utils import * + +import src.item as itm +import src.category as cat + + + +class TestItem(unittest.TestCase): + def setUp(self): + self.test_data_random = { + "name": get_random_string(10, 40), + "price": get_random_float(100, 10000), + "cat_id": randint(1, 100), + "desc": get_random_string(10, 255), + } + self.item_random = itm.create_item(self.test_data_random["name"], self.test_data_random["price"], self.test_data_random["cat_id"], self.test_data_random["desc"], active=False) + + self.test_data = { + "name": "Test Item Name", + "price": 999.99, + "cat_id": cat.get_cat_list()[0].get_id(), + "desc": "Test Item's description here. Blah-blah-blah..." + } + self.item = itm.create_item(self.test_data["name"], self.test_data["price"], self.test_data["cat_id"], self.test_data["desc"], active=False) + + + def tearDown(self): + self.item.delete() + + def test_name(self): + self.assertEqual(self.item_random.get_name(), self.test_data_random["name"]) + name_random = get_random_string(2, 20) + self.item_random.set_name(name_random) + self.test_data_random["name"] = name_random + self.assertEqual(self.item_random.get_name(), self.test_data_random["name"]) + + self.assertEqual(self.item.get_name(), self.test_data["name"]) + name = "New test Item name" + self.item.set_name(name) + self.test_data["name"] = name + self.assertEqual(self.item.get_name(), self.test_data["name"]) + + def test_price(self): + self.assertEqual(self.item_random.get_price(), self.test_data_random["price"]) + price_random = get_random_float(10, 1000) + self.item_random.set_price(price_random) + self.test_data_random["price"] = price_random + self.assertEqual(self.item_random.get_price(), self.test_data_random["price"]) + + self.assertEqual(self.item.get_price(), self.test_data["price"]) + price = 123.12 + self.item.set_price(price) + self.test_data["price"] = price + self.assertEqual(self.item.get_price(), self.test_data["price"]) + + def test_cat_id(self): + self.assertEqual(self.item_random.get_cat_id(), self.test_data_random["cat_id"]) + cat_id_random = randint(1, 100) + self.item_random.set_cat_id(cat_id_random) + self.test_data_random["cat_id"] = cat_id_random + self.assertEqual(self.item_random.get_cat_id(), self.test_data_random["cat_id"]) + + self.assertEqual(self.item.get_cat_id(), self.test_data["cat_id"]) + cat_id = 2 + self.item.set_cat_id(cat_id) + self.test_data["cat_id"] = cat_id + self.assertEqual(self.item.get_cat_id(), self.test_data["cat_id"]) + + def test_desc(self): + self.assertEqual(self.item_random.get_desc(), self.test_data_random["desc"]) + desc_random = get_random_string(10, 255) + self.item_random.set_desc(desc_random) + self.test_data_random["desc"] = desc_random + self.assertEqual(self.item_random.get_desc(), self.test_data_random["desc"]) + + self.assertEqual(self.item.get_desc(), self.test_data["desc"]) + desc = "New desc" + self.item.set_desc(desc) + self.test_data["desc"] = desc + self.assertEqual(self.item.get_desc(), self.test_data["desc"]) + + def test_active(self): + self.assertFalse(self.item.is_active()) + + self.item.set_active(1) + self.assertTrue(self.item.is_active()) + self.item.set_active(0) + self.assertFalse(self.item.is_active()) + + def test_amount(self): + self.assertEqual(self.item_random.get_amount(), 0) + amount_random = randint(1, 1000) + self.item_random.set_amount(amount_random) + self.test_data_random["amount"] = amount_random + self.assertEqual(self.item_random.get_amount(), self.test_data_random["amount"]) + + self.assertEqual(self.item.get_amount(), 0) + amount = 90 + self.item.set_amount(amount) + self.test_data["amount"] = amount + self.assertEqual(self.item.get_amount(), self.test_data["amount"]) + + # TODO: image test cases + @unittest.SkipTest + def test_get_image_id(self): + pass + + @unittest.SkipTest + def test_get_image(self): + pass + + @unittest.SkipTest + def test_set_image_id(self): + pass + + def test_hide_image(self): + self.assertFalse(self.item.is_hide_image()) + self.item.set_hide_image(1) + self.assertTrue(self.item.is_hide_image()) + self.item.set_hide_image(0) + self.assertFalse(self.item.is_hide_image()) + + +if __name__ == "__main__": + unittest.main() diff --git a/doners/shop-telegram-bot/tests/utils.py b/doners/shop-telegram-bot/tests/utils.py new file mode 100644 index 0000000..97d791e --- /dev/null +++ b/doners/shop-telegram-bot/tests/utils.py @@ -0,0 +1,8 @@ +from random import randint, choice +from string import ascii_lowercase, digits + +def get_random_string(a, b): + return ''.join([choice(ascii_lowercase+digits) for _ in range(randint(a, b))]) + +def get_random_float(a, b): + return float(f"{randint(a, b)}.{randint(0, 99)}") diff --git a/doners/shopbot/README.md b/doners/shopbot/README.md new file mode 100644 index 0000000..b4d8c0a --- /dev/null +++ b/doners/shopbot/README.md @@ -0,0 +1,215 @@ +# Сообщение от разработчика (31.01.2023) +Всем привет, код писался мною когда я только учился пайтону, просьба не используйте данный код в коммерческих целях. + +Скажу откровенно, код очень не граммотный и требует полного рефакторинга + +Почему? Потому что данный код написан в одном файле что является очень плохим способом. + +Кому не лень - перепишите, я посмотрю и закомичу Ваш апдейт :D + +Возможно я ещё возьмусь за данного бота и переработаю его чтобы Вы могли использовать в коммерческих целях и в личных :D + +Спасибо! + +Так-же хочу выразить глубокую благодарность за ваши звёзды и форки, приятно это видеть, даже если код откровенно ужасен. + +Хочу посоветовать Вам, братья учите пайтон, не пожалеете. Данный код пока что используйте только в ознакомительных целях, не думайте что он хороший и его нужно использовать. + +# Обновление 4.0 (18.10.2021) +✅ Обновления: +1. Был полностью оптимизирован код, теперь если пользователь введёт что-то не так, бот не будет крашиться, а напишет + +![image](https://user-images.githubusercontent.com/94528892/142433868-40037a8b-5315-430e-b0ef-6409b71933aa.png) + +2. Инлайновые кнопки были полностью изменены, вместо текста, эмодзи. + +![image](https://user-images.githubusercontent.com/94528892/142434248-5501e81b-8bdb-488d-8b80-ae9e67bdc82d.png) + +3. Обновлён профиль пользователя + +![image](https://user-images.githubusercontent.com/94528892/142441734-2ae0cd85-8e00-45cd-915f-8b4c4bc07524.png) + +4. Добавлена команда для просмотра всех пользователей `/users` (Синяя ссылка это кликабельно) + +![image](https://user-images.githubusercontent.com/94528892/142456863-6b5f112a-c3f6-4e7b-9a02-c650ebb04faa.png) + +# Обновление 3.0 (17.10.2021) +✅ Обновления: +1. Добавлена команда для просмотра купленных товаров `/mybuy` +2. При покупке товара, если у пользователя уже куплен товар пишет + +![image](https://user-images.githubusercontent.com/94528892/142281705-32ca0121-258c-471e-8f93-d2b94d97de10.png) + +3. Добавлена команда для связи с тех.поддержкой `/teh` +4. Для администратора добавлена команда `/ot` для ответа пользователю (отправки сообщения пользователю) + +# Обновление 2.0 (17.10.2021) + +✅ Обновления: +1. Добавлена команда для удаления товаров `/rembuy` +2. Добавлена команда для изменения данных о товаре (Название, Цена, Ссылка) `/editbuy` +3. Код стал более оптимизированым + +# ShopBot Telegram + +🤖 Бот Магазин для Телеграмма на Python 🤖 + + +✅ Функционал бота: +1. Уровни доступа (Пользователь, Администратор, Разработчик) +2. База данных SQLite3 +3. Добавление товаров +4. Продажа товаров +5. Пополнение счёта при помощи QIWI (Библиотека SimpleQIWI) +6. Просмотр профилей других пользователей +7. Выдача средств, выдача уровней доступа. +8. Изменение данных о товаре +9. Удаление товаров +10. Список купленных товаров +11. Система тех.поддержки + +🎄 В будущем: +1. Ссылка для оплаты при попоплнении счёта + +Список будет дополняться и изменятся. + +📄 Список библиотек которые нужно установить: + +`pip install pyTelegramBotAPI` +`pip install SimpleQIWI` +`pip install requests` + +👾 Список команд: + +`/start` - Зарегистрироваться в боте + +`/profile` - Профиль + +`/donate` - Пополнить счёт + +`/buy` - Купить товар + +`/help` - Помощь по командам + +`/mybuy` - Список купленных товаров + +`/teh` - Связаться с тех.поддержкой + +🚨 Админские команды: + +`/getprofile` - Посмотреть чужой профиль + +`/getid` - Узнать ID пользователя + +`/getcid` - Узнать Conference ID + +`/access` - Выдать уровень доступа + +`/giverub` - Выдать средства пользователю + +`/addbuy` - Добавить товар + +`/getrazrab` - Выдать себе разработчика (читать установку) + +`/editbuy` - Изменить данные о товаре + +`/rembuy` - Удалить товар + +`/ot` - Ответить пользователю (отправить сообщение) + + +# 🛠 Установка бота: +1. Скачать файлы, затем распоковать папку в любое удобное место. +2. Открыть папку затем открыть файл `configure.py` +3. Найти бота `@BotFather` в телеграмм затем написать `/newbot` +4. Ввести имя бота, ник бота затем вылезет токен + +![image](https://user-images.githubusercontent.com/94528892/142181389-673cf369-2fe2-4c73-ae62-6e10ea822d38.png) + +5. Копируем токен, затем открываем файл `configure.py` + +![image](https://user-images.githubusercontent.com/94528892/142181704-064533e2-7af9-421e-b08e-82be42200835.png) + +У вас будет пустой файл в строку `'name'` нужно вписать имя бота пример: + +![image](https://user-images.githubusercontent.com/94528892/142181871-63a7cd9c-7428-44d3-b2d8-63b7952a10b2.png) + +В строку `'token'` нужно вписать токен который мы получили ранее: + +![image](https://user-images.githubusercontent.com/94528892/142182021-cd38d9e2-97ab-4eb6-a61e-41df4975f5a2.png) + +Далее переходим на сайт `qiwi.com/api` +Затем нажимаем на кнопку Выпустить новый токен + +![image](https://user-images.githubusercontent.com/94528892/142182251-166e666b-55c8-419c-b6ac-8b66d014f7c4.png) + +Выбираем все галочки, нажимаем на кнопку выбрать всё, затем нажимаем продолжить + +![image](https://user-images.githubusercontent.com/94528892/142182421-919cf712-94f9-4a31-8c86-e4b22465799d.png) + +Далее копируем токен и вставляем его в строку `'tokenqiwi'` + +![image](https://user-images.githubusercontent.com/94528892/142182632-4aba3460-54d9-45c0-a904-38ea00978d90.png) + +Далее в строку `'phoneqiwi'` вставляем свой номер телефона от QIWI кошелька + +![image](https://user-images.githubusercontent.com/94528892/142182904-01d53418-6e1a-4252-b288-f75dd17b5e55.png) + +Обязательно с + и кодом страны + +Отлично! Всё почти готово. + +Переходим по ссылке которая в сообщении с токеном бота + +![image](https://user-images.githubusercontent.com/94528892/142183064-5031e215-cc3d-4d41-8685-e92f4d3c87f2.png) + +Запускаем бота открывая файл `start.bat` + +Далее нажимаем кнопку `Начать` либо `/start` + +![image](https://user-images.githubusercontent.com/94528892/142184773-1c887e02-b542-40e1-ac62-fbcf3d567ea9.png) + +Изменить этот текст Вы сможете тут + +![image](https://user-images.githubusercontent.com/94528892/142184832-fd03b4b4-84f6-497c-99bb-2eafcc0bdf42.png) + +Весь исходный код находиться в файле `bot.py` + +Открываем файл `bot.py` затем ищем строку + +![image](https://user-images.githubusercontent.com/94528892/142184961-c7d2d0be-7815-4d35-bda2-351032b32e77.png) + +В чате с ботом пишем `/profile` + +![image](https://user-images.githubusercontent.com/94528892/142185194-f71a4acd-f48d-470f-aaaa-66c666a755b1.png) + +Копируем ваш ID затем вставялем в строку `WHERE id = 596060542` и `if message.from_user.id == 596060542:` ваш ID + +![image](https://user-images.githubusercontent.com/94528892/142185458-d0a034b3-0302-45d4-ba7d-11fe138025be.png) + +Затем сохраняем и перезапускаем бота. +В чат с ботом пишем `/getrazrab` и всё! Вы получили админку разработчика + +![image](https://user-images.githubusercontent.com/94528892/142185660-f9b67c5b-d8c7-4a21-97d9-ff4a95471398.png) + +![image](https://user-images.githubusercontent.com/94528892/142185690-eff4296c-ede4-4322-837d-7d9827655d68.png) + +Далее находим строку 223 'def donateyesoplacheno' + +![image](https://user-images.githubusercontent.com/94528892/142187463-7a46f327-857c-42a5-abf3-8017f774af5d.png) + +И вместо `596060542` вписываем свой ID + +Всё, бот полностью настроен! +Советую поставить бота на хостинг по типу: + +`eu.pythonanywhere.com` + +Либо на ВДС или на Выделенный сервер + +Бот делал лично Я с нуля, один. Просьба если будете его пересливать, дорабатывать и сливать то указывайте автора исходного кода. ❤ + +# 📞 Связь со мной: + +1. ВК: https://vk.com/nosemka +2. Дискорд: semmy#0068 diff --git a/doners/shopbot/bot.py b/doners/shopbot/bot.py new file mode 100644 index 0000000..afaabae --- /dev/null +++ b/doners/shopbot/bot.py @@ -0,0 +1,883 @@ +import telebot +import configure +import sqlite3 +from telebot import types +import threading +from requests import get +from time import sleep +from SimpleQIWI import * + +client = telebot.TeleBot(configure.config['token']) +db = sqlite3.connect('baza.db', check_same_thread=False) +sql = db.cursor() +lock = threading.Lock() +api = QApi(token=configure.config['tokenqiwi'], phone=configure.config['phoneqiwi']) +markdown = """ + *bold text* + _italic text_ + [text](URL) + """ + +#database + +sql.execute("""CREATE TABLE IF NOT EXISTS users (id BIGINT, nick TEXT, cash INT, access INT, bought INT)""") +sql.execute("""CREATE TABLE IF NOT EXISTS shop (id INT, name TEXT, price INT, tovar TEXT, whobuy TEXT)""") +db.commit() + +@client.message_handler(commands=['start']) +def start(message): + try: + getname = message.from_user.first_name + cid = message.chat.id + uid = message.from_user.id + + sql.execute(f"SELECT id FROM users WHERE id = {uid}") + if sql.fetchone() is None: + sql.execute(f"INSERT INTO users VALUES ({uid}, '{getname}', 0, 0, 0)") + client.send_message(cid, f"🛒 | Добро пожаловать, {getname}!\nТы попал в бота магазин\nИзмените этот текст!") + db.commit() + else: + client.send_message(cid, f"⛔️ | Ты уже зарегистрирован! Пропиши /help чтобы узнать команды.") + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['profile', 'myinfo', 'myprofile']) +def myprofile(message): + try: + cid = message.chat.id + uid = message.from_user.id + sql.execute(f"SELECT * FROM users WHERE id = {uid}") + getaccess = sql.fetchone()[3] + if getaccess == 0: + accessname = 'Пользователь' + elif getaccess == 1: + accessname = 'Администратор' + elif getaccess == 777: + accessname = 'Разработчик' + for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"): + client.send_message(cid, f"*📇 | Твой профиль:*\n\n*👤 | Ваш ID:* {info[0]}\n*💸 | Баланс:* {info[2]} ₽\n*👑 | Уровень доступа:* {accessname}\n*🛒 | Куплено товаров:* {info[4]}\n\n*🗂 Чтобы посмотреть список купленных товаров напишите /mybuy*", parse_mode='Markdown') + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['users']) +def allusers(message): + try: + cid = message.chat.id + uid = message.from_user.id + sql.execute(f"SELECT * FROM users WHERE id = {uid}") + getaccess = sql.fetchone()[3] + accessquery = 1 + if getaccess < accessquery: + client.send_message(cid, '⚠️ | У вас нет доступа!') + else: + text = '*🗃 | Список всех пользователей:*\n\n' + idusernumber = 0 + for info in sql.execute(f"SELECT * FROM users"): + if info[3] == 0: + accessname = 'Пользователь' + elif info[3] == 1: + accessname = 'Администратор' + elif info[3] == 777: + accessname = 'Разработчик' + idusernumber += 1 + text += f"*{idusernumber}. {info[0]} ({info[1]})*\n*💸 | Баланс:* {info[2]} ₽\n*👑 | Уровень доступа:* {accessname}\n*✉️ | Профиль:*" + f" [{info[1]}](tg://user?id="+str(info[0])+")\n\n" + client.send_message(cid, f"{text}",parse_mode='Markdown') + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['mybuy']) +def mybuy(message): + try: + cid = message.chat.id + uid = message.from_user.id + text = '*🗂 | Список купленных товаров:*\n\n' + for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"): + for infoshop in sql.execute(f"SELECT * FROM shop"): + if str(info[0]) in infoshop[4]: + text += f"*{infoshop[0]}. {infoshop[1]}*\nТовар: {infoshop[3]}\n\n" + client.send_message(cid,f"{text}",parse_mode='Markdown',disable_web_page_preview=True) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['getprofile', 'info']) +def getprofile(message): + try: + cid = message.chat.id + uid = message.from_user.id + sql.execute(f"SELECT * FROM users WHERE id = {uid}") + getaccess = sql.fetchone()[3] + accessquery = 1 + if getaccess < accessquery: + client.send_message(cid, '⚠️ | У вас нет доступа!') + else: + for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"): + msg = client.send_message(cid, f'Введите ID пользователя:\nПример: {info[0]}') + client.register_next_step_handler(msg, getprofile_next) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def getprofile_next(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + getprofileid = message.text + for info in sql.execute(f"SELECT * FROM users WHERE id = {getprofileid}"): + if info[3] == 0: + accessname = 'Пользователь' + elif info[3] == 1: + accessname = 'Администратор' + elif info[3] == 777: + accessname = 'Разработчик' + client.send_message(cid, f"*📇 | Профиль {info[1]}:*\n\n*ID пользователя:* {info[0]}\n*Баланс:* {info[2]} ₽\n*Уровень доступа:* {accessname}\n*Куплено товаров:* {info[4]}",parse_mode='Markdown') + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['editbuy']) +def editbuy(message): + try: + cid = message.chat.id + uid = message.from_user.id + accessquery = 1 + with lock: + sql.execute(f"SELECT * FROM users WHERE id = {uid}") + getaccess = sql.fetchone()[3] + if getaccess < 1: + client.send_message(cid, '⚠️ | У вас нет доступа!') + else: + rmk = types.InlineKeyboardMarkup() + item_name = types.InlineKeyboardButton(text='Название',callback_data='editbuyname') + item_price = types.InlineKeyboardButton(text='Цена',callback_data='editbuyprice') + item_tovar = types.InlineKeyboardButton(text='Товар',callback_data='editbuytovar') + rmk.add(item_name, item_price, item_tovar) + msg = client.send_message(cid, f"🔰 | Выберите что Вы хотите изменить:",reply_markup=rmk,parse_mode='Markdown') + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def editbuy_name(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global editbuynameidtovar + editbuynameidtovar = int(message.text) + msg = client.send_message(cid, f"*Введите новое название товара:*",parse_mode='Markdown') + client.register_next_step_handler(msg, editbuy_name_new_name) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def editbuy_name_new_name(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global editbuynametovar + editbuynametovar = message.text + for infoshop in sql.execute(f"SELECT * FROM shop WHERE id = {editbuynameidtovar}"): + rmk = types.InlineKeyboardMarkup() + item_yes = types.InlineKeyboardButton(text='✅', callback_data='editbuynewnametovaryes') + item_no = types.InlineKeyboardButton(text='❌', callback_data='editbuynewnametovarno') + rmk.add(item_yes, item_no) + msg = client.send_message(cid, f"*🔰 | Данные об изменении названия товара:*\n\nID товара: {editbuynameidtovar}\nСтарое имя товара: {infoshop[1]}\nНовое имя товара: {editbuynametovar}\n\nВы подверждаете изменения?",parse_mode='Markdown',reply_markup=rmk) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def editbuy_price(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global editbuypriceidtovar + editbuypriceidtovar = int(message.text) + msg = client.send_message(cid, f"*Введите новую цену товара:*",parse_mode='Markdown') + client.register_next_step_handler(msg, editbuy_price_new_price) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def editbuy_price_new_price(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global editbuypricetovar + editbuypricetovar = int(message.text) + for infoshop in sql.execute(f"SELECT * FROM shop WHERE id = {editbuypriceidtovar}"): + rmk = types.InlineKeyboardMarkup() + item_yes = types.InlineKeyboardButton(text='✅', callback_data='editbuynewpricetovaryes') + item_no = types.InlineKeyboardButton(text='❌', callback_data='editbuynewpricetovarno') + rmk.add(item_yes, item_no) + msg = client.send_message(cid, f"*🔰 | Данные об изменении цены товара:*\n\nID товара: {editbuypriceidtovar}\nСтарая цена: {infoshop[2]}\nНовая цена: {editbuypricetovar}\n\nВы подверждаете изменения?",parse_mode='Markdown',reply_markup=rmk) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def editbuy_tovar(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global editbuytovaridtovar + editbuytovaridtovar = int(message.text) + msg = client.send_message(cid, f"*Введите новую ссылку на товар:*",parse_mode='Markdown') + client.register_next_step_handler(msg, editbuy_tovar_new_tovar) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def editbuy_tovar_new_tovar(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global editbuytovartovar + editbuytovartovar = message.text + for infoshop in sql.execute(f"SELECT * FROM shop WHERE id = {editbuytovaridtovar}"): + rmk = types.InlineKeyboardMarkup() + item_yes = types.InlineKeyboardButton(text='✅', callback_data='editbuynewtovartovaryes') + item_no = types.InlineKeyboardButton(text='❌', callback_data='editbuynewtovartovarno') + rmk.add(item_yes, item_no) + msg = client.send_message(cid, f"*🔰 | Данные об изменении сcылки товара:*\n\nID товара: {editbuytovaridtovar}\nСтарая ссылка: {infoshop[3]}\nНовая ссылка: {editbuytovartovar}\n\nВы подверждаете изменения?",parse_mode='Markdown',reply_markup=rmk) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.callback_query_handler(lambda call: call.data == 'editbuynewtovartovaryes' or call.data == 'editbuynewtovartovarno') +def editbuy_tovar_new_callback(call): + try: + if call.data == 'editbuynewtovartovaryes': + sql.execute(f"SELECT * FROM shop WHERE id = {editbuytovaridtovar}") + sql.execute(f"UPDATE shop SET tovar = '{editbuytovartovar}' WHERE id = {editbuytovaridtovar}") + db.commit() + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"✅ | Вы успешно изменили ссылку на товар на {editbuytovartovar}") + elif call.data == 'editbuynewtovartovarno': + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"🚫 | Вы отменили изменение сcылки товара") + client.answer_callback_query(callback_query_id=call.id) + except: + client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды') + +@client.callback_query_handler(lambda call: call.data == 'editbuynewpricetovaryes' or call.data == 'editbuynewpricetovarno') +def editbuy_price_new_callback(call): + try: + if call.data == 'editbuynewpricetovaryes': + sql.execute(f"SELECT * FROM shop WHERE id = {editbuypriceidtovar}") + sql.execute(f"UPDATE shop SET price = {editbuypricetovar} WHERE id = {editbuypriceidtovar}") + db.commit() + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"✅ | Вы успешно изменили цену товара на {editbuypricetovar}") + elif call.data == 'editbuynewpricetovarno': + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"🚫 | Вы отменили изменение цены товара") + client.answer_callback_query(callback_query_id=call.id) + except: + client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды') +@client.callback_query_handler(lambda call: call.data == 'editbuynewnametovaryes' or call.data == 'editbuynewnametovarno') +def editbuy_name_new_callback(call): + try: + if call.data == 'editbuynewnametovaryes': + sql.execute(f"SELECT * FROM shop WHERE id = {editbuynameidtovar}") + sql.execute(f"UPDATE shop SET name = '{editbuynametovar}' WHERE id = {editbuynameidtovar}") + db.commit() + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"✅ | Вы успешно изменили название товара на {editbuynametovar}") + elif call.data == 'editbuynewnametovarno': + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"🚫 | Вы отменили изменение названия товара") + client.answer_callback_query(callback_query_id=call.id) + except: + client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды') + + +@client.callback_query_handler(lambda call: call.data == 'editbuyname' or call.data == 'editbuyprice' or call.data == 'editbuytovar') +def editbuy_first_callback(call): + try: + if call.data == 'editbuyname': + msg = client.send_message(call.message.chat.id, f"*Введите ID товара которому хотите изменить название:*",parse_mode='Markdown') + client.register_next_step_handler(msg, editbuy_name) + elif call.data == 'editbuyprice': + msg = client.send_message(call.message.chat.id, f"*Введите ID товара которому хотите изменить цену:*",parse_mode='Markdown') + client.register_next_step_handler(msg, editbuy_price) + elif call.data == 'editbuytovar': + msg = client.send_message(call.message.chat.id, f"*Введите ID товара которому хотите изменить ссылку:*",parse_mode='Markdown') + client.register_next_step_handler(msg, editbuy_tovar) + client.answer_callback_query(callback_query_id=call.id) + except: + client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['rembuy']) +def removebuy(message): + try: + cid = message.chat.id + uid = message.from_user.id + accessquery = 1 + with lock: + sql.execute(f"SELECT * FROM users WHERE id = {uid}") + getaccess = sql.fetchone()[3] + if getaccess < 1: + client.send_message(cid, '⚠️ | У вас нет доступа!') + else: + msg = client.send_message(cid, f"*Введите ID товара который хотите удалить:*",parse_mode='Markdown') + client.register_next_step_handler(msg, removebuy_next) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def removebuy_next(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global removeidtovar + removeidtovar = int(message.text) + for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"): + for infoshop in sql.execute(f"SELECT * FROM shop WHERE id = {removeidtovar}"): + rmk = types.InlineKeyboardMarkup() + item_yes = types.InlineKeyboardButton(text='✅',callback_data='removebuytovaryes') + item_no = types.InlineKeyboardButton(text='❌',callback_data='removebuytovarno') + rmk.add(item_yes, item_no) + msg = client.send_message(cid, f"🔰 | Данные об удалении:\n\nID товара: {infoshop[0]}\nИмя товара: {infoshop[1]}\nЦена товара: {infoshop[2]}\nТовар: {infoshop[3]}\n\nВы действительно хотите удалить товар? Отменить действие будет НЕВОЗМОЖНО.",reply_markup=rmk) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.callback_query_handler(lambda call: call.data == 'removebuytovaryes' or call.data == 'removebuytovarno') +def removebuy_callback(call): + try: + if call.data == 'removebuytovaryes': + sql.execute(f"SELECT * FROM shop") + sql.execute(f"DELETE FROM shop WHERE id = {removeidtovar}") + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"✅ | Вы успешно удалили товар") + db.commit() + elif call.data == 'removebuytovarno': + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"🚫 | Вы отменили удаление товара") + client.answer_callback_query(callback_query_id=call.id) + except: + client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['addbuy']) +def addbuy(message): + try: + cid = message.chat.id + uid = message.from_user.id + with lock: + sql.execute(f"SELECT * FROM users WHERE id = {uid}") + getaccess = sql.fetchone()[3] + if getaccess < 1: + client.send_message(cid, '⚠️ | У вас нет доступа!') + else: + msg = client.send_message(cid, '*Введите ID товара:*',parse_mode='Markdown') + client.register_next_step_handler(msg, addbuy_id) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def addbuy_id(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global addbuyid + addbuyid = message.text + msg = client.send_message(cid, '*Введите цену товара:*',parse_mode='Markdown') + client.register_next_step_handler(msg, addbuy_price) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def addbuy_price(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global addbuyprice + addbuyprice = message.text + msg = client.send_message(cid, '*Введите название товара:*',parse_mode='Markdown') + client.register_next_step_handler(msg, addbuy_name) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def addbuy_name(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global addbuyname + addbuyname = message.text + msg = client.send_message(cid, '*Введите ссылку на товар:*',parse_mode='Markdown') + client.register_next_step_handler(msg, addbuy_result) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def addbuy_result(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global addbuytovar + addbuytovar = message.text + sql.execute(f"SELECT name FROM shop WHERE name = '{addbuyname}'") + if sql.fetchone() is None: + sql.execute(f"INSERT INTO shop VALUES ({addbuyid}, '{addbuyname}', {addbuyprice}, '{addbuytovar}', '')") + db.commit() + sql.execute(f"SELECT * FROM shop WHERE name = '{addbuyname}'") + client.send_message(cid, f'✅ | Вы успешно добавили товар\nID товара: {sql.fetchone()[0]}\nИмя: {addbuyname}\nЦена: {addbuyprice}\nСсылка на товар: {addbuytovar}') + else: + client.send_message(cid, f"⛔️ | Данный товар уже добавлен!") + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['buy']) +def buy(message): + try: + cid = message.chat.id + uid = message.from_user.id + + text = '🛒 | *Список товаров*\n\n' + for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"): + for infoshop in sql.execute(f"SELECT * FROM shop"): + text += f"{infoshop[0]}. {infoshop[1]}\nЦена: {infoshop[2]}\n\n" + rmk = types.InlineKeyboardMarkup() + item_yes = types.InlineKeyboardButton(text='✅', callback_data='firstbuytovaryes') + item_no = types.InlineKeyboardButton(text='❌', callback_data='firstbuytovarno') + rmk.add(item_yes, item_no) + msg = client.send_message(cid, f'{text}*Вы хотите перейти к покупке товара?*',parse_mode='Markdown',reply_markup=rmk) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def buy_next(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global tovarid + tovarid = int(message.text) + for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"): + for infoshop in sql.execute(f"SELECT * FROM shop WHERE id = {tovarid}"): + if info[2] < infoshop[2]: + client.send_message(cid, '⚠️ | У вас недостаточно средств для приобретения товара!\n\nЧтобы пополнить счёт напишите /donate') + else: + rmk = types.InlineKeyboardMarkup() + item_yes = types.InlineKeyboardButton(text='✅',callback_data='buytovaryes') + item_no = types.InlineKeyboardButton(text='❌',callback_data='buytovarno') + rmk.add(item_yes, item_no) + msg = client.send_message(cid, f"💸 | Вы подверждаете покупку товара?\n\nВернуть средства за данный товар НЕВОЗМОЖНО.",reply_markup=rmk) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.callback_query_handler(lambda call: call.data == 'firstbuytovaryes' or call.data == 'firstbuytovarno') +def firstbuy_callback(call): + try: + if call.data == 'firstbuytovaryes': + msg = client.send_message(call.message.chat.id, f"*Введите ID товара который хотите купить:*",parse_mode='Markdown') + client.register_next_step_handler(msg, buy_next) + elif call.data == 'firstbuytovarno': + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"🚫 | Вы отменили покупку товара") + client.answer_callback_query(callback_query_id=call.id) + except: + client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды') + +@client.callback_query_handler(lambda call: call.data == 'buytovaryes' or call.data == 'buytovarno') +def buy_callback(call): + try: + if call.data == 'buytovaryes': + for info in sql.execute(f"SELECT * FROM users WHERE id = {call.from_user.id}"): + for infoshop in sql.execute(f"SELECT * FROM shop WHERE id = {tovarid}"): + if str(info[0]) not in infoshop[4]: + cashtovar = int(info[2] - infoshop[2]) + boughttovar = int(info[4] + 1) + whobuytovarinttostr = str(info[0]) + whobuytovar = str(infoshop[4] + whobuytovarinttostr + ',') + sql.execute(f"SELECT * FROM users WHERE id = {call.from_user.id}") + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"✅ | Вы успешно купили товар\n\nНазвание товара: {infoshop[1]}\nЦена: {infoshop[2]}\n\nТовар: {infoshop[3]}\n\nСпасибо за покупку!") + sql.execute(f"UPDATE users SET cash = {cashtovar} WHERE id = {call.from_user.id}") + sql.execute(f"UPDATE users SET bought = {boughttovar} WHERE id = {call.from_user.id}") + sql.execute(f"SELECT * FROM shop WHERE id = {tovarid}") + sql.execute(f"UPDATE shop SET whobuy = '{whobuytovar}' WHERE id = {tovarid}") + db.commit() + else: + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"*⛔️ | Данный товар уже куплен!*\n\nЧтобы посмотреть список купленных товаров напишите /mybuy",parse_mode='Markdown') + elif call.data == 'buytovarno': + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"❌ | Вы отменили покупку товара!") + client.answer_callback_query(callback_query_id=call.id) + except: + client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['donate']) +def donate(message): + try: + cid = message.chat.id + global uid + uid = message.from_user.id + msg = client.send_message(cid, f"*💰 | Введите сумму для пополнения:*",parse_mode='Markdown') + client.register_next_step_handler(msg, donate_value) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def donate_value(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global donatevalue + global commentdonate + global getusername + global getuserdonateid + getusername = message.from_user.first_name + getuserdonateid = message.from_user.id + sql.execute(f"SELECT * FROM users WHERE id = {uid}") + commentdonate = sql.fetchone()[0] + donatevalue = int(message.text) + rmk = types.InlineKeyboardMarkup() + item_yes = types.InlineKeyboardButton(text='✅',callback_data='donateyes') + item_no = types.InlineKeyboardButton(text='❌',callback_data='donateno') + rmk.add(item_yes, item_no) + global qiwibalancebe + qiwibalancebe = api.balance + msg = client.send_message(cid, f"🔰 | Заявка на пополнение средств успешно создана\n\nВы действительно хотите пополнить средства?",parse_mode='Markdown',reply_markup=rmk) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def donateyesoplacheno(message): + try: + cid = message.chat.id + uid = message.from_user.id + removekeyboard = types.ReplyKeyboardRemove() + if message.text == '✅ Оплачено': + client.send_message(cid, f"✉️ | Ваш запрос отправлен администраторам, ожидайте одобрения и выдачи средств.",reply_markup=removekeyboard) + client.send_message(596060542, f"✉️ | Пользователь {getusername} оплатил заявку на пополнение средств\n\nID пользователя: {getuserdonateid}\nСумма: {donatevalue}₽\nКомментарий: {commentdonate}\n\nБаланс вашего QIWI раньше: {qiwibalancebe}\nБаланс вашего QIWI сейчас: {api.balance}\n\nПерепроверьте верность оплаты затем подтвердите выдачу средств.\nДля выдачи средств напишите: /giverub") + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.callback_query_handler(lambda call: call.data == 'donateyes' or call.data == 'donateno') +def donate_result(call): + try: + removekeyboard = types.ReplyKeyboardRemove() + rmk = types.ReplyKeyboardMarkup(resize_keyboard=True) + rmk.add(types.KeyboardButton('✅ Оплачено')) + if call.data == 'donateyes': + client.delete_message(call.message.chat.id, call.message.message_id-0) + msg = client.send_message(call.message.chat.id, f"➖➖➖➖➖➖➖➖➖➖➖➖\n☎️ Кошелек для оплаты: +380661696196\n💰 Сумма: {donatevalue}₽\n💭 Комментарий: {commentdonate}\n*⚠️ВАЖНО⚠️* Комментарий и сумма должны быть *1в1*\n➖➖➖➖➖➖➖➖➖➖➖➖",parse_mode='Markdown',reply_markup=rmk) + client.register_next_step_handler(msg, donateyesoplacheno) + elif call.data == 'donateno': + client.send_message(call.message.chat.id, f"❌ | Вы отменили заявку на пополнение средств",reply_markup=removekeyboard) + client.answer_callback_query(callback_query_id=call.id) + except: + client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['getcid']) +def getcid(message): + client.send_message(message.chat.id, f"ID чата | {message.chat.id}\nТвой ID | {message.from_user.id}") + +@client.message_handler(commands=['help']) +def helpcmd(message): + cid = message.chat.id + uid = message.from_user.id + with lock: + sql.execute(f"SELECT * FROM users WHERE id = {uid}") + getaccess = sql.fetchone()[3] + if getaccess >= 1: + client.send_message(cid, '*Помощь по командам:*\n\n/profile - Посмотреть свой профиль\n/help - Посмотреть список команд\n/buy - Купить товар\n/donate - Пополнить счёт\n/mybuy - Посмотреть список купленных товаров\n/teh - Связаться с тех.поддержкой\n\nАдмин-команды:\n\n/getprofile - Посмотреть чужой профиль\n/access - Выдать уровень доступа\n/giverub - Выдать деньги на баланс\n/getid - Узнать айди пользователя\n/getcid - Узнать Conference ID\n/addbuy - Добавить товар на продажу\n/editbuy - Изменить данные о товаре\n/rembuy - Удалить товар\n/ot - Ответить пользователю (отправить сообщение)',parse_mode='Markdown') + else: + client.send_message(cid, '*Помощь по командам:*\n\n/profile - Посмотреть свой профиль\n/help - Посмотреть список команд\n/buy - Купить товар\n/donate - Пополнить счёт\n/mybuy - Посмотреть список купленных товаров\n/teh - Связаться с тех.поддержкой',parse_mode='Markdown') + +@client.message_handler(commands=['access', 'setaccess', 'dostup']) +def setaccess(message): + try: + cid = message.chat.id + uid = message.from_user.id + sql.execute(f"SELECT * FROM users WHERE id = {uid}") + getaccess = sql.fetchone()[3] + accessquery = 777 + if getaccess < accessquery: + client.send_message(cid, f"⚠️ | У вас нет доступа!") + else: + for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"): + msg = client.send_message(cid, 'Введите ID пользователя:\nПример: 596060542', parse_mode="Markdown") + client.register_next_step_handler(msg, access_user_id_answer) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') +def access_user_id_answer(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global usridaccess + usridaccess = message.text + rmk = types.ReplyKeyboardMarkup(resize_keyboard=True) + rmk.add(types.KeyboardButton('Пользователь'), types.KeyboardButton('Администратор'), types.KeyboardButton('Разработчик')) + msg = client.send_message(cid, 'Какой уровень доступа Вы хотите выдать?:', reply_markup=rmk, parse_mode="Markdown") + client.register_next_step_handler(msg, access_user_access_answer) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def access_user_access_answer(message): + try: + global accessgaved + global accessgavedname + cid = message.chat.id + uid = message.from_user.id + rmk = types.InlineKeyboardMarkup() + access_yes = types.InlineKeyboardButton(text='✅',callback_data='setaccessyes') + access_no = types.InlineKeyboardButton(text='❌',callback_data='setaccessno') + rmk.add(access_yes, access_no) + for info in sql.execute(f"SELECT * FROM users WHERE id = {usridaccess}"): + if message.text == "Пользователь": + accessgavedname = "Пользователь" + accessgaved = 0 + elif message.text == "Администратор": + accessgavedname = "Администратор" + accessgaved = 1 + elif message.text == "Разработчик": + accessgavedname = "Разработчик" + accessgaved = 777 + + client.send_message(cid, f'Данные для выдачи:\nID пользователя: {usridaccess} ({info[1]})\nУровень доступа: {message.text}\n\nВерно?', reply_markup=rmk) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.callback_query_handler(lambda call: call.data == 'setaccessyes' or call.data == 'setaccessno') +def access_user_gave_access(call): + try: + removekeyboard = types.ReplyKeyboardRemove() + if call.data == 'setaccessyes': + for info in sql.execute(f"SELECT * FROM users WHERE id = {usridaccess}"): + sql.execute(f"UPDATE users SET access = {accessgaved} WHERE id = {usridaccess}") + db.commit() + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f'✅ | Пользователю {info[1]} выдан уровень доступа {accessgavedname}', reply_markup=removekeyboard) + elif call.data == 'setaccessno': + for info in sql.execute(f"SELECT * FROM users WHERE id = {usridaccess}"): + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f'🚫 | Вы отменили выдачу уровня доступа {accessgavedname} пользователю {info[1]}', reply_markup=removekeyboard) + client.answer_callback_query(callback_query_id=call.id) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['getrazrab']) +def getrazrabotchik(message): + if message.from_user.id == 596060542: + sql.execute(f"UPDATE users SET access = 777 WHERE id = 596060542") + client.send_message(message.chat.id, f"✅ | Вы выдали себе Разработчика") + db.commit() + else: + client.send_message(message.chat.id, f"⛔️ | Отказано в доступе!") + +@client.message_handler(commands=['giverub', 'givedonate', 'givebal']) +def giverubles(message): + try: + cid = message.chat.id + uid = message.from_user.id + sql.execute(f"SELECT * FROM users WHERE id = {uid}") + getaccess = sql.fetchone()[3] + accessquery = 777 + if getaccess < accessquery: + client.send_message(cid, f"⚠️ | У вас нет доступа!") + else: + for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"): + msg = client.send_message(cid, 'Введите ID пользователя:\nПример: 596060542', parse_mode="Markdown") + client.register_next_step_handler(msg, rubles_user_id_answer) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def rubles_user_id_answer(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global usridrubles + usridrubles = message.text + rmk = types.ReplyKeyboardMarkup(resize_keyboard=True) + rmk.add(types.KeyboardButton('10'), types.KeyboardButton('100'), types.KeyboardButton('1000'), types.KeyboardButton('Другая сумма')) + msg = client.send_message(cid, 'Выберите сумму для выдачи:', reply_markup=rmk, parse_mode="Markdown") + client.register_next_step_handler(msg, rubles_user_rubles_answer) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def rubles_user_rubles_answer(message): + try: + cid = message.chat.id + uid = message.from_user.id + global rublesgavedvalue + removekeyboard = types.ReplyKeyboardRemove() + rmk = types.InlineKeyboardMarkup() + access_yes = types.InlineKeyboardButton(text='✅',callback_data='giverublesyes') + access_no = types.InlineKeyboardButton(text='❌',callback_data='giverublesno') + rmk.add(access_yes, access_no) + for info in sql.execute(f"SELECT * FROM users WHERE id = {usridrubles}"): + if message.text == '10': + rublesgavedvalue = 10 + client.send_message(cid, f'Данные для выдачи:\nID пользователя: {usridrubles} ({info[1]})\nСумма: {rublesgavedvalue}\n\nВерно?',reply_markup=rmk) + elif message.text == '100': + rublesgavedvalue = 100 + client.send_message(cid, f'Данные для выдачи:\nID пользователя: {usridrubles} ({info[1]})\nСумма: {rublesgavedvalue}\n\nВерно?',reply_markup=rmk) + elif message.text == '1000': + rublesgavedvalue = 1000 + client.send_message(cid, f'Данные для выдачи:\nID пользователя: {usridrubles} ({info[1]})\nСумма: {rublesgavedvalue}\n\nВерно?',reply_markup=rmk) + elif message.text == 'Другая сумма': + msg = client.send_message(cid, f"*Введите сумму для выдачи:*",parse_mode='Markdown',reply_markup=removekeyboard) + client.register_next_step_handler(msg, rubles_user_rubles_answer_other) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def rubles_user_rubles_answer_other(message): + try: + cid = message.chat.id + uid = message.from_user.id + global rublesgavedvalue + rmk = types.InlineKeyboardMarkup() + access_yes = types.InlineKeyboardButton(text='✅',callback_data='giverublesyes') + access_no = types.InlineKeyboardButton(text='❌',callback_data='giverublesno') + rmk.add(access_yes, access_no) + for info in sql.execute(f"SELECT * FROM users WHERE id = {usridrubles}"): + if message.text == message.text: + rublesgavedvalue = int(message.text) + client.send_message(cid, f'Данные для выдачи:\nID пользователя: {usridrubles} ({info[1]})\nСумма: {rublesgavedvalue}\n\nВерно?',reply_markup=rmk) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.callback_query_handler(func=lambda call: call.data == 'giverublesyes' or call.data == 'giverublesno') +def rubles_gave_rubles_user(call): + try: + removekeyboard = types.ReplyKeyboardRemove() + for info in sql.execute(f"SELECT * FROM users WHERE id = {usridrubles}"): + rubless = int(info[2] + rublesgavedvalue) + if call.data == 'giverublesyes': + for info in sql.execute(f"SELECT * FROM users WHERE id = {usridrubles}"): + sql.execute(f"UPDATE users SET cash = {rubless} WHERE id = {usridrubles}") + db.commit() + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f'✅ | Пользователю {info[1]} выдано {rublesgavedvalue} рублей', reply_markup=removekeyboard) + elif call.data == 'giverublesno': + for info in sql.execute(f"SELECT * FROM users WHERE id = {usridrubles}"): + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f'🚫 | Вы отменили выдачу рублей пользователю {info[1]}', reply_markup=removekeyboard) + client.answer_callback_query(callback_query_id=call.id) + except: + client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['teh']) +def teh(message): + try: + cid = message.chat.id + uid = message.from_user.id + msg = client.send_message(cid, f"*📨 | Введите текст который хотите отправить тех.поддержке*",parse_mode='Markdown') + client.register_next_step_handler(msg, teh_next) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def teh_next(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + global tehtextbyuser + global tehnamebyuser + global tehidbyuser + tehidbyuser = int(message.from_user.id) + tehnamebyuser = str(message.from_user.first_name) + tehtextbyuser = str(message.text) + rmk = types.InlineKeyboardMarkup() + item_yes = types.InlineKeyboardButton(text='✉️',callback_data='tehsend') + item_no = types.InlineKeyboardButton(text='❌',callback_data='tehno') + rmk.add(item_yes, item_no) + msg = client.send_message(cid, f"✉️ | Данные об отправке:\n\nТекст для отправки: {tehtextbyuser}\n\nВы действительно хотите отправить это тех.поддержке?",parse_mode='Markdown',reply_markup=rmk) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.callback_query_handler(func=lambda call: call.data == 'tehsend' or call.data == 'tehno') +def teh_callback(call): + try: + if call.data == 'tehsend': + for info in sql.execute(f"SELECT * FROM users WHERE id = {call.from_user.id}"): + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"✉️ | Ваше сообщение отправлено тех.поддержке, ожидайте ответа.") + client.send_message(596060542, f"✉️ | Пользователь {tehnamebyuser} отправил сообщение в тех.поддержку\n\nID пользователя: {tehidbyuser}\nТекст: {tehtextbyuser}\n\nЧтобы ответить пользователю напишите /ot") + elif call.data == 'tehno': + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"🚫 | Вы отменили отправку сообщения тех.поддержке") + client.answer_callback_query(callback_query_id=call.id) + except: + client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['ot']) +def sendmsgtouser(message): + try: + cid = message.chat.id + + msg = client.send_message(cid, f"👤 | Введите ID пользователя которому хотите отправить сообщение:") + client.register_next_step_handler(msg, sendmsgtouser_next) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def sendmsgtouser_next(message): + try: + cid = message.chat.id + + if message.text == message.text: + global getsendmsgtouserid + getsendmsgtouserid = int(message.text) + msg = client.send_message(cid, f"📨 | Введите текст который хотите отправить пользователю:") + client.register_next_step_handler(msg, sendmsgtouser_next_text) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def sendmsgtouser_next_text(message): + try: + cid = message.chat.id + + if message.text == message.text: + global getsendmsgtousertext + getsendmsgtousertext = str(message.text) + rmk = types.InlineKeyboardMarkup() + item_yes = types.InlineKeyboardButton(text='✅',callback_data='sendmsgtouseryes') + item_no = types.InlineKeyboardButton(text='❌',callback_data='sendmsgtouserno') + rmk.add(item_yes, item_no) + msg = client.send_message(cid, f"🔰 | Данные об отправке сообщения:\n\nID пользователя: {getsendmsgtouserid}\nТекст для отправки: {getsendmsgtousertext}\n\nОтправить сообщение?",reply_markup=rmk) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +@client.callback_query_handler(func=lambda call: call.data == 'sendmsgtouseryes' or call.data == 'sendmsgtouserno') +def sendmsgtouser_callback(call): + try: + if call.data == 'sendmsgtouseryes': + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"✉️ | Сообщение отправлено!") + client.send_message(getsendmsgtouserid, f"✉️ | Администратор прислал вам сообщение:\n\n{getsendmsgtousertext}") + elif call.data == 'sendmsgtouserno': + client.delete_message(call.message.chat.id, call.message.message_id-0) + client.send_message(call.message.chat.id, f"🚫 | Вы отменили отправку сообщения пользователю") + client.answer_callback_query(callback_query_id=call.id) + except: + client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды') + +@client.message_handler(commands=['getid']) +def getiduser(message): + try: + cid = message.chat.id + uid = message.from_user.id + sql.execute(f"SELECT * FROM users WHERE id = {uid}") + getaccess = sql.fetchone()[3] + accessquery = 1 + if getaccess < accessquery: + client.send_message(cid, f"⚠️ | У вас нет доступа!") + else: + msg = client.send_message(cid, 'Введите никнейм пользователя:') + client.register_next_step_handler(msg, next_getiduser_name) + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + +def next_getiduser_name(message): + try: + cid = message.chat.id + uid = message.from_user.id + if message.text == message.text: + getusername = message.text + sql.execute(f"SELECT * FROM users WHERE nick = '{getusername}'") + result = sql.fetchone()[0] + client.send_message(cid, f'👤 | ID пользователя: {result}') + except: + client.send_message(cid, f'🚫 | Ошибка при выполнении команды') + + + +client.polling(none_stop=True,interval=0) \ No newline at end of file diff --git a/doners/shopbot/configure.py b/doners/shopbot/configure.py new file mode 100644 index 0000000..3c9a57c --- /dev/null +++ b/doners/shopbot/configure.py @@ -0,0 +1,6 @@ +config = { + 'name': '', + 'token': '', + 'tokenqiwi': '', + 'phoneqiwi': '' +} \ No newline at end of file diff --git a/doners/telegram-shop-bot-template/.gitattributes b/doners/telegram-shop-bot-template/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/doners/telegram-shop-bot-template/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/doners/telegram-shop-bot-template/.gitignore b/doners/telegram-shop-bot-template/.gitignore new file mode 100644 index 0000000..d9005f2 --- /dev/null +++ b/doners/telegram-shop-bot-template/.gitignore @@ -0,0 +1,152 @@ +# 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/ +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 +*.spec + +# 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 +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# 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 +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .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 + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# 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/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/doners/telegram-shop-bot-template/LICENSE b/doners/telegram-shop-bot-template/LICENSE new file mode 100644 index 0000000..1352d4d --- /dev/null +++ b/doners/telegram-shop-bot-template/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 ssayand + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/doners/telegram-shop-bot-template/README.md b/doners/telegram-shop-bot-template/README.md new file mode 100644 index 0000000..eafa8b0 --- /dev/null +++ b/doners/telegram-shop-bot-template/README.md @@ -0,0 +1,47 @@ +i dont like this project tbh :\ + +# Telegram shop bot template +![licence](https://img.shields.io/badge/License-MIT-green.svg) +![version](https://img.shields.io/badge/Version-v1.3_beta-blue) +[![codecov](https://codecov.io/gh/anekobtw/telegram-shop-bot/graph/badge.svg?token=TXQWSC0UR9)](https://codecov.io/gh/anekobtw/telegram-shop-bot-template) +![made with love](https://img.shields.io/badge/Made_with-Love-red) + +Customizable telegram shop bot + +## Installing +Clone the project to your local machine. +```console +$ git clone https://github.com/anekobtw/telegram-shop-bot-template.git +``` + +## Setting up +Do not modify any files except `config.py`. Doing so may result in numerous bugs :) + +- Replace `BOT_TOKEN` with your Telegram bot token (Obtain it from [BotFather](https://web.telegram.org/k/#@BotFather)). +```python +BOT_TOKEN = '' #replace with your token +``` + +- Change the currency you're using for selling items. (optional) +```python +currency = '$' +``` + +- Furthermore, add the products you're selling into the dictionary using the following format:\ +`'product name' - price` + +```python +items = { + 'Item1': 100, + 'Item2': 200, + 'Item3': 500 +} +``` + +Remember to restart the bot to apply the changes. + +## Acknowledgements + - [aiogram](https://github.com/aiogram/aiogram) - A modern and fully asynchronous framework for Telegram Bot API written in Python using asyncio + +## Contributing +Contributions are always welcome! If you have any suggestions, feature requests, or bug reports, please feel free to open an issue on the [GitHub repository](https://github.com/anekobtw/timewise). diff --git a/doners/telegram-shop-bot-template/src/config.py b/doners/telegram-shop-bot-template/src/config.py new file mode 100644 index 0000000..b93f274 --- /dev/null +++ b/doners/telegram-shop-bot-template/src/config.py @@ -0,0 +1,17 @@ +# Insert here your bot token here +BOT_TOKEN = "" + +# Enter here the shop owners' telegram ids (@getmyid_bot may help you) +# You may add as much as you can, but I recommend not to add too much. +admins = {1718021890} + +currency = "$" + +items = { + "tissue box": 10, + "magnet": 20, + "table": 50, + "glow stick": 30, + "bottle cap": 20, + "toothpaste": 15 + } diff --git a/doners/telegram-shop-bot-template/src/database.py b/doners/telegram-shop-bot-template/src/database.py new file mode 100644 index 0000000..aba44d8 --- /dev/null +++ b/doners/telegram-shop-bot-template/src/database.py @@ -0,0 +1,77 @@ +import sqlite3 +import time +from datetime import datetime + + +class Order: + def __init__(self, order_id, tgid, tg_nickname, product, is_open, timestamp) -> None: + """Initialize an order object with provided attributes""" + self.order_id = order_id + self.tgid = tgid + self.tg_nickname = tg_nickname + self.product = product + self.is_open = is_open + self.timestamp = timestamp + + @property + def formatted_timestamp(self) -> str: + """Return the timestamp in a formatted string (YYYY-MM-DD HH:MM:SS)""" + return datetime.fromtimestamp(self.timestamp).strftime("%Y-%m-%d %H:%M:%S") + + def all_data(self) -> str: + return f"""Order ID: {self.order_id} +Telegram ID: {self.tgid} +Telegram Nickname: {self.tg_nickname} +Product: {self.product} +Date and Time of Purchase: {self.formatted_timestamp} + +""" + + +class OrderManager: + DATABASE_FILE = "database.db" + TIMEZONE_OFFSET = 0 # Assuming offset in seconds + + def __init__(self) -> None: + self.connection = sqlite3.connect(self.DATABASE_FILE) + self.cursor = self.connection.cursor() + + self.cursor.execute( + """CREATE TABLE IF NOT EXISTS orders ( + order_id INTEGER PRIMARY KEY AUTOINCREMENT, + tgid INT, + tg_nickname TEXT, + product TEXT, + is_open INTEGER, + timestamp DATETIME + ) + """ + ) + self.connection.commit() + + def get_active_orders(self) -> list: + """Retrieve all orders that are not completed from the database""" + self.cursor.execute("SELECT * FROM orders WHERE is_open = 1") + return self.cursor.fetchall() + + def get_user_orders(self, tgid: int) -> list: + """Get the information about all user's orders""" + self.cursor.execute("SELECT * FROM orders WHERE tgid = ?", (tgid,)) + return self.cursor.fetchall() + + def get_order(self, order_id: int) -> int: + """Get the order info""" + self.cursor.execute("SELECT * FROM orders WHERE order_id = ?", (order_id,)) + return self.cursor.fetchone() + + def insert_order(self, tgid: int, tg_nickname: str, product: str) -> None: + """Insert a new order into the database with the provided details""" + timestamp = round(time.time()) + self.TIMEZONE_OFFSET + values = (tgid, tg_nickname, product, 1, timestamp) + self.cursor.execute("INSERT INTO orders(tgid, tg_nickname, product, is_open, timestamp) VALUES (?, ?, ?, ?, ?)", values) + self.connection.commit() + + def delete_order(self, order_id: int) -> None: + """Delete the order""" + self.cursor.execute("DELETE FROM orders WHERE order_id = ?", (order_id,)) + self.connection.commit() diff --git a/doners/telegram-shop-bot-template/src/main.py b/doners/telegram-shop-bot-template/src/main.py new file mode 100644 index 0000000..e6b35ff --- /dev/null +++ b/doners/telegram-shop-bot-template/src/main.py @@ -0,0 +1,124 @@ +import logging + +import config +import database +from aiogram import Bot, Dispatcher, types +from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup +from aiogram.utils import executor +from aiogram.utils.callback_data import CallbackData + +# <<< Constants >>> +admins = config.admins +order_manager = database.OrderManager() +order_cb = CallbackData("order", "action", "item") + + +# <<< Initializing >>> +bot = Bot(token=config.BOT_TOKEN) +dp = Dispatcher(bot) +logging.basicConfig(level=logging.INFO, filename="log.txt") + + +async def notify_admins_order(order_id: int) -> None: + for admin_id in admins: + await bot.send_message(chat_id=admin_id, text=f"❗ Check out a new order!\nOrder id: {order_id}\n") + + +# <<< Inline start buttons >>> +@dp.message_handler(commands=["start"]) +async def start(message: types.Message): + keyboard = InlineKeyboardMarkup() + keyboard.add( + InlineKeyboardButton(text="Check the shop", callback_data="items"), + InlineKeyboardButton(text="My orders", callback_data="my_orders"), + InlineKeyboardButton(text="Developer", callback_data="developer"), + ) + + await message.answer(f"Hi!, {message.from_user.mention}!", reply_markup=keyboard) + + +@dp.callback_query_handler(lambda c: c.data == "items") +async def process_callback_items(callback_query: types.CallbackQuery): + await callback_query.answer() + await items(callback_query.message) + + +@dp.callback_query_handler(lambda c: c.data == "my_orders") +async def process_callback_my_orders(callback_query: types.CallbackQuery): + await callback_query.answer() + orders_data = order_manager.get_user_orders(callback_query.from_user.id) + if orders_data: + orders_text = "\n".join(database.Order(*order_data).all_data() for order_data in orders_data) + await bot.send_message(chat_id=callback_query.from_user.id, text=orders_text) + else: + await bot.send_message(chat_id=callback_query.from_user.id, text="You don't have any orders yet.") + + +@dp.callback_query_handler(lambda c: c.data == "developer") +async def process_callback_developer(callback_query: types.CallbackQuery): + await bot.send_message( + chat_id=callback_query.from_user.id, + text="""Bot developer is @anekobtw +Telegram channel: @anekobtww +Github: https://github.com/anekobtw +Source code of this bot: https://github.com/anekobtw/telegram-shop-bot""", + ) + + +# <<< Items >>> +@dp.message_handler(commands=["items"]) +async def items(message: types.Message): + keyboard = InlineKeyboardMarkup() + for item, price in config.items.items(): + keyboard.add(InlineKeyboardButton(text=f"{item} - {price}{config.currency}", callback_data=order_cb.new(action="create", item=item))) + + await message.answer(text="Choose the product to buy:", reply_markup=keyboard) + + +@dp.callback_query_handler(order_cb.filter(action="create")) +async def order_cb_handler(query: types.CallbackQuery, callback_data: dict): + item = callback_data["item"] + order_manager.insert_order(query.from_user.id, query.from_user.full_name, item) + order_id = database.Order(*order_manager.get_active_orders()[-1]).order_id + + text = f"""You\'ve bought {item} that costs {config.items[item]}{config.currency} +Order_id: {order_id} +However, if it was an accident, you may delete your purchase by simply typing /delete_order {order_id}""" + await bot.send_message(chat_id=query.from_user.id, text=text) + await notify_admins_order(order_id=order_id) + + +# <<< Admin commands >>> +@dp.message_handler(commands=["orders"]) +async def orders(message: types.Message): + if message.from_user.id in admins: + orders_data = order_manager.get_active_orders() + for order_data in orders_data: + order_obj = database.Order(*order_data) + await message.answer(order_obj.all_data()) + + +@dp.message_handler(commands=["next"]) +async def next1(message: types.Message): + orders_data = order_manager.get_active_orders() + order_obj = database.Order(*orders_data[0]) + await message.answer(order_obj.all_data()) + + +# <<< Others >>> +@dp.message_handler(commands=["delete_order"]) +async def delete_order(message: types.Message): + try: + args = message.get_args() + order_id = int(args.split()[0]) + except (ValueError, IndexError): + await message.answer("The command is used incorrectly. Please, provide me with the order id. Example:\n/delete_order 4 ") + else: + order_data = order_manager.get_order(order_id) + if database.Order(*order_data).tgid == message.from_user.id: + order_manager.delete_order(order_id=order_id) + await message.answer(f"Order {order_id} was deleted successfully.") + + +if __name__ == "__main__": + executor.start_polling(dp) diff --git a/doners/telegram-shop-bot-template/src/requirements.txt b/doners/telegram-shop-bot-template/src/requirements.txt new file mode 100644 index 0000000..b38eacf --- /dev/null +++ b/doners/telegram-shop-bot-template/src/requirements.txt @@ -0,0 +1 @@ +aiogram==2.23.1 diff --git a/handlers/__init__.py b/handlers/__init__.py index e69de29..d2123c3 100644 --- a/handlers/__init__.py +++ b/handlers/__init__.py @@ -0,0 +1,7 @@ +from aiogram import Dispatcher + +def register_handlers(dp: Dispatcher): + from . import start, admin, polls + start.register_handlers(dp) + admin.register_handlers(dp) + polls.register_handlers(dp) diff --git a/handlers/admin.py b/handlers/admin.py index e69de29..2411248 100644 --- a/handlers/admin.py +++ b/handlers/admin.py @@ -0,0 +1,13 @@ +from aiogram import types, Dispatcher +from config import load_config + +config = load_config() + +async def admin_command(message: types.Message): + if message.from_user.id not in config['ADMINS']: + await message.reply("Вы не являетесь админом!") + return + await message.reply("Приветствую, повелитель!") + +def register_handlers(dp: Dispatcher): + dp.register_message_handler(admin_command, commands=['admin']) diff --git a/handlers/polls.py b/handlers/polls.py index e69de29..0ff3378 100644 --- a/handlers/polls.py +++ b/handlers/polls.py @@ -0,0 +1,22 @@ +from aiogram import types, Dispatcher +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton +from models.poll import Poll +from database import Session + +async def create_poll(message: types.Message): + markup = InlineKeyboardMarkup() + markup.add(InlineKeyboardButton('Option 1', callback_data='vote_1')) + markup.add(InlineKeyboardButton('Option 2', callback_data='vote_2')) + await message.reply("Choose an option:", reply_markup=markup) + +async def handle_vote(callback_query: types.CallbackQuery): + option = callback_query.data.split('_')[1] + with Session() as session: + poll = Poll(option=option) + session.add(poll) + session.commit() + await callback_query.answer(f"You voted for option {option}") + +def register_handlers(dp: Dispatcher): + dp.register_message_handler(create_poll, commands=['poll']) + dp.register_callback_query_handler(handle_vote, lambda c: c.data.startswith('vote_')) diff --git a/handlers/start.py b/handlers/start.py index e69de29..31398a7 100644 --- a/handlers/start.py +++ b/handlers/start.py @@ -0,0 +1,7 @@ +from aiogram import types, Dispatcher + +async def start_command(message: types.Message): + await message.reply("Приветсвие") + +def register_handlers(dp: Dispatcher): + dp.register_message_handler(start_command, commands=['start']) diff --git a/keyboards/inline.py b/keyboards/inline.py index e69de29..6996fa1 100644 --- a/keyboards/inline.py +++ b/keyboards/inline.py @@ -0,0 +1,7 @@ +from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton + +def create_poll_keyboard(): + markup = InlineKeyboardMarkup() + markup.add(InlineKeyboardButton('Option 1', callback_data='vote_1')) + markup.add(InlineKeyboardButton('Option 2', callback_data='vote_2')) + return markup diff --git a/middlewares/logging.py b/middlewares/logging.py index e69de29..0322d2e 100644 --- a/middlewares/logging.py +++ b/middlewares/logging.py @@ -0,0 +1,6 @@ +from aiogram.dispatcher.middlewares import BaseMiddleware +import logging + +class LoggingMiddleware(BaseMiddleware): + async def on_process_message(self, message, data): + logging.info(f"Received message from {message.from_user.id}: {message.text}") diff --git a/models/poll.py b/models/poll.py index e69de29..bfcebe8 100644 --- a/models/poll.py +++ b/models/poll.py @@ -0,0 +1,8 @@ +from sqlalchemy import Column, Integer, String +from database import Base + +class Poll(Base): + __tablename__ = 'polls' + + id = Column(Integer, primary_key=True) + option = Column(String, nullable=False) diff --git a/req.pip b/req.pip index e69de29..445943f 100644 --- a/req.pip +++ b/req.pip @@ -0,0 +1,3 @@ +aiogram==2.25.2 +python-dotenv +sqlalchemy \ No newline at end of file diff --git a/utils/misc.py b/utils/misc.py index e69de29..007c485 100644 --- a/utils/misc.py +++ b/utils/misc.py @@ -0,0 +1 @@ +# В этом файле можно разместить различные вспомогательные функции.