old_01
324
.gitignore
vendored
@@ -1,162 +1,162 @@
|
|||||||
# ---> Python
|
# ---> Python
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
*$py.class
|
*$py.class
|
||||||
|
|
||||||
# C extensions
|
# C extensions
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
# Distribution / packaging
|
# Distribution / packaging
|
||||||
.Python
|
.Python
|
||||||
build/
|
build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
dist/
|
dist/
|
||||||
downloads/
|
downloads/
|
||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
lib/
|
lib/
|
||||||
lib64/
|
lib64/
|
||||||
parts/
|
parts/
|
||||||
sdist/
|
sdist/
|
||||||
var/
|
var/
|
||||||
wheels/
|
wheels/
|
||||||
share/python-wheels/
|
share/python-wheels/
|
||||||
*.egg-info/
|
*.egg-info/
|
||||||
.installed.cfg
|
.installed.cfg
|
||||||
*.egg
|
*.egg
|
||||||
MANIFEST
|
MANIFEST
|
||||||
|
|
||||||
# PyInstaller
|
# PyInstaller
|
||||||
# Usually these files are written by a python script from a template
|
# 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.
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
*.manifest
|
*.manifest
|
||||||
*.spec
|
*.spec
|
||||||
|
|
||||||
# Installer logs
|
# Installer logs
|
||||||
pip-log.txt
|
pip-log.txt
|
||||||
pip-delete-this-directory.txt
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
# Unit test / coverage reports
|
# Unit test / coverage reports
|
||||||
htmlcov/
|
htmlcov/
|
||||||
.tox/
|
.tox/
|
||||||
.nox/
|
.nox/
|
||||||
.coverage
|
.coverage
|
||||||
.coverage.*
|
.coverage.*
|
||||||
.cache
|
.cache
|
||||||
nosetests.xml
|
nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
*.cover
|
*.cover
|
||||||
*.py,cover
|
*.py,cover
|
||||||
.hypothesis/
|
.hypothesis/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
cover/
|
cover/
|
||||||
|
|
||||||
# Translations
|
# Translations
|
||||||
*.mo
|
*.mo
|
||||||
*.pot
|
*.pot
|
||||||
|
|
||||||
# Django stuff:
|
# Django stuff:
|
||||||
*.log
|
*.log
|
||||||
local_settings.py
|
local_settings.py
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
db.sqlite3-journal
|
db.sqlite3-journal
|
||||||
|
|
||||||
# Flask stuff:
|
# Flask stuff:
|
||||||
instance/
|
instance/
|
||||||
.webassets-cache
|
.webassets-cache
|
||||||
|
|
||||||
# Scrapy stuff:
|
# Scrapy stuff:
|
||||||
.scrapy
|
.scrapy
|
||||||
|
|
||||||
# Sphinx documentation
|
# Sphinx documentation
|
||||||
docs/_build/
|
docs/_build/
|
||||||
|
|
||||||
# PyBuilder
|
# PyBuilder
|
||||||
.pybuilder/
|
.pybuilder/
|
||||||
target/
|
target/
|
||||||
|
|
||||||
# Jupyter Notebook
|
# Jupyter Notebook
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
|
||||||
# IPython
|
# IPython
|
||||||
profile_default/
|
profile_default/
|
||||||
ipython_config.py
|
ipython_config.py
|
||||||
|
|
||||||
# pyenv
|
# pyenv
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
# 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:
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
# .python-version
|
# .python-version
|
||||||
|
|
||||||
# pipenv
|
# pipenv
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
# 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
|
# 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
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
# install all needed dependencies.
|
# install all needed dependencies.
|
||||||
#Pipfile.lock
|
#Pipfile.lock
|
||||||
|
|
||||||
# poetry
|
# poetry
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
# 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
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
# commonly ignored for libraries.
|
# commonly ignored for libraries.
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
#poetry.lock
|
#poetry.lock
|
||||||
|
|
||||||
# pdm
|
# pdm
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
#pdm.lock
|
#pdm.lock
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
# in version control.
|
# in version control.
|
||||||
# https://pdm.fming.dev/#use-with-ide
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
.pdm.toml
|
.pdm.toml
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
__pypackages__/
|
__pypackages__/
|
||||||
|
|
||||||
# Celery stuff
|
# Celery stuff
|
||||||
celerybeat-schedule
|
celerybeat-schedule
|
||||||
celerybeat.pid
|
celerybeat.pid
|
||||||
|
|
||||||
# SageMath parsed files
|
# SageMath parsed files
|
||||||
*.sage.py
|
*.sage.py
|
||||||
|
|
||||||
# Environments
|
# Environments
|
||||||
.env
|
.env
|
||||||
.venv
|
.venv
|
||||||
.env/
|
.env/
|
||||||
venv/
|
venv/
|
||||||
.env/
|
.env/
|
||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
.spyderproject
|
.spyderproject
|
||||||
.spyproject
|
.spyproject
|
||||||
|
|
||||||
# Rope project settings
|
# Rope project settings
|
||||||
.ropeproject
|
.ropeproject
|
||||||
|
|
||||||
# mkdocs documentation
|
# mkdocs documentation
|
||||||
/site
|
/site
|
||||||
|
|
||||||
# mypy
|
# mypy
|
||||||
.mypy_cache/
|
.mypy_cache/
|
||||||
.dmypy.json
|
.dmypy.json
|
||||||
dmypy.json
|
dmypy.json
|
||||||
|
|
||||||
# Pyre type checker
|
# Pyre type checker
|
||||||
.pyre/
|
.pyre/
|
||||||
|
|
||||||
# pytype static type analyzer
|
# pytype static type analyzer
|
||||||
.pytype/
|
.pytype/
|
||||||
|
|
||||||
# Cython debug symbols
|
# Cython debug symbols
|
||||||
cython_debug/
|
cython_debug/
|
||||||
|
|
||||||
# PyCharm
|
# PyCharm
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
# 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
|
# 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
|
# 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.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
|||||||
6
.idea/.gitignore
generated
vendored
@@ -1,3 +1,3 @@
|
|||||||
# Default ignored files
|
# Default ignored files
|
||||||
/shelf/
|
/shelf/
|
||||||
/workspace.xml
|
/workspace.xml
|
||||||
|
|||||||
18
.idea/BOTKlining.iml
generated
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="PYTHON_MODULE" version="4">
|
<module type="PYTHON_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="jdk" jdkName="Python 3.11 (BOTKlining)" jdkType="Python SDK" />
|
<orderEntry type="jdk" jdkName="Python 3.11 (BOTKlining)" jdkType="Python SDK" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
11
.idea/encodings.xml
generated
@@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="Encoding">
|
<component name="Encoding">
|
||||||
<file url="file://$PROJECT_DIR$/old/bot.log" charset="windows-1251" />
|
<file url="file://$PROJECT_DIR$/doners/old/bot.log" charset="windows-1251" />
|
||||||
<file url="file://$PROJECT_DIR$/old/bot.log" charset="windows-1251" />
|
</component>
|
||||||
</component>
|
|
||||||
</project>
|
</project>
|
||||||
10
.idea/inspectionProfiles/profiles_settings.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
<version value="1.0" />
|
<version value="1.0" />
|
||||||
</settings>
|
</settings>
|
||||||
</component>
|
</component>
|
||||||
12
.idea/misc.xml
generated
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="Black">
|
<component name="Black">
|
||||||
<option name="sdkName" value="Python 3.11 (BOTklining)" />
|
<option name="sdkName" value="Python 3.11 (BOTklining)" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (BOTKlining)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.11 (BOTKlining)" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
||||||
14
.idea/modules.xml
generated
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/BOTKlining.iml" filepath="$PROJECT_DIR$/.idea/BOTKlining.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/BOTKlining.iml" filepath="$PROJECT_DIR$/.idea/BOTKlining.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
10
.idea/vcs.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
532
README.md
@@ -1,157 +1,375 @@
|
|||||||
# BOTKlining
|
# BOTKlining
|
||||||
|
|
||||||
## Структура
|
## Структура
|
||||||
|
|
||||||
1. `main.py`: основной файл для запуска бота.
|
1. `main.py`: основной файл для запуска бота.
|
||||||
2. `handlers.py`: обработчики команд и состояний.
|
2. `handlers.py`: обработчики команд и состояний.
|
||||||
3. `database.py`: взаимодействие с базой данных.
|
3. `database.py`: взаимодействие с базой данных.
|
||||||
4. `logger.py`: логирование действий пользователей.
|
4. `logger.py`: логирование действий пользователей.
|
||||||
5. `.env`: файл для хранения конфиденциальных данных.
|
5. `.env`: файл для хранения конфиденциальных данных.
|
||||||
|
|
||||||
Для создания бота с указанной функциональностью и логикой, а также использования файла .env для хранения конфиденциальных данных, мы разобьем задачу на несколько шагов:
|
Для создания бота с указанной функциональностью и логикой, а также использования файла .env для хранения конфиденциальных данных, мы разобьем задачу на несколько шагов:
|
||||||
|
|
||||||
Настройка окружения и установка зависимостей
|
Настройка окружения и установка зависимостей
|
||||||
Создание базы данных
|
Создание базы данных
|
||||||
Разработка основных обработчиков команд и колбэков
|
Разработка основных обработчиков команд и колбэков
|
||||||
Создание функций для работы с базой данных
|
Создание функций для работы с базой данных
|
||||||
Настройка логирования
|
Настройка логирования
|
||||||
Реализация основной логики бота
|
Реализация основной логики бота
|
||||||
|
|
||||||
Вот подробное описание структуры и логики бота:
|
Вот подробное описание структуры и логики бота:
|
||||||
|
|
||||||
## Inline клавиатура бота:
|
## Inline клавиатура бота:
|
||||||
1. Учетные данные
|
1. Учетные данные
|
||||||
|
|
||||||
- Изменить ФИО: Пользователь может изменить свое ФИО.
|
- Изменить ФИО: Пользователь может изменить свое ФИО.
|
||||||
- Изменить номер телефона: Позволяет изменить номер телефона.
|
- Изменить номер телефона: Позволяет изменить номер телефона.
|
||||||
- Добавить адрес: Пользователь может добавить новый адрес.
|
- Добавить адрес: Пользователь может добавить новый адрес.
|
||||||
- Удалить адрес: Пользователь может удалить ранее добавленный адрес.
|
- Удалить адрес: Пользователь может удалить ранее добавленный адрес.
|
||||||
- Поделиться контактом: Позволяет поделиться контактными данными, для автоматического заполнения в базе данных.
|
- Поделиться контактом: Позволяет поделиться контактными данными, для автоматического заполнения в базе данных.
|
||||||
|
|
||||||
2. Сделать заказ
|
2. Сделать заказ
|
||||||
|
|
||||||
- Добавить учетные данные: Если у пользователя нет учетных данных, он должен их добавить. Если данные есть они заполняются автоматически.
|
- Добавить учетные данные: Если у пользователя нет учетных данных, он должен их добавить. Если данные есть они заполняются автоматически.
|
||||||
- Выбрать адрес: Пользователь выбирает адрес или добавляет новый.
|
- Выбрать адрес: Пользователь выбирает адрес или добавляет новый.
|
||||||
- Выбрать время уборки: Предлагает утро, день, вечер.
|
- Выбрать время уборки: Предлагает утро, день, вечер.
|
||||||
- Выбрать тип уборки: Влажная, сухая, генеральная.
|
- Выбрать тип уборки: Влажная, сухая, генеральная.
|
||||||
- Выбрать способ оплаты: Картой, наличными.
|
- Выбрать способ оплаты: Картой, наличными.
|
||||||
- Подтвердить заказ: Подтверждает заказ или позволяет изменить ответы. После подтверждения заказа автоматически отправлять уведомление в группу операторов, а также добавлять заказ в историю.
|
- Подтвердить заказ: Подтверждает заказ или позволяет изменить ответы. После подтверждения заказа автоматически отправлять уведомление в группу операторов, а также добавлять заказ в историю.
|
||||||
- Заказу присваивается ID номер
|
- Заказу присваивается ID номер
|
||||||
|
|
||||||
3. История заказов
|
3. История заказов
|
||||||
|
|
||||||
- Просмотреть историю заказов: Позволяет пользователю просмотреть свою историю заказов.
|
- Просмотреть историю заказов: Позволяет пользователю просмотреть свою историю заказов.
|
||||||
- Добавить функцию повторного заказа на основе предыдущих заказов.
|
- Добавить функцию повторного заказа на основе предыдущих заказов.
|
||||||
|
|
||||||
4. Заказать звонок
|
4. Заказать звонок
|
||||||
|
|
||||||
- Добавить учетные данные: Если у пользователя нет учетных данных, он должен их добавить. Если данные есть они заполняются автоматически.
|
- Добавить учетные данные: Если у пользователя нет учетных данных, он должен их добавить. Если данные есть они заполняются автоматически.
|
||||||
- Заказать звонок: Отправляет учетные данные (ФИО, номер телефона) в группу операторов, с подписью об звонке. Пользователь видит сообщение "ожидайте звонок в течении 30 минут"
|
- Заказать звонок: Отправляет учетные данные (ФИО, номер телефона) в группу операторов, с подписью об звонке. Пользователь видит сообщение "ожидайте звонок в течении 30 минут"
|
||||||
|
|
||||||
5. Начать разговор с оператором
|
5. Начать разговор с оператором
|
||||||
|
|
||||||
- Добавить учетные данные: Если у пользователя нет учетных данных, он должен их добавить. Если данные есть они заполняются автоматически.
|
- Добавить учетные данные: Если у пользователя нет учетных данных, он должен их добавить. Если данные есть они заполняются автоматически.
|
||||||
- Начать разговор: Отправляет учетные данные (ФИО, номер телефона, ID номер крайнего заказа) в группу операторов.
|
- Начать разговор: Отправляет учетные данные (ФИО, номер телефона, ID номер крайнего заказа) в группу операторов.
|
||||||
- Завершить разговор: Пользователь завершает разговор.
|
- Завершить разговор: Пользователь завершает разговор.
|
||||||
- После завершения разговора автоматически отправлять анкету о качестве обслуживания пользователю для обратной связи. Автоматически закрыть анкету в случае не ответа 2 часов.
|
- После завершения разговора автоматически отправлять анкету о качестве обслуживания пользователю для обратной связи. Автоматически закрыть анкету в случае не ответа 2 часов.
|
||||||
|
|
||||||
6. Назад
|
6. Назад
|
||||||
|
|
||||||
- Возврат к предыдущему набору кнопок: Позволяет вернуться к предыдущему меню кнопок.
|
- Возврат к предыдущему набору кнопок: Позволяет вернуться к предыдущему меню кнопок.
|
||||||
|
|
||||||
## Логика нажатия кнопок и сохранения данных:
|
## Логика нажатия кнопок и сохранения данных:
|
||||||
- При первом использовании бота запросить у пользователя учетные данные (ФИО, номер телефона) и сохранить их для последующих заказов. Можно использовать личные данные из Telegram (если пользователь разрешил) для заполнения некоторых полей автоматически.
|
- При первом использовании бота запросить у пользователя учетные данные (ФИО, номер телефона) и сохранить их для последующих заказов. Можно использовать личные данные из Telegram (если пользователь разрешил) для заполнения некоторых полей автоматически.
|
||||||
- При нажатии кнопок пользователем, бот запоминает предыдущий выбор и отображает следующий набор кнопок соответственно.
|
- При нажатии кнопок пользователем, бот запоминает предыдущий выбор и отображает следующий набор кнопок соответственно.
|
||||||
- Когда пользователь нажимает на любую из кнопок, предыдущий выбор автоматически скрывается, а вместо него появляется следующий набор кнопок. Кнопка "Назад" позволяет вернуться к предыдущему набору кнопок в случае необходимости. Это помогает сделать интерфейс более удобным и интуитивно понятным для пользователей.
|
- Когда пользователь нажимает на любую из кнопок, предыдущий выбор автоматически скрывается, а вместо него появляется следующий набор кнопок. Кнопка "Назад" позволяет вернуться к предыдущему набору кнопок в случае необходимости. Это помогает сделать интерфейс более удобным и интуитивно понятным для пользователей.
|
||||||
- Данные пользователя (учетные данные, адреса, заказы и т.д.) сохраняются в базе данных SQLite3 для последующего использования.
|
- Данные пользователя (учетные данные, адреса, заказы и т.д.) сохраняются в базе данных SQLite3 для последующего использования.
|
||||||
- Логирование действий пользователей осуществляется путем записи информации о каждом действии в лог-файлы, включая дату, время, ID пользователя и другие параметры.
|
- Логирование действий пользователей осуществляется путем записи информации о каждом действии в лог-файлы, включая дату, время, ID пользователя и другие параметры.
|
||||||
## Управление из группы и внутри бота:
|
## Управление из группы и внутри бота:
|
||||||
- Группа для заказов управляется операторами и администраторами, которые имеют доступ к соответствующим функциям.
|
- Группа для заказов управляется операторами и администраторами, которые имеют доступ к соответствующим функциям.
|
||||||
- Операторы могут просматривать историю заказов пользователей, отвечать на сообщения, завершать разговоры и управлять заказами.
|
- Операторы могут просматривать историю заказов пользователей, отвечать на сообщения, завершать разговоры и управлять заказами.
|
||||||
- Администраторы имеют дополнительные функции, такие как выгрузка переписки в формате JSON.
|
- Администраторы имеют дополнительные функции, такие как выгрузка переписки в формате JSON.
|
||||||
- Бот на aiogram используется для более эффективной работы и реализации функций бота через асинхронные методы.
|
- Бот на aiogram используется для более эффективной работы и реализации функций бота через асинхронные методы.
|
||||||
- Эта структура и логика позволят создать функционального бота для управления заказами и общения с операторами, обеспечивая удобство пользователей и контроль над данными.
|
- Эта структура и логика позволят создать функционального бота для управления заказами и общения с операторами, обеспечивая удобство пользователей и контроль над данными.
|
||||||
- Данные токена id групп, id администраторов, операторов дложны храниться в файле env
|
- Данные токена id групп, id администраторов, операторов дложны храниться в файле env
|
||||||
## Автоматизация
|
## Автоматизация
|
||||||
|
|
||||||
- Добавить таймеры для отслеживания неактивности пользователя и предложения начать разговор с оператором или оформить заказ при необходимости.
|
- Добавить таймеры для отслеживания неактивности пользователя и предложения начать разговор с оператором или оформить заказ при необходимости.
|
||||||
- Использовать базу данных для хранения информации о пользователях, заказах и истории коммуникации с операторами.
|
- Использовать базу данных для хранения информации о пользователях, заказах и истории коммуникации с операторами.
|
||||||
- Реализовать механизм автоматического анализа данных о заказах для предложения персонализированных услуг или скидок.
|
- Реализовать механизм автоматического анализа данных о заказах для предложения персонализированных услуг или скидок.
|
||||||
- Создаем базу данных SQLite3 для хранения информации о пользователях, их учетных данных, заказах и истории сообщений с операторами.
|
- Создаем базу данных SQLite3 для хранения информации о пользователях, их учетных данных, заказах и истории сообщений с операторами.
|
||||||
- Создаем таблицы для каждой категории данных: пользователи, заказы, история сообщений и т.д.
|
- Создаем таблицы для каждой категории данных: пользователи, заказы, история сообщений и т.д.
|
||||||
- Связываем таблицы между собой по ключам (например, по ID пользователя).
|
- Связываем таблицы между собой по ключам (например, по ID пользователя).
|
||||||
- При каждом действии пользователя (нажатие кнопки, отправка сообщения) записываем соответствующую информацию в лог-файлы.
|
- При каждом действии пользователя (нажатие кнопки, отправка сообщения) записываем соответствующую информацию в лог-файлы.
|
||||||
- Логируем дату, время действия, ID пользователя, текстовые данные (если есть) и другие необходимые параметры.
|
- Логируем дату, время действия, ID пользователя, текстовые данные (если есть) и другие необходимые параметры.
|
||||||
- Создаем специальную группу для приема заказов, где только операторы и администраторы имеют доступ.
|
- Создаем специальную группу для приема заказов, где только операторы и администраторы имеют доступ.
|
||||||
- Назначаем операторов и администраторов, которые могут управлять заказами в этой группе.
|
- Назначаем операторов и администраторов, которые могут управлять заказами в этой группе.
|
||||||
|
|
||||||
1. Функционал операторов:
|
1. Функционал операторов:
|
||||||
- Операторы могут просматривать историю заказов конкретного пользователя по его ID.
|
- Операторы могут просматривать историю заказов конкретного пользователя по его ID.
|
||||||
- Операторы могут отвечать пользователю из группы используя функцию ответа (replay).
|
- Операторы могут отвечать пользователю из группы используя функцию ответа (replay).
|
||||||
- Операторы могут завершать разговор с пользователем с помощью соответствующей кнопки.
|
- Операторы могут завершать разговор с пользователем с помощью соответствующей кнопки.
|
||||||
|
|
||||||
2. Функционал администраторов:
|
2. Функционал администраторов:
|
||||||
- Администраторы имеют все функции операторов.
|
- Администраторы имеют все функции операторов.
|
||||||
- Дополнительно администраторы могут выгружать переписку операторов и пользователя в виде JSON файла.
|
- Дополнительно администраторы могут выгружать переписку операторов и пользователя в виде JSON файла.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Логика Бота
|
Логика Бота
|
||||||
есть inline клавиотура со следующими кнопками:
|
есть inline клавиотура со следующими кнопками:
|
||||||
Учетные данные
|
Учетные данные
|
||||||
Изменить ФИО
|
Изменить ФИО
|
||||||
Изменить номер телефона
|
Изменить номер телефона
|
||||||
Добавит адрес
|
Добавит адрес
|
||||||
Поделиться контактом
|
Поделиться контактом
|
||||||
сделать заказ
|
сделать заказ
|
||||||
Добавит учетные данные, если данные не введены, если введены то пропустить вопрос.
|
Добавит учетные данные, если данные не введены, если введены то пропустить вопрос.
|
||||||
выбрать адрес или указать новый адрес. все веденные адреса сохраняються.
|
выбрать адрес или указать новый адрес. все веденные адреса сохраняються.
|
||||||
спросить время для уборки: утро/День/вечер
|
спросить время для уборки: утро/День/вечер
|
||||||
Спросить тип уборки: Влажная/Сухая/Генеральная
|
Спросить тип уборки: Влажная/Сухая/Генеральная
|
||||||
Спросить способ оплаты: Картой/Наличными
|
Спросить способ оплаты: Картой/Наличными
|
||||||
Подьвердить заказ, или предложить изменить ответ на вопрос. далее кнопки вопросов что бы изменить один ответ и после изменения повторно подтвердить.
|
Подьвердить заказ, или предложить изменить ответ на вопрос. далее кнопки вопросов что бы изменить один ответ и после изменения повторно подтвердить.
|
||||||
После подтверждения, сообщения с ответами отправляеться группу операторов.
|
После подтверждения, сообщения с ответами отправляеться группу операторов.
|
||||||
История заказов
|
История заказов
|
||||||
Все отправленные ответы в группу сохраняются как заказ и храниться в базе данных.
|
Все отправленные ответы в группу сохраняются как заказ и храниться в базе данных.
|
||||||
Заказать звонок
|
Заказать звонок
|
||||||
Добавит учетные данные, если данные не введены, если введены то пропустить вопрос.
|
Добавит учетные данные, если данные не введены, если введены то пропустить вопрос.
|
||||||
Кнопка заказать звонок, отправляет учетные данные (ФИО, номер телефона) в группу операторов.
|
Кнопка заказать звонок, отправляет учетные данные (ФИО, номер телефона) в группу операторов.
|
||||||
Начать разговор с оератором
|
Начать разговор с оератором
|
||||||
Добавит учетные данные, если данные не введены, если введены то пропустить вопрос.
|
Добавит учетные данные, если данные не введены, если введены то пропустить вопрос.
|
||||||
Кнопка начать разговор, отправляет учетные данные (ФИО, номер телефона) в группу операторов.
|
Кнопка начать разговор, отправляет учетные данные (ФИО, номер телефона) в группу операторов.
|
||||||
Оператор из группы нажимает ответить(replay) и пишет сообщение, сообшение отправляеться пользователю
|
Оператор из группы нажимает ответить(replay) и пишет сообщение, сообшение отправляеться пользователю
|
||||||
пользователь пишет сообщение и оно отправляеться в группу до дех под пока пользователь не нажмет завершить разговор.
|
пользователь пишет сообщение и оно отправляеться в группу до дех под пока пользователь не нажмет завершить разговор.
|
||||||
Если кнопка завершить разговор не нажато, то через 1 час разговор с оператором завершаеться автоматически.
|
Если кнопка завершить разговор не нажато, то через 1 час разговор с оператором завершаеться автоматически.
|
||||||
При заходе пользователя через 2 часа появляеться первоначальное меню кнопок.
|
При заходе пользователя через 2 часа появляеться первоначальное меню кнопок.
|
||||||
Допиши логику, слелай её более автоматизированной.
|
Допиши логику, слелай её более автоматизированной.
|
||||||
|
|
||||||
ChatGPT
|
ChatGPT
|
||||||
Ты создал очень хорошую базу для логики бота! Вот несколько дополнений и идей для автоматизации:
|
Ты создал очень хорошую базу для логики бота! Вот несколько дополнений и идей для автоматизации:
|
||||||
|
|
||||||
Учетные данные:
|
Учетные данные:
|
||||||
|
|
||||||
При первом использовании бота запросить у пользователя учетные данные (ФИО, номер телефона) и сохранить их для последующих заказов. Можно использовать личные данные из Telegram (если пользователь разрешил) для заполнения некоторых полей автоматически.
|
При первом использовании бота запросить у пользователя учетные данные (ФИО, номер телефона) и сохранить их для последующих заказов. Можно использовать личные данные из Telegram (если пользователь разрешил) для заполнения некоторых полей автоматически.
|
||||||
Добавить возможность изменения учетных данных через команду или кнопку "Изменить учетные данные".
|
Добавить возможность изменения учетных данных через команду или кнопку "Изменить учетные данные".
|
||||||
Сделать заказ:
|
Сделать заказ:
|
||||||
|
|
||||||
После выбора адреса показывать пользователю карту с указанным адресом для подтверждения или возможности изменения.
|
После выбора адреса показывать пользователю карту с указанным адресом для подтверждения или возможности изменения.
|
||||||
Автоматически предлагать время уборки в зависимости от текущего времени (например, если пользователь заходит в 9 утра, предложить утреннее время уборки).
|
Автоматически предлагать время уборки в зависимости от текущего времени (например, если пользователь заходит в 9 утра, предложить утреннее время уборки).
|
||||||
После подтверждения заказа автоматически отправлять уведомление в группу операторов, а также добавлять заказ в историю.
|
После подтверждения заказа автоматически отправлять уведомление в группу операторов, а также добавлять заказ в историю.
|
||||||
История заказов:
|
История заказов:
|
||||||
|
|
||||||
Позволить пользователю просматривать историю своих заказов через бота.
|
Позволить пользователю просматривать историю своих заказов через бота.
|
||||||
Добавить функцию повторного заказа на основе предыдущих заказов.
|
Добавить функцию повторного заказа на основе предыдущих заказов.
|
||||||
Заказать звонок:
|
Заказать звонок:
|
||||||
|
|
||||||
Позволить пользователю выбрать время для звонка (например, "Заказать звонок через 30 минут").
|
Позволить пользователю выбрать время для звонка (например, "Заказать звонок через 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=<your_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
|
||||||
|
Эта структура предоставляет базу для дальнейшего расширения функциональности бота. Вы можете добавлять дополнительные обработчики, модели и утилиты по мере необходимости.
|
||||||
24
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)
|
||||||
|
|||||||
12
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(','))),
|
||||||
|
}
|
||||||
|
|||||||
13
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)
|
||||||
|
|||||||
14
doners/Shop-bot/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Cache files
|
||||||
|
__pycache__/
|
||||||
|
|
||||||
|
# Project files
|
||||||
|
.idea/
|
||||||
|
*.log
|
||||||
|
.env
|
||||||
|
*.session
|
||||||
|
data/config.py
|
||||||
|
data/database.db
|
||||||
|
|
||||||
|
# vscode
|
||||||
|
.vscode/
|
||||||
|
.history/
|
||||||
9
doners/Shop-bot/Dockerfile
Normal file
@@ -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
|
||||||
21
doners/Shop-bot/LICENSE
Normal file
@@ -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.
|
||||||
47
doners/Shop-bot/README.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="https://t.me/example_store_bot"><img src="data/assets/logo.png" alt="ShopBot"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
------
|
||||||
|
|
||||||
|
Then, after making sure everything is in place, proceed to checkout by clicking `📦 Place Order`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Contacting Administration
|
||||||
|
|
||||||
|
To ask the admin a question, simply select the `/sos` command. There is a limit on the number of questions.
|
||||||
|
|
||||||
|

|
||||||
0
doners/Shop-bot/__init__.py
Normal file
92
doners/Shop-bot/app.py
Normal file
@@ -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 поможет связаться с админами, которые постараются как можно быстрее откликнуться.
|
||||||
|
|
||||||
|
🤝 Заказать похожего бота? Свяжитесь с разработчиком <a href="https://t.me/NikolaySimakov">Nikolay Simakov</a>, он не кусается)))
|
||||||
|
''', 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)
|
||||||
0
doners/Shop-bot/data/__init__.py
Normal file
BIN
doners/Shop-bot/data/assets/1.png
Normal file
|
After Width: | Height: | Size: 616 KiB |
BIN
doners/Shop-bot/data/assets/2.png
Normal file
|
After Width: | Height: | Size: 947 KiB |
BIN
doners/Shop-bot/data/assets/3.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
doners/Shop-bot/data/assets/4.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
doners/Shop-bot/data/assets/5.png
Normal file
|
After Width: | Height: | Size: 647 KiB |
BIN
doners/Shop-bot/data/assets/6.png
Normal file
|
After Width: | Height: | Size: 399 KiB |
BIN
doners/Shop-bot/data/assets/7.png
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
doners/Shop-bot/data/assets/logo.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
doners/Shop-bot/data/assets/logo_mini.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
7
doners/Shop-bot/filters/__init__.py
Normal file
@@ -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])
|
||||||
9
doners/Shop-bot/filters/is_admin.py
Normal file
@@ -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
|
||||||
9
doners/Shop-bot/filters/is_user.py
Normal file
@@ -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
|
||||||
4
doners/Shop-bot/handlers/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
from .admin import dp
|
||||||
|
from .user import dp
|
||||||
|
|
||||||
|
__all__ = ['dp']
|
||||||
5
doners/Shop-bot/handlers/admin/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from .add import dp
|
||||||
|
from .questions import dp
|
||||||
|
from .orders import dp
|
||||||
|
|
||||||
|
__all__ = ['dp']
|
||||||
285
doners/Shop-bot/handlers/admin/add.py
Normal file
@@ -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"Изменить название с <b>{data['title']}</b>?", 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"Изменить описание с <b>{data['body']}</b>?", 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'<b>{title}</b>\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"Изменить цену с <b>{data['price']}</b>?", 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'<b>{title}</b>\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)
|
||||||
22
doners/Shop-bot/handlers/admin/orders.py
Normal file
@@ -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'Заказ <b>№{order[3]}</b>\n\n'
|
||||||
|
|
||||||
|
await message.answer(res)
|
||||||
78
doners/Shop-bot/handlers/admin/questions.py
Normal file
@@ -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'Вопрос: <b>{question}</b>\n\nОтвет: <b>{answer}</b>'
|
||||||
|
|
||||||
|
await message.answer('Отправлено!', reply_markup=ReplyKeyboardRemove())
|
||||||
|
await bot.send_message(cid, text)
|
||||||
|
|
||||||
|
await state.finish()
|
||||||
8
doners/Shop-bot/handlers/user/__init__.py
Normal file
@@ -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']
|
||||||
247
doners/Shop-bot/handlers/user/cart.py
Normal file
@@ -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'<b>{title}</b>\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'<b>{title}</b> * {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('Изменить имя с <b>' + data['name'] + '</b>?',
|
||||||
|
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('Изменить адрес с <b>' + data['address'] + '</b>?',
|
||||||
|
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Имя: <b>' + data['name'] + '</b>\nАдрес: <b>' + data['address'] + '</b>',
|
||||||
|
reply_markup=markup)
|
||||||
|
else:
|
||||||
|
|
||||||
|
await message.answer('У вас недостаточно денег на счете. Пополните баланс!',
|
||||||
|
reply_markup=markup)
|
||||||
|
|
||||||
|
await state.finish()
|
||||||
58
doners/Shop-bot/handlers/user/catalog.py
Normal file
@@ -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'<b>{title}</b>\n\n{body}'
|
||||||
|
|
||||||
|
await m.answer_photo(photo=image,
|
||||||
|
caption=text,
|
||||||
|
reply_markup=markup)
|
||||||
31
doners/Shop-bot/handlers/user/delivery_status.py
Normal file
@@ -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'Заказ <b>№{order[3]}</b>'
|
||||||
|
answer = [
|
||||||
|
' лежит на складе.',
|
||||||
|
' уже в пути!',
|
||||||
|
' прибыл и ждет вас на почте!'
|
||||||
|
]
|
||||||
|
|
||||||
|
res += answer[0]
|
||||||
|
res += '\n\n'
|
||||||
|
|
||||||
|
await message.answer(res)
|
||||||
30
doners/Shop-bot/handlers/user/menu.py
Normal file
@@ -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)
|
||||||
54
doners/Shop-bot/handlers/user/sos.py
Normal file
@@ -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()
|
||||||
18
doners/Shop-bot/handlers/user/wallet.py
Normal file
@@ -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('Ваш кошелек пуст! Чтобы его пополнить нужно...')
|
||||||
|
|
||||||
2
doners/Shop-bot/keyboards/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from . import inline
|
||||||
|
from . import default
|
||||||
1
doners/Shop-bot/keyboards/default/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import markups
|
||||||
31
doners/Shop-bot/keyboards/default/markups.py
Normal file
@@ -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
|
||||||
3
doners/Shop-bot/keyboards/inline/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from . import products_from_catalog
|
||||||
|
from . import products_from_cart
|
||||||
|
from . import categories
|
||||||
16
doners/Shop-bot/keyboards/inline/categories.py
Normal file
@@ -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
|
||||||
16
doners/Shop-bot/keyboards/inline/products_from_cart.py
Normal file
@@ -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
|
||||||
15
doners/Shop-bot/keyboards/inline/products_from_catalog.py
Normal file
@@ -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
|
||||||
10
doners/Shop-bot/loader.py
Normal file
@@ -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')
|
||||||
1
doners/Shop-bot/requirements.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
aiogram==2.9.2
|
||||||
3
doners/Shop-bot/states/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .checkout_state import CheckoutState
|
||||||
|
from .product_state import ProductState, CategoryState
|
||||||
|
from .sos_state import SosState, AnswerState
|
||||||
7
doners/Shop-bot/states/checkout_state.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from aiogram.dispatcher.filters.state import StatesGroup, State
|
||||||
|
|
||||||
|
class CheckoutState(StatesGroup):
|
||||||
|
check_cart = State()
|
||||||
|
name = State()
|
||||||
|
address = State()
|
||||||
|
confirm = State()
|
||||||
11
doners/Shop-bot/states/product_state.py
Normal file
@@ -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()
|
||||||
9
doners/Shop-bot/states/sos_state.py
Normal file
@@ -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()
|
||||||
1
doners/Shop-bot/utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from . import db
|
||||||
1
doners/Shop-bot/utils/db/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .storage import DatabaseManager
|
||||||
59
doners/Shop-bot/utils/db/storage.py
Normal file
@@ -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
|
||||||
|
|
||||||
|
'''
|
||||||
0
doners/__init__.py
Normal file
6
doners/bot-market/.idea/.gitignore
generated
vendored
@@ -1,3 +1,3 @@
|
|||||||
# Default ignored files
|
# Default ignored files
|
||||||
/shelf/
|
/shelf/
|
||||||
/workspace.xml
|
/workspace.xml
|
||||||
|
|||||||
18
doners/bot-market/.idea/bot-market.iml
generated
@@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<module type="PYTHON_MODULE" version="4">
|
<module type="PYTHON_MODULE" version="4">
|
||||||
<component name="NewModuleRootManager">
|
<component name="NewModuleRootManager">
|
||||||
<content url="file://$MODULE_DIR$">
|
<content url="file://$MODULE_DIR$">
|
||||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
</content>
|
</content>
|
||||||
<orderEntry type="inheritedJdk" />
|
<orderEntry type="inheritedJdk" />
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<settings>
|
<settings>
|
||||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
<version value="1.0" />
|
<version value="1.0" />
|
||||||
</settings>
|
</settings>
|
||||||
</component>
|
</component>
|
||||||
12
doners/bot-market/.idea/misc.xml
generated
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="Black">
|
<component name="Black">
|
||||||
<option name="sdkName" value="Python 3.10 (bot-market)" />
|
<option name="sdkName" value="Python 3.10 (bot-market)" />
|
||||||
</component>
|
</component>
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (bot-market)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (bot-market)" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
||||||
14
doners/bot-market/.idea/modules.xml
generated
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/bot-market.iml" filepath="$PROJECT_DIR$/.idea/bot-market.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/bot-market.iml" filepath="$PROJECT_DIR$/.idea/bot-market.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
0
doners/bot-market/__init__.py
Normal file
0
doners/bot-market/app/__init__.py
Normal file
@@ -1,94 +1,94 @@
|
|||||||
from aiogram import Router, F
|
from aiogram import Router, F
|
||||||
from aiogram.types import Message, CallbackQuery
|
from aiogram.types import Message, CallbackQuery
|
||||||
from aiogram.filters import CommandStart, Command, Filter
|
from aiogram.filters import CommandStart, Command, Filter
|
||||||
from aiogram.fsm.context import FSMContext
|
from aiogram.fsm.context import FSMContext
|
||||||
from aiogram.fsm.state import State, StatesGroup
|
from aiogram.fsm.state import State, StatesGroup
|
||||||
|
|
||||||
import app.keyboards as kb
|
import app.keyboards as kb
|
||||||
from app.database.requests import get_users, set_item
|
from app.database.requests import get_users, set_item
|
||||||
|
|
||||||
admin = Router()
|
admin = Router()
|
||||||
|
|
||||||
|
|
||||||
class Newsletter(StatesGroup):
|
class Newsletter(StatesGroup):
|
||||||
message = State()
|
message = State()
|
||||||
|
|
||||||
|
|
||||||
class AddItem(StatesGroup):
|
class AddItem(StatesGroup):
|
||||||
name = State()
|
name = State()
|
||||||
category = State()
|
category = State()
|
||||||
description = State()
|
description = State()
|
||||||
photo = State()
|
photo = State()
|
||||||
price = State()
|
price = State()
|
||||||
|
|
||||||
|
|
||||||
class AdminProtect(Filter):
|
class AdminProtect(Filter):
|
||||||
async def __call__(self, message: Message):
|
async def __call__(self, message: Message):
|
||||||
return message.from_user.id in [1477217831]
|
return message.from_user.id in [1477217831]
|
||||||
|
|
||||||
|
|
||||||
@admin.message(AdminProtect(), Command('apanel'))
|
@admin.message(AdminProtect(), Command('apanel'))
|
||||||
async def apanel(message: Message):
|
async def apanel(message: Message):
|
||||||
await message.answer('Возможные команды: /newsletter\n/add_item')
|
await message.answer('Возможные команды: /newsletter\n/add_item')
|
||||||
|
|
||||||
|
|
||||||
@admin.message(AdminProtect(), Command('newsletter'))
|
@admin.message(AdminProtect(), Command('newsletter'))
|
||||||
async def newsletter(message: Message, state: FSMContext):
|
async def newsletter(message: Message, state: FSMContext):
|
||||||
await state.set_state(Newsletter.message)
|
await state.set_state(Newsletter.message)
|
||||||
await message.answer('Отправьте сообщение, которое вы хотите разослать всем пользователям')
|
await message.answer('Отправьте сообщение, которое вы хотите разослать всем пользователям')
|
||||||
|
|
||||||
|
|
||||||
@admin.message(AdminProtect(), Newsletter.message)
|
@admin.message(AdminProtect(), Newsletter.message)
|
||||||
async def newsletter_message(message: Message, state: FSMContext):
|
async def newsletter_message(message: Message, state: FSMContext):
|
||||||
await message.answer('Подождите... идёт рассылка.')
|
await message.answer('Подождите... идёт рассылка.')
|
||||||
for user in await get_users():
|
for user in await get_users():
|
||||||
try:
|
try:
|
||||||
await message.send_copy(chat_id=user.tg_id)
|
await message.send_copy(chat_id=user.tg_id)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
await message.answer('Рассылка успешно завершена.')
|
await message.answer('Рассылка успешно завершена.')
|
||||||
await state.clear()
|
await state.clear()
|
||||||
|
|
||||||
|
|
||||||
@admin.message(AdminProtect(), Command('add_item'))
|
@admin.message(AdminProtect(), Command('add_item'))
|
||||||
async def add_item(message: Message, state: FSMContext):
|
async def add_item(message: Message, state: FSMContext):
|
||||||
await state.set_state(AddItem.name)
|
await state.set_state(AddItem.name)
|
||||||
await message.answer('Введите название товара')
|
await message.answer('Введите название товара')
|
||||||
|
|
||||||
|
|
||||||
@admin.message(AdminProtect(), AddItem.name)
|
@admin.message(AdminProtect(), AddItem.name)
|
||||||
async def add_item_name(message: Message, state: FSMContext):
|
async def add_item_name(message: Message, state: FSMContext):
|
||||||
await state.update_data(name=message.text)
|
await state.update_data(name=message.text)
|
||||||
await state.set_state(AddItem.category)
|
await state.set_state(AddItem.category)
|
||||||
await message.answer('Выберите категорию товара', reply_markup=await kb.categories())
|
await message.answer('Выберите категорию товара', reply_markup=await kb.categories())
|
||||||
|
|
||||||
|
|
||||||
@admin.callback_query(AdminProtect(), AddItem.category)
|
@admin.callback_query(AdminProtect(), AddItem.category)
|
||||||
async def add_item_category(callback: CallbackQuery, state: FSMContext):
|
async def add_item_category(callback: CallbackQuery, state: FSMContext):
|
||||||
await state.update_data(category=callback.data.split('_')[1])
|
await state.update_data(category=callback.data.split('_')[1])
|
||||||
await state.set_state(AddItem.description)
|
await state.set_state(AddItem.description)
|
||||||
await callback.answer('')
|
await callback.answer('')
|
||||||
await callback.message.answer('Введите описание товара')
|
await callback.message.answer('Введите описание товара')
|
||||||
|
|
||||||
|
|
||||||
@admin.message(AdminProtect(), AddItem.description)
|
@admin.message(AdminProtect(), AddItem.description)
|
||||||
async def add_item_description(message: Message, state: FSMContext):
|
async def add_item_description(message: Message, state: FSMContext):
|
||||||
await state.update_data(description=message.text)
|
await state.update_data(description=message.text)
|
||||||
await state.set_state(AddItem.photo)
|
await state.set_state(AddItem.photo)
|
||||||
await message.answer('Отправьте фото товара')
|
await message.answer('Отправьте фото товара')
|
||||||
|
|
||||||
|
|
||||||
@admin.message(AdminProtect(), AddItem.photo, F.photo)
|
@admin.message(AdminProtect(), AddItem.photo, F.photo)
|
||||||
async def add_item_photo(message: Message, state: FSMContext):
|
async def add_item_photo(message: Message, state: FSMContext):
|
||||||
await state.update_data(photo=message.photo[-1].file_id)
|
await state.update_data(photo=message.photo[-1].file_id)
|
||||||
await state.set_state(AddItem.price)
|
await state.set_state(AddItem.price)
|
||||||
await message.answer('Введите цену товара')
|
await message.answer('Введите цену товара')
|
||||||
|
|
||||||
|
|
||||||
@admin.message(AdminProtect(), AddItem.price)
|
@admin.message(AdminProtect(), AddItem.price)
|
||||||
async def add_item_price(message: Message, state: FSMContext):
|
async def add_item_price(message: Message, state: FSMContext):
|
||||||
await state.update_data(price=message.text)
|
await state.update_data(price=message.text)
|
||||||
data = await state.get_data()
|
data = await state.get_data()
|
||||||
await set_item(data)
|
await set_item(data)
|
||||||
await message.answer('Товар успешно добавлен')
|
await message.answer('Товар успешно добавлен')
|
||||||
await state.clear()
|
await state.clear()
|
||||||
0
doners/bot-market/app/database/__init__.py
Normal file
@@ -1,62 +1,62 @@
|
|||||||
from sqlalchemy import BigInteger, ForeignKey, String
|
from sqlalchemy import BigInteger, ForeignKey, String
|
||||||
from sqlalchemy.orm import Mapped, mapped_column, relationship, DeclarativeBase
|
from sqlalchemy.orm import Mapped, mapped_column, relationship, DeclarativeBase
|
||||||
from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine
|
from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine
|
||||||
|
|
||||||
from typing import List
|
from typing import List
|
||||||
from config import ENGINE, ECHO
|
from config import ENGINE, ECHO
|
||||||
|
|
||||||
engine = create_async_engine(url=ENGINE)#, echo=ECHO)
|
engine = create_async_engine(url=ENGINE)#, echo=ECHO)
|
||||||
|
|
||||||
async_session = async_sessionmaker(engine)
|
async_session = async_sessionmaker(engine)
|
||||||
|
|
||||||
|
|
||||||
class Base(AsyncAttrs, DeclarativeBase):
|
class Base(AsyncAttrs, DeclarativeBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class User(Base):
|
class User(Base):
|
||||||
__tablename__ = 'users'
|
__tablename__ = 'users'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
tg_id = mapped_column(BigInteger)
|
tg_id = mapped_column(BigInteger)
|
||||||
|
|
||||||
basket_rel: Mapped[List['Basket']] = relationship(back_populates='user_rel')
|
basket_rel: Mapped[List['Basket']] = relationship(back_populates='user_rel')
|
||||||
|
|
||||||
|
|
||||||
class Category(Base):
|
class Category(Base):
|
||||||
__tablename__ = 'categories'
|
__tablename__ = 'categories'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
name: Mapped[str] = mapped_column(String(50))
|
name: Mapped[str] = mapped_column(String(50))
|
||||||
|
|
||||||
item_rel: Mapped[List['Item']] = relationship(back_populates='category_rel')
|
item_rel: Mapped[List['Item']] = relationship(back_populates='category_rel')
|
||||||
|
|
||||||
|
|
||||||
class Item(Base):
|
class Item(Base):
|
||||||
__tablename__ = 'items'
|
__tablename__ = 'items'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
name: Mapped[str] = mapped_column(String(50))
|
name: Mapped[str] = mapped_column(String(50))
|
||||||
description: Mapped[str] = mapped_column(String(200))
|
description: Mapped[str] = mapped_column(String(200))
|
||||||
photo: Mapped[str] = mapped_column(String(200))
|
photo: Mapped[str] = mapped_column(String(200))
|
||||||
price: Mapped[int] = mapped_column()
|
price: Mapped[int] = mapped_column()
|
||||||
category: Mapped[int] = mapped_column(ForeignKey('categories.id'))
|
category: Mapped[int] = mapped_column(ForeignKey('categories.id'))
|
||||||
|
|
||||||
category_rel: Mapped['Category'] = relationship(back_populates='item_rel')
|
category_rel: Mapped['Category'] = relationship(back_populates='item_rel')
|
||||||
basket_rel: Mapped[List['Basket']] = relationship(back_populates='item_rel')
|
basket_rel: Mapped[List['Basket']] = relationship(back_populates='item_rel')
|
||||||
|
|
||||||
|
|
||||||
class Basket(Base):
|
class Basket(Base):
|
||||||
__tablename__ = 'basket'
|
__tablename__ = 'basket'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
user: Mapped[int] = mapped_column(ForeignKey('users.id'))
|
user: Mapped[int] = mapped_column(ForeignKey('users.id'))
|
||||||
item: Mapped[int] = mapped_column(ForeignKey('items.id'))
|
item: Mapped[int] = mapped_column(ForeignKey('items.id'))
|
||||||
|
|
||||||
user_rel: Mapped['User'] = relationship(back_populates='basket_rel')
|
user_rel: Mapped['User'] = relationship(back_populates='basket_rel')
|
||||||
item_rel: Mapped['Item'] = relationship(back_populates='basket_rel')
|
item_rel: Mapped['Item'] = relationship(back_populates='basket_rel')
|
||||||
|
|
||||||
|
|
||||||
async def async_main():
|
async def async_main():
|
||||||
async with engine.begin() as conn:
|
async with engine.begin() as conn:
|
||||||
await conn.run_sync(Base.metadata.create_all)
|
await conn.run_sync(Base.metadata.create_all)
|
||||||
@@ -1,64 +1,64 @@
|
|||||||
from app.database.models import User, Category, Item, Basket
|
from app.database.models import User, Category, Item, Basket
|
||||||
from app.database.models import async_session
|
from app.database.models import async_session
|
||||||
|
|
||||||
from sqlalchemy import select, update, delete
|
from sqlalchemy import select, update, delete
|
||||||
|
|
||||||
|
|
||||||
async def set_user(tg_id):
|
async def set_user(tg_id):
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
||||||
|
|
||||||
if not user:
|
if not user:
|
||||||
session.add(User(tg_id=tg_id))
|
session.add(User(tg_id=tg_id))
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
|
||||||
async def set_item(data):
|
async def set_item(data):
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
session.add(Item(**data))
|
session.add(Item(**data))
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
|
||||||
async def set_basket(tg_id, item_id):
|
async def set_basket(tg_id, item_id):
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
||||||
session.add(Basket(user=user.id, item=item_id))
|
session.add(Basket(user=user.id, item=item_id))
|
||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
|
||||||
async def get_basket(tg_id):
|
async def get_basket(tg_id):
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
||||||
basket = await session.scalars(select(Basket).where(Basket.user == user.id))
|
basket = await session.scalars(select(Basket).where(Basket.user == user.id))
|
||||||
return basket
|
return basket
|
||||||
|
|
||||||
|
|
||||||
async def get_users():
|
async def get_users():
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
users = await session.scalars(select(User))
|
users = await session.scalars(select(User))
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
|
||||||
async def get_categories():
|
async def get_categories():
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
categories = await session.scalars(select(Category))
|
categories = await session.scalars(select(Category))
|
||||||
return categories
|
return categories
|
||||||
|
|
||||||
|
|
||||||
async def get_items_by_category(category_id: int):
|
async def get_items_by_category(category_id: int):
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
items = await session.scalars(select(Item).where(Item.category == category_id))
|
items = await session.scalars(select(Item).where(Item.category == category_id))
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|
||||||
async def get_item_by_id(item_id: int):
|
async def get_item_by_id(item_id: int):
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
item = await session.scalar(select(Item).where(Item.id == item_id))
|
item = await session.scalar(select(Item).where(Item.id == item_id))
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
async def delete_basket(tg_id, item_id):
|
async def delete_basket(tg_id, item_id):
|
||||||
async with async_session() as session:
|
async with async_session() as session:
|
||||||
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
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.execute(delete(Basket).where(Basket.user == user.id, Basket.item == item_id))
|
||||||
await session.commit()
|
await session.commit()
|
||||||
@@ -1,72 +1,72 @@
|
|||||||
from aiogram import Router, F
|
from aiogram import Router, F
|
||||||
from aiogram.types import Message, CallbackQuery
|
from aiogram.types import Message, CallbackQuery
|
||||||
from aiogram.filters import CommandStart, Command
|
from aiogram.filters import CommandStart, Command
|
||||||
|
|
||||||
import app.keyboards as kb
|
import app.keyboards as kb
|
||||||
from app.database.requests import (get_item_by_id, set_user,
|
from app.database.requests import (get_item_by_id, set_user,
|
||||||
set_basket, get_basket, get_item_by_id, delete_basket)
|
set_basket, get_basket, get_item_by_id, delete_basket)
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
@router.message(CommandStart())
|
@router.message(CommandStart())
|
||||||
@router.callback_query(F.data == 'to_main')
|
@router.callback_query(F.data == 'to_main')
|
||||||
async def cmd_start(message: Message | CallbackQuery):
|
async def cmd_start(message: Message | CallbackQuery):
|
||||||
if isinstance(message, Message):
|
if isinstance(message, Message):
|
||||||
await set_user(message.from_user.id)
|
await set_user(message.from_user.id)
|
||||||
await message.answer("Добро пожаловать в интернет магазин!",
|
await message.answer("Добро пожаловать в интернет магазин!",
|
||||||
reply_markup=kb.main)
|
reply_markup=kb.main)
|
||||||
else:
|
else:
|
||||||
await message.answer('Вы вернулись на главную')
|
await message.answer('Вы вернулись на главную')
|
||||||
await message.message.answer("Добро пожаловать в интернет магазин!",
|
await message.message.answer("Добро пожаловать в интернет магазин!",
|
||||||
reply_markup=kb.main)
|
reply_markup=kb.main)
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data == 'catalog')
|
@router.callback_query(F.data == 'catalog')
|
||||||
async def catalog(callback: CallbackQuery):
|
async def catalog(callback: CallbackQuery):
|
||||||
await callback.answer('')
|
await callback.answer('')
|
||||||
await callback.message.edit_text(text='Выберите категорию.',
|
await callback.message.edit_text(text='Выберите категорию.',
|
||||||
reply_markup=await kb.categories())
|
reply_markup=await kb.categories())
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith('category_'))
|
@router.callback_query(F.data.startswith('category_'))
|
||||||
async def category(callback: CallbackQuery):
|
async def category(callback: CallbackQuery):
|
||||||
await callback.answer('')
|
await callback.answer('')
|
||||||
await callback.message.edit_text('Выберите товар',
|
await callback.message.edit_text('Выберите товар',
|
||||||
reply_markup=await kb.items(callback.data.split('_')[1]))
|
reply_markup=await kb.items(callback.data.split('_')[1]))
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith('item_'))
|
@router.callback_query(F.data.startswith('item_'))
|
||||||
async def category(callback: CallbackQuery):
|
async def category(callback: CallbackQuery):
|
||||||
item = await get_item_by_id(callback.data.split('_')[1])
|
item = await get_item_by_id(callback.data.split('_')[1])
|
||||||
await callback.answer('')
|
await callback.answer('')
|
||||||
await callback.message.answer_photo(photo=item.photo,
|
await callback.message.answer_photo(photo=item.photo,
|
||||||
caption=f'{item.name}\n\n{item.description}\n\nЦена: {item.price} рублей',
|
caption=f'{item.name}\n\n{item.description}\n\nЦена: {item.price} рублей',
|
||||||
reply_markup=await kb.basket(item.id))
|
reply_markup=await kb.basket(item.id))
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith('order_'))
|
@router.callback_query(F.data.startswith('order_'))
|
||||||
async def basket(callback: CallbackQuery):
|
async def basket(callback: CallbackQuery):
|
||||||
await set_basket(callback.from_user.id, callback.data.split('_')[1])
|
await set_basket(callback.from_user.id, callback.data.split('_')[1])
|
||||||
await callback.answer('Товар добавлен в корзину')
|
await callback.answer('Товар добавлен в корзину')
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data == 'mybasket')
|
@router.callback_query(F.data == 'mybasket')
|
||||||
async def mybasket(callback: CallbackQuery):
|
async def mybasket(callback: CallbackQuery):
|
||||||
await callback.answer('')
|
await callback.answer('')
|
||||||
basket = await get_basket(callback.from_user.id)
|
basket = await get_basket(callback.from_user.id)
|
||||||
counter = 0
|
counter = 0
|
||||||
for item_info in basket:
|
for item_info in basket:
|
||||||
item = await get_item_by_id(item_info.item)
|
item = await get_item_by_id(item_info.item)
|
||||||
await callback.message.answer_photo(photo=item.photo,
|
await callback.message.answer_photo(photo=item.photo,
|
||||||
caption=f'{item.name}\n\n{item.description}\n\nЦена: {item.price} рублей',
|
caption=f'{item.name}\n\n{item.description}\n\nЦена: {item.price} рублей',
|
||||||
reply_markup=await kb.delete_from_basket(item.id))
|
reply_markup=await kb.delete_from_basket(item.id))
|
||||||
counter += 1
|
counter += 1
|
||||||
await callback.message.answer('Ваша корзина пуста') if counter == 0 else await callback.answer('')
|
await callback.message.answer('Ваша корзина пуста') if counter == 0 else await callback.answer('')
|
||||||
|
|
||||||
|
|
||||||
@router.callback_query(F.data.startswith('delete_'))
|
@router.callback_query(F.data.startswith('delete_'))
|
||||||
async def delete_from_basket(callback: CallbackQuery):
|
async def delete_from_basket(callback: CallbackQuery):
|
||||||
await delete_basket(callback.from_user.id, callback.data.split('_')[1])
|
await delete_basket(callback.from_user.id, callback.data.split('_')[1])
|
||||||
await callback.message.delete()
|
await callback.message.delete()
|
||||||
await callback.answer('Вы удалили товар из корзины')
|
await callback.answer('Вы удалили товар из корзины')
|
||||||
@@ -1,49 +1,49 @@
|
|||||||
|
|
||||||
from aiogram.types import (ReplyKeyboardMarkup, KeyboardButton,
|
from aiogram.types import (ReplyKeyboardMarkup, KeyboardButton,
|
||||||
InlineKeyboardMarkup, InlineKeyboardButton)
|
InlineKeyboardMarkup, InlineKeyboardButton)
|
||||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||||
|
|
||||||
from app.database.requests import get_categories, get_items_by_category
|
from app.database.requests import get_categories, get_items_by_category
|
||||||
|
|
||||||
main = InlineKeyboardMarkup(inline_keyboard=[
|
main = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text='Каталог', callback_data='catalog')],
|
[InlineKeyboardButton(text='Каталог', callback_data='catalog')],
|
||||||
[InlineKeyboardButton(text='Корзина', callback_data='mybasket'),
|
[InlineKeyboardButton(text='Корзина', callback_data='mybasket'),
|
||||||
InlineKeyboardButton(text='Контакты', callback_data='contacts')]
|
InlineKeyboardButton(text='Контакты', callback_data='contacts')]
|
||||||
])
|
])
|
||||||
|
|
||||||
to_main = InlineKeyboardMarkup(inline_keyboard=[
|
to_main = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text='На главную', callback_data='to_main')]
|
[InlineKeyboardButton(text='На главную', callback_data='to_main')]
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
||||||
async def delete_from_basket(order_id):
|
async def delete_from_basket(order_id):
|
||||||
keyboard = InlineKeyboardBuilder()
|
keyboard = InlineKeyboardBuilder()
|
||||||
keyboard.add(InlineKeyboardButton(text='Удалить из корзины', callback_data=f'delete_{order_id}'))
|
keyboard.add(InlineKeyboardButton(text='Удалить из корзины', callback_data=f'delete_{order_id}'))
|
||||||
return keyboard.adjust(2).as_markup()
|
return keyboard.adjust(2).as_markup()
|
||||||
|
|
||||||
|
|
||||||
async def basket(order_id):
|
async def basket(order_id):
|
||||||
keyboard = InlineKeyboardBuilder()
|
keyboard = InlineKeyboardBuilder()
|
||||||
keyboard.add(InlineKeyboardButton(text='Оформить заказ', callback_data=f'order_{order_id}'))
|
keyboard.add(InlineKeyboardButton(text='Оформить заказ', callback_data=f'order_{order_id}'))
|
||||||
keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main'))
|
keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main'))
|
||||||
return keyboard.adjust(2).as_markup()
|
return keyboard.adjust(2).as_markup()
|
||||||
|
|
||||||
|
|
||||||
async def categories():
|
async def categories():
|
||||||
all_categories = await get_categories()
|
all_categories = await get_categories()
|
||||||
keyboard = InlineKeyboardBuilder()
|
keyboard = InlineKeyboardBuilder()
|
||||||
for category in all_categories:
|
for category in all_categories:
|
||||||
keyboard.add(InlineKeyboardButton(text=category.name,
|
keyboard.add(InlineKeyboardButton(text=category.name,
|
||||||
callback_data=f'category_{category.id}'))
|
callback_data=f'category_{category.id}'))
|
||||||
keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main'))
|
keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main'))
|
||||||
return keyboard.adjust(2).as_markup()
|
return keyboard.adjust(2).as_markup()
|
||||||
|
|
||||||
|
|
||||||
async def items(category_id: int):
|
async def items(category_id: int):
|
||||||
items = await get_items_by_category(category_id)
|
items = await get_items_by_category(category_id)
|
||||||
keyboard = InlineKeyboardBuilder()
|
keyboard = InlineKeyboardBuilder()
|
||||||
for item in items:
|
for item in items:
|
||||||
keyboard.add(InlineKeyboardButton(text=item.name,
|
keyboard.add(InlineKeyboardButton(text=item.name,
|
||||||
callback_data=f"item_{item.id}"))
|
callback_data=f"item_{item.id}"))
|
||||||
keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main'))
|
keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main'))
|
||||||
return keyboard.adjust(2).as_markup()
|
return keyboard.adjust(2).as_markup()
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
TOKEN='7103505936:AAEpiQxlKNd9Uji9IziTdNzSTH38PavqXZM'
|
TOKEN='7103505936:AAEpiQxlKNd9Uji9IziTdNzSTH38PavqXZM'
|
||||||
ENGINE='sqlite+aiosqlite:///db.sqlite3'
|
ENGINE='sqlite+aiosqlite:///db.sqlite3'
|
||||||
ECHO=''
|
ECHO=''
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher
|
from aiogram import Bot, Dispatcher
|
||||||
|
|
||||||
from config import TOKEN
|
from config import TOKEN
|
||||||
from app.database.models import async_main
|
from app.database.models import async_main
|
||||||
from app.handlers import router
|
from app.handlers import router
|
||||||
from app.admin import admin
|
from app.admin import admin
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
await async_main()
|
await async_main()
|
||||||
bot = Bot(token=TOKEN)
|
bot = Bot(token=TOKEN)
|
||||||
dp = Dispatcher()
|
dp = Dispatcher()
|
||||||
dp.include_routers(admin, router)
|
dp.include_routers(admin, router)
|
||||||
await dp.start_polling(bot)
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
try:
|
try:
|
||||||
asyncio.run(main())
|
asyncio.run(main())
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print('Exit')
|
print('Exit')
|
||||||
0
doners/old/__init__.py
Normal file
@@ -1,103 +1,103 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from aiogram import Bot, Dispatcher, types
|
from aiogram import Bot, Dispatcher, types
|
||||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
from aiogram.utils import executor
|
from aiogram.utils import executor
|
||||||
from aiogram.dispatcher.filters import Text
|
from aiogram.dispatcher.filters import Text
|
||||||
from database import create_tables
|
from database import create_tables
|
||||||
from logger import log_action
|
from logger import log_action
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
# Загрузка переменных окружения из .env файла
|
# Загрузка переменных окружения из .env файла
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
||||||
OPERATORS_GROUP_ID = os.getenv('OPERATORS_GROUP_ID')
|
OPERATORS_GROUP_ID = os.getenv('OPERATORS_GROUP_ID')
|
||||||
ADMIN_GROUP_ID = os.getenv('ADMIN_GROUP_ID')
|
ADMIN_GROUP_ID = os.getenv('ADMIN_GROUP_ID')
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
dp.middleware.setup(LoggingMiddleware())
|
dp.middleware.setup(LoggingMiddleware())
|
||||||
|
|
||||||
# Inline клавиатуры
|
# Inline клавиатуры
|
||||||
def main_menu_keyboard():
|
def main_menu_keyboard():
|
||||||
keyboard = InlineKeyboardMarkup()
|
keyboard = InlineKeyboardMarkup()
|
||||||
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
||||||
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
||||||
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
||||||
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
||||||
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
||||||
return keyboard
|
return keyboard
|
||||||
|
|
||||||
def account_data_keyboard():
|
def account_data_keyboard():
|
||||||
keyboard = InlineKeyboardMarkup()
|
keyboard = InlineKeyboardMarkup()
|
||||||
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
||||||
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
||||||
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
||||||
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
||||||
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
||||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||||
return keyboard
|
return keyboard
|
||||||
|
|
||||||
def order_keyboard():
|
def order_keyboard():
|
||||||
keyboard = InlineKeyboardMarkup()
|
keyboard = InlineKeyboardMarkup()
|
||||||
keyboard.add(InlineKeyboardButton('Добавить учетные данные', callback_data='add_account_data'))
|
keyboard.add(InlineKeyboardButton('Добавить учетные данные', callback_data='add_account_data'))
|
||||||
keyboard.add(InlineKeyboardButton('Выбрать адрес', callback_data='select_address'))
|
keyboard.add(InlineKeyboardButton('Выбрать адрес', callback_data='select_address'))
|
||||||
keyboard.add(InlineKeyboardButton('Выбрать время для уборки', callback_data='select_time'))
|
keyboard.add(InlineKeyboardButton('Выбрать время для уборки', callback_data='select_time'))
|
||||||
keyboard.add(InlineKeyboardButton('Выбрать тип уборки', callback_data='select_cleaning_type'))
|
keyboard.add(InlineKeyboardButton('Выбрать тип уборки', callback_data='select_cleaning_type'))
|
||||||
keyboard.add(InlineKeyboardButton('Выбрать способ оплаты', callback_data='select_payment_method'))
|
keyboard.add(InlineKeyboardButton('Выбрать способ оплаты', callback_data='select_payment_method'))
|
||||||
keyboard.add(InlineKeyboardButton('Подтвердить заказ', callback_data='confirm_order'))
|
keyboard.add(InlineKeyboardButton('Подтвердить заказ', callback_data='confirm_order'))
|
||||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||||
return keyboard
|
return keyboard
|
||||||
|
|
||||||
# Обработчики команд
|
# Обработчики команд
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands=['start'])
|
||||||
async def send_welcome(message: types.Message):
|
async def send_welcome(message: types.Message):
|
||||||
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
||||||
log_action(message.from_user.id, 'start')
|
log_action(message.from_user.id, 'start')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
||||||
async def process_account_data(callback_query: types.CallbackQuery):
|
async def process_account_data(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard())
|
await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard())
|
||||||
log_action(callback_query.from_user.id, 'account_data')
|
log_action(callback_query.from_user.id, 'account_data')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
||||||
async def process_make_order(callback_query: types.CallbackQuery):
|
async def process_make_order(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Сделать заказ:", reply_markup=order_keyboard())
|
await bot.send_message(callback_query.from_user.id, "Сделать заказ:", reply_markup=order_keyboard())
|
||||||
log_action(callback_query.from_user.id, 'make_order')
|
log_action(callback_query.from_user.id, 'make_order')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
||||||
async def process_order_history(callback_query: types.CallbackQuery):
|
async def process_order_history(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
# Добавьте логику для просмотра истории заказов
|
# Добавьте логику для просмотра истории заказов
|
||||||
await bot.send_message(callback_query.from_user.id, "История заказов:")
|
await bot.send_message(callback_query.from_user.id, "История заказов:")
|
||||||
log_action(callback_query.from_user.id, 'order_history')
|
log_action(callback_query.from_user.id, 'order_history')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
||||||
async def process_request_call(callback_query: types.CallbackQuery):
|
async def process_request_call(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
# Добавьте логику для заказа звонка
|
# Добавьте логику для заказа звонка
|
||||||
await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
||||||
log_action(callback_query.from_user.id, 'request_call')
|
log_action(callback_query.from_user.id, 'request_call')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
||||||
async def process_talk_operator(callback_query: types.CallbackQuery):
|
async def process_talk_operator(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
# Добавьте логику для начала разговора с оператором
|
# Добавьте логику для начала разговора с оператором
|
||||||
await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.")
|
await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.")
|
||||||
log_action(callback_query.from_user.id, 'talk_operator')
|
log_action(callback_query.from_user.id, 'talk_operator')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
||||||
async def process_back_to_main(callback_query: types.CallbackQuery):
|
async def process_back_to_main(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard())
|
await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard())
|
||||||
log_action(callback_query.from_user.id, 'back_to_main')
|
log_action(callback_query.from_user.id, 'back_to_main')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
create_tables()
|
create_tables()
|
||||||
executor.start_polling(dp, skip_updates=True)
|
executor.start_polling(dp, skip_updates=True)
|
||||||
|
|||||||
@@ -1,254 +1,254 @@
|
|||||||
import logging
|
import logging
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from aiogram import types
|
from aiogram import types
|
||||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
from aiogram.dispatcher import FSMContext
|
from aiogram.dispatcher import FSMContext
|
||||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||||
from aiogram.utils import executor
|
from aiogram.utils import executor
|
||||||
from aiogram import Bot, Dispatcher
|
from aiogram import Bot, Dispatcher
|
||||||
from aiogram.dispatcher.filters import Text
|
from aiogram.dispatcher.filters import Text
|
||||||
from database import create_tables, add_user, update_user_name, update_user_phone, add_address, delete_address
|
from database import create_tables, add_user, update_user_name, update_user_phone, add_address, delete_address
|
||||||
from logger import log_action
|
from logger import log_action
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Загрузка переменных окружения из .env файла
|
# Загрузка переменных окружения из .env файла
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
||||||
OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID'))
|
OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID'))
|
||||||
ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID'))
|
ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID'))
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
dp.middleware.setup(LoggingMiddleware())
|
dp.middleware.setup(LoggingMiddleware())
|
||||||
|
|
||||||
# Состояния
|
# Состояния
|
||||||
class OrderForm(StatesGroup):
|
class OrderForm(StatesGroup):
|
||||||
waiting_for_name = State()
|
waiting_for_name = State()
|
||||||
waiting_for_phone = State()
|
waiting_for_phone = State()
|
||||||
waiting_for_address = State()
|
waiting_for_address = State()
|
||||||
waiting_for_cleaning_time = State()
|
waiting_for_cleaning_time = State()
|
||||||
waiting_for_cleaning_type = State()
|
waiting_for_cleaning_type = State()
|
||||||
waiting_for_payment_method = State()
|
waiting_for_payment_method = State()
|
||||||
confirmation = State()
|
confirmation = State()
|
||||||
|
|
||||||
class CallRequest(StatesGroup):
|
class CallRequest(StatesGroup):
|
||||||
waiting_for_name = State()
|
waiting_for_name = State()
|
||||||
waiting_for_phone = State()
|
waiting_for_phone = State()
|
||||||
|
|
||||||
class OperatorChat(StatesGroup):
|
class OperatorChat(StatesGroup):
|
||||||
in_chat = State()
|
in_chat = State()
|
||||||
|
|
||||||
# Inline клавиатуры
|
# Inline клавиатуры
|
||||||
def main_menu_keyboard():
|
def main_menu_keyboard():
|
||||||
keyboard = InlineKeyboardMarkup()
|
keyboard = InlineKeyboardMarkup()
|
||||||
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
||||||
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
||||||
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
||||||
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
||||||
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
||||||
return keyboard
|
return keyboard
|
||||||
|
|
||||||
def account_data_keyboard():
|
def account_data_keyboard():
|
||||||
keyboard = InlineKeyboardMarkup()
|
keyboard = InlineKeyboardMarkup()
|
||||||
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
||||||
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
||||||
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
||||||
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
||||||
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
||||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||||
return keyboard
|
return keyboard
|
||||||
|
|
||||||
def order_keyboard():
|
def order_keyboard():
|
||||||
keyboard = InlineKeyboardMarkup()
|
keyboard = InlineKeyboardMarkup()
|
||||||
keyboard.add(InlineKeyboardButton('Добавить учетные данные', callback_data='add_account_data'))
|
keyboard.add(InlineKeyboardButton('Добавить учетные данные', callback_data='add_account_data'))
|
||||||
keyboard.add(InlineKeyboardButton('Выбрать адрес', callback_data='select_address'))
|
keyboard.add(InlineKeyboardButton('Выбрать адрес', callback_data='select_address'))
|
||||||
keyboard.add(InlineKeyboardButton('Выбрать время для уборки', callback_data='select_time'))
|
keyboard.add(InlineKeyboardButton('Выбрать время для уборки', callback_data='select_time'))
|
||||||
keyboard.add(InlineKeyboardButton('Выбрать тип уборки', callback_data='select_cleaning_type'))
|
keyboard.add(InlineKeyboardButton('Выбрать тип уборки', callback_data='select_cleaning_type'))
|
||||||
keyboard.add(InlineKeyboardButton('Выбрать способ оплаты', callback_data='select_payment_method'))
|
keyboard.add(InlineKeyboardButton('Выбрать способ оплаты', callback_data='select_payment_method'))
|
||||||
keyboard.add(InlineKeyboardButton('Подтвердить заказ', callback_data='confirm_order'))
|
keyboard.add(InlineKeyboardButton('Подтвердить заказ', callback_data='confirm_order'))
|
||||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||||
return keyboard
|
return keyboard
|
||||||
|
|
||||||
# Основные команды
|
# Основные команды
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands=['start'])
|
||||||
async def send_welcome(message: types.Message):
|
async def send_welcome(message: types.Message):
|
||||||
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
||||||
log_action(message.from_user.id, 'start')
|
log_action(message.from_user.id, 'start')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
||||||
async def process_account_data(callback_query: types.CallbackQuery):
|
async def process_account_data(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard())
|
await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard())
|
||||||
log_action(callback_query.from_user.id, 'account_data')
|
log_action(callback_query.from_user.id, 'account_data')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
||||||
async def process_make_order(callback_query: types.CallbackQuery):
|
async def process_make_order(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Сделать заказ:", reply_markup=order_keyboard())
|
await bot.send_message(callback_query.from_user.id, "Сделать заказ:", reply_markup=order_keyboard())
|
||||||
log_action(callback_query.from_user.id, 'make_order')
|
log_action(callback_query.from_user.id, 'make_order')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
||||||
async def process_order_history(callback_query: types.CallbackQuery):
|
async def process_order_history(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
# Добавьте логику для просмотра истории заказов
|
# Добавьте логику для просмотра истории заказов
|
||||||
await bot.send_message(callback_query.from_user.id, "История заказов:")
|
await bot.send_message(callback_query.from_user.id, "История заказов:")
|
||||||
log_action(callback_query.from_user.id, 'order_history')
|
log_action(callback_query.from_user.id, 'order_history')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
||||||
async def process_request_call(callback_query: types.CallbackQuery):
|
async def process_request_call(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
# Добавьте логику для заказа звонка
|
# Добавьте логику для заказа звонка
|
||||||
await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
||||||
log_action(callback_query.from_user.id, 'request_call')
|
log_action(callback_query.from_user.id, 'request_call')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
||||||
async def process_talk_operator(callback_query: types.CallbackQuery):
|
async def process_talk_operator(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
# Добавьте логику для начала разговора с оператором
|
# Добавьте логику для начала разговора с оператором
|
||||||
await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.")
|
await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.")
|
||||||
log_action(callback_query.from_user.id, 'talk_operator')
|
log_action(callback_query.from_user.id, 'talk_operator')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
||||||
async def process_back_to_main(callback_query: types.CallbackQuery):
|
async def process_back_to_main(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard())
|
await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard())
|
||||||
log_action(callback_query.from_user.id, 'back_to_main')
|
log_action(callback_query.from_user.id, 'back_to_main')
|
||||||
|
|
||||||
# Добавление учетных данных
|
# Добавление учетных данных
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'add_account_data')
|
@dp.callback_query_handler(lambda c: c.data == 'add_account_data')
|
||||||
async def process_add_account_data(callback_query: types.CallbackQuery):
|
async def process_add_account_data(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Введите ваше ФИО:")
|
await bot.send_message(callback_query.from_user.id, "Введите ваше ФИО:")
|
||||||
await OrderForm.waiting_for_name.set()
|
await OrderForm.waiting_for_name.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT)
|
@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT)
|
||||||
async def process_name(message: types.Message, state: FSMContext):
|
async def process_name(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['name'] = message.text
|
data['name'] = message.text
|
||||||
await message.answer("Введите ваш номер телефона:")
|
await message.answer("Введите ваш номер телефона:")
|
||||||
await OrderForm.waiting_for_phone.set()
|
await OrderForm.waiting_for_phone.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT)
|
@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT)
|
||||||
async def process_phone(message: types.Message, state: FSMContext):
|
async def process_phone(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['phone'] = message.text
|
data['phone'] = message.text
|
||||||
user_id = message.from_user.id
|
user_id = message.from_user.id
|
||||||
name = data['name']
|
name = data['name']
|
||||||
phone = data['phone']
|
phone = data['phone']
|
||||||
add_user(user_id, name, phone)
|
add_user(user_id, name, phone)
|
||||||
await message.answer("Ваши данные успешно сохранены!")
|
await message.answer("Ваши данные успешно сохранены!")
|
||||||
await state.finish()
|
await state.finish()
|
||||||
|
|
||||||
# Изменение учетных данных
|
# Изменение учетных данных
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'change_name')
|
@dp.callback_query_handler(lambda c: c.data == 'change_name')
|
||||||
async def process_change_name(callback_query: types.CallbackQuery):
|
async def process_change_name(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Введите новое ФИО:")
|
await bot.send_message(callback_query.from_user.id, "Введите новое ФИО:")
|
||||||
await OrderForm.waiting_for_name.set()
|
await OrderForm.waiting_for_name.set()
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'change_phone')
|
@dp.callback_query_handler(lambda c: c.data == 'change_phone')
|
||||||
async def process_change_phone(callback_query: types.CallbackQuery):
|
async def process_change_phone(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Введите новый номер телефона:")
|
await bot.send_message(callback_query.from_user.id, "Введите новый номер телефона:")
|
||||||
await OrderForm.waiting_for_phone.set()
|
await OrderForm.waiting_for_phone.set()
|
||||||
|
|
||||||
# Добавление адреса
|
# Добавление адреса
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'add_address')
|
@dp.callback_query_handler(lambda c: c.data == 'add_address')
|
||||||
async def process_add_address(callback_query: types.CallbackQuery):
|
async def process_add_address(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Введите адрес для добавления:")
|
await bot.send_message(callback_query.from_user.id, "Введите адрес для добавления:")
|
||||||
await OrderForm.waiting_for_address.set()
|
await OrderForm.waiting_for_address.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||||
async def process_add_address_step(message: types.Message, state: FSMContext):
|
async def process_add_address_step(message: types.Message, state: FSMContext):
|
||||||
user_id = message.from_user.id
|
user_id = message.from_user.id
|
||||||
address = message.text
|
address = message.text
|
||||||
add_address(user_id, address)
|
add_address(user_id, address)
|
||||||
await message.answer("Адрес успешно добавлен!")
|
await message.answer("Адрес успешно добавлен!")
|
||||||
await state.finish()
|
await state.finish()
|
||||||
|
|
||||||
# Удаление адреса
|
# Удаление адреса
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'delete_address')
|
@dp.callback_query_handler(lambda c: c.data == 'delete_address')
|
||||||
async def process_delete_address(callback_query: types.CallbackQuery):
|
async def process_delete_address(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Введите адрес для удаления:")
|
await bot.send_message(callback_query.from_user.id, "Введите адрес для удаления:")
|
||||||
await OrderForm.waiting_for_address.set()
|
await OrderForm.waiting_for_address.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||||
async def process_delete_address_step(message: types.Message, state: FSMContext):
|
async def process_delete_address_step(message: types.Message, state: FSMContext):
|
||||||
user_id = message.from_user.id
|
user_id = message.from_user.id
|
||||||
address = message.text
|
address = message.text
|
||||||
delete_address(user_id, address)
|
delete_address(user_id, address)
|
||||||
await message.answer("Адрес успешно удален!")
|
await message.answer("Адрес успешно удален!")
|
||||||
await state.finish()
|
await state.finish()
|
||||||
|
|
||||||
# Обработка заказа
|
# Обработка заказа
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'select_address')
|
@dp.callback_query_handler(lambda c: c.data == 'select_address')
|
||||||
async def process_select_address(callback_query: types.CallbackQuery):
|
async def process_select_address(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Введите адрес для уборки:")
|
await bot.send_message(callback_query.from_user.id, "Введите адрес для уборки:")
|
||||||
await OrderForm.waiting_for_address.set()
|
await OrderForm.waiting_for_address.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||||
async def process_order_address(message: types.Message, state: FSMContext):
|
async def process_order_address(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['address'] = message.text
|
data['address'] = message.text
|
||||||
await message.answer("Выберите время для уборки (утро/день/вечер):")
|
await message.answer("Выберите время для уборки (утро/день/вечер):")
|
||||||
await OrderForm.waiting_for_cleaning_time.set()
|
await OrderForm.waiting_for_cleaning_time.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT)
|
@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 def process_cleaning_time(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['cleaning_time'] = message.text
|
data['cleaning_time'] = message.text
|
||||||
await message.answer("Выберите тип уборки (влажная/сухая/генеральная):")
|
await message.answer("Выберите тип уборки (влажная/сухая/генеральная):")
|
||||||
await OrderForm.waiting_for_cleaning_type.set()
|
await OrderForm.waiting_for_cleaning_type.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT)
|
@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 def process_cleaning_type(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['cleaning_type'] = message.text
|
data['cleaning_type'] = message.text
|
||||||
await message.answer("Выберите способ оплаты (картой/наличными):")
|
await message.answer("Выберите способ оплаты (картой/наличными):")
|
||||||
await OrderForm.waiting_for_payment_method.set()
|
await OrderForm.waiting_for_payment_method.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT)
|
@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 def process_payment_method(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['payment_method'] = message.text
|
data['payment_method'] = message.text
|
||||||
|
|
||||||
# Отправка подтверждения заказа
|
# Отправка подтверждения заказа
|
||||||
await message.answer("Подтвердите заказ:\n"
|
await message.answer("Подтвердите заказ:\n"
|
||||||
f"Адрес: {data['address']}\n"
|
f"Адрес: {data['address']}\n"
|
||||||
f"Время уборки: {data['cleaning_time']}\n"
|
f"Время уборки: {data['cleaning_time']}\n"
|
||||||
f"Тип уборки: {data['cleaning_type']}\n"
|
f"Тип уборки: {data['cleaning_type']}\n"
|
||||||
f"Способ оплаты: {data['payment_method']}\n"
|
f"Способ оплаты: {data['payment_method']}\n"
|
||||||
"Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.",
|
"Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.",
|
||||||
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order')))
|
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order')))
|
||||||
await OrderForm.confirmation.set()
|
await OrderForm.confirmation.set()
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation)
|
@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation)
|
||||||
async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext):
|
async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
order_details = (f"Новый заказ:\n"
|
order_details = (f"Новый заказ:\n"
|
||||||
f"ФИО: {data['name']}\n"
|
f"ФИО: {data['name']}\n"
|
||||||
f"Номер телефона: {data['phone']}\n"
|
f"Номер телефона: {data['phone']}\n"
|
||||||
f"Адрес: {data['address']}\n"
|
f"Адрес: {data['address']}\n"
|
||||||
f"Время уборки: {data['cleaning_time']}\n"
|
f"Время уборки: {data['cleaning_time']}\n"
|
||||||
f"Тип уборки: {data['cleaning_type']}\n"
|
f"Тип уборки: {data['cleaning_type']}\n"
|
||||||
f"Способ оплаты: {data['payment_method']}")
|
f"Способ оплаты: {data['payment_method']}")
|
||||||
|
|
||||||
await bot.send_message(OPERATORS_GROUP_ID, order_details)
|
await bot.send_message(OPERATORS_GROUP_ID, order_details)
|
||||||
log_action(callback_query.from_user.id, 'confirm_order')
|
log_action(callback_query.from_user.id, 'confirm_order')
|
||||||
|
|
||||||
await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.")
|
await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.")
|
||||||
await state.finish()
|
await state.finish()
|
||||||
|
|
||||||
# Основное приложение
|
# Основное приложение
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
create_tables()
|
create_tables()
|
||||||
executor.start_polling(dp, skip_updates=True)
|
executor.start_polling(dp, skip_updates=True)
|
||||||
|
|||||||
@@ -1,89 +1,89 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
|
|
||||||
def create_tables():
|
def create_tables():
|
||||||
conn = sqlite3.connect('bot_klining.db')
|
conn = sqlite3.connect('bot_klining.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
telegram_id INTEGER UNIQUE,
|
telegram_id INTEGER UNIQUE,
|
||||||
name TEXT,
|
name TEXT,
|
||||||
phone TEXT
|
phone TEXT
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS addresses (
|
CREATE TABLE IF NOT EXISTS addresses (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
user_id INTEGER,
|
user_id INTEGER,
|
||||||
address TEXT,
|
address TEXT,
|
||||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS orders (
|
CREATE TABLE IF NOT EXISTS orders (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
user_id INTEGER,
|
user_id INTEGER,
|
||||||
address TEXT,
|
address TEXT,
|
||||||
cleaning_time TEXT,
|
cleaning_time TEXT,
|
||||||
cleaning_type TEXT,
|
cleaning_type TEXT,
|
||||||
payment_method TEXT,
|
payment_method TEXT,
|
||||||
order_id TEXT UNIQUE,
|
order_id TEXT UNIQUE,
|
||||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS logs (
|
CREATE TABLE IF NOT EXISTS logs (
|
||||||
id INTEGER PRIMARY KEY,
|
id INTEGER PRIMARY KEY,
|
||||||
user_id INTEGER,
|
user_id INTEGER,
|
||||||
action TEXT,
|
action TEXT,
|
||||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def add_user(telegram_id, name, phone):
|
def add_user(telegram_id, name, phone):
|
||||||
conn = sqlite3.connect('bot_klining.db')
|
conn = sqlite3.connect('bot_klining.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('INSERT OR IGNORE INTO users (telegram_id, name, phone) VALUES (?, ?, ?)', (telegram_id, name, phone))
|
cursor.execute('INSERT OR IGNORE INTO users (telegram_id, name, phone) VALUES (?, ?, ?)', (telegram_id, name, phone))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def update_user_name(telegram_id, name):
|
def update_user_name(telegram_id, name):
|
||||||
conn = sqlite3.connect('bot_klining.db')
|
conn = sqlite3.connect('bot_klining.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('UPDATE users SET name = ? WHERE telegram_id = ?', (name, telegram_id))
|
cursor.execute('UPDATE users SET name = ? WHERE telegram_id = ?', (name, telegram_id))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def update_user_phone(telegram_id, phone):
|
def update_user_phone(telegram_id, phone):
|
||||||
conn = sqlite3.connect('bot_klining.db')
|
conn = sqlite3.connect('bot_klining.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('UPDATE users SET phone = ? WHERE telegram_id = ?', (phone, telegram_id))
|
cursor.execute('UPDATE users SET phone = ? WHERE telegram_id = ?', (phone, telegram_id))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def add_address(telegram_id, address):
|
def add_address(telegram_id, address):
|
||||||
conn = sqlite3.connect('bot_klining.db')
|
conn = sqlite3.connect('bot_klining.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('SELECT id FROM users WHERE telegram_id = ?', (telegram_id,))
|
cursor.execute('SELECT id FROM users WHERE telegram_id = ?', (telegram_id,))
|
||||||
user_id = cursor.fetchone()[0]
|
user_id = cursor.fetchone()[0]
|
||||||
cursor.execute('INSERT INTO addresses (user_id, address) VALUES (?, ?)', (user_id, address))
|
cursor.execute('INSERT INTO addresses (user_id, address) VALUES (?, ?)', (user_id, address))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def delete_address(telegram_id, address):
|
def delete_address(telegram_id, address):
|
||||||
conn = sqlite3.connect('bot_klining.db')
|
conn = sqlite3.connect('bot_klining.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('SELECT id FROM users WHERE telegram_id = ?', (telegram_id,))
|
cursor.execute('SELECT id FROM users WHERE telegram_id = ?', (telegram_id,))
|
||||||
user_id = cursor.fetchone()[0]
|
user_id = cursor.fetchone()[0]
|
||||||
cursor.execute('DELETE FROM addresses WHERE user_id = ? AND address = ?', (user_id, address))
|
cursor.execute('DELETE FROM addresses WHERE user_id = ? AND address = ?', (user_id, address))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -1,183 +1,183 @@
|
|||||||
import logging
|
import logging
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from aiogram import types
|
from aiogram import types
|
||||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
from aiogram.dispatcher import FSMContext
|
from aiogram.dispatcher import FSMContext
|
||||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||||
from aiogram.utils import executor
|
from aiogram.utils import executor
|
||||||
from aiogram import Bot, Dispatcher
|
from aiogram import Bot, Dispatcher
|
||||||
from aiogram.dispatcher.filters import Text
|
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 database import create_tables, add_user, update_user_name, update_user_phone, add_address, delete_address, add_order
|
||||||
from logger import log_action
|
from logger import log_action
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Загрузка переменных окружения из .env файла
|
# Загрузка переменных окружения из .env файла
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
||||||
OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID'))
|
OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID'))
|
||||||
ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID'))
|
ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID'))
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
dp.middleware.setup(LoggingMiddleware())
|
dp.middleware.setup(LoggingMiddleware())
|
||||||
|
|
||||||
# Состояния для заказа
|
# Состояния для заказа
|
||||||
class OrderForm(StatesGroup):
|
class OrderForm(StatesGroup):
|
||||||
waiting_for_name = State()
|
waiting_for_name = State()
|
||||||
waiting_for_phone = State()
|
waiting_for_phone = State()
|
||||||
waiting_for_address = State()
|
waiting_for_address = State()
|
||||||
waiting_for_cleaning_time = State()
|
waiting_for_cleaning_time = State()
|
||||||
waiting_for_cleaning_type = State()
|
waiting_for_cleaning_type = State()
|
||||||
waiting_for_payment_method = State()
|
waiting_for_payment_method = State()
|
||||||
confirmation = State()
|
confirmation = State()
|
||||||
|
|
||||||
# Inline клавиатуры
|
# Inline клавиатуры
|
||||||
def main_menu_keyboard():
|
def main_menu_keyboard():
|
||||||
keyboard = InlineKeyboardMarkup()
|
keyboard = InlineKeyboardMarkup()
|
||||||
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
||||||
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
||||||
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
||||||
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
||||||
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
||||||
return keyboard
|
return keyboard
|
||||||
|
|
||||||
def account_data_keyboard():
|
def account_data_keyboard():
|
||||||
keyboard = InlineKeyboardMarkup()
|
keyboard = InlineKeyboardMarkup()
|
||||||
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
||||||
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
||||||
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
||||||
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
||||||
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
||||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||||
return keyboard
|
return keyboard
|
||||||
|
|
||||||
# Основные команды
|
# Основные команды
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands=['start'])
|
||||||
async def send_welcome(message: types.Message):
|
async def send_welcome(message: types.Message):
|
||||||
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
||||||
log_action(message.from_user.id, 'start')
|
log_action(message.from_user.id, 'start')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
||||||
async def process_account_data(callback_query: types.CallbackQuery):
|
async def process_account_data(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard())
|
await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard())
|
||||||
log_action(callback_query.from_user.id, 'account_data')
|
log_action(callback_query.from_user.id, 'account_data')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
||||||
async def process_make_order(callback_query: types.CallbackQuery, state: FSMContext):
|
async def process_make_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Начнем с ваших учетных данных. Введите ваше ФИО:")
|
await bot.send_message(callback_query.from_user.id, "Начнем с ваших учетных данных. Введите ваше ФИО:")
|
||||||
await OrderForm.waiting_for_name.set()
|
await OrderForm.waiting_for_name.set()
|
||||||
log_action(callback_query.from_user.id, 'make_order')
|
log_action(callback_query.from_user.id, 'make_order')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
||||||
async def process_order_history(callback_query: types.CallbackQuery):
|
async def process_order_history(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
# Добавьте логику для просмотра истории заказов
|
# Добавьте логику для просмотра истории заказов
|
||||||
await bot.send_message(callback_query.from_user.id, "История заказов:")
|
await bot.send_message(callback_query.from_user.id, "История заказов:")
|
||||||
log_action(callback_query.from_user.id, 'order_history')
|
log_action(callback_query.from_user.id, 'order_history')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
||||||
async def process_request_call(callback_query: types.CallbackQuery):
|
async def process_request_call(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
# Добавьте логику для заказа звонка
|
# Добавьте логику для заказа звонка
|
||||||
await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
||||||
log_action(callback_query.from_user.id, 'request_call')
|
log_action(callback_query.from_user.id, 'request_call')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
||||||
async def process_talk_operator(callback_query: types.CallbackQuery):
|
async def process_talk_operator(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
# Добавьте логику для начала разговора с оператором
|
# Добавьте логику для начала разговора с оператором
|
||||||
await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.")
|
await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.")
|
||||||
log_action(callback_query.from_user.id, 'talk_operator')
|
log_action(callback_query.from_user.id, 'talk_operator')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
||||||
async def process_back_to_main(callback_query: types.CallbackQuery):
|
async def process_back_to_main(callback_query: types.CallbackQuery):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard())
|
await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard())
|
||||||
log_action(callback_query.from_user.id, 'back_to_main')
|
log_action(callback_query.from_user.id, 'back_to_main')
|
||||||
|
|
||||||
# Обработка шагов заказа
|
# Обработка шагов заказа
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT)
|
@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT)
|
||||||
async def process_name(message: types.Message, state: FSMContext):
|
async def process_name(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['name'] = message.text
|
data['name'] = message.text
|
||||||
await message.answer("Введите ваш номер телефона:")
|
await message.answer("Введите ваш номер телефона:")
|
||||||
await OrderForm.waiting_for_phone.set()
|
await OrderForm.waiting_for_phone.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT)
|
@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT)
|
||||||
async def process_phone(message: types.Message, state: FSMContext):
|
async def process_phone(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['phone'] = message.text
|
data['phone'] = message.text
|
||||||
await message.answer("Введите адрес для уборки:")
|
await message.answer("Введите адрес для уборки:")
|
||||||
await OrderForm.waiting_for_address.set()
|
await OrderForm.waiting_for_address.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||||
async def process_order_address(message: types.Message, state: FSMContext):
|
async def process_order_address(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['address'] = message.text
|
data['address'] = message.text
|
||||||
await message.answer("Выберите время для уборки (утро/день/вечер):")
|
await message.answer("Выберите время для уборки (утро/день/вечер):")
|
||||||
await OrderForm.waiting_for_cleaning_time.set()
|
await OrderForm.waiting_for_cleaning_time.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT)
|
@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 def process_cleaning_time(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['cleaning_time'] = message.text
|
data['cleaning_time'] = message.text
|
||||||
await message.answer("Выберите тип уборки (влажная/сухая/генеральная):")
|
await message.answer("Выберите тип уборки (влажная/сухая/генеральная):")
|
||||||
await OrderForm.waiting_for_cleaning_type.set()
|
await OrderForm.waiting_for_cleaning_type.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT)
|
@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 def process_cleaning_type(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['cleaning_type'] = message.text
|
data['cleaning_type'] = message.text
|
||||||
await message.answer("Выберите способ оплаты (картой/наличными):")
|
await message.answer("Выберите способ оплаты (картой/наличными):")
|
||||||
await OrderForm.waiting_for_payment_method.set()
|
await OrderForm.waiting_for_payment_method.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT)
|
@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 def process_payment_method(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['payment_method'] = message.text
|
data['payment_method'] = message.text
|
||||||
|
|
||||||
# Отправка подтверждения заказа
|
# Отправка подтверждения заказа
|
||||||
await message.answer("Подтвердите заказ:\n"
|
await message.answer("Подтвердите заказ:\n"
|
||||||
f"ФИО: {data['name']}\n"
|
f"ФИО: {data['name']}\n"
|
||||||
f"Номер телефона: {data['phone']}\n"
|
f"Номер телефона: {data['phone']}\n"
|
||||||
f"Адрес: {data['address']}\n"
|
f"Адрес: {data['address']}\n"
|
||||||
f"Время уборки: {data['cleaning_time']}\n"
|
f"Время уборки: {data['cleaning_time']}\n"
|
||||||
f"Тип уборки: {data['cleaning_type']}\n"
|
f"Тип уборки: {data['cleaning_type']}\n"
|
||||||
f"Способ оплаты: {data['payment_method']}\n"
|
f"Способ оплаты: {data['payment_method']}\n"
|
||||||
"Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.",
|
"Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.",
|
||||||
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order')))
|
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order')))
|
||||||
await OrderForm.confirmation.set()
|
await OrderForm.confirmation.set()
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation)
|
@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation)
|
||||||
async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext):
|
async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||||
await bot.answer_callback_query(callback_query.id)
|
await bot.answer_callback_query(callback_query.id)
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
order_details = (f"Новый заказ:\n"
|
order_details = (f"Новый заказ:\n"
|
||||||
f"ФИО: {data['name']}\n"
|
f"ФИО: {data['name']}\n"
|
||||||
f"Номер телефона: {data['phone']}\n"
|
f"Номер телефона: {data['phone']}\n"
|
||||||
f"Адрес: {data['address']}\n"
|
f"Адрес: {data['address']}\n"
|
||||||
f"Время уборки: {data['cleaning_time']}\n"
|
f"Время уборки: {data['cleaning_time']}\n"
|
||||||
f"Тип уборки: {data['cleaning_type']}\n"
|
f"Тип уборки: {data['cleaning_type']}\n"
|
||||||
f"Способ оплаты: {data['payment_method']}")
|
f"Способ оплаты: {data['payment_method']}")
|
||||||
|
|
||||||
# Отправка заказа операторам
|
# Отправка заказа операторам
|
||||||
await bot.send_message(OPERATORS_GROUP_ID, order_details)
|
await bot.send_message(OPERATORS_GROUP_ID, order_details)
|
||||||
log_action(callback_query.from_user.id, 'confirm_order')
|
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'])
|
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 bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.")
|
||||||
await state.finish()
|
await state.finish()
|
||||||
|
|
||||||
# Основное приложение
|
# Основное приложение
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
create_tables()
|
create_tables()
|
||||||
executor.start_polling(dp, skip_updates=True)
|
executor.start_polling(dp, skip_updates=True)
|
||||||
|
|||||||
@@ -1,185 +1,185 @@
|
|||||||
import logging
|
import logging
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import asyncio
|
import asyncio
|
||||||
from aiogram import Bot, Dispatcher, types
|
from aiogram import Bot, Dispatcher, types
|
||||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
# Устанавливаем уровень логов
|
# Устанавливаем уровень логов
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
# Подключаемся к базе данных SQLite
|
# Подключаемся к базе данных SQLite
|
||||||
conn = sqlite3.connect("answers.db")
|
conn = sqlite3.connect("answers.db")
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# Создание таблицы для хранения ответов
|
# Создание таблицы для хранения ответов
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS answers (
|
CREATE TABLE IF NOT EXISTS answers (
|
||||||
user_id INTEGER,
|
user_id INTEGER,
|
||||||
question_id INTEGER,
|
question_id INTEGER,
|
||||||
answer TEXT
|
answer TEXT
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# Заменить на свой токен
|
# Заменить на свой токен
|
||||||
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
||||||
GROUP_ID = '-1001961537659'
|
GROUP_ID = '-1001961537659'
|
||||||
|
|
||||||
# Инициализация бота и диспетчера
|
# Инициализация бота и диспетчера
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
dp.middleware.setup(LoggingMiddleware())
|
dp.middleware.setup(LoggingMiddleware())
|
||||||
|
|
||||||
# Словарь для хранения вопросов и ответов
|
# Словарь для хранения вопросов и ответов
|
||||||
questions = {
|
questions = {
|
||||||
1: "Как вас зовут?",
|
1: "Как вас зовут?",
|
||||||
2: "Укажите номер телефона для связи",
|
2: "Укажите номер телефона для связи",
|
||||||
3: "Укажите район, улицу, дом",
|
3: "Укажите район, улицу, дом",
|
||||||
4: "Какая уборка нужна, влажная или сухая?",
|
4: "Какая уборка нужна, влажная или сухая?",
|
||||||
5: "На какое время?",
|
5: "На какое время?",
|
||||||
6: "Оплата наличными или картой?"
|
6: "Оплата наличными или картой?"
|
||||||
}
|
}
|
||||||
|
|
||||||
answer_map = {
|
answer_map = {
|
||||||
'wet_cleaning': 'Влажная уборка',
|
'wet_cleaning': 'Влажная уборка',
|
||||||
'dry_cleaning': 'Сухая уборка',
|
'dry_cleaning': 'Сухая уборка',
|
||||||
'morning_time': 'Утро',
|
'morning_time': 'Утро',
|
||||||
'day_time': 'День',
|
'day_time': 'День',
|
||||||
'evening_time': 'Вечер',
|
'evening_time': 'Вечер',
|
||||||
'cash_payment': 'Наличные',
|
'cash_payment': 'Наличные',
|
||||||
'card_payment': 'Карта'
|
'card_payment': 'Карта'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Обработчик команды /start
|
# Обработчик команды /start
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands=['start'])
|
||||||
async def start(message: types.Message):
|
async def start(message: types.Message):
|
||||||
await message.answer("Привет! Я задам тебе 6 вопросов. Давай начнем.")
|
await message.answer("Привет! Я задам тебе 6 вопросов. Давай начнем.")
|
||||||
await ask_question(message.chat.id, 1)
|
await ask_question(message.chat.id, 1)
|
||||||
|
|
||||||
# Функция для задания вопроса
|
# Функция для задания вопроса
|
||||||
async def ask_question(user_id, question_id):
|
async def ask_question(user_id, question_id):
|
||||||
if question_id in [4, 5, 6]:
|
if question_id in [4, 5, 6]:
|
||||||
if question_id == 4:
|
if question_id == 4:
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")],
|
[InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")],
|
||||||
[InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")]
|
[InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")]
|
||||||
])
|
])
|
||||||
elif question_id == 5:
|
elif question_id == 5:
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Утро", callback_data="morning_time")],
|
[InlineKeyboardButton(text="Утро", callback_data="morning_time")],
|
||||||
[InlineKeyboardButton(text="День", callback_data="day_time")],
|
[InlineKeyboardButton(text="День", callback_data="day_time")],
|
||||||
[InlineKeyboardButton(text="Вечер", callback_data="evening_time")]
|
[InlineKeyboardButton(text="Вечер", callback_data="evening_time")]
|
||||||
])
|
])
|
||||||
elif question_id == 6:
|
elif question_id == 6:
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Наличные", callback_data="cash_payment")],
|
[InlineKeyboardButton(text="Наличные", callback_data="cash_payment")],
|
||||||
[InlineKeyboardButton(text="Карта", callback_data="card_payment")]
|
[InlineKeyboardButton(text="Карта", callback_data="card_payment")]
|
||||||
])
|
])
|
||||||
await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard)
|
await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard)
|
||||||
else:
|
else:
|
||||||
await bot.send_message(user_id, text=questions[question_id])
|
await bot.send_message(user_id, text=questions[question_id])
|
||||||
|
|
||||||
# Обработчик текстовых ответов на первые три вопроса
|
# Обработчик текстовых ответов на первые три вопроса
|
||||||
@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"])
|
@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"])
|
||||||
async def handle_text_answer(message: types.Message):
|
async def handle_text_answer(message: types.Message):
|
||||||
user_id = message.chat.id
|
user_id = message.chat.id
|
||||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||||
prev_question_id = cursor.fetchone()[0]
|
prev_question_id = cursor.fetchone()[0]
|
||||||
|
|
||||||
if prev_question_id is None:
|
if prev_question_id is None:
|
||||||
question_id = 1
|
question_id = 1
|
||||||
else:
|
else:
|
||||||
question_id = prev_question_id + 1
|
question_id = prev_question_id + 1
|
||||||
answer = message.text
|
answer = message.text
|
||||||
|
|
||||||
await save_answer(user_id, question_id, answer)
|
await save_answer(user_id, question_id, answer)
|
||||||
|
|
||||||
if question_id < 6:
|
if question_id < 6:
|
||||||
await ask_question(user_id, question_id + 1)
|
await ask_question(user_id, question_id + 1)
|
||||||
else:
|
else:
|
||||||
await show_confirmation_options(user_id)
|
await show_confirmation_options(user_id)
|
||||||
|
|
||||||
# Обработчик inline-кнопок
|
# Обработчик inline-кнопок
|
||||||
@dp.callback_query_handler(lambda query: query.data in answer_map.keys())
|
@dp.callback_query_handler(lambda query: query.data in answer_map.keys())
|
||||||
async def handle_callback_answer(query: types.CallbackQuery):
|
async def handle_callback_answer(query: types.CallbackQuery):
|
||||||
user_id = query.from_user.id
|
user_id = query.from_user.id
|
||||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||||
prev_question_id = cursor.fetchone()[0]
|
prev_question_id = cursor.fetchone()[0]
|
||||||
|
|
||||||
if prev_question_id is None:
|
if prev_question_id is None:
|
||||||
question_id = 1
|
question_id = 1
|
||||||
else:
|
else:
|
||||||
question_id = prev_question_id + 1
|
question_id = prev_question_id + 1
|
||||||
|
|
||||||
await save_answer(user_id, question_id, answer_map[query.data])
|
await save_answer(user_id, question_id, answer_map[query.data])
|
||||||
await query.answer()
|
await query.answer()
|
||||||
|
|
||||||
if question_id < 6:
|
if question_id < 6:
|
||||||
await ask_question(user_id, question_id + 1)
|
await ask_question(user_id, question_id + 1)
|
||||||
else:
|
else:
|
||||||
await show_confirmation_options(user_id)
|
await show_confirmation_options(user_id)
|
||||||
|
|
||||||
# Функция для сохранения ответа в базе данных
|
# Функция для сохранения ответа в базе данных
|
||||||
async def save_answer(user_id, question_id, answer):
|
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))
|
cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# Функция для отображения опций подтверждения
|
# Функция для отображения опций подтверждения
|
||||||
async def show_confirmation_options(user_id):
|
async def show_confirmation_options(user_id):
|
||||||
cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,))
|
cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,))
|
||||||
answers = cursor.fetchall()
|
answers = cursor.fetchall()
|
||||||
answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)])
|
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}")
|
await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}")
|
||||||
|
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Отправить", callback_data="send_answers")],
|
[InlineKeyboardButton(text="Отправить", callback_data="send_answers")],
|
||||||
[InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")]
|
[InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")]
|
||||||
])
|
])
|
||||||
await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard)
|
await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard)
|
||||||
|
|
||||||
# Обработчик выбора действия подтверждения
|
# Обработчик выбора действия подтверждения
|
||||||
@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers'])
|
@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers'])
|
||||||
async def handle_confirmation(query: types.CallbackQuery):
|
async def handle_confirmation(query: types.CallbackQuery):
|
||||||
user_id = query.from_user.id
|
user_id = query.from_user.id
|
||||||
if query.data == "send_answers":
|
if query.data == "send_answers":
|
||||||
await send_answers_to_group(user_id)
|
await send_answers_to_group(user_id)
|
||||||
elif query.data == "rewrite_answers":
|
elif query.data == "rewrite_answers":
|
||||||
await rewrite_answers(user_id)
|
await rewrite_answers(user_id)
|
||||||
|
|
||||||
# Функция для отправки ответов в группу
|
# Функция для отправки ответов в группу
|
||||||
async def send_answers_to_group(user_id):
|
async def send_answers_to_group(user_id):
|
||||||
cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,))
|
cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,))
|
||||||
answers = cursor.fetchall()
|
answers = cursor.fetchall()
|
||||||
|
|
||||||
if answers:
|
if answers:
|
||||||
answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in 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,))
|
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# Отправляем ответы в группу
|
# Отправляем ответы в группу
|
||||||
await bot.send_message(GROUP_ID, answer_text)
|
await bot.send_message(GROUP_ID, answer_text)
|
||||||
await bot.send_message(user_id, "Ваши ответы отправлены.")
|
await bot.send_message(user_id, "Ваши ответы отправлены.")
|
||||||
else:
|
else:
|
||||||
await bot.send_message(user_id, "Ответов нет")
|
await bot.send_message(user_id, "Ответов нет")
|
||||||
|
|
||||||
# Функция для переписывания ответов
|
# Функция для переписывания ответов
|
||||||
async def rewrite_answers(user_id):
|
async def rewrite_answers(user_id):
|
||||||
# Очищаем таблицу с ответами
|
# Очищаем таблицу с ответами
|
||||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.")
|
await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.")
|
||||||
await ask_question(user_id, 1)
|
await ask_question(user_id, 1)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
try:
|
try:
|
||||||
loop.create_task(dp.start_polling())
|
loop.create_task(dp.start_polling())
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import logging
|
import logging
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
# Настройка логирования
|
# Настройка логирования
|
||||||
logging.basicConfig(filename='bot.log', level=logging.INFO,
|
logging.basicConfig(filename='bot.log', level=logging.INFO,
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||||
|
|
||||||
def log_action(user_id, action, details=""):
|
def log_action(user_id, action, details=""):
|
||||||
logging.info(f"User ID: {user_id} - Action: {action} - Details: {details}")
|
logging.info(f"User ID: {user_id} - Action: {action} - Details: {details}")
|
||||||
|
|
||||||
|
|
||||||
def log_action(user_id, action):
|
def log_action(user_id, action):
|
||||||
conn = sqlite3.connect('bot_klining.db')
|
conn = sqlite3.connect('bot_klining.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('INSERT INTO logs (user_id, action) VALUES ((SELECT id FROM users WHERE telegram_id = ?), ?)', (user_id, action))
|
cursor.execute('INSERT INTO logs (user_id, action) VALUES ((SELECT id FROM users WHERE telegram_id = ?), ?)', (user_id, action))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -1,213 +1,213 @@
|
|||||||
import logging
|
import logging
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import asyncio
|
import asyncio
|
||||||
from aiogram import Bot, Dispatcher, types
|
from aiogram import Bot, Dispatcher, types
|
||||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
# Устанавливаем уровень логов
|
# Устанавливаем уровень логов
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
# Подключаемся к базе данных SQLite
|
# Подключаемся к базе данных SQLite
|
||||||
conn = sqlite3.connect("answers.db")
|
conn = sqlite3.connect("answers.db")
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# Создание таблицы для хранения ответов
|
# Создание таблицы для хранения ответов
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS answers (
|
CREATE TABLE IF NOT EXISTS answers (
|
||||||
user_id INTEGER,
|
user_id INTEGER,
|
||||||
question_id INTEGER,
|
question_id INTEGER,
|
||||||
answer TEXT
|
answer TEXT
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# Заменить на свой токен
|
# Заменить на свой токен
|
||||||
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
||||||
GROUP_ID = '-1001961537659'
|
GROUP_ID = '-1001961537659'
|
||||||
|
|
||||||
# Инициализация бота и диспетчера
|
# Инициализация бота и диспетчера
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
dp.middleware.setup(LoggingMiddleware())
|
dp.middleware.setup(LoggingMiddleware())
|
||||||
|
|
||||||
# Словарь для хранения вопросов и ответов
|
# Словарь для хранения вопросов и ответов
|
||||||
questions = {
|
questions = {
|
||||||
1: "Как вас зовут?",
|
1: "Как вас зовут?",
|
||||||
2: "Укажите номер телефона для связи",
|
2: "Укажите номер телефона для связи",
|
||||||
3: "Укажите район, улицу, дом",
|
3: "Укажите район, улицу, дом",
|
||||||
4: "Какая уборка нужна, влажная или сухая?",
|
4: "Какая уборка нужна, влажная или сухая?",
|
||||||
5: "На какое время?",
|
5: "На какое время?",
|
||||||
6: "Оплата наличными или картой?"
|
6: "Оплата наличными или картой?"
|
||||||
}
|
}
|
||||||
|
|
||||||
answer_map = {
|
answer_map = {
|
||||||
'wet_cleaning': 'Влажная уборка',
|
'wet_cleaning': 'Влажная уборка',
|
||||||
'dry_cleaning': 'Сухая уборка',
|
'dry_cleaning': 'Сухая уборка',
|
||||||
'morning_time': 'Утро',
|
'morning_time': 'Утро',
|
||||||
'day_time': 'День',
|
'day_time': 'День',
|
||||||
'evening_time': 'Вечер',
|
'evening_time': 'Вечер',
|
||||||
'cash_payment': 'Наличные',
|
'cash_payment': 'Наличные',
|
||||||
'card_payment': 'Карта'
|
'card_payment': 'Карта'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Обработчик команды /start
|
# Обработчик команды /start
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands=['start'])
|
||||||
async def start(message: types.Message):
|
async def start(message: types.Message):
|
||||||
if message.chat.id != int(GROUP_ID):
|
if message.chat.id != int(GROUP_ID):
|
||||||
await message.answer("Привет! Я задам тебе 6 вопросов. Давай начнем.")
|
await message.answer("Привет! Я задам тебе 6 вопросов. Давай начнем.")
|
||||||
await ask_question(message.chat.id, 1)
|
await ask_question(message.chat.id, 1)
|
||||||
|
|
||||||
# Функция для задания вопроса
|
# Функция для задания вопроса
|
||||||
async def ask_question(user_id, question_id):
|
async def ask_question(user_id, question_id):
|
||||||
if question_id in [4, 5, 6]:
|
if question_id in [4, 5, 6]:
|
||||||
if question_id == 4:
|
if question_id == 4:
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")],
|
[InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")],
|
||||||
[InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")]
|
[InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")]
|
||||||
])
|
])
|
||||||
elif question_id == 5:
|
elif question_id == 5:
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Утро", callback_data="morning_time")],
|
[InlineKeyboardButton(text="Утро", callback_data="morning_time")],
|
||||||
[InlineKeyboardButton(text="День", callback_data="day_time")],
|
[InlineKeyboardButton(text="День", callback_data="day_time")],
|
||||||
[InlineKeyboardButton(text="Вечер", callback_data="evening_time")]
|
[InlineKeyboardButton(text="Вечер", callback_data="evening_time")]
|
||||||
])
|
])
|
||||||
elif question_id == 6:
|
elif question_id == 6:
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Наличные", callback_data="cash_payment")],
|
[InlineKeyboardButton(text="Наличные", callback_data="cash_payment")],
|
||||||
[InlineKeyboardButton(text="Карта", callback_data="card_payment")]
|
[InlineKeyboardButton(text="Карта", callback_data="card_payment")]
|
||||||
])
|
])
|
||||||
await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard)
|
await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard)
|
||||||
else:
|
else:
|
||||||
await bot.send_message(user_id, text=questions[question_id])
|
await bot.send_message(user_id, text=questions[question_id])
|
||||||
|
|
||||||
# Обработчик текстовых ответов на первые три вопроса
|
# Обработчик текстовых ответов на первые три вопроса
|
||||||
@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"])
|
@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"])
|
||||||
async def handle_text_answer(message: types.Message):
|
async def handle_text_answer(message: types.Message):
|
||||||
if message.chat.id != int(GROUP_ID):
|
if message.chat.id != int(GROUP_ID):
|
||||||
user_id = message.chat.id
|
user_id = message.chat.id
|
||||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||||
prev_question_id = cursor.fetchone()[0]
|
prev_question_id = cursor.fetchone()[0]
|
||||||
|
|
||||||
if prev_question_id is None:
|
if prev_question_id is None:
|
||||||
question_id = 1
|
question_id = 1
|
||||||
else:
|
else:
|
||||||
question_id = prev_question_id + 1
|
question_id = prev_question_id + 1
|
||||||
answer = message.text
|
answer = message.text
|
||||||
|
|
||||||
await save_answer(user_id, question_id, answer)
|
await save_answer(user_id, question_id, answer)
|
||||||
|
|
||||||
if question_id < 6:
|
if question_id < 6:
|
||||||
await ask_question(user_id, question_id + 1)
|
await ask_question(user_id, question_id + 1)
|
||||||
else:
|
else:
|
||||||
await show_confirmation_options(user_id)
|
await show_confirmation_options(user_id)
|
||||||
|
|
||||||
# Обработчик inline-кнопок
|
# Обработчик inline-кнопок
|
||||||
@dp.callback_query_handler(lambda query: query.data in answer_map.keys())
|
@dp.callback_query_handler(lambda query: query.data in answer_map.keys())
|
||||||
async def handle_callback_answer(query: types.CallbackQuery):
|
async def handle_callback_answer(query: types.CallbackQuery):
|
||||||
user_id = query.from_user.id
|
user_id = query.from_user.id
|
||||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||||
prev_question_id = cursor.fetchone()[0]
|
prev_question_id = cursor.fetchone()[0]
|
||||||
|
|
||||||
if prev_question_id is None:
|
if prev_question_id is None:
|
||||||
question_id = 1
|
question_id = 1
|
||||||
else:
|
else:
|
||||||
question_id = prev_question_id + 1
|
question_id = prev_question_id + 1
|
||||||
|
|
||||||
await save_answer(user_id, question_id, answer_map[query.data])
|
await save_answer(user_id, question_id, answer_map[query.data])
|
||||||
await query.answer()
|
await query.answer()
|
||||||
|
|
||||||
if question_id < 6:
|
if question_id < 6:
|
||||||
await ask_question(user_id, question_id + 1)
|
await ask_question(user_id, question_id + 1)
|
||||||
else:
|
else:
|
||||||
await show_confirmation_options(user_id)
|
await show_confirmation_options(user_id)
|
||||||
|
|
||||||
# Функция для сохранения ответа в базе данных
|
# Функция для сохранения ответа в базе данных
|
||||||
async def save_answer(user_id, question_id, answer):
|
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))
|
cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# Функция для отображения опций подтверждения
|
# Функция для отображения опций подтверждения
|
||||||
async def show_confirmation_options(user_id):
|
async def show_confirmation_options(user_id):
|
||||||
cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,))
|
cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,))
|
||||||
answers = cursor.fetchall()
|
answers = cursor.fetchall()
|
||||||
answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)])
|
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}")
|
await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}")
|
||||||
|
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Отправить", callback_data="send_answers")],
|
[InlineKeyboardButton(text="Отправить", callback_data="send_answers")],
|
||||||
[InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")]
|
[InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")]
|
||||||
])
|
])
|
||||||
await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard)
|
await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard)
|
||||||
|
|
||||||
# Обработчик выбора действия подтверждения
|
# Обработчик выбора действия подтверждения
|
||||||
@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers'])
|
@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers'])
|
||||||
async def handle_confirmation(query: types.CallbackQuery):
|
async def handle_confirmation(query: types.CallbackQuery):
|
||||||
user_id = query.from_user.id
|
user_id = query.from_user.id
|
||||||
if query.data == "send_answers":
|
if query.data == "send_answers":
|
||||||
await send_answers_to_group(user_id)
|
await send_answers_to_group(user_id)
|
||||||
elif query.data == "rewrite_answers":
|
elif query.data == "rewrite_answers":
|
||||||
await rewrite_answers(user_id)
|
await rewrite_answers(user_id)
|
||||||
|
|
||||||
# Функция для отправки ответов в группу
|
# Функция для отправки ответов в группу
|
||||||
async def send_answers_to_group(user_id):
|
async def send_answers_to_group(user_id):
|
||||||
cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,))
|
cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,))
|
||||||
answers = cursor.fetchall()
|
answers = cursor.fetchall()
|
||||||
|
|
||||||
if answers:
|
if answers:
|
||||||
answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in 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,))
|
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# Отправляем ответы в группу и добавляем ID пользователя
|
# Отправляем ответы в группу и добавляем ID пользователя
|
||||||
sent_message = await bot.send_message(GROUP_ID, f"Ответы от пользователя {user_id}:\n\n{answer_text}")
|
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})",
|
await bot.send_message(user_id, f"Ваши ответы отправлены. Можете посмотреть их [здесь](https://t.me/{sent_message.chat.username}/{sent_message.message_id})",
|
||||||
parse_mode='Markdown')
|
parse_mode='Markdown')
|
||||||
else:
|
else:
|
||||||
await bot.send_message(user_id, "Ответов нет")
|
await bot.send_message(user_id, "Ответов нет")
|
||||||
|
|
||||||
# Функция для переписывания ответов
|
# Функция для переписывания ответов
|
||||||
async def rewrite_answers(user_id):
|
async def rewrite_answers(user_id):
|
||||||
# Очищаем таблицу с ответами
|
# Очищаем таблицу с ответами
|
||||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.")
|
await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.")
|
||||||
await ask_question(user_id, 1)
|
await ask_question(user_id, 1)
|
||||||
|
|
||||||
# Функция для обработки сообщений из группы и пересылки их пользователю
|
# Функция для обработки сообщений из группы и пересылки их пользователю
|
||||||
@dp.message_handler(lambda message: message.chat.id == int(GROUP_ID) and message.reply_to_message)
|
@dp.message_handler(lambda message: message.chat.id == int(GROUP_ID) and message.reply_to_message)
|
||||||
async def handle_group_reply(message: types.Message):
|
async def handle_group_reply(message: types.Message):
|
||||||
# Проверяем, что сообщение является ответом на сообщение бота
|
# Проверяем, что сообщение является ответом на сообщение бота
|
||||||
if message.reply_to_message.from_user.id == bot.id:
|
if message.reply_to_message.from_user.id == bot.id:
|
||||||
# Извлекаем ID пользователя из текста сообщения
|
# Извлекаем ID пользователя из текста сообщения
|
||||||
try:
|
try:
|
||||||
user_id = int(message.reply_to_message.text.split()[2].strip(':'))
|
user_id = int(message.reply_to_message.text.split()[2].strip(':'))
|
||||||
await bot.send_message(user_id, f"Сообщение от администратора:\n\n{message.text}")
|
await bot.send_message(user_id, f"Сообщение от администратора:\n\n{message.text}")
|
||||||
# Сохраняем ответ пользователя для пересылки обратно в группу
|
# Сохраняем ответ пользователя для пересылки обратно в группу
|
||||||
cursor.execute("INSERT INTO admin_user_conversation (user_id, admin_message_id) VALUES (?, ?)",
|
cursor.execute("INSERT INTO admin_user_conversation (user_id, admin_message_id) VALUES (?, ?)",
|
||||||
(user_id, message.message_id))
|
(user_id, message.message_id))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
except (IndexError, ValueError):
|
except (IndexError, ValueError):
|
||||||
await message.reply("Не удалось определить ID пользователя для ответа.")
|
await message.reply("Не удалось определить ID пользователя для ответа.")
|
||||||
|
|
||||||
# Обработчик сообщений от пользователя в ответ на администратора
|
# Обработчик сообщений от пользователя в ответ на администратора
|
||||||
@dp.message_handler(lambda message: message.chat.id != int(GROUP_ID))
|
@dp.message_handler(lambda message: message.chat.id != int(GROUP_ID))
|
||||||
async def handle_user_reply(message: types.Message):
|
async def handle_user_reply(message: types.Message):
|
||||||
user_id = message.chat.id
|
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,))
|
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()
|
admin_message_id = cursor.fetchone()
|
||||||
if admin_message_id:
|
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])
|
await bot.send_message(GROUP_ID, f"Сообщение от пользователя {user_id}:\n\n{message.text}", reply_to_message_id=admin_message_id[0])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
try:
|
try:
|
||||||
loop.create_task(dp.start_polling())
|
loop.create_task(dp.start_polling())
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -1,221 +1,221 @@
|
|||||||
import logging
|
import logging
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import asyncio
|
import asyncio
|
||||||
from aiogram import Bot, Dispatcher, types
|
from aiogram import Bot, Dispatcher, types
|
||||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||||
from aiogram.dispatcher.filters import Command
|
from aiogram.dispatcher.filters import Command
|
||||||
from aiogram.dispatcher import FSMContext
|
from aiogram.dispatcher import FSMContext
|
||||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||||
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
|
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
# Устанавливаем уровень логов
|
# Устанавливаем уровень логов
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
# Подключаемся к базе данных SQLite
|
# Подключаемся к базе данных SQLite
|
||||||
conn = sqlite3.connect("answers.db")
|
conn = sqlite3.connect("answers.db")
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# Создание таблицы для хранения ответов
|
# Создание таблицы для хранения ответов
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
CREATE TABLE IF NOT EXISTS answers (
|
CREATE TABLE IF NOT EXISTS answers (
|
||||||
user_id INTEGER,
|
user_id INTEGER,
|
||||||
question_id INTEGER,
|
question_id INTEGER,
|
||||||
answer TEXT
|
answer TEXT
|
||||||
)
|
)
|
||||||
""")
|
""")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# Заменить на свой токен
|
# Заменить на свой токен
|
||||||
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
||||||
GROUP_ID = '-1001961537659'
|
GROUP_ID = '-1001961537659'
|
||||||
|
|
||||||
# Инициализация бота и диспетчера
|
# Инициализация бота и диспетчера
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
dp.middleware.setup(LoggingMiddleware())
|
dp.middleware.setup(LoggingMiddleware())
|
||||||
|
|
||||||
# Словарь для хранения вопросов и ответов
|
# Словарь для хранения вопросов и ответов
|
||||||
questions = {
|
questions = {
|
||||||
1: "Как вас зовут?",
|
1: "Как вас зовут?",
|
||||||
2: "Укажите номер телефона для связи",
|
2: "Укажите номер телефона для связи",
|
||||||
3: "Укажите район, улицу, дом",
|
3: "Укажите район, улицу, дом",
|
||||||
4: "Какая уборка нужна, влажная или сухая?",
|
4: "Какая уборка нужна, влажная или сухая?",
|
||||||
5: "На какое время?",
|
5: "На какое время?",
|
||||||
6: "Оплата наличными или картой?"
|
6: "Оплата наличными или картой?"
|
||||||
}
|
}
|
||||||
|
|
||||||
answer_map = {
|
answer_map = {
|
||||||
'wet_cleaning': 'Влажная уборка',
|
'wet_cleaning': 'Влажная уборка',
|
||||||
'dry_cleaning': 'Сухая уборка',
|
'dry_cleaning': 'Сухая уборка',
|
||||||
'morning_time': 'Утро',
|
'morning_time': 'Утро',
|
||||||
'day_time': 'День',
|
'day_time': 'День',
|
||||||
'evening_time': 'Вечер',
|
'evening_time': 'Вечер',
|
||||||
'cash_payment': 'Наличные',
|
'cash_payment': 'Наличные',
|
||||||
'card_payment': 'Карта'
|
'card_payment': 'Карта'
|
||||||
}
|
}
|
||||||
|
|
||||||
# Обработчик команды /start
|
# Обработчик команды /start
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands=['start'])
|
||||||
async def start(message: types.Message):
|
async def start(message: types.Message):
|
||||||
if message.chat.id != int(GROUP_ID):
|
if message.chat.id != int(GROUP_ID):
|
||||||
await message.answer("Привет! Я задам тебе 6 вопросов Чтобы создать заказ. Давай начнем.")
|
await message.answer("Привет! Я задам тебе 6 вопросов Чтобы создать заказ. Давай начнем.")
|
||||||
await ask_question(message.chat.id, 1)
|
await ask_question(message.chat.id, 1)
|
||||||
|
|
||||||
@dp.message_handler(Command("start"))
|
@dp.message_handler(Command("start"))
|
||||||
async def show_menu(message: types.Message):
|
async def show_menu(message: types.Message):
|
||||||
await message.reply("Выбери действие:", reply_markup=ReplyKeyboardMarkup(
|
await message.reply("Выбери действие:", reply_markup=ReplyKeyboardMarkup(
|
||||||
resize_keyboard=True).add(KeyboardButton("Создать заказ")))
|
resize_keyboard=True).add(KeyboardButton("Создать заказ")))
|
||||||
|
|
||||||
# Функция для задания вопроса
|
# Функция для задания вопроса
|
||||||
async def ask_question(user_id, question_id):
|
async def ask_question(user_id, question_id):
|
||||||
if question_id in [4, 5, 6]:
|
if question_id in [4, 5, 6]:
|
||||||
if question_id == 4:
|
if question_id == 4:
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")],
|
[InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")],
|
||||||
[InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")]
|
[InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")]
|
||||||
])
|
])
|
||||||
elif question_id == 5:
|
elif question_id == 5:
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Утро", callback_data="morning_time")],
|
[InlineKeyboardButton(text="Утро", callback_data="morning_time")],
|
||||||
[InlineKeyboardButton(text="День", callback_data="day_time")],
|
[InlineKeyboardButton(text="День", callback_data="day_time")],
|
||||||
[InlineKeyboardButton(text="Вечер", callback_data="evening_time")]
|
[InlineKeyboardButton(text="Вечер", callback_data="evening_time")]
|
||||||
])
|
])
|
||||||
elif question_id == 6:
|
elif question_id == 6:
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Наличные", callback_data="cash_payment")],
|
[InlineKeyboardButton(text="Наличные", callback_data="cash_payment")],
|
||||||
[InlineKeyboardButton(text="Карта", callback_data="card_payment")]
|
[InlineKeyboardButton(text="Карта", callback_data="card_payment")]
|
||||||
])
|
])
|
||||||
await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard)
|
await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard)
|
||||||
else:
|
else:
|
||||||
await bot.send_message(user_id, text=questions[question_id])
|
await bot.send_message(user_id, text=questions[question_id])
|
||||||
|
|
||||||
# Обработчик текстовых ответов на первые три вопроса
|
# Обработчик текстовых ответов на первые три вопроса
|
||||||
@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"])
|
@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"])
|
||||||
async def handle_text_answer(message: types.Message):
|
async def handle_text_answer(message: types.Message):
|
||||||
if message.chat.id != int(GROUP_ID):
|
if message.chat.id != int(GROUP_ID):
|
||||||
user_id = message.chat.id
|
user_id = message.chat.id
|
||||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||||
prev_question_id = cursor.fetchone()[0]
|
prev_question_id = cursor.fetchone()[0]
|
||||||
|
|
||||||
if prev_question_id is None:
|
if prev_question_id is None:
|
||||||
question_id = 1
|
question_id = 1
|
||||||
else:
|
else:
|
||||||
question_id = prev_question_id + 1
|
question_id = prev_question_id + 1
|
||||||
answer = message.text
|
answer = message.text
|
||||||
|
|
||||||
await save_answer(user_id, question_id, answer)
|
await save_answer(user_id, question_id, answer)
|
||||||
|
|
||||||
if question_id < 6:
|
if question_id < 6:
|
||||||
await ask_question(user_id, question_id + 1)
|
await ask_question(user_id, question_id + 1)
|
||||||
else:
|
else:
|
||||||
await show_confirmation_options(user_id)
|
await show_confirmation_options(user_id)
|
||||||
|
|
||||||
# Обработчик inline-кнопок
|
# Обработчик inline-кнопок
|
||||||
@dp.callback_query_handler(lambda query: query.data in answer_map.keys())
|
@dp.callback_query_handler(lambda query: query.data in answer_map.keys())
|
||||||
async def handle_callback_answer(query: types.CallbackQuery):
|
async def handle_callback_answer(query: types.CallbackQuery):
|
||||||
user_id = query.from_user.id
|
user_id = query.from_user.id
|
||||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||||
prev_question_id = cursor.fetchone()[0]
|
prev_question_id = cursor.fetchone()[0]
|
||||||
|
|
||||||
if prev_question_id is None:
|
if prev_question_id is None:
|
||||||
question_id = 1
|
question_id = 1
|
||||||
else:
|
else:
|
||||||
question_id = prev_question_id + 1
|
question_id = prev_question_id + 1
|
||||||
|
|
||||||
await save_answer(user_id, question_id, answer_map[query.data])
|
await save_answer(user_id, question_id, answer_map[query.data])
|
||||||
await query.answer()
|
await query.answer()
|
||||||
|
|
||||||
if question_id < 6:
|
if question_id < 6:
|
||||||
await ask_question(user_id, question_id + 1)
|
await ask_question(user_id, question_id + 1)
|
||||||
else:
|
else:
|
||||||
await show_confirmation_options(user_id)
|
await show_confirmation_options(user_id)
|
||||||
|
|
||||||
# Функция для сохранения ответа в базе данных
|
# Функция для сохранения ответа в базе данных
|
||||||
async def save_answer(user_id, question_id, answer):
|
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))
|
cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# Функция для отображения опций подтверждения
|
# Функция для отображения опций подтверждения
|
||||||
async def show_confirmation_options(user_id):
|
async def show_confirmation_options(user_id):
|
||||||
cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,))
|
cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,))
|
||||||
answers = cursor.fetchall()
|
answers = cursor.fetchall()
|
||||||
answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)])
|
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}")
|
await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}")
|
||||||
|
|
||||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
[InlineKeyboardButton(text="Отправить", callback_data="send_answers")],
|
[InlineKeyboardButton(text="Отправить", callback_data="send_answers")],
|
||||||
[InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")]
|
[InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")]
|
||||||
])
|
])
|
||||||
await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard)
|
await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard)
|
||||||
|
|
||||||
# Обработчик выбора действия подтверждения
|
# Обработчик выбора действия подтверждения
|
||||||
@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers'])
|
@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers'])
|
||||||
async def handle_confirmation(query: types.CallbackQuery):
|
async def handle_confirmation(query: types.CallbackQuery):
|
||||||
user_id = query.from_user.id
|
user_id = query.from_user.id
|
||||||
if query.data == "send_answers":
|
if query.data == "send_answers":
|
||||||
await send_answers_to_group(user_id)
|
await send_answers_to_group(user_id)
|
||||||
elif query.data == "rewrite_answers":
|
elif query.data == "rewrite_answers":
|
||||||
await rewrite_answers(user_id)
|
await rewrite_answers(user_id)
|
||||||
|
|
||||||
# Функция для отправки ответов в группу
|
# Функция для отправки ответов в группу
|
||||||
async def send_answers_to_group(user_id):
|
async def send_answers_to_group(user_id):
|
||||||
cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,))
|
cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,))
|
||||||
answers = cursor.fetchall()
|
answers = cursor.fetchall()
|
||||||
|
|
||||||
if answers:
|
if answers:
|
||||||
answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in 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,))
|
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
# Отправляем ответы в группу и добавляем ID пользователя
|
# Отправляем ответы в группу и добавляем ID пользователя
|
||||||
sent_message = await bot.send_message(GROUP_ID, f"Ответы от пользователя {user_id}:\n\n{answer_text}")
|
sent_message = await bot.send_message(GROUP_ID, f"Ответы от пользователя {user_id}:\n\n{answer_text}")
|
||||||
await bot.send_message(user_id, f"Ваши ответы отправлены. Оператор с вами свяжится)",
|
await bot.send_message(user_id, f"Ваши ответы отправлены. Оператор с вами свяжится)",
|
||||||
parse_mode='Markdown')
|
parse_mode='Markdown')
|
||||||
else:
|
else:
|
||||||
await bot.send_message(user_id, "Ответов нет")
|
await bot.send_message(user_id, "Ответов нет")
|
||||||
|
|
||||||
# Функция для переписывания ответов
|
# Функция для переписывания ответов
|
||||||
async def rewrite_answers(user_id):
|
async def rewrite_answers(user_id):
|
||||||
# Очищаем таблицу с ответами
|
# Очищаем таблицу с ответами
|
||||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.")
|
await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.")
|
||||||
await ask_question(user_id, 1)
|
await ask_question(user_id, 1)
|
||||||
|
|
||||||
# Функция для обработки сообщений из группы и пересылки их пользователю
|
# Функция для обработки сообщений из группы и пересылки их пользователю
|
||||||
@dp.message_handler(lambda message: message.chat.id == int(GROUP_ID) and message.reply_to_message)
|
@dp.message_handler(lambda message: message.chat.id == int(GROUP_ID) and message.reply_to_message)
|
||||||
async def handle_group_reply(message: types.Message):
|
async def handle_group_reply(message: types.Message):
|
||||||
# Проверяем, что сообщение является ответом на сообщение бота
|
# Проверяем, что сообщение является ответом на сообщение бота
|
||||||
if message.reply_to_message.from_user.id == bot.id:
|
if message.reply_to_message.from_user.id == bot.id:
|
||||||
# Извлекаем ID пользователя из текста сообщения
|
# Извлекаем ID пользователя из текста сообщения
|
||||||
try:
|
try:
|
||||||
user_id = int(message.reply_to_message.text.split()[2].strip(':'))
|
user_id = int(message.reply_to_message.text.split()[2].strip(':'))
|
||||||
await bot.send_message(user_id, f"Сообщение от администратора:\n\n{message.text}")
|
await bot.send_message(user_id, f"Сообщение от администратора:\n\n{message.text}")
|
||||||
# Сохраняем ответ пользователя для пересылки обратно в группу
|
# Сохраняем ответ пользователя для пересылки обратно в группу
|
||||||
cursor.execute("INSERT INTO admin_user_conversation (user_id, admin_message_id) VALUES (?, ?)",
|
cursor.execute("INSERT INTO admin_user_conversation (user_id, admin_message_id) VALUES (?, ?)",
|
||||||
(user_id, message.message_id))
|
(user_id, message.message_id))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
except (IndexError, ValueError):
|
except (IndexError, ValueError):
|
||||||
await message.reply("Не удалось определить ID пользователя для ответа.")
|
await message.reply("Не удалось определить ID пользователя для ответа.")
|
||||||
|
|
||||||
# Обработчик сообщений от пользователя в ответ на администратора
|
# Обработчик сообщений от пользователя в ответ на администратора
|
||||||
@dp.message_handler(lambda message: message.chat.id != int(GROUP_ID))
|
@dp.message_handler(lambda message: message.chat.id != int(GROUP_ID))
|
||||||
async def handle_user_reply(message: types.Message):
|
async def handle_user_reply(message: types.Message):
|
||||||
user_id = message.chat.id
|
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,))
|
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()
|
admin_message_id = cursor.fetchone()
|
||||||
if admin_message_id:
|
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])
|
await bot.send_message(GROUP_ID, f"Сообщение от пользователя {user_id}:\n\n{message.text}", reply_to_message_id=admin_message_id[0])
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
try:
|
try:
|
||||||
loop.create_task(dp.start_polling())
|
loop.create_task(dp.start_polling())
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -1,76 +1,76 @@
|
|||||||
import logging
|
import logging
|
||||||
from aiogram import Bot, Dispatcher, types
|
from aiogram import Bot, Dispatcher, types
|
||||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||||
from aiogram.dispatcher import FSMContext
|
from aiogram.dispatcher import FSMContext
|
||||||
from aiogram.dispatcher.filters import Command
|
from aiogram.dispatcher.filters import Command
|
||||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||||
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
|
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
||||||
GROUP_ID = '-1001961537659'
|
GROUP_ID = '-1001961537659'
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
dp.middleware.setup(LoggingMiddleware())
|
dp.middleware.setup(LoggingMiddleware())
|
||||||
|
|
||||||
# Define states
|
# Define states
|
||||||
class OrderForm(StatesGroup):
|
class OrderForm(StatesGroup):
|
||||||
question_1 = State()
|
question_1 = State()
|
||||||
question_2 = State()
|
question_2 = State()
|
||||||
question_3 = State()
|
question_3 = State()
|
||||||
question_4 = State()
|
question_4 = State()
|
||||||
question_5 = State()
|
question_5 = State()
|
||||||
question_6 = State()
|
question_6 = State()
|
||||||
|
|
||||||
@dp.message_handler(Command("start"))
|
@dp.message_handler(Command("start"))
|
||||||
async def cmd_start(message: types.Message):
|
async def cmd_start(message: types.Message):
|
||||||
await message.reply("Привет! Чтобы создать заказ, нажми на кнопку 'Создать заказ' внизу.", reply_markup=ReplyKeyboardMarkup(
|
await message.reply("Привет! Чтобы создать заказ, нажми на кнопку 'Создать заказ' внизу.", reply_markup=ReplyKeyboardMarkup(
|
||||||
resize_keyboard=True).add(KeyboardButton("Создать заказ")))
|
resize_keyboard=True).add(KeyboardButton("Создать заказ")))
|
||||||
|
|
||||||
@dp.message_handler(Command("menu"))
|
@dp.message_handler(Command("menu"))
|
||||||
async def show_menu(message: types.Message):
|
async def show_menu(message: types.Message):
|
||||||
await message.reply("Выбери действие:", reply_markup=ReplyKeyboardMarkup(
|
await message.reply("Выбери действие:", reply_markup=ReplyKeyboardMarkup(
|
||||||
resize_keyboard=True).add(KeyboardButton("Создать заказ")))
|
resize_keyboard=True).add(KeyboardButton("Создать заказ")))
|
||||||
|
|
||||||
@dp.message_handler(Command("create_order"))
|
@dp.message_handler(Command("create_order"))
|
||||||
async def cmd_create_order(message: types.Message):
|
async def cmd_create_order(message: types.Message):
|
||||||
await OrderForm.question_1.set()
|
await OrderForm.question_1.set()
|
||||||
await message.reply("Ответьте на первый вопрос:")
|
await message.reply("Ответьте на первый вопрос:")
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.question_1)
|
@dp.message_handler(state=OrderForm.question_1)
|
||||||
async def process_question_1(message: types.Message, state: FSMContext):
|
async def process_question_1(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['question_1'] = message.text
|
data['question_1'] = message.text
|
||||||
await OrderForm.next()
|
await OrderForm.next()
|
||||||
await message.reply("Ответьте на второй вопрос:")
|
await message.reply("Ответьте на второй вопрос:")
|
||||||
|
|
||||||
# Аналогично для остальных вопросов
|
# Аналогично для остальных вопросов
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.question_6)
|
@dp.message_handler(state=OrderForm.question_6)
|
||||||
async def process_question_6(message: types.Message, state: FSMContext):
|
async def process_question_6(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['question_6'] = message.text
|
data['question_6'] = message.text
|
||||||
await message.reply("Отлично! Ваши ответы:")
|
await message.reply("Отлично! Ваши ответы:")
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
await message.reply(f"{key}: {value}")
|
await message.reply(f"{key}: {value}")
|
||||||
|
|
||||||
# Предложить отправить или переписать ответы
|
# Предложить отправить или переписать ответы
|
||||||
keyboard = InlineKeyboardMarkup().add(InlineKeyboardButton("Отправить", callback_data="send_order")).add(InlineKeyboardButton("Переписать", callback_data="rewrite_order"))
|
keyboard = InlineKeyboardMarkup().add(InlineKeyboardButton("Отправить", callback_data="send_order")).add(InlineKeyboardButton("Переписать", callback_data="rewrite_order"))
|
||||||
await message.reply("Хотите отправить эти ответы или переписать?", reply_markup=keyboard)
|
await message.reply("Хотите отправить эти ответы или переписать?", reply_markup=keyboard)
|
||||||
|
|
||||||
@dp.callback_query_handler(text="send_order", state="*")
|
@dp.callback_query_handler(text="send_order", state="*")
|
||||||
async def send_order(callback_query: types.CallbackQuery, state: FSMContext):
|
async def send_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
await bot.send_message(callback_query.from_user.id, f"{key}: {value}")
|
await bot.send_message(callback_query.from_user.id, f"{key}: {value}")
|
||||||
|
|
||||||
@dp.callback_query_handler(text="rewrite_order", state="*")
|
@dp.callback_query_handler(text="rewrite_order", state="*")
|
||||||
async def rewrite_order(callback_query: types.CallbackQuery):
|
async def rewrite_order(callback_query: types.CallbackQuery):
|
||||||
await cmd_create_order(callback_query.message)
|
await cmd_create_order(callback_query.message)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from aiogram import executor
|
from aiogram import executor
|
||||||
executor.start_polling(dp, skip_updates=True)
|
executor.start_polling(dp, skip_updates=True)
|
||||||
|
|||||||
@@ -1,53 +1,53 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
|
|
||||||
def create_tables():
|
def create_tables():
|
||||||
conn = sqlite3.connect('bot_data.db')
|
conn = sqlite3.connect('bot_data.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS orders (
|
CREATE TABLE IF NOT EXISTS orders (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
name TEXT,
|
name TEXT,
|
||||||
phone TEXT,
|
phone TEXT,
|
||||||
address TEXT,
|
address TEXT,
|
||||||
cleaning_time TEXT,
|
cleaning_time TEXT,
|
||||||
cleaning_type TEXT,
|
cleaning_type TEXT,
|
||||||
payment_method TEXT
|
payment_method TEXT
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
user_id INTEGER PRIMARY KEY,
|
user_id INTEGER PRIMARY KEY,
|
||||||
name TEXT,
|
name TEXT,
|
||||||
phone TEXT,
|
phone TEXT,
|
||||||
address TEXT
|
address TEXT
|
||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def add_order(name, phone, address, cleaning_time, cleaning_type, payment_method):
|
def add_order(name, phone, address, cleaning_time, cleaning_type, payment_method):
|
||||||
conn = sqlite3.connect('bot_data.db')
|
conn = sqlite3.connect('bot_data.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT INTO orders (name, phone, address, cleaning_time, cleaning_type, payment_method)
|
INSERT INTO orders (name, phone, address, cleaning_time, cleaning_type, payment_method)
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?)
|
||||||
''', (name, phone, address, cleaning_time, cleaning_type, payment_method))
|
''', (name, phone, address, cleaning_time, cleaning_type, payment_method))
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
def get_order_history(user_id):
|
def get_order_history(user_id):
|
||||||
conn = sqlite3.connect('bot_data.db')
|
conn = sqlite3.connect('bot_data.db')
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
SELECT * FROM orders WHERE user_id = ?
|
SELECT * FROM orders WHERE user_id = ?
|
||||||
''', (user_id,))
|
''', (user_id,))
|
||||||
|
|
||||||
orders = cursor.fetchall()
|
orders = cursor.fetchall()
|
||||||
conn.close()
|
conn.close()
|
||||||
return orders
|
return orders
|
||||||
|
|||||||
@@ -1,158 +1,158 @@
|
|||||||
import logging
|
import logging
|
||||||
from aiogram import types
|
from aiogram import types
|
||||||
from aiogram.dispatcher import FSMContext
|
from aiogram.dispatcher import FSMContext
|
||||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
from database import add_order, get_order_history
|
from database import add_order, get_order_history
|
||||||
from logger import log_action
|
from logger import log_action
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
class OrderForm(StatesGroup):
|
class OrderForm(StatesGroup):
|
||||||
waiting_for_name = State()
|
waiting_for_name = State()
|
||||||
waiting_for_phone = State()
|
waiting_for_phone = State()
|
||||||
waiting_for_address = State()
|
waiting_for_address = State()
|
||||||
waiting_for_cleaning_time = State()
|
waiting_for_cleaning_time = State()
|
||||||
waiting_for_cleaning_type = State()
|
waiting_for_cleaning_type = State()
|
||||||
waiting_for_payment_method = State()
|
waiting_for_payment_method = State()
|
||||||
confirmation = State()
|
confirmation = State()
|
||||||
|
|
||||||
def main_menu_keyboard():
|
def main_menu_keyboard():
|
||||||
keyboard = InlineKeyboardMarkup()
|
keyboard = InlineKeyboardMarkup()
|
||||||
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
||||||
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
||||||
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
||||||
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
||||||
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
||||||
return keyboard
|
return keyboard
|
||||||
|
|
||||||
def account_data_keyboard():
|
def account_data_keyboard():
|
||||||
keyboard = InlineKeyboardMarkup()
|
keyboard = InlineKeyboardMarkup()
|
||||||
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
||||||
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
||||||
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
||||||
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
||||||
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
||||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||||
return keyboard
|
return keyboard
|
||||||
|
|
||||||
def order_confirmation_keyboard():
|
def order_confirmation_keyboard():
|
||||||
keyboard = InlineKeyboardMarkup()
|
keyboard = InlineKeyboardMarkup()
|
||||||
keyboard.add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order'))
|
keyboard.add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order'))
|
||||||
return keyboard
|
return keyboard
|
||||||
|
|
||||||
def register_handlers(dp, OPERATORS_GROUP_ID):
|
def register_handlers(dp, OPERATORS_GROUP_ID):
|
||||||
@dp.message_handler(commands=['start'])
|
@dp.message_handler(commands=['start'])
|
||||||
async def send_welcome(message: types.Message):
|
async def send_welcome(message: types.Message):
|
||||||
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
||||||
log_action(message.from_user.id, 'start')
|
log_action(message.from_user.id, 'start')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
||||||
async def process_account_data(callback_query: types.CallbackQuery):
|
async def process_account_data(callback_query: types.CallbackQuery):
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
await callback_query.message.answer("Учетные данные:", reply_markup=account_data_keyboard())
|
await callback_query.message.answer("Учетные данные:", reply_markup=account_data_keyboard())
|
||||||
log_action(callback_query.from_user.id, 'account_data')
|
log_action(callback_query.from_user.id, 'account_data')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
||||||
async def process_make_order(callback_query: types.CallbackQuery, state: FSMContext):
|
async def process_make_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
await callback_query.message.answer("Начнем с ваших учетных данных. Введите ваше ФИО:")
|
await callback_query.message.answer("Начнем с ваших учетных данных. Введите ваше ФИО:")
|
||||||
await OrderForm.waiting_for_name.set()
|
await OrderForm.waiting_for_name.set()
|
||||||
log_action(callback_query.from_user.id, 'make_order')
|
log_action(callback_query.from_user.id, 'make_order')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
||||||
async def process_order_history(callback_query: types.CallbackQuery):
|
async def process_order_history(callback_query: types.CallbackQuery):
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
orders = get_order_history(callback_query.from_user.id)
|
orders = get_order_history(callback_query.from_user.id)
|
||||||
if orders:
|
if orders:
|
||||||
history_message = "\n\n".join([f"Заказ {order['id']}:\nФИО: {order['name']}\nАдрес: {order['address']}\nТип уборки: {order['cleaning_type']}" for order in 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}")
|
await callback_query.message.answer(f"История заказов:\n{history_message}")
|
||||||
else:
|
else:
|
||||||
await callback_query.message.answer("История заказов пуста.")
|
await callback_query.message.answer("История заказов пуста.")
|
||||||
log_action(callback_query.from_user.id, 'order_history')
|
log_action(callback_query.from_user.id, 'order_history')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
||||||
async def process_request_call(callback_query: types.CallbackQuery):
|
async def process_request_call(callback_query: types.CallbackQuery):
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
await callback_query.message.answer("Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
await callback_query.message.answer("Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
||||||
log_action(callback_query.from_user.id, 'request_call')
|
log_action(callback_query.from_user.id, 'request_call')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
||||||
async def process_talk_operator(callback_query: types.CallbackQuery):
|
async def process_talk_operator(callback_query: types.CallbackQuery):
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
await callback_query.message.answer("Разговор с оператором начат.")
|
await callback_query.message.answer("Разговор с оператором начат.")
|
||||||
log_action(callback_query.from_user.id, 'talk_operator')
|
log_action(callback_query.from_user.id, 'talk_operator')
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
||||||
async def process_back_to_main(callback_query: types.CallbackQuery):
|
async def process_back_to_main(callback_query: types.CallbackQuery):
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
await callback_query.message.answer("Главное меню:", reply_markup=main_menu_keyboard())
|
await callback_query.message.answer("Главное меню:", reply_markup=main_menu_keyboard())
|
||||||
log_action(callback_query.from_user.id, 'back_to_main')
|
log_action(callback_query.from_user.id, 'back_to_main')
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT)
|
@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT)
|
||||||
async def process_name(message: types.Message, state: FSMContext):
|
async def process_name(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['name'] = message.text
|
data['name'] = message.text
|
||||||
await message.answer("Введите ваш номер телефона:")
|
await message.answer("Введите ваш номер телефона:")
|
||||||
await OrderForm.waiting_for_phone.set()
|
await OrderForm.waiting_for_phone.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT)
|
@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT)
|
||||||
async def process_phone(message: types.Message, state: FSMContext):
|
async def process_phone(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['phone'] = message.text
|
data['phone'] = message.text
|
||||||
await message.answer("Введите адрес для уборки:")
|
await message.answer("Введите адрес для уборки:")
|
||||||
await OrderForm.waiting_for_address.set()
|
await OrderForm.waiting_for_address.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||||
async def process_order_address(message: types.Message, state: FSMContext):
|
async def process_order_address(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['address'] = message.text
|
data['address'] = message.text
|
||||||
await message.answer("Выберите время для уборки (утро/день/вечер):")
|
await message.answer("Выберите время для уборки (утро/день/вечер):")
|
||||||
await OrderForm.waiting_for_cleaning_time.set()
|
await OrderForm.waiting_for_cleaning_time.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT)
|
@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 def process_cleaning_time(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['cleaning_time'] = message.text
|
data['cleaning_time'] = message.text
|
||||||
await message.answer("Выберите тип уборки (влажная/сухая/генеральная):")
|
await message.answer("Выберите тип уборки (влажная/сухая/генеральная):")
|
||||||
await OrderForm.waiting_for_cleaning_type.set()
|
await OrderForm.waiting_for_cleaning_type.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT)
|
@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 def process_cleaning_type(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['cleaning_type'] = message.text
|
data['cleaning_type'] = message.text
|
||||||
await message.answer("Выберите способ оплаты (картой/наличными):")
|
await message.answer("Выберите способ оплаты (картой/наличными):")
|
||||||
await OrderForm.waiting_for_payment_method.set()
|
await OrderForm.waiting_for_payment_method.set()
|
||||||
|
|
||||||
@dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT)
|
@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 def process_payment_method(message: types.Message, state: FSMContext):
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
data['payment_method'] = message.text
|
data['payment_method'] = message.text
|
||||||
|
|
||||||
await message.answer("Подтвердите заказ:\n"
|
await message.answer("Подтвердите заказ:\n"
|
||||||
f"ФИО: {data['name']}\n"
|
f"ФИО: {data['name']}\n"
|
||||||
f"Номер телефона: {data['phone']}\n"
|
f"Номер телефона: {data['phone']}\n"
|
||||||
f"Адрес: {data['address']}\n"
|
f"Адрес: {data['address']}\n"
|
||||||
f"Время уборки: {data['cleaning_time']}\n"
|
f"Время уборки: {data['cleaning_time']}\n"
|
||||||
f"Тип уборки: {data['cleaning_type']}\n"
|
f"Тип уборки: {data['cleaning_type']}\n"
|
||||||
f"Способ оплаты: {data['payment_method']}\n"
|
f"Способ оплаты: {data['payment_method']}\n"
|
||||||
"Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.",
|
"Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.",
|
||||||
reply_markup=order_confirmation_keyboard())
|
reply_markup=order_confirmation_keyboard())
|
||||||
await OrderForm.confirmation.set()
|
await OrderForm.confirmation.set()
|
||||||
|
|
||||||
@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation)
|
@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation)
|
||||||
async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext):
|
async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||||
await callback_query.answer()
|
await callback_query.answer()
|
||||||
async with state.proxy() as data:
|
async with state.proxy() as data:
|
||||||
order_details = (f"Новый заказ:\n"
|
order_details = (f"Новый заказ:\n"
|
||||||
f"ФИО: {data['name']}\n"
|
f"ФИО: {data['name']}\n"
|
||||||
f"Номер телефона: {data['phone']}\n"
|
f"Номер телефона: {data['phone']}\n"
|
||||||
f"Адрес: {data['address']}\n"
|
f"Адрес: {data['address']}\n"
|
||||||
f"Время уборки: {data['cleaning_time']}\n"
|
f"Время уборки: {data['cleaning_time']}\n"
|
||||||
f"Тип уборки: {data['cleaning_type']}\n"
|
f"Тип уборки: {data['cleaning_type']}\n"
|
||||||
f"Способ оплаты: {data['payment_method']}")
|
f"Способ оплаты: {data['payment_method']}")
|
||||||
await bot.send_message(OPERATORS_GROUP_ID, order_details)
|
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'])
|
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 bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.")
|
||||||
await state.finish()
|
await state.finish()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
def log_action(user_id, action):
|
def log_action(user_id, action):
|
||||||
logging.info(f"User {user_id} performed action: {action}")
|
logging.info(f"User {user_id} performed action: {action}")
|
||||||
|
|||||||
@@ -1,31 +1,31 @@
|
|||||||
import logging
|
import logging
|
||||||
from aiogram import Bot, Dispatcher, types
|
from aiogram import Bot, Dispatcher, types
|
||||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||||
from aiogram.utils import executor
|
from aiogram.utils import executor
|
||||||
from aiogram.dispatcher import FSMContext
|
from aiogram.dispatcher import FSMContext
|
||||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from handlers import register_handlers
|
from handlers import register_handlers
|
||||||
from database import create_tables
|
from database import create_tables
|
||||||
|
|
||||||
# Загрузка переменных окружения из .env файла
|
# Загрузка переменных окружения из .env файла
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
||||||
OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID'))
|
OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID'))
|
||||||
ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID'))
|
ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID'))
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
bot = Bot(token=API_TOKEN)
|
bot = Bot(token=API_TOKEN)
|
||||||
dp = Dispatcher(bot)
|
dp = Dispatcher(bot)
|
||||||
dp.middleware.setup(LoggingMiddleware())
|
dp.middleware.setup(LoggingMiddleware())
|
||||||
|
|
||||||
# Основное приложение
|
# Основное приложение
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
create_tables()
|
create_tables()
|
||||||
register_handlers(dp, OPERATORS_GROUP_ID)
|
register_handlers(dp, OPERATORS_GROUP_ID)
|
||||||
executor.start_polling(dp, skip_updates=True)
|
executor.start_polling(dp, skip_updates=True)
|
||||||
|
|||||||
23
doners/shop-telegram-bot/.dockerignore
Normal file
@@ -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
|
||||||
2
doners/shop-telegram-bot/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Monero: 43fxouHQFZ5guiyYSUWh6eL1ZQ7pDtVV8D1kKUzp4aYwQBgLjyY8q7KjeEbDvvTYCDPFEdDxz9duBdRrZPnjiSMwVGV4jZj
|
||||||
|
Bitcoin: bc1qnzft2cfty0hqptxjx9ajk4m2q9x3a30gvpylh2
|
||||||
27
doners/shop-telegram-bot/.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Помогите нам стать лучше
|
||||||
|
title: "[BUG]"
|
||||||
|
labels: bug
|
||||||
|
assignees: w1png
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Опишите ошибку**
|
||||||
|
Краткое и понятное описание проблемы.
|
||||||
|
|
||||||
|
**Как воспроизвести ошибку**
|
||||||
|
1. Откройте '...'
|
||||||
|
2. Нажмите на '....'
|
||||||
|
3. Пролистайте до '....'
|
||||||
|
4. Ошибка
|
||||||
|
|
||||||
|
**Ожидаемое поведение**
|
||||||
|
Краткое и понятное описание ожидаемого поведения.
|
||||||
|
|
||||||
|
**Скриншоты**
|
||||||
|
Прикрепите скриншот с проблемой.
|
||||||
|
|
||||||
|
**Информация о системе:**
|
||||||
|
- Операционная система
|
||||||
|
- Версия Python
|
||||||
15
doners/shop-telegram-bot/.gitignore
vendored
Normal file
@@ -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/
|
||||||
BIN
doners/shop-telegram-bot/DOCS/bot_overview.gif
Normal file
|
After Width: | Height: | Size: 11 MiB |
7
doners/shop-telegram-bot/Dockerfile
Normal file
@@ -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
|
||||||
674
doners/shop-telegram-bot/LICENSE
Normal file
@@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
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.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
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
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
0
doners/shop-telegram-bot/__init__.py
Normal file
174
doners/shop-telegram-bot/installer.py
Normal file
@@ -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, чтобы продолжить...")
|
||||||
83
doners/shop-telegram-bot/readme.md
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
# ⚠️ Эта версия бота больше не обновляется!
|
||||||
|
Сейчас я переписываю бота, за обновлениями следите на ветке rewrite :)
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## 🪲 В случае возникновения ошибок открывайте тикет во вкладке Issues :)
|
||||||
|
|
||||||
|
# Навигация
|
||||||
|
|
||||||
|
- [Навигация](#навигация)
|
||||||
|
- [Зачем нужен этот бот?](#зачем-нужен-этот-бот)
|
||||||
|
- [Установка](#установка)
|
||||||
|
- [Docker](#docker)
|
||||||
|
- [Python](#python)
|
||||||
|
- [Установка необходимых Python-пакетов](#установка-необходимых-python-пакетов)
|
||||||
|
- [Запуск установщика](#запуск-установщика)
|
||||||
|
- [Запуск бота](#запуск-бота)
|
||||||
|
- [Запуск с помощью скрипта](#запуск-с-помощью-скрипта)
|
||||||
|
- [Linux](#linux)
|
||||||
|
- [Macos](#macos)
|
||||||
|
- [Запуск вручную](#запуск-вручную)
|
||||||
|
- [Режим отладки](#режим-отладки)
|
||||||
|
|
||||||
|
# Зачем нужен этот бот?
|
||||||
|
|
||||||
|
Зачастую люди, желающие открыть маленький интернет-бизнес, делают это с помощью профиля в социальных сетях, что требует вручную обрабатывать каждую заявку. Этот бот позволит каждому быстро открыть автоматизированный магазин на базе телеграм бота, что значительно уменьшит время обработки заказов.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
# Установка
|
||||||
|
|
||||||
|
## 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: <MESSAGE/CALL> [<user_id>] <Сообщение/вызов>
|
||||||
|
|
||||||
|
*Пример: `DEBUG CALL [462741] admin_itemManagement`*
|
||||||