old_01
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
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
|
||||
18
doners/bot-market/.idea/bot-market.iml
generated
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,6 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
12
doners/bot-market/.idea/misc.xml
generated
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.10 (bot-market)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (bot-market)" project-jdk-type="Python SDK" />
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.10 (bot-market)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (bot-market)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
14
doners/bot-market/.idea/modules.xml
generated
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/bot-market.iml" filepath="$PROJECT_DIR$/.idea/bot-market.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/bot-market.iml" filepath="$PROJECT_DIR$/.idea/bot-market.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</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.types import Message, CallbackQuery
|
||||
from aiogram.filters import CommandStart, Command, Filter
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
|
||||
import app.keyboards as kb
|
||||
from app.database.requests import get_users, set_item
|
||||
|
||||
admin = Router()
|
||||
|
||||
|
||||
class Newsletter(StatesGroup):
|
||||
message = State()
|
||||
|
||||
|
||||
class AddItem(StatesGroup):
|
||||
name = State()
|
||||
category = State()
|
||||
description = State()
|
||||
photo = State()
|
||||
price = State()
|
||||
|
||||
|
||||
class AdminProtect(Filter):
|
||||
async def __call__(self, message: Message):
|
||||
return message.from_user.id in [1477217831]
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), Command('apanel'))
|
||||
async def apanel(message: Message):
|
||||
await message.answer('Возможные команды: /newsletter\n/add_item')
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), Command('newsletter'))
|
||||
async def newsletter(message: Message, state: FSMContext):
|
||||
await state.set_state(Newsletter.message)
|
||||
await message.answer('Отправьте сообщение, которое вы хотите разослать всем пользователям')
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), Newsletter.message)
|
||||
async def newsletter_message(message: Message, state: FSMContext):
|
||||
await message.answer('Подождите... идёт рассылка.')
|
||||
for user in await get_users():
|
||||
try:
|
||||
await message.send_copy(chat_id=user.tg_id)
|
||||
except:
|
||||
pass
|
||||
await message.answer('Рассылка успешно завершена.')
|
||||
await state.clear()
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), Command('add_item'))
|
||||
async def add_item(message: Message, state: FSMContext):
|
||||
await state.set_state(AddItem.name)
|
||||
await message.answer('Введите название товара')
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), AddItem.name)
|
||||
async def add_item_name(message: Message, state: FSMContext):
|
||||
await state.update_data(name=message.text)
|
||||
await state.set_state(AddItem.category)
|
||||
await message.answer('Выберите категорию товара', reply_markup=await kb.categories())
|
||||
|
||||
|
||||
@admin.callback_query(AdminProtect(), AddItem.category)
|
||||
async def add_item_category(callback: CallbackQuery, state: FSMContext):
|
||||
await state.update_data(category=callback.data.split('_')[1])
|
||||
await state.set_state(AddItem.description)
|
||||
await callback.answer('')
|
||||
await callback.message.answer('Введите описание товара')
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), AddItem.description)
|
||||
async def add_item_description(message: Message, state: FSMContext):
|
||||
await state.update_data(description=message.text)
|
||||
await state.set_state(AddItem.photo)
|
||||
await message.answer('Отправьте фото товара')
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), AddItem.photo, F.photo)
|
||||
async def add_item_photo(message: Message, state: FSMContext):
|
||||
await state.update_data(photo=message.photo[-1].file_id)
|
||||
await state.set_state(AddItem.price)
|
||||
await message.answer('Введите цену товара')
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), AddItem.price)
|
||||
async def add_item_price(message: Message, state: FSMContext):
|
||||
await state.update_data(price=message.text)
|
||||
data = await state.get_data()
|
||||
await set_item(data)
|
||||
await message.answer('Товар успешно добавлен')
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from aiogram.filters import CommandStart, Command, Filter
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
|
||||
import app.keyboards as kb
|
||||
from app.database.requests import get_users, set_item
|
||||
|
||||
admin = Router()
|
||||
|
||||
|
||||
class Newsletter(StatesGroup):
|
||||
message = State()
|
||||
|
||||
|
||||
class AddItem(StatesGroup):
|
||||
name = State()
|
||||
category = State()
|
||||
description = State()
|
||||
photo = State()
|
||||
price = State()
|
||||
|
||||
|
||||
class AdminProtect(Filter):
|
||||
async def __call__(self, message: Message):
|
||||
return message.from_user.id in [1477217831]
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), Command('apanel'))
|
||||
async def apanel(message: Message):
|
||||
await message.answer('Возможные команды: /newsletter\n/add_item')
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), Command('newsletter'))
|
||||
async def newsletter(message: Message, state: FSMContext):
|
||||
await state.set_state(Newsletter.message)
|
||||
await message.answer('Отправьте сообщение, которое вы хотите разослать всем пользователям')
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), Newsletter.message)
|
||||
async def newsletter_message(message: Message, state: FSMContext):
|
||||
await message.answer('Подождите... идёт рассылка.')
|
||||
for user in await get_users():
|
||||
try:
|
||||
await message.send_copy(chat_id=user.tg_id)
|
||||
except:
|
||||
pass
|
||||
await message.answer('Рассылка успешно завершена.')
|
||||
await state.clear()
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), Command('add_item'))
|
||||
async def add_item(message: Message, state: FSMContext):
|
||||
await state.set_state(AddItem.name)
|
||||
await message.answer('Введите название товара')
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), AddItem.name)
|
||||
async def add_item_name(message: Message, state: FSMContext):
|
||||
await state.update_data(name=message.text)
|
||||
await state.set_state(AddItem.category)
|
||||
await message.answer('Выберите категорию товара', reply_markup=await kb.categories())
|
||||
|
||||
|
||||
@admin.callback_query(AdminProtect(), AddItem.category)
|
||||
async def add_item_category(callback: CallbackQuery, state: FSMContext):
|
||||
await state.update_data(category=callback.data.split('_')[1])
|
||||
await state.set_state(AddItem.description)
|
||||
await callback.answer('')
|
||||
await callback.message.answer('Введите описание товара')
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), AddItem.description)
|
||||
async def add_item_description(message: Message, state: FSMContext):
|
||||
await state.update_data(description=message.text)
|
||||
await state.set_state(AddItem.photo)
|
||||
await message.answer('Отправьте фото товара')
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), AddItem.photo, F.photo)
|
||||
async def add_item_photo(message: Message, state: FSMContext):
|
||||
await state.update_data(photo=message.photo[-1].file_id)
|
||||
await state.set_state(AddItem.price)
|
||||
await message.answer('Введите цену товара')
|
||||
|
||||
|
||||
@admin.message(AdminProtect(), AddItem.price)
|
||||
async def add_item_price(message: Message, state: FSMContext):
|
||||
await state.update_data(price=message.text)
|
||||
data = await state.get_data()
|
||||
await set_item(data)
|
||||
await message.answer('Товар успешно добавлен')
|
||||
await state.clear()
|
||||
0
doners/bot-market/app/database/__init__.py
Normal file
@@ -1,62 +1,62 @@
|
||||
from sqlalchemy import BigInteger, ForeignKey, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship, DeclarativeBase
|
||||
from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine
|
||||
|
||||
from typing import List
|
||||
from config import ENGINE, ECHO
|
||||
|
||||
engine = create_async_engine(url=ENGINE)#, echo=ECHO)
|
||||
|
||||
async_session = async_sessionmaker(engine)
|
||||
|
||||
|
||||
class Base(AsyncAttrs, DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'users'
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
tg_id = mapped_column(BigInteger)
|
||||
|
||||
basket_rel: Mapped[List['Basket']] = relationship(back_populates='user_rel')
|
||||
|
||||
|
||||
class Category(Base):
|
||||
__tablename__ = 'categories'
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(50))
|
||||
|
||||
item_rel: Mapped[List['Item']] = relationship(back_populates='category_rel')
|
||||
|
||||
|
||||
class Item(Base):
|
||||
__tablename__ = 'items'
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(50))
|
||||
description: Mapped[str] = mapped_column(String(200))
|
||||
photo: Mapped[str] = mapped_column(String(200))
|
||||
price: Mapped[int] = mapped_column()
|
||||
category: Mapped[int] = mapped_column(ForeignKey('categories.id'))
|
||||
|
||||
category_rel: Mapped['Category'] = relationship(back_populates='item_rel')
|
||||
basket_rel: Mapped[List['Basket']] = relationship(back_populates='item_rel')
|
||||
|
||||
|
||||
class Basket(Base):
|
||||
__tablename__ = 'basket'
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
user: Mapped[int] = mapped_column(ForeignKey('users.id'))
|
||||
item: Mapped[int] = mapped_column(ForeignKey('items.id'))
|
||||
|
||||
user_rel: Mapped['User'] = relationship(back_populates='basket_rel')
|
||||
item_rel: Mapped['Item'] = relationship(back_populates='basket_rel')
|
||||
|
||||
|
||||
async def async_main():
|
||||
async with engine.begin() as conn:
|
||||
from sqlalchemy import BigInteger, ForeignKey, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship, DeclarativeBase
|
||||
from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine
|
||||
|
||||
from typing import List
|
||||
from config import ENGINE, ECHO
|
||||
|
||||
engine = create_async_engine(url=ENGINE)#, echo=ECHO)
|
||||
|
||||
async_session = async_sessionmaker(engine)
|
||||
|
||||
|
||||
class Base(AsyncAttrs, DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'users'
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
tg_id = mapped_column(BigInteger)
|
||||
|
||||
basket_rel: Mapped[List['Basket']] = relationship(back_populates='user_rel')
|
||||
|
||||
|
||||
class Category(Base):
|
||||
__tablename__ = 'categories'
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(50))
|
||||
|
||||
item_rel: Mapped[List['Item']] = relationship(back_populates='category_rel')
|
||||
|
||||
|
||||
class Item(Base):
|
||||
__tablename__ = 'items'
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
name: Mapped[str] = mapped_column(String(50))
|
||||
description: Mapped[str] = mapped_column(String(200))
|
||||
photo: Mapped[str] = mapped_column(String(200))
|
||||
price: Mapped[int] = mapped_column()
|
||||
category: Mapped[int] = mapped_column(ForeignKey('categories.id'))
|
||||
|
||||
category_rel: Mapped['Category'] = relationship(back_populates='item_rel')
|
||||
basket_rel: Mapped[List['Basket']] = relationship(back_populates='item_rel')
|
||||
|
||||
|
||||
class Basket(Base):
|
||||
__tablename__ = 'basket'
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True)
|
||||
user: Mapped[int] = mapped_column(ForeignKey('users.id'))
|
||||
item: Mapped[int] = mapped_column(ForeignKey('items.id'))
|
||||
|
||||
user_rel: Mapped['User'] = relationship(back_populates='basket_rel')
|
||||
item_rel: Mapped['Item'] = relationship(back_populates='basket_rel')
|
||||
|
||||
|
||||
async def async_main():
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
@@ -1,64 +1,64 @@
|
||||
from app.database.models import User, Category, Item, Basket
|
||||
from app.database.models import async_session
|
||||
|
||||
from sqlalchemy import select, update, delete
|
||||
|
||||
|
||||
async def set_user(tg_id):
|
||||
async with async_session() as session:
|
||||
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
||||
|
||||
if not user:
|
||||
session.add(User(tg_id=tg_id))
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def set_item(data):
|
||||
async with async_session() as session:
|
||||
session.add(Item(**data))
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def set_basket(tg_id, item_id):
|
||||
async with async_session() as session:
|
||||
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
||||
session.add(Basket(user=user.id, item=item_id))
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def get_basket(tg_id):
|
||||
async with async_session() as session:
|
||||
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
||||
basket = await session.scalars(select(Basket).where(Basket.user == user.id))
|
||||
return basket
|
||||
|
||||
|
||||
async def get_users():
|
||||
async with async_session() as session:
|
||||
users = await session.scalars(select(User))
|
||||
return users
|
||||
|
||||
|
||||
async def get_categories():
|
||||
async with async_session() as session:
|
||||
categories = await session.scalars(select(Category))
|
||||
return categories
|
||||
|
||||
|
||||
async def get_items_by_category(category_id: int):
|
||||
async with async_session() as session:
|
||||
items = await session.scalars(select(Item).where(Item.category == category_id))
|
||||
return items
|
||||
|
||||
|
||||
async def get_item_by_id(item_id: int):
|
||||
async with async_session() as session:
|
||||
item = await session.scalar(select(Item).where(Item.id == item_id))
|
||||
return item
|
||||
|
||||
|
||||
async def delete_basket(tg_id, item_id):
|
||||
async with async_session() as session:
|
||||
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
||||
await session.execute(delete(Basket).where(Basket.user == user.id, Basket.item == item_id))
|
||||
from app.database.models import User, Category, Item, Basket
|
||||
from app.database.models import async_session
|
||||
|
||||
from sqlalchemy import select, update, delete
|
||||
|
||||
|
||||
async def set_user(tg_id):
|
||||
async with async_session() as session:
|
||||
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
||||
|
||||
if not user:
|
||||
session.add(User(tg_id=tg_id))
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def set_item(data):
|
||||
async with async_session() as session:
|
||||
session.add(Item(**data))
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def set_basket(tg_id, item_id):
|
||||
async with async_session() as session:
|
||||
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
||||
session.add(Basket(user=user.id, item=item_id))
|
||||
await session.commit()
|
||||
|
||||
|
||||
async def get_basket(tg_id):
|
||||
async with async_session() as session:
|
||||
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
||||
basket = await session.scalars(select(Basket).where(Basket.user == user.id))
|
||||
return basket
|
||||
|
||||
|
||||
async def get_users():
|
||||
async with async_session() as session:
|
||||
users = await session.scalars(select(User))
|
||||
return users
|
||||
|
||||
|
||||
async def get_categories():
|
||||
async with async_session() as session:
|
||||
categories = await session.scalars(select(Category))
|
||||
return categories
|
||||
|
||||
|
||||
async def get_items_by_category(category_id: int):
|
||||
async with async_session() as session:
|
||||
items = await session.scalars(select(Item).where(Item.category == category_id))
|
||||
return items
|
||||
|
||||
|
||||
async def get_item_by_id(item_id: int):
|
||||
async with async_session() as session:
|
||||
item = await session.scalar(select(Item).where(Item.id == item_id))
|
||||
return item
|
||||
|
||||
|
||||
async def delete_basket(tg_id, item_id):
|
||||
async with async_session() as session:
|
||||
user = await session.scalar(select(User).where(User.tg_id == tg_id))
|
||||
await session.execute(delete(Basket).where(Basket.user == user.id, Basket.item == item_id))
|
||||
await session.commit()
|
||||
@@ -1,72 +1,72 @@
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from aiogram.filters import CommandStart, Command
|
||||
|
||||
import app.keyboards as kb
|
||||
from app.database.requests import (get_item_by_id, set_user,
|
||||
set_basket, get_basket, get_item_by_id, delete_basket)
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.message(CommandStart())
|
||||
@router.callback_query(F.data == 'to_main')
|
||||
async def cmd_start(message: Message | CallbackQuery):
|
||||
if isinstance(message, Message):
|
||||
await set_user(message.from_user.id)
|
||||
await message.answer("Добро пожаловать в интернет магазин!",
|
||||
reply_markup=kb.main)
|
||||
else:
|
||||
await message.answer('Вы вернулись на главную')
|
||||
await message.message.answer("Добро пожаловать в интернет магазин!",
|
||||
reply_markup=kb.main)
|
||||
|
||||
|
||||
@router.callback_query(F.data == 'catalog')
|
||||
async def catalog(callback: CallbackQuery):
|
||||
await callback.answer('')
|
||||
await callback.message.edit_text(text='Выберите категорию.',
|
||||
reply_markup=await kb.categories())
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith('category_'))
|
||||
async def category(callback: CallbackQuery):
|
||||
await callback.answer('')
|
||||
await callback.message.edit_text('Выберите товар',
|
||||
reply_markup=await kb.items(callback.data.split('_')[1]))
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith('item_'))
|
||||
async def category(callback: CallbackQuery):
|
||||
item = await get_item_by_id(callback.data.split('_')[1])
|
||||
await callback.answer('')
|
||||
await callback.message.answer_photo(photo=item.photo,
|
||||
caption=f'{item.name}\n\n{item.description}\n\nЦена: {item.price} рублей',
|
||||
reply_markup=await kb.basket(item.id))
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith('order_'))
|
||||
async def basket(callback: CallbackQuery):
|
||||
await set_basket(callback.from_user.id, callback.data.split('_')[1])
|
||||
await callback.answer('Товар добавлен в корзину')
|
||||
|
||||
|
||||
@router.callback_query(F.data == 'mybasket')
|
||||
async def mybasket(callback: CallbackQuery):
|
||||
await callback.answer('')
|
||||
basket = await get_basket(callback.from_user.id)
|
||||
counter = 0
|
||||
for item_info in basket:
|
||||
item = await get_item_by_id(item_info.item)
|
||||
await callback.message.answer_photo(photo=item.photo,
|
||||
caption=f'{item.name}\n\n{item.description}\n\nЦена: {item.price} рублей',
|
||||
reply_markup=await kb.delete_from_basket(item.id))
|
||||
counter += 1
|
||||
await callback.message.answer('Ваша корзина пуста') if counter == 0 else await callback.answer('')
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith('delete_'))
|
||||
async def delete_from_basket(callback: CallbackQuery):
|
||||
await delete_basket(callback.from_user.id, callback.data.split('_')[1])
|
||||
await callback.message.delete()
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from aiogram.filters import CommandStart, Command
|
||||
|
||||
import app.keyboards as kb
|
||||
from app.database.requests import (get_item_by_id, set_user,
|
||||
set_basket, get_basket, get_item_by_id, delete_basket)
|
||||
|
||||
router = Router()
|
||||
|
||||
|
||||
@router.message(CommandStart())
|
||||
@router.callback_query(F.data == 'to_main')
|
||||
async def cmd_start(message: Message | CallbackQuery):
|
||||
if isinstance(message, Message):
|
||||
await set_user(message.from_user.id)
|
||||
await message.answer("Добро пожаловать в интернет магазин!",
|
||||
reply_markup=kb.main)
|
||||
else:
|
||||
await message.answer('Вы вернулись на главную')
|
||||
await message.message.answer("Добро пожаловать в интернет магазин!",
|
||||
reply_markup=kb.main)
|
||||
|
||||
|
||||
@router.callback_query(F.data == 'catalog')
|
||||
async def catalog(callback: CallbackQuery):
|
||||
await callback.answer('')
|
||||
await callback.message.edit_text(text='Выберите категорию.',
|
||||
reply_markup=await kb.categories())
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith('category_'))
|
||||
async def category(callback: CallbackQuery):
|
||||
await callback.answer('')
|
||||
await callback.message.edit_text('Выберите товар',
|
||||
reply_markup=await kb.items(callback.data.split('_')[1]))
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith('item_'))
|
||||
async def category(callback: CallbackQuery):
|
||||
item = await get_item_by_id(callback.data.split('_')[1])
|
||||
await callback.answer('')
|
||||
await callback.message.answer_photo(photo=item.photo,
|
||||
caption=f'{item.name}\n\n{item.description}\n\nЦена: {item.price} рублей',
|
||||
reply_markup=await kb.basket(item.id))
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith('order_'))
|
||||
async def basket(callback: CallbackQuery):
|
||||
await set_basket(callback.from_user.id, callback.data.split('_')[1])
|
||||
await callback.answer('Товар добавлен в корзину')
|
||||
|
||||
|
||||
@router.callback_query(F.data == 'mybasket')
|
||||
async def mybasket(callback: CallbackQuery):
|
||||
await callback.answer('')
|
||||
basket = await get_basket(callback.from_user.id)
|
||||
counter = 0
|
||||
for item_info in basket:
|
||||
item = await get_item_by_id(item_info.item)
|
||||
await callback.message.answer_photo(photo=item.photo,
|
||||
caption=f'{item.name}\n\n{item.description}\n\nЦена: {item.price} рублей',
|
||||
reply_markup=await kb.delete_from_basket(item.id))
|
||||
counter += 1
|
||||
await callback.message.answer('Ваша корзина пуста') if counter == 0 else await callback.answer('')
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith('delete_'))
|
||||
async def delete_from_basket(callback: CallbackQuery):
|
||||
await delete_basket(callback.from_user.id, callback.data.split('_')[1])
|
||||
await callback.message.delete()
|
||||
await callback.answer('Вы удалили товар из корзины')
|
||||
@@ -1,49 +1,49 @@
|
||||
|
||||
from aiogram.types import (ReplyKeyboardMarkup, KeyboardButton,
|
||||
InlineKeyboardMarkup, InlineKeyboardButton)
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from app.database.requests import get_categories, get_items_by_category
|
||||
|
||||
main = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text='Каталог', callback_data='catalog')],
|
||||
[InlineKeyboardButton(text='Корзина', callback_data='mybasket'),
|
||||
InlineKeyboardButton(text='Контакты', callback_data='contacts')]
|
||||
])
|
||||
|
||||
to_main = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text='На главную', callback_data='to_main')]
|
||||
])
|
||||
|
||||
|
||||
async def delete_from_basket(order_id):
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
keyboard.add(InlineKeyboardButton(text='Удалить из корзины', callback_data=f'delete_{order_id}'))
|
||||
return keyboard.adjust(2).as_markup()
|
||||
|
||||
|
||||
async def basket(order_id):
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
keyboard.add(InlineKeyboardButton(text='Оформить заказ', callback_data=f'order_{order_id}'))
|
||||
keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main'))
|
||||
return keyboard.adjust(2).as_markup()
|
||||
|
||||
|
||||
async def categories():
|
||||
all_categories = await get_categories()
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
for category in all_categories:
|
||||
keyboard.add(InlineKeyboardButton(text=category.name,
|
||||
callback_data=f'category_{category.id}'))
|
||||
keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main'))
|
||||
return keyboard.adjust(2).as_markup()
|
||||
|
||||
|
||||
async def items(category_id: int):
|
||||
items = await get_items_by_category(category_id)
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
for item in items:
|
||||
keyboard.add(InlineKeyboardButton(text=item.name,
|
||||
callback_data=f"item_{item.id}"))
|
||||
keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main'))
|
||||
|
||||
from aiogram.types import (ReplyKeyboardMarkup, KeyboardButton,
|
||||
InlineKeyboardMarkup, InlineKeyboardButton)
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder
|
||||
|
||||
from app.database.requests import get_categories, get_items_by_category
|
||||
|
||||
main = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text='Каталог', callback_data='catalog')],
|
||||
[InlineKeyboardButton(text='Корзина', callback_data='mybasket'),
|
||||
InlineKeyboardButton(text='Контакты', callback_data='contacts')]
|
||||
])
|
||||
|
||||
to_main = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text='На главную', callback_data='to_main')]
|
||||
])
|
||||
|
||||
|
||||
async def delete_from_basket(order_id):
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
keyboard.add(InlineKeyboardButton(text='Удалить из корзины', callback_data=f'delete_{order_id}'))
|
||||
return keyboard.adjust(2).as_markup()
|
||||
|
||||
|
||||
async def basket(order_id):
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
keyboard.add(InlineKeyboardButton(text='Оформить заказ', callback_data=f'order_{order_id}'))
|
||||
keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main'))
|
||||
return keyboard.adjust(2).as_markup()
|
||||
|
||||
|
||||
async def categories():
|
||||
all_categories = await get_categories()
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
for category in all_categories:
|
||||
keyboard.add(InlineKeyboardButton(text=category.name,
|
||||
callback_data=f'category_{category.id}'))
|
||||
keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main'))
|
||||
return keyboard.adjust(2).as_markup()
|
||||
|
||||
|
||||
async def items(category_id: int):
|
||||
items = await get_items_by_category(category_id)
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
for item in items:
|
||||
keyboard.add(InlineKeyboardButton(text=item.name,
|
||||
callback_data=f"item_{item.id}"))
|
||||
keyboard.add(InlineKeyboardButton(text='Назад', callback_data='to_main'))
|
||||
return keyboard.adjust(2).as_markup()
|
||||
@@ -1,3 +1,3 @@
|
||||
TOKEN='7103505936:AAEpiQxlKNd9Uji9IziTdNzSTH38PavqXZM'
|
||||
ENGINE='sqlite+aiosqlite:///db.sqlite3'
|
||||
TOKEN='7103505936:AAEpiQxlKNd9Uji9IziTdNzSTH38PavqXZM'
|
||||
ENGINE='sqlite+aiosqlite:///db.sqlite3'
|
||||
ECHO=''
|
||||
@@ -1,25 +1,25 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from aiogram import Bot, Dispatcher
|
||||
|
||||
from config import TOKEN
|
||||
from app.database.models import async_main
|
||||
from app.handlers import router
|
||||
from app.admin import admin
|
||||
|
||||
|
||||
async def main():
|
||||
await async_main()
|
||||
bot = Bot(token=TOKEN)
|
||||
dp = Dispatcher()
|
||||
dp.include_routers(admin, router)
|
||||
await dp.start_polling(bot)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from aiogram import Bot, Dispatcher
|
||||
|
||||
from config import TOKEN
|
||||
from app.database.models import async_main
|
||||
from app.handlers import router
|
||||
from app.admin import admin
|
||||
|
||||
|
||||
async def main():
|
||||
await async_main()
|
||||
bot = Bot(token=TOKEN)
|
||||
dp = Dispatcher()
|
||||
dp.include_routers(admin, router)
|
||||
await dp.start_polling(bot)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
print('Exit')
|
||||
0
doners/old/__init__.py
Normal file
@@ -1,103 +1,103 @@
|
||||
import os
|
||||
import logging
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.utils import executor
|
||||
from aiogram.dispatcher.filters import Text
|
||||
from database import create_tables
|
||||
from logger import log_action
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Загрузка переменных окружения из .env файла
|
||||
load_dotenv()
|
||||
|
||||
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
||||
OPERATORS_GROUP_ID = os.getenv('OPERATORS_GROUP_ID')
|
||||
ADMIN_GROUP_ID = os.getenv('ADMIN_GROUP_ID')
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Inline клавиатуры
|
||||
def main_menu_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
||||
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
||||
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
||||
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
||||
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
||||
return keyboard
|
||||
|
||||
def account_data_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
||||
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
||||
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
||||
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
||||
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||
return keyboard
|
||||
|
||||
def order_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Добавить учетные данные', callback_data='add_account_data'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать адрес', callback_data='select_address'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать время для уборки', callback_data='select_time'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать тип уборки', callback_data='select_cleaning_type'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать способ оплаты', callback_data='select_payment_method'))
|
||||
keyboard.add(InlineKeyboardButton('Подтвердить заказ', callback_data='confirm_order'))
|
||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||
return keyboard
|
||||
|
||||
# Обработчики команд
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def send_welcome(message: types.Message):
|
||||
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
||||
log_action(message.from_user.id, 'start')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
||||
async def process_account_data(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard())
|
||||
log_action(callback_query.from_user.id, 'account_data')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
||||
async def process_make_order(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Сделать заказ:", reply_markup=order_keyboard())
|
||||
log_action(callback_query.from_user.id, 'make_order')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
||||
async def process_order_history(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для просмотра истории заказов
|
||||
await bot.send_message(callback_query.from_user.id, "История заказов:")
|
||||
log_action(callback_query.from_user.id, 'order_history')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
||||
async def process_request_call(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для заказа звонка
|
||||
await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
||||
log_action(callback_query.from_user.id, 'request_call')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
||||
async def process_talk_operator(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для начала разговора с оператором
|
||||
await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.")
|
||||
log_action(callback_query.from_user.id, 'talk_operator')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
||||
async def process_back_to_main(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard())
|
||||
log_action(callback_query.from_user.id, 'back_to_main')
|
||||
|
||||
if __name__ == '__main__':
|
||||
create_tables()
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
import os
|
||||
import logging
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.utils import executor
|
||||
from aiogram.dispatcher.filters import Text
|
||||
from database import create_tables
|
||||
from logger import log_action
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Загрузка переменных окружения из .env файла
|
||||
load_dotenv()
|
||||
|
||||
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
||||
OPERATORS_GROUP_ID = os.getenv('OPERATORS_GROUP_ID')
|
||||
ADMIN_GROUP_ID = os.getenv('ADMIN_GROUP_ID')
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Inline клавиатуры
|
||||
def main_menu_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
||||
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
||||
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
||||
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
||||
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
||||
return keyboard
|
||||
|
||||
def account_data_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
||||
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
||||
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
||||
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
||||
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||
return keyboard
|
||||
|
||||
def order_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Добавить учетные данные', callback_data='add_account_data'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать адрес', callback_data='select_address'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать время для уборки', callback_data='select_time'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать тип уборки', callback_data='select_cleaning_type'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать способ оплаты', callback_data='select_payment_method'))
|
||||
keyboard.add(InlineKeyboardButton('Подтвердить заказ', callback_data='confirm_order'))
|
||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||
return keyboard
|
||||
|
||||
# Обработчики команд
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def send_welcome(message: types.Message):
|
||||
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
||||
log_action(message.from_user.id, 'start')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
||||
async def process_account_data(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard())
|
||||
log_action(callback_query.from_user.id, 'account_data')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
||||
async def process_make_order(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Сделать заказ:", reply_markup=order_keyboard())
|
||||
log_action(callback_query.from_user.id, 'make_order')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
||||
async def process_order_history(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для просмотра истории заказов
|
||||
await bot.send_message(callback_query.from_user.id, "История заказов:")
|
||||
log_action(callback_query.from_user.id, 'order_history')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
||||
async def process_request_call(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для заказа звонка
|
||||
await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
||||
log_action(callback_query.from_user.id, 'request_call')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
||||
async def process_talk_operator(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для начала разговора с оператором
|
||||
await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.")
|
||||
log_action(callback_query.from_user.id, 'talk_operator')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
||||
async def process_back_to_main(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard())
|
||||
log_action(callback_query.from_user.id, 'back_to_main')
|
||||
|
||||
if __name__ == '__main__':
|
||||
create_tables()
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
||||
@@ -1,254 +1,254 @@
|
||||
import logging
|
||||
import sqlite3
|
||||
from aiogram import types
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.utils import executor
|
||||
from aiogram import Bot, Dispatcher
|
||||
from aiogram.dispatcher.filters import Text
|
||||
from database import create_tables, add_user, update_user_name, update_user_phone, add_address, delete_address
|
||||
from logger import log_action
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
# Загрузка переменных окружения из .env файла
|
||||
load_dotenv()
|
||||
|
||||
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
||||
OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID'))
|
||||
ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID'))
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Состояния
|
||||
class OrderForm(StatesGroup):
|
||||
waiting_for_name = State()
|
||||
waiting_for_phone = State()
|
||||
waiting_for_address = State()
|
||||
waiting_for_cleaning_time = State()
|
||||
waiting_for_cleaning_type = State()
|
||||
waiting_for_payment_method = State()
|
||||
confirmation = State()
|
||||
|
||||
class CallRequest(StatesGroup):
|
||||
waiting_for_name = State()
|
||||
waiting_for_phone = State()
|
||||
|
||||
class OperatorChat(StatesGroup):
|
||||
in_chat = State()
|
||||
|
||||
# Inline клавиатуры
|
||||
def main_menu_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
||||
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
||||
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
||||
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
||||
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
||||
return keyboard
|
||||
|
||||
def account_data_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
||||
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
||||
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
||||
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
||||
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||
return keyboard
|
||||
|
||||
def order_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Добавить учетные данные', callback_data='add_account_data'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать адрес', callback_data='select_address'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать время для уборки', callback_data='select_time'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать тип уборки', callback_data='select_cleaning_type'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать способ оплаты', callback_data='select_payment_method'))
|
||||
keyboard.add(InlineKeyboardButton('Подтвердить заказ', callback_data='confirm_order'))
|
||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||
return keyboard
|
||||
|
||||
# Основные команды
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def send_welcome(message: types.Message):
|
||||
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
||||
log_action(message.from_user.id, 'start')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
||||
async def process_account_data(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard())
|
||||
log_action(callback_query.from_user.id, 'account_data')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
||||
async def process_make_order(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Сделать заказ:", reply_markup=order_keyboard())
|
||||
log_action(callback_query.from_user.id, 'make_order')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
||||
async def process_order_history(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для просмотра истории заказов
|
||||
await bot.send_message(callback_query.from_user.id, "История заказов:")
|
||||
log_action(callback_query.from_user.id, 'order_history')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
||||
async def process_request_call(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для заказа звонка
|
||||
await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
||||
log_action(callback_query.from_user.id, 'request_call')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
||||
async def process_talk_operator(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для начала разговора с оператором
|
||||
await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.")
|
||||
log_action(callback_query.from_user.id, 'talk_operator')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
||||
async def process_back_to_main(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard())
|
||||
log_action(callback_query.from_user.id, 'back_to_main')
|
||||
|
||||
# Добавление учетных данных
|
||||
@dp.callback_query_handler(lambda c: c.data == 'add_account_data')
|
||||
async def process_add_account_data(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Введите ваше ФИО:")
|
||||
await OrderForm.waiting_for_name.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT)
|
||||
async def process_name(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['name'] = message.text
|
||||
await message.answer("Введите ваш номер телефона:")
|
||||
await OrderForm.waiting_for_phone.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT)
|
||||
async def process_phone(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['phone'] = message.text
|
||||
user_id = message.from_user.id
|
||||
name = data['name']
|
||||
phone = data['phone']
|
||||
add_user(user_id, name, phone)
|
||||
await message.answer("Ваши данные успешно сохранены!")
|
||||
await state.finish()
|
||||
|
||||
# Изменение учетных данных
|
||||
@dp.callback_query_handler(lambda c: c.data == 'change_name')
|
||||
async def process_change_name(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Введите новое ФИО:")
|
||||
await OrderForm.waiting_for_name.set()
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'change_phone')
|
||||
async def process_change_phone(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Введите новый номер телефона:")
|
||||
await OrderForm.waiting_for_phone.set()
|
||||
|
||||
# Добавление адреса
|
||||
@dp.callback_query_handler(lambda c: c.data == 'add_address')
|
||||
async def process_add_address(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Введите адрес для добавления:")
|
||||
await OrderForm.waiting_for_address.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||
async def process_add_address_step(message: types.Message, state: FSMContext):
|
||||
user_id = message.from_user.id
|
||||
address = message.text
|
||||
add_address(user_id, address)
|
||||
await message.answer("Адрес успешно добавлен!")
|
||||
await state.finish()
|
||||
|
||||
# Удаление адреса
|
||||
@dp.callback_query_handler(lambda c: c.data == 'delete_address')
|
||||
async def process_delete_address(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Введите адрес для удаления:")
|
||||
await OrderForm.waiting_for_address.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||
async def process_delete_address_step(message: types.Message, state: FSMContext):
|
||||
user_id = message.from_user.id
|
||||
address = message.text
|
||||
delete_address(user_id, address)
|
||||
await message.answer("Адрес успешно удален!")
|
||||
await state.finish()
|
||||
|
||||
# Обработка заказа
|
||||
@dp.callback_query_handler(lambda c: c.data == 'select_address')
|
||||
async def process_select_address(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Введите адрес для уборки:")
|
||||
await OrderForm.waiting_for_address.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||
async def process_order_address(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['address'] = message.text
|
||||
await message.answer("Выберите время для уборки (утро/день/вечер):")
|
||||
await OrderForm.waiting_for_cleaning_time.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT)
|
||||
async def process_cleaning_time(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['cleaning_time'] = message.text
|
||||
await message.answer("Выберите тип уборки (влажная/сухая/генеральная):")
|
||||
await OrderForm.waiting_for_cleaning_type.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT)
|
||||
async def process_cleaning_type(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['cleaning_type'] = message.text
|
||||
await message.answer("Выберите способ оплаты (картой/наличными):")
|
||||
await OrderForm.waiting_for_payment_method.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT)
|
||||
async def process_payment_method(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['payment_method'] = message.text
|
||||
|
||||
# Отправка подтверждения заказа
|
||||
await message.answer("Подтвердите заказ:\n"
|
||||
f"Адрес: {data['address']}\n"
|
||||
f"Время уборки: {data['cleaning_time']}\n"
|
||||
f"Тип уборки: {data['cleaning_type']}\n"
|
||||
f"Способ оплаты: {data['payment_method']}\n"
|
||||
"Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.",
|
||||
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order')))
|
||||
await OrderForm.confirmation.set()
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation)
|
||||
async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
async with state.proxy() as data:
|
||||
order_details = (f"Новый заказ:\n"
|
||||
f"ФИО: {data['name']}\n"
|
||||
f"Номер телефона: {data['phone']}\n"
|
||||
f"Адрес: {data['address']}\n"
|
||||
f"Время уборки: {data['cleaning_time']}\n"
|
||||
f"Тип уборки: {data['cleaning_type']}\n"
|
||||
f"Способ оплаты: {data['payment_method']}")
|
||||
|
||||
await bot.send_message(OPERATORS_GROUP_ID, order_details)
|
||||
log_action(callback_query.from_user.id, 'confirm_order')
|
||||
|
||||
await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.")
|
||||
await state.finish()
|
||||
|
||||
# Основное приложение
|
||||
if __name__ == '__main__':
|
||||
create_tables()
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
import logging
|
||||
import sqlite3
|
||||
from aiogram import types
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.utils import executor
|
||||
from aiogram import Bot, Dispatcher
|
||||
from aiogram.dispatcher.filters import Text
|
||||
from database import create_tables, add_user, update_user_name, update_user_phone, add_address, delete_address
|
||||
from logger import log_action
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
# Загрузка переменных окружения из .env файла
|
||||
load_dotenv()
|
||||
|
||||
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
||||
OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID'))
|
||||
ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID'))
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Состояния
|
||||
class OrderForm(StatesGroup):
|
||||
waiting_for_name = State()
|
||||
waiting_for_phone = State()
|
||||
waiting_for_address = State()
|
||||
waiting_for_cleaning_time = State()
|
||||
waiting_for_cleaning_type = State()
|
||||
waiting_for_payment_method = State()
|
||||
confirmation = State()
|
||||
|
||||
class CallRequest(StatesGroup):
|
||||
waiting_for_name = State()
|
||||
waiting_for_phone = State()
|
||||
|
||||
class OperatorChat(StatesGroup):
|
||||
in_chat = State()
|
||||
|
||||
# Inline клавиатуры
|
||||
def main_menu_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
||||
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
||||
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
||||
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
||||
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
||||
return keyboard
|
||||
|
||||
def account_data_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
||||
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
||||
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
||||
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
||||
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||
return keyboard
|
||||
|
||||
def order_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Добавить учетные данные', callback_data='add_account_data'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать адрес', callback_data='select_address'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать время для уборки', callback_data='select_time'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать тип уборки', callback_data='select_cleaning_type'))
|
||||
keyboard.add(InlineKeyboardButton('Выбрать способ оплаты', callback_data='select_payment_method'))
|
||||
keyboard.add(InlineKeyboardButton('Подтвердить заказ', callback_data='confirm_order'))
|
||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||
return keyboard
|
||||
|
||||
# Основные команды
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def send_welcome(message: types.Message):
|
||||
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
||||
log_action(message.from_user.id, 'start')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
||||
async def process_account_data(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard())
|
||||
log_action(callback_query.from_user.id, 'account_data')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
||||
async def process_make_order(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Сделать заказ:", reply_markup=order_keyboard())
|
||||
log_action(callback_query.from_user.id, 'make_order')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
||||
async def process_order_history(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для просмотра истории заказов
|
||||
await bot.send_message(callback_query.from_user.id, "История заказов:")
|
||||
log_action(callback_query.from_user.id, 'order_history')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
||||
async def process_request_call(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для заказа звонка
|
||||
await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
||||
log_action(callback_query.from_user.id, 'request_call')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
||||
async def process_talk_operator(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для начала разговора с оператором
|
||||
await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.")
|
||||
log_action(callback_query.from_user.id, 'talk_operator')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
||||
async def process_back_to_main(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard())
|
||||
log_action(callback_query.from_user.id, 'back_to_main')
|
||||
|
||||
# Добавление учетных данных
|
||||
@dp.callback_query_handler(lambda c: c.data == 'add_account_data')
|
||||
async def process_add_account_data(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Введите ваше ФИО:")
|
||||
await OrderForm.waiting_for_name.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT)
|
||||
async def process_name(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['name'] = message.text
|
||||
await message.answer("Введите ваш номер телефона:")
|
||||
await OrderForm.waiting_for_phone.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT)
|
||||
async def process_phone(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['phone'] = message.text
|
||||
user_id = message.from_user.id
|
||||
name = data['name']
|
||||
phone = data['phone']
|
||||
add_user(user_id, name, phone)
|
||||
await message.answer("Ваши данные успешно сохранены!")
|
||||
await state.finish()
|
||||
|
||||
# Изменение учетных данных
|
||||
@dp.callback_query_handler(lambda c: c.data == 'change_name')
|
||||
async def process_change_name(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Введите новое ФИО:")
|
||||
await OrderForm.waiting_for_name.set()
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'change_phone')
|
||||
async def process_change_phone(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Введите новый номер телефона:")
|
||||
await OrderForm.waiting_for_phone.set()
|
||||
|
||||
# Добавление адреса
|
||||
@dp.callback_query_handler(lambda c: c.data == 'add_address')
|
||||
async def process_add_address(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Введите адрес для добавления:")
|
||||
await OrderForm.waiting_for_address.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||
async def process_add_address_step(message: types.Message, state: FSMContext):
|
||||
user_id = message.from_user.id
|
||||
address = message.text
|
||||
add_address(user_id, address)
|
||||
await message.answer("Адрес успешно добавлен!")
|
||||
await state.finish()
|
||||
|
||||
# Удаление адреса
|
||||
@dp.callback_query_handler(lambda c: c.data == 'delete_address')
|
||||
async def process_delete_address(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Введите адрес для удаления:")
|
||||
await OrderForm.waiting_for_address.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||
async def process_delete_address_step(message: types.Message, state: FSMContext):
|
||||
user_id = message.from_user.id
|
||||
address = message.text
|
||||
delete_address(user_id, address)
|
||||
await message.answer("Адрес успешно удален!")
|
||||
await state.finish()
|
||||
|
||||
# Обработка заказа
|
||||
@dp.callback_query_handler(lambda c: c.data == 'select_address')
|
||||
async def process_select_address(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Введите адрес для уборки:")
|
||||
await OrderForm.waiting_for_address.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||
async def process_order_address(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['address'] = message.text
|
||||
await message.answer("Выберите время для уборки (утро/день/вечер):")
|
||||
await OrderForm.waiting_for_cleaning_time.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT)
|
||||
async def process_cleaning_time(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['cleaning_time'] = message.text
|
||||
await message.answer("Выберите тип уборки (влажная/сухая/генеральная):")
|
||||
await OrderForm.waiting_for_cleaning_type.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT)
|
||||
async def process_cleaning_type(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['cleaning_type'] = message.text
|
||||
await message.answer("Выберите способ оплаты (картой/наличными):")
|
||||
await OrderForm.waiting_for_payment_method.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT)
|
||||
async def process_payment_method(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['payment_method'] = message.text
|
||||
|
||||
# Отправка подтверждения заказа
|
||||
await message.answer("Подтвердите заказ:\n"
|
||||
f"Адрес: {data['address']}\n"
|
||||
f"Время уборки: {data['cleaning_time']}\n"
|
||||
f"Тип уборки: {data['cleaning_type']}\n"
|
||||
f"Способ оплаты: {data['payment_method']}\n"
|
||||
"Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.",
|
||||
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order')))
|
||||
await OrderForm.confirmation.set()
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation)
|
||||
async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
async with state.proxy() as data:
|
||||
order_details = (f"Новый заказ:\n"
|
||||
f"ФИО: {data['name']}\n"
|
||||
f"Номер телефона: {data['phone']}\n"
|
||||
f"Адрес: {data['address']}\n"
|
||||
f"Время уборки: {data['cleaning_time']}\n"
|
||||
f"Тип уборки: {data['cleaning_type']}\n"
|
||||
f"Способ оплаты: {data['payment_method']}")
|
||||
|
||||
await bot.send_message(OPERATORS_GROUP_ID, order_details)
|
||||
log_action(callback_query.from_user.id, 'confirm_order')
|
||||
|
||||
await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.")
|
||||
await state.finish()
|
||||
|
||||
# Основное приложение
|
||||
if __name__ == '__main__':
|
||||
create_tables()
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
||||
@@ -1,89 +1,89 @@
|
||||
import sqlite3
|
||||
|
||||
|
||||
def create_tables():
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
telegram_id INTEGER UNIQUE,
|
||||
name TEXT,
|
||||
phone TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS addresses (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER,
|
||||
address TEXT,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER,
|
||||
address TEXT,
|
||||
cleaning_time TEXT,
|
||||
cleaning_type TEXT,
|
||||
payment_method TEXT,
|
||||
order_id TEXT UNIQUE,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS logs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER,
|
||||
action TEXT,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||
)
|
||||
''')
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def add_user(telegram_id, name, phone):
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('INSERT OR IGNORE INTO users (telegram_id, name, phone) VALUES (?, ?, ?)', (telegram_id, name, phone))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def update_user_name(telegram_id, name):
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('UPDATE users SET name = ? WHERE telegram_id = ?', (name, telegram_id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def update_user_phone(telegram_id, phone):
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('UPDATE users SET phone = ? WHERE telegram_id = ?', (phone, telegram_id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def add_address(telegram_id, address):
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT id FROM users WHERE telegram_id = ?', (telegram_id,))
|
||||
user_id = cursor.fetchone()[0]
|
||||
cursor.execute('INSERT INTO addresses (user_id, address) VALUES (?, ?)', (user_id, address))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def delete_address(telegram_id, address):
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT id FROM users WHERE telegram_id = ?', (telegram_id,))
|
||||
user_id = cursor.fetchone()[0]
|
||||
cursor.execute('DELETE FROM addresses WHERE user_id = ? AND address = ?', (user_id, address))
|
||||
conn.commit()
|
||||
import sqlite3
|
||||
|
||||
|
||||
def create_tables():
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY,
|
||||
telegram_id INTEGER UNIQUE,
|
||||
name TEXT,
|
||||
phone TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS addresses (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER,
|
||||
address TEXT,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER,
|
||||
address TEXT,
|
||||
cleaning_time TEXT,
|
||||
cleaning_type TEXT,
|
||||
payment_method TEXT,
|
||||
order_id TEXT UNIQUE,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS logs (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER,
|
||||
action TEXT,
|
||||
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY(user_id) REFERENCES users(id)
|
||||
)
|
||||
''')
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def add_user(telegram_id, name, phone):
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('INSERT OR IGNORE INTO users (telegram_id, name, phone) VALUES (?, ?, ?)', (telegram_id, name, phone))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def update_user_name(telegram_id, name):
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('UPDATE users SET name = ? WHERE telegram_id = ?', (name, telegram_id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def update_user_phone(telegram_id, phone):
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('UPDATE users SET phone = ? WHERE telegram_id = ?', (phone, telegram_id))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def add_address(telegram_id, address):
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT id FROM users WHERE telegram_id = ?', (telegram_id,))
|
||||
user_id = cursor.fetchone()[0]
|
||||
cursor.execute('INSERT INTO addresses (user_id, address) VALUES (?, ?)', (user_id, address))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def delete_address(telegram_id, address):
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('SELECT id FROM users WHERE telegram_id = ?', (telegram_id,))
|
||||
user_id = cursor.fetchone()[0]
|
||||
cursor.execute('DELETE FROM addresses WHERE user_id = ? AND address = ?', (user_id, address))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
@@ -1,183 +1,183 @@
|
||||
import logging
|
||||
import sqlite3
|
||||
from aiogram import types
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.utils import executor
|
||||
from aiogram import Bot, Dispatcher
|
||||
from aiogram.dispatcher.filters import Text
|
||||
from database import create_tables, add_user, update_user_name, update_user_phone, add_address, delete_address, add_order
|
||||
from logger import log_action
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
# Загрузка переменных окружения из .env файла
|
||||
load_dotenv()
|
||||
|
||||
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
||||
OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID'))
|
||||
ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID'))
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Состояния для заказа
|
||||
class OrderForm(StatesGroup):
|
||||
waiting_for_name = State()
|
||||
waiting_for_phone = State()
|
||||
waiting_for_address = State()
|
||||
waiting_for_cleaning_time = State()
|
||||
waiting_for_cleaning_type = State()
|
||||
waiting_for_payment_method = State()
|
||||
confirmation = State()
|
||||
|
||||
# Inline клавиатуры
|
||||
def main_menu_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
||||
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
||||
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
||||
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
||||
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
||||
return keyboard
|
||||
|
||||
def account_data_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
||||
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
||||
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
||||
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
||||
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||
return keyboard
|
||||
|
||||
# Основные команды
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def send_welcome(message: types.Message):
|
||||
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
||||
log_action(message.from_user.id, 'start')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
||||
async def process_account_data(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard())
|
||||
log_action(callback_query.from_user.id, 'account_data')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
||||
async def process_make_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Начнем с ваших учетных данных. Введите ваше ФИО:")
|
||||
await OrderForm.waiting_for_name.set()
|
||||
log_action(callback_query.from_user.id, 'make_order')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
||||
async def process_order_history(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для просмотра истории заказов
|
||||
await bot.send_message(callback_query.from_user.id, "История заказов:")
|
||||
log_action(callback_query.from_user.id, 'order_history')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
||||
async def process_request_call(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для заказа звонка
|
||||
await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
||||
log_action(callback_query.from_user.id, 'request_call')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
||||
async def process_talk_operator(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для начала разговора с оператором
|
||||
await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.")
|
||||
log_action(callback_query.from_user.id, 'talk_operator')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
||||
async def process_back_to_main(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard())
|
||||
log_action(callback_query.from_user.id, 'back_to_main')
|
||||
|
||||
# Обработка шагов заказа
|
||||
@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT)
|
||||
async def process_name(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['name'] = message.text
|
||||
await message.answer("Введите ваш номер телефона:")
|
||||
await OrderForm.waiting_for_phone.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT)
|
||||
async def process_phone(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['phone'] = message.text
|
||||
await message.answer("Введите адрес для уборки:")
|
||||
await OrderForm.waiting_for_address.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||
async def process_order_address(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['address'] = message.text
|
||||
await message.answer("Выберите время для уборки (утро/день/вечер):")
|
||||
await OrderForm.waiting_for_cleaning_time.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT)
|
||||
async def process_cleaning_time(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['cleaning_time'] = message.text
|
||||
await message.answer("Выберите тип уборки (влажная/сухая/генеральная):")
|
||||
await OrderForm.waiting_for_cleaning_type.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT)
|
||||
async def process_cleaning_type(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['cleaning_type'] = message.text
|
||||
await message.answer("Выберите способ оплаты (картой/наличными):")
|
||||
await OrderForm.waiting_for_payment_method.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT)
|
||||
async def process_payment_method(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['payment_method'] = message.text
|
||||
|
||||
# Отправка подтверждения заказа
|
||||
await message.answer("Подтвердите заказ:\n"
|
||||
f"ФИО: {data['name']}\n"
|
||||
f"Номер телефона: {data['phone']}\n"
|
||||
f"Адрес: {data['address']}\n"
|
||||
f"Время уборки: {data['cleaning_time']}\n"
|
||||
f"Тип уборки: {data['cleaning_type']}\n"
|
||||
f"Способ оплаты: {data['payment_method']}\n"
|
||||
"Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.",
|
||||
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order')))
|
||||
await OrderForm.confirmation.set()
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation)
|
||||
async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
async with state.proxy() as data:
|
||||
order_details = (f"Новый заказ:\n"
|
||||
f"ФИО: {data['name']}\n"
|
||||
f"Номер телефона: {data['phone']}\n"
|
||||
f"Адрес: {data['address']}\n"
|
||||
f"Время уборки: {data['cleaning_time']}\n"
|
||||
f"Тип уборки: {data['cleaning_type']}\n"
|
||||
f"Способ оплаты: {data['payment_method']}")
|
||||
|
||||
# Отправка заказа операторам
|
||||
await bot.send_message(OPERATORS_GROUP_ID, order_details)
|
||||
log_action(callback_query.from_user.id, 'confirm_order')
|
||||
|
||||
# Сохранение заказа в базе данных
|
||||
add_order(data['name'], data['phone'], data['address'], data['cleaning_time'], data['cleaning_type'], data['payment_method'])
|
||||
|
||||
await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.")
|
||||
await state.finish()
|
||||
|
||||
# Основное приложение
|
||||
if __name__ == '__main__':
|
||||
create_tables()
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
import logging
|
||||
import sqlite3
|
||||
from aiogram import types
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.utils import executor
|
||||
from aiogram import Bot, Dispatcher
|
||||
from aiogram.dispatcher.filters import Text
|
||||
from database import create_tables, add_user, update_user_name, update_user_phone, add_address, delete_address, add_order
|
||||
from logger import log_action
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
# Загрузка переменных окружения из .env файла
|
||||
load_dotenv()
|
||||
|
||||
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
||||
OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID'))
|
||||
ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID'))
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Состояния для заказа
|
||||
class OrderForm(StatesGroup):
|
||||
waiting_for_name = State()
|
||||
waiting_for_phone = State()
|
||||
waiting_for_address = State()
|
||||
waiting_for_cleaning_time = State()
|
||||
waiting_for_cleaning_type = State()
|
||||
waiting_for_payment_method = State()
|
||||
confirmation = State()
|
||||
|
||||
# Inline клавиатуры
|
||||
def main_menu_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
||||
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
||||
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
||||
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
||||
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
||||
return keyboard
|
||||
|
||||
def account_data_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
||||
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
||||
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
||||
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
||||
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||
return keyboard
|
||||
|
||||
# Основные команды
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def send_welcome(message: types.Message):
|
||||
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
||||
log_action(message.from_user.id, 'start')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
||||
async def process_account_data(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Учетные данные:", reply_markup=account_data_keyboard())
|
||||
log_action(callback_query.from_user.id, 'account_data')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
||||
async def process_make_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Начнем с ваших учетных данных. Введите ваше ФИО:")
|
||||
await OrderForm.waiting_for_name.set()
|
||||
log_action(callback_query.from_user.id, 'make_order')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
||||
async def process_order_history(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для просмотра истории заказов
|
||||
await bot.send_message(callback_query.from_user.id, "История заказов:")
|
||||
log_action(callback_query.from_user.id, 'order_history')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
||||
async def process_request_call(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для заказа звонка
|
||||
await bot.send_message(callback_query.from_user.id, "Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
||||
log_action(callback_query.from_user.id, 'request_call')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
||||
async def process_talk_operator(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
# Добавьте логику для начала разговора с оператором
|
||||
await bot.send_message(callback_query.from_user.id, "Разговор с оператором начат.")
|
||||
log_action(callback_query.from_user.id, 'talk_operator')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
||||
async def process_back_to_main(callback_query: types.CallbackQuery):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
await bot.send_message(callback_query.from_user.id, "Главное меню:", reply_markup=main_menu_keyboard())
|
||||
log_action(callback_query.from_user.id, 'back_to_main')
|
||||
|
||||
# Обработка шагов заказа
|
||||
@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT)
|
||||
async def process_name(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['name'] = message.text
|
||||
await message.answer("Введите ваш номер телефона:")
|
||||
await OrderForm.waiting_for_phone.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT)
|
||||
async def process_phone(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['phone'] = message.text
|
||||
await message.answer("Введите адрес для уборки:")
|
||||
await OrderForm.waiting_for_address.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||
async def process_order_address(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['address'] = message.text
|
||||
await message.answer("Выберите время для уборки (утро/день/вечер):")
|
||||
await OrderForm.waiting_for_cleaning_time.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT)
|
||||
async def process_cleaning_time(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['cleaning_time'] = message.text
|
||||
await message.answer("Выберите тип уборки (влажная/сухая/генеральная):")
|
||||
await OrderForm.waiting_for_cleaning_type.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT)
|
||||
async def process_cleaning_type(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['cleaning_type'] = message.text
|
||||
await message.answer("Выберите способ оплаты (картой/наличными):")
|
||||
await OrderForm.waiting_for_payment_method.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT)
|
||||
async def process_payment_method(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['payment_method'] = message.text
|
||||
|
||||
# Отправка подтверждения заказа
|
||||
await message.answer("Подтвердите заказ:\n"
|
||||
f"ФИО: {data['name']}\n"
|
||||
f"Номер телефона: {data['phone']}\n"
|
||||
f"Адрес: {data['address']}\n"
|
||||
f"Время уборки: {data['cleaning_time']}\n"
|
||||
f"Тип уборки: {data['cleaning_type']}\n"
|
||||
f"Способ оплаты: {data['payment_method']}\n"
|
||||
"Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.",
|
||||
reply_markup=InlineKeyboardMarkup().add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order')))
|
||||
await OrderForm.confirmation.set()
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation)
|
||||
async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await bot.answer_callback_query(callback_query.id)
|
||||
async with state.proxy() as data:
|
||||
order_details = (f"Новый заказ:\n"
|
||||
f"ФИО: {data['name']}\n"
|
||||
f"Номер телефона: {data['phone']}\n"
|
||||
f"Адрес: {data['address']}\n"
|
||||
f"Время уборки: {data['cleaning_time']}\n"
|
||||
f"Тип уборки: {data['cleaning_type']}\n"
|
||||
f"Способ оплаты: {data['payment_method']}")
|
||||
|
||||
# Отправка заказа операторам
|
||||
await bot.send_message(OPERATORS_GROUP_ID, order_details)
|
||||
log_action(callback_query.from_user.id, 'confirm_order')
|
||||
|
||||
# Сохранение заказа в базе данных
|
||||
add_order(data['name'], data['phone'], data['address'], data['cleaning_time'], data['cleaning_type'], data['payment_method'])
|
||||
|
||||
await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.")
|
||||
await state.finish()
|
||||
|
||||
# Основное приложение
|
||||
if __name__ == '__main__':
|
||||
create_tables()
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
||||
@@ -1,185 +1,185 @@
|
||||
import logging
|
||||
import sqlite3
|
||||
import asyncio
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
# Устанавливаем уровень логов
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Подключаемся к базе данных SQLite
|
||||
conn = sqlite3.connect("answers.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Создание таблицы для хранения ответов
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS answers (
|
||||
user_id INTEGER,
|
||||
question_id INTEGER,
|
||||
answer TEXT
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
# Заменить на свой токен
|
||||
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
||||
GROUP_ID = '-1001961537659'
|
||||
|
||||
# Инициализация бота и диспетчера
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Словарь для хранения вопросов и ответов
|
||||
questions = {
|
||||
1: "Как вас зовут?",
|
||||
2: "Укажите номер телефона для связи",
|
||||
3: "Укажите район, улицу, дом",
|
||||
4: "Какая уборка нужна, влажная или сухая?",
|
||||
5: "На какое время?",
|
||||
6: "Оплата наличными или картой?"
|
||||
}
|
||||
|
||||
answer_map = {
|
||||
'wet_cleaning': 'Влажная уборка',
|
||||
'dry_cleaning': 'Сухая уборка',
|
||||
'morning_time': 'Утро',
|
||||
'day_time': 'День',
|
||||
'evening_time': 'Вечер',
|
||||
'cash_payment': 'Наличные',
|
||||
'card_payment': 'Карта'
|
||||
}
|
||||
|
||||
# Обработчик команды /start
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def start(message: types.Message):
|
||||
await message.answer("Привет! Я задам тебе 6 вопросов. Давай начнем.")
|
||||
await ask_question(message.chat.id, 1)
|
||||
|
||||
# Функция для задания вопроса
|
||||
async def ask_question(user_id, question_id):
|
||||
if question_id in [4, 5, 6]:
|
||||
if question_id == 4:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")],
|
||||
[InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")]
|
||||
])
|
||||
elif question_id == 5:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Утро", callback_data="morning_time")],
|
||||
[InlineKeyboardButton(text="День", callback_data="day_time")],
|
||||
[InlineKeyboardButton(text="Вечер", callback_data="evening_time")]
|
||||
])
|
||||
elif question_id == 6:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Наличные", callback_data="cash_payment")],
|
||||
[InlineKeyboardButton(text="Карта", callback_data="card_payment")]
|
||||
])
|
||||
await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard)
|
||||
else:
|
||||
await bot.send_message(user_id, text=questions[question_id])
|
||||
|
||||
# Обработчик текстовых ответов на первые три вопроса
|
||||
@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"])
|
||||
async def handle_text_answer(message: types.Message):
|
||||
user_id = message.chat.id
|
||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||
prev_question_id = cursor.fetchone()[0]
|
||||
|
||||
if prev_question_id is None:
|
||||
question_id = 1
|
||||
else:
|
||||
question_id = prev_question_id + 1
|
||||
answer = message.text
|
||||
|
||||
await save_answer(user_id, question_id, answer)
|
||||
|
||||
if question_id < 6:
|
||||
await ask_question(user_id, question_id + 1)
|
||||
else:
|
||||
await show_confirmation_options(user_id)
|
||||
|
||||
# Обработчик inline-кнопок
|
||||
@dp.callback_query_handler(lambda query: query.data in answer_map.keys())
|
||||
async def handle_callback_answer(query: types.CallbackQuery):
|
||||
user_id = query.from_user.id
|
||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||
prev_question_id = cursor.fetchone()[0]
|
||||
|
||||
if prev_question_id is None:
|
||||
question_id = 1
|
||||
else:
|
||||
question_id = prev_question_id + 1
|
||||
|
||||
await save_answer(user_id, question_id, answer_map[query.data])
|
||||
await query.answer()
|
||||
|
||||
if question_id < 6:
|
||||
await ask_question(user_id, question_id + 1)
|
||||
else:
|
||||
await show_confirmation_options(user_id)
|
||||
|
||||
# Функция для сохранения ответа в базе данных
|
||||
async def save_answer(user_id, question_id, answer):
|
||||
cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer))
|
||||
conn.commit()
|
||||
|
||||
# Функция для отображения опций подтверждения
|
||||
async def show_confirmation_options(user_id):
|
||||
cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,))
|
||||
answers = cursor.fetchall()
|
||||
answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)])
|
||||
await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}")
|
||||
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Отправить", callback_data="send_answers")],
|
||||
[InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")]
|
||||
])
|
||||
await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard)
|
||||
|
||||
# Обработчик выбора действия подтверждения
|
||||
@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers'])
|
||||
async def handle_confirmation(query: types.CallbackQuery):
|
||||
user_id = query.from_user.id
|
||||
if query.data == "send_answers":
|
||||
await send_answers_to_group(user_id)
|
||||
elif query.data == "rewrite_answers":
|
||||
await rewrite_answers(user_id)
|
||||
|
||||
# Функция для отправки ответов в группу
|
||||
async def send_answers_to_group(user_id):
|
||||
cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,))
|
||||
answers = cursor.fetchall()
|
||||
|
||||
if answers:
|
||||
answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in answers])
|
||||
|
||||
# Очищаем таблицу с ответами
|
||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
# Отправляем ответы в группу
|
||||
await bot.send_message(GROUP_ID, answer_text)
|
||||
await bot.send_message(user_id, "Ваши ответы отправлены.")
|
||||
else:
|
||||
await bot.send_message(user_id, "Ответов нет")
|
||||
|
||||
# Функция для переписывания ответов
|
||||
async def rewrite_answers(user_id):
|
||||
# Очищаем таблицу с ответами
|
||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.")
|
||||
await ask_question(user_id, 1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop.create_task(dp.start_polling())
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
conn.close()
|
||||
import logging
|
||||
import sqlite3
|
||||
import asyncio
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
# Устанавливаем уровень логов
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Подключаемся к базе данных SQLite
|
||||
conn = sqlite3.connect("answers.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Создание таблицы для хранения ответов
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS answers (
|
||||
user_id INTEGER,
|
||||
question_id INTEGER,
|
||||
answer TEXT
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
# Заменить на свой токен
|
||||
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
||||
GROUP_ID = '-1001961537659'
|
||||
|
||||
# Инициализация бота и диспетчера
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Словарь для хранения вопросов и ответов
|
||||
questions = {
|
||||
1: "Как вас зовут?",
|
||||
2: "Укажите номер телефона для связи",
|
||||
3: "Укажите район, улицу, дом",
|
||||
4: "Какая уборка нужна, влажная или сухая?",
|
||||
5: "На какое время?",
|
||||
6: "Оплата наличными или картой?"
|
||||
}
|
||||
|
||||
answer_map = {
|
||||
'wet_cleaning': 'Влажная уборка',
|
||||
'dry_cleaning': 'Сухая уборка',
|
||||
'morning_time': 'Утро',
|
||||
'day_time': 'День',
|
||||
'evening_time': 'Вечер',
|
||||
'cash_payment': 'Наличные',
|
||||
'card_payment': 'Карта'
|
||||
}
|
||||
|
||||
# Обработчик команды /start
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def start(message: types.Message):
|
||||
await message.answer("Привет! Я задам тебе 6 вопросов. Давай начнем.")
|
||||
await ask_question(message.chat.id, 1)
|
||||
|
||||
# Функция для задания вопроса
|
||||
async def ask_question(user_id, question_id):
|
||||
if question_id in [4, 5, 6]:
|
||||
if question_id == 4:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")],
|
||||
[InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")]
|
||||
])
|
||||
elif question_id == 5:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Утро", callback_data="morning_time")],
|
||||
[InlineKeyboardButton(text="День", callback_data="day_time")],
|
||||
[InlineKeyboardButton(text="Вечер", callback_data="evening_time")]
|
||||
])
|
||||
elif question_id == 6:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Наличные", callback_data="cash_payment")],
|
||||
[InlineKeyboardButton(text="Карта", callback_data="card_payment")]
|
||||
])
|
||||
await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard)
|
||||
else:
|
||||
await bot.send_message(user_id, text=questions[question_id])
|
||||
|
||||
# Обработчик текстовых ответов на первые три вопроса
|
||||
@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"])
|
||||
async def handle_text_answer(message: types.Message):
|
||||
user_id = message.chat.id
|
||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||
prev_question_id = cursor.fetchone()[0]
|
||||
|
||||
if prev_question_id is None:
|
||||
question_id = 1
|
||||
else:
|
||||
question_id = prev_question_id + 1
|
||||
answer = message.text
|
||||
|
||||
await save_answer(user_id, question_id, answer)
|
||||
|
||||
if question_id < 6:
|
||||
await ask_question(user_id, question_id + 1)
|
||||
else:
|
||||
await show_confirmation_options(user_id)
|
||||
|
||||
# Обработчик inline-кнопок
|
||||
@dp.callback_query_handler(lambda query: query.data in answer_map.keys())
|
||||
async def handle_callback_answer(query: types.CallbackQuery):
|
||||
user_id = query.from_user.id
|
||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||
prev_question_id = cursor.fetchone()[0]
|
||||
|
||||
if prev_question_id is None:
|
||||
question_id = 1
|
||||
else:
|
||||
question_id = prev_question_id + 1
|
||||
|
||||
await save_answer(user_id, question_id, answer_map[query.data])
|
||||
await query.answer()
|
||||
|
||||
if question_id < 6:
|
||||
await ask_question(user_id, question_id + 1)
|
||||
else:
|
||||
await show_confirmation_options(user_id)
|
||||
|
||||
# Функция для сохранения ответа в базе данных
|
||||
async def save_answer(user_id, question_id, answer):
|
||||
cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer))
|
||||
conn.commit()
|
||||
|
||||
# Функция для отображения опций подтверждения
|
||||
async def show_confirmation_options(user_id):
|
||||
cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,))
|
||||
answers = cursor.fetchall()
|
||||
answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)])
|
||||
await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}")
|
||||
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Отправить", callback_data="send_answers")],
|
||||
[InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")]
|
||||
])
|
||||
await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard)
|
||||
|
||||
# Обработчик выбора действия подтверждения
|
||||
@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers'])
|
||||
async def handle_confirmation(query: types.CallbackQuery):
|
||||
user_id = query.from_user.id
|
||||
if query.data == "send_answers":
|
||||
await send_answers_to_group(user_id)
|
||||
elif query.data == "rewrite_answers":
|
||||
await rewrite_answers(user_id)
|
||||
|
||||
# Функция для отправки ответов в группу
|
||||
async def send_answers_to_group(user_id):
|
||||
cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,))
|
||||
answers = cursor.fetchall()
|
||||
|
||||
if answers:
|
||||
answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in answers])
|
||||
|
||||
# Очищаем таблицу с ответами
|
||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
# Отправляем ответы в группу
|
||||
await bot.send_message(GROUP_ID, answer_text)
|
||||
await bot.send_message(user_id, "Ваши ответы отправлены.")
|
||||
else:
|
||||
await bot.send_message(user_id, "Ответов нет")
|
||||
|
||||
# Функция для переписывания ответов
|
||||
async def rewrite_answers(user_id):
|
||||
# Очищаем таблицу с ответами
|
||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.")
|
||||
await ask_question(user_id, 1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop.create_task(dp.start_polling())
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import logging
|
||||
import sqlite3
|
||||
|
||||
# Настройка логирования
|
||||
logging.basicConfig(filename='bot.log', level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
|
||||
def log_action(user_id, action, details=""):
|
||||
logging.info(f"User ID: {user_id} - Action: {action} - Details: {details}")
|
||||
|
||||
|
||||
def log_action(user_id, action):
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('INSERT INTO logs (user_id, action) VALUES ((SELECT id FROM users WHERE telegram_id = ?), ?)', (user_id, action))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
import logging
|
||||
import sqlite3
|
||||
|
||||
# Настройка логирования
|
||||
logging.basicConfig(filename='bot.log', level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
||||
|
||||
def log_action(user_id, action, details=""):
|
||||
logging.info(f"User ID: {user_id} - Action: {action} - Details: {details}")
|
||||
|
||||
|
||||
def log_action(user_id, action):
|
||||
conn = sqlite3.connect('bot_klining.db')
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('INSERT INTO logs (user_id, action) VALUES ((SELECT id FROM users WHERE telegram_id = ?), ?)', (user_id, action))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
@@ -1,213 +1,213 @@
|
||||
import logging
|
||||
import sqlite3
|
||||
import asyncio
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
# Устанавливаем уровень логов
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Подключаемся к базе данных SQLite
|
||||
conn = sqlite3.connect("answers.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Создание таблицы для хранения ответов
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS answers (
|
||||
user_id INTEGER,
|
||||
question_id INTEGER,
|
||||
answer TEXT
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
# Заменить на свой токен
|
||||
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
||||
GROUP_ID = '-1001961537659'
|
||||
|
||||
# Инициализация бота и диспетчера
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Словарь для хранения вопросов и ответов
|
||||
questions = {
|
||||
1: "Как вас зовут?",
|
||||
2: "Укажите номер телефона для связи",
|
||||
3: "Укажите район, улицу, дом",
|
||||
4: "Какая уборка нужна, влажная или сухая?",
|
||||
5: "На какое время?",
|
||||
6: "Оплата наличными или картой?"
|
||||
}
|
||||
|
||||
answer_map = {
|
||||
'wet_cleaning': 'Влажная уборка',
|
||||
'dry_cleaning': 'Сухая уборка',
|
||||
'morning_time': 'Утро',
|
||||
'day_time': 'День',
|
||||
'evening_time': 'Вечер',
|
||||
'cash_payment': 'Наличные',
|
||||
'card_payment': 'Карта'
|
||||
}
|
||||
|
||||
# Обработчик команды /start
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def start(message: types.Message):
|
||||
if message.chat.id != int(GROUP_ID):
|
||||
await message.answer("Привет! Я задам тебе 6 вопросов. Давай начнем.")
|
||||
await ask_question(message.chat.id, 1)
|
||||
|
||||
# Функция для задания вопроса
|
||||
async def ask_question(user_id, question_id):
|
||||
if question_id in [4, 5, 6]:
|
||||
if question_id == 4:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")],
|
||||
[InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")]
|
||||
])
|
||||
elif question_id == 5:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Утро", callback_data="morning_time")],
|
||||
[InlineKeyboardButton(text="День", callback_data="day_time")],
|
||||
[InlineKeyboardButton(text="Вечер", callback_data="evening_time")]
|
||||
])
|
||||
elif question_id == 6:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Наличные", callback_data="cash_payment")],
|
||||
[InlineKeyboardButton(text="Карта", callback_data="card_payment")]
|
||||
])
|
||||
await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard)
|
||||
else:
|
||||
await bot.send_message(user_id, text=questions[question_id])
|
||||
|
||||
# Обработчик текстовых ответов на первые три вопроса
|
||||
@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"])
|
||||
async def handle_text_answer(message: types.Message):
|
||||
if message.chat.id != int(GROUP_ID):
|
||||
user_id = message.chat.id
|
||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||
prev_question_id = cursor.fetchone()[0]
|
||||
|
||||
if prev_question_id is None:
|
||||
question_id = 1
|
||||
else:
|
||||
question_id = prev_question_id + 1
|
||||
answer = message.text
|
||||
|
||||
await save_answer(user_id, question_id, answer)
|
||||
|
||||
if question_id < 6:
|
||||
await ask_question(user_id, question_id + 1)
|
||||
else:
|
||||
await show_confirmation_options(user_id)
|
||||
|
||||
# Обработчик inline-кнопок
|
||||
@dp.callback_query_handler(lambda query: query.data in answer_map.keys())
|
||||
async def handle_callback_answer(query: types.CallbackQuery):
|
||||
user_id = query.from_user.id
|
||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||
prev_question_id = cursor.fetchone()[0]
|
||||
|
||||
if prev_question_id is None:
|
||||
question_id = 1
|
||||
else:
|
||||
question_id = prev_question_id + 1
|
||||
|
||||
await save_answer(user_id, question_id, answer_map[query.data])
|
||||
await query.answer()
|
||||
|
||||
if question_id < 6:
|
||||
await ask_question(user_id, question_id + 1)
|
||||
else:
|
||||
await show_confirmation_options(user_id)
|
||||
|
||||
# Функция для сохранения ответа в базе данных
|
||||
async def save_answer(user_id, question_id, answer):
|
||||
cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer))
|
||||
conn.commit()
|
||||
|
||||
# Функция для отображения опций подтверждения
|
||||
async def show_confirmation_options(user_id):
|
||||
cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,))
|
||||
answers = cursor.fetchall()
|
||||
answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)])
|
||||
await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}")
|
||||
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Отправить", callback_data="send_answers")],
|
||||
[InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")]
|
||||
])
|
||||
await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard)
|
||||
|
||||
# Обработчик выбора действия подтверждения
|
||||
@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers'])
|
||||
async def handle_confirmation(query: types.CallbackQuery):
|
||||
user_id = query.from_user.id
|
||||
if query.data == "send_answers":
|
||||
await send_answers_to_group(user_id)
|
||||
elif query.data == "rewrite_answers":
|
||||
await rewrite_answers(user_id)
|
||||
|
||||
# Функция для отправки ответов в группу
|
||||
async def send_answers_to_group(user_id):
|
||||
cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,))
|
||||
answers = cursor.fetchall()
|
||||
|
||||
if answers:
|
||||
answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in answers])
|
||||
|
||||
# Очищаем таблицу с ответами
|
||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
# Отправляем ответы в группу и добавляем ID пользователя
|
||||
sent_message = await bot.send_message(GROUP_ID, f"Ответы от пользователя {user_id}:\n\n{answer_text}")
|
||||
await bot.send_message(user_id, f"Ваши ответы отправлены. Можете посмотреть их [здесь](https://t.me/{sent_message.chat.username}/{sent_message.message_id})",
|
||||
parse_mode='Markdown')
|
||||
else:
|
||||
await bot.send_message(user_id, "Ответов нет")
|
||||
|
||||
# Функция для переписывания ответов
|
||||
async def rewrite_answers(user_id):
|
||||
# Очищаем таблицу с ответами
|
||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.")
|
||||
await ask_question(user_id, 1)
|
||||
|
||||
# Функция для обработки сообщений из группы и пересылки их пользователю
|
||||
@dp.message_handler(lambda message: message.chat.id == int(GROUP_ID) and message.reply_to_message)
|
||||
async def handle_group_reply(message: types.Message):
|
||||
# Проверяем, что сообщение является ответом на сообщение бота
|
||||
if message.reply_to_message.from_user.id == bot.id:
|
||||
# Извлекаем ID пользователя из текста сообщения
|
||||
try:
|
||||
user_id = int(message.reply_to_message.text.split()[2].strip(':'))
|
||||
await bot.send_message(user_id, f"Сообщение от администратора:\n\n{message.text}")
|
||||
# Сохраняем ответ пользователя для пересылки обратно в группу
|
||||
cursor.execute("INSERT INTO admin_user_conversation (user_id, admin_message_id) VALUES (?, ?)",
|
||||
(user_id, message.message_id))
|
||||
conn.commit()
|
||||
except (IndexError, ValueError):
|
||||
await message.reply("Не удалось определить ID пользователя для ответа.")
|
||||
|
||||
# Обработчик сообщений от пользователя в ответ на администратора
|
||||
@dp.message_handler(lambda message: message.chat.id != int(GROUP_ID))
|
||||
async def handle_user_reply(message: types.Message):
|
||||
user_id = message.chat.id
|
||||
cursor.execute("SELECT admin_message_id FROM admin_user_conversation WHERE user_id=? ORDER BY admin_message_id DESC LIMIT 1", (user_id,))
|
||||
admin_message_id = cursor.fetchone()
|
||||
if admin_message_id:
|
||||
await bot.send_message(GROUP_ID, f"Сообщение от пользователя {user_id}:\n\n{message.text}", reply_to_message_id=admin_message_id[0])
|
||||
|
||||
if __name__ == '__main__':
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop.create_task(dp.start_polling())
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
conn.close()
|
||||
import logging
|
||||
import sqlite3
|
||||
import asyncio
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
# Устанавливаем уровень логов
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Подключаемся к базе данных SQLite
|
||||
conn = sqlite3.connect("answers.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Создание таблицы для хранения ответов
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS answers (
|
||||
user_id INTEGER,
|
||||
question_id INTEGER,
|
||||
answer TEXT
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
# Заменить на свой токен
|
||||
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
||||
GROUP_ID = '-1001961537659'
|
||||
|
||||
# Инициализация бота и диспетчера
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Словарь для хранения вопросов и ответов
|
||||
questions = {
|
||||
1: "Как вас зовут?",
|
||||
2: "Укажите номер телефона для связи",
|
||||
3: "Укажите район, улицу, дом",
|
||||
4: "Какая уборка нужна, влажная или сухая?",
|
||||
5: "На какое время?",
|
||||
6: "Оплата наличными или картой?"
|
||||
}
|
||||
|
||||
answer_map = {
|
||||
'wet_cleaning': 'Влажная уборка',
|
||||
'dry_cleaning': 'Сухая уборка',
|
||||
'morning_time': 'Утро',
|
||||
'day_time': 'День',
|
||||
'evening_time': 'Вечер',
|
||||
'cash_payment': 'Наличные',
|
||||
'card_payment': 'Карта'
|
||||
}
|
||||
|
||||
# Обработчик команды /start
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def start(message: types.Message):
|
||||
if message.chat.id != int(GROUP_ID):
|
||||
await message.answer("Привет! Я задам тебе 6 вопросов. Давай начнем.")
|
||||
await ask_question(message.chat.id, 1)
|
||||
|
||||
# Функция для задания вопроса
|
||||
async def ask_question(user_id, question_id):
|
||||
if question_id in [4, 5, 6]:
|
||||
if question_id == 4:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")],
|
||||
[InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")]
|
||||
])
|
||||
elif question_id == 5:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Утро", callback_data="morning_time")],
|
||||
[InlineKeyboardButton(text="День", callback_data="day_time")],
|
||||
[InlineKeyboardButton(text="Вечер", callback_data="evening_time")]
|
||||
])
|
||||
elif question_id == 6:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Наличные", callback_data="cash_payment")],
|
||||
[InlineKeyboardButton(text="Карта", callback_data="card_payment")]
|
||||
])
|
||||
await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard)
|
||||
else:
|
||||
await bot.send_message(user_id, text=questions[question_id])
|
||||
|
||||
# Обработчик текстовых ответов на первые три вопроса
|
||||
@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"])
|
||||
async def handle_text_answer(message: types.Message):
|
||||
if message.chat.id != int(GROUP_ID):
|
||||
user_id = message.chat.id
|
||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||
prev_question_id = cursor.fetchone()[0]
|
||||
|
||||
if prev_question_id is None:
|
||||
question_id = 1
|
||||
else:
|
||||
question_id = prev_question_id + 1
|
||||
answer = message.text
|
||||
|
||||
await save_answer(user_id, question_id, answer)
|
||||
|
||||
if question_id < 6:
|
||||
await ask_question(user_id, question_id + 1)
|
||||
else:
|
||||
await show_confirmation_options(user_id)
|
||||
|
||||
# Обработчик inline-кнопок
|
||||
@dp.callback_query_handler(lambda query: query.data in answer_map.keys())
|
||||
async def handle_callback_answer(query: types.CallbackQuery):
|
||||
user_id = query.from_user.id
|
||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||
prev_question_id = cursor.fetchone()[0]
|
||||
|
||||
if prev_question_id is None:
|
||||
question_id = 1
|
||||
else:
|
||||
question_id = prev_question_id + 1
|
||||
|
||||
await save_answer(user_id, question_id, answer_map[query.data])
|
||||
await query.answer()
|
||||
|
||||
if question_id < 6:
|
||||
await ask_question(user_id, question_id + 1)
|
||||
else:
|
||||
await show_confirmation_options(user_id)
|
||||
|
||||
# Функция для сохранения ответа в базе данных
|
||||
async def save_answer(user_id, question_id, answer):
|
||||
cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer))
|
||||
conn.commit()
|
||||
|
||||
# Функция для отображения опций подтверждения
|
||||
async def show_confirmation_options(user_id):
|
||||
cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,))
|
||||
answers = cursor.fetchall()
|
||||
answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)])
|
||||
await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}")
|
||||
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Отправить", callback_data="send_answers")],
|
||||
[InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")]
|
||||
])
|
||||
await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard)
|
||||
|
||||
# Обработчик выбора действия подтверждения
|
||||
@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers'])
|
||||
async def handle_confirmation(query: types.CallbackQuery):
|
||||
user_id = query.from_user.id
|
||||
if query.data == "send_answers":
|
||||
await send_answers_to_group(user_id)
|
||||
elif query.data == "rewrite_answers":
|
||||
await rewrite_answers(user_id)
|
||||
|
||||
# Функция для отправки ответов в группу
|
||||
async def send_answers_to_group(user_id):
|
||||
cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,))
|
||||
answers = cursor.fetchall()
|
||||
|
||||
if answers:
|
||||
answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in answers])
|
||||
|
||||
# Очищаем таблицу с ответами
|
||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
# Отправляем ответы в группу и добавляем ID пользователя
|
||||
sent_message = await bot.send_message(GROUP_ID, f"Ответы от пользователя {user_id}:\n\n{answer_text}")
|
||||
await bot.send_message(user_id, f"Ваши ответы отправлены. Можете посмотреть их [здесь](https://t.me/{sent_message.chat.username}/{sent_message.message_id})",
|
||||
parse_mode='Markdown')
|
||||
else:
|
||||
await bot.send_message(user_id, "Ответов нет")
|
||||
|
||||
# Функция для переписывания ответов
|
||||
async def rewrite_answers(user_id):
|
||||
# Очищаем таблицу с ответами
|
||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.")
|
||||
await ask_question(user_id, 1)
|
||||
|
||||
# Функция для обработки сообщений из группы и пересылки их пользователю
|
||||
@dp.message_handler(lambda message: message.chat.id == int(GROUP_ID) and message.reply_to_message)
|
||||
async def handle_group_reply(message: types.Message):
|
||||
# Проверяем, что сообщение является ответом на сообщение бота
|
||||
if message.reply_to_message.from_user.id == bot.id:
|
||||
# Извлекаем ID пользователя из текста сообщения
|
||||
try:
|
||||
user_id = int(message.reply_to_message.text.split()[2].strip(':'))
|
||||
await bot.send_message(user_id, f"Сообщение от администратора:\n\n{message.text}")
|
||||
# Сохраняем ответ пользователя для пересылки обратно в группу
|
||||
cursor.execute("INSERT INTO admin_user_conversation (user_id, admin_message_id) VALUES (?, ?)",
|
||||
(user_id, message.message_id))
|
||||
conn.commit()
|
||||
except (IndexError, ValueError):
|
||||
await message.reply("Не удалось определить ID пользователя для ответа.")
|
||||
|
||||
# Обработчик сообщений от пользователя в ответ на администратора
|
||||
@dp.message_handler(lambda message: message.chat.id != int(GROUP_ID))
|
||||
async def handle_user_reply(message: types.Message):
|
||||
user_id = message.chat.id
|
||||
cursor.execute("SELECT admin_message_id FROM admin_user_conversation WHERE user_id=? ORDER BY admin_message_id DESC LIMIT 1", (user_id,))
|
||||
admin_message_id = cursor.fetchone()
|
||||
if admin_message_id:
|
||||
await bot.send_message(GROUP_ID, f"Сообщение от пользователя {user_id}:\n\n{message.text}", reply_to_message_id=admin_message_id[0])
|
||||
|
||||
if __name__ == '__main__':
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop.create_task(dp.start_polling())
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@@ -1,221 +1,221 @@
|
||||
import logging
|
||||
import sqlite3
|
||||
import asyncio
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.dispatcher.filters import Command
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
# Устанавливаем уровень логов
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Подключаемся к базе данных SQLite
|
||||
conn = sqlite3.connect("answers.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Создание таблицы для хранения ответов
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS answers (
|
||||
user_id INTEGER,
|
||||
question_id INTEGER,
|
||||
answer TEXT
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
# Заменить на свой токен
|
||||
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
||||
GROUP_ID = '-1001961537659'
|
||||
|
||||
# Инициализация бота и диспетчера
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Словарь для хранения вопросов и ответов
|
||||
questions = {
|
||||
1: "Как вас зовут?",
|
||||
2: "Укажите номер телефона для связи",
|
||||
3: "Укажите район, улицу, дом",
|
||||
4: "Какая уборка нужна, влажная или сухая?",
|
||||
5: "На какое время?",
|
||||
6: "Оплата наличными или картой?"
|
||||
}
|
||||
|
||||
answer_map = {
|
||||
'wet_cleaning': 'Влажная уборка',
|
||||
'dry_cleaning': 'Сухая уборка',
|
||||
'morning_time': 'Утро',
|
||||
'day_time': 'День',
|
||||
'evening_time': 'Вечер',
|
||||
'cash_payment': 'Наличные',
|
||||
'card_payment': 'Карта'
|
||||
}
|
||||
|
||||
# Обработчик команды /start
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def start(message: types.Message):
|
||||
if message.chat.id != int(GROUP_ID):
|
||||
await message.answer("Привет! Я задам тебе 6 вопросов Чтобы создать заказ. Давай начнем.")
|
||||
await ask_question(message.chat.id, 1)
|
||||
|
||||
@dp.message_handler(Command("start"))
|
||||
async def show_menu(message: types.Message):
|
||||
await message.reply("Выбери действие:", reply_markup=ReplyKeyboardMarkup(
|
||||
resize_keyboard=True).add(KeyboardButton("Создать заказ")))
|
||||
|
||||
# Функция для задания вопроса
|
||||
async def ask_question(user_id, question_id):
|
||||
if question_id in [4, 5, 6]:
|
||||
if question_id == 4:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")],
|
||||
[InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")]
|
||||
])
|
||||
elif question_id == 5:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Утро", callback_data="morning_time")],
|
||||
[InlineKeyboardButton(text="День", callback_data="day_time")],
|
||||
[InlineKeyboardButton(text="Вечер", callback_data="evening_time")]
|
||||
])
|
||||
elif question_id == 6:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Наличные", callback_data="cash_payment")],
|
||||
[InlineKeyboardButton(text="Карта", callback_data="card_payment")]
|
||||
])
|
||||
await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard)
|
||||
else:
|
||||
await bot.send_message(user_id, text=questions[question_id])
|
||||
|
||||
# Обработчик текстовых ответов на первые три вопроса
|
||||
@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"])
|
||||
async def handle_text_answer(message: types.Message):
|
||||
if message.chat.id != int(GROUP_ID):
|
||||
user_id = message.chat.id
|
||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||
prev_question_id = cursor.fetchone()[0]
|
||||
|
||||
if prev_question_id is None:
|
||||
question_id = 1
|
||||
else:
|
||||
question_id = prev_question_id + 1
|
||||
answer = message.text
|
||||
|
||||
await save_answer(user_id, question_id, answer)
|
||||
|
||||
if question_id < 6:
|
||||
await ask_question(user_id, question_id + 1)
|
||||
else:
|
||||
await show_confirmation_options(user_id)
|
||||
|
||||
# Обработчик inline-кнопок
|
||||
@dp.callback_query_handler(lambda query: query.data in answer_map.keys())
|
||||
async def handle_callback_answer(query: types.CallbackQuery):
|
||||
user_id = query.from_user.id
|
||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||
prev_question_id = cursor.fetchone()[0]
|
||||
|
||||
if prev_question_id is None:
|
||||
question_id = 1
|
||||
else:
|
||||
question_id = prev_question_id + 1
|
||||
|
||||
await save_answer(user_id, question_id, answer_map[query.data])
|
||||
await query.answer()
|
||||
|
||||
if question_id < 6:
|
||||
await ask_question(user_id, question_id + 1)
|
||||
else:
|
||||
await show_confirmation_options(user_id)
|
||||
|
||||
# Функция для сохранения ответа в базе данных
|
||||
async def save_answer(user_id, question_id, answer):
|
||||
cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer))
|
||||
conn.commit()
|
||||
|
||||
# Функция для отображения опций подтверждения
|
||||
async def show_confirmation_options(user_id):
|
||||
cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,))
|
||||
answers = cursor.fetchall()
|
||||
answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)])
|
||||
await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}")
|
||||
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Отправить", callback_data="send_answers")],
|
||||
[InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")]
|
||||
])
|
||||
await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard)
|
||||
|
||||
# Обработчик выбора действия подтверждения
|
||||
@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers'])
|
||||
async def handle_confirmation(query: types.CallbackQuery):
|
||||
user_id = query.from_user.id
|
||||
if query.data == "send_answers":
|
||||
await send_answers_to_group(user_id)
|
||||
elif query.data == "rewrite_answers":
|
||||
await rewrite_answers(user_id)
|
||||
|
||||
# Функция для отправки ответов в группу
|
||||
async def send_answers_to_group(user_id):
|
||||
cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,))
|
||||
answers = cursor.fetchall()
|
||||
|
||||
if answers:
|
||||
answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in answers])
|
||||
|
||||
# Очищаем таблицу с ответами
|
||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
# Отправляем ответы в группу и добавляем ID пользователя
|
||||
sent_message = await bot.send_message(GROUP_ID, f"Ответы от пользователя {user_id}:\n\n{answer_text}")
|
||||
await bot.send_message(user_id, f"Ваши ответы отправлены. Оператор с вами свяжится)",
|
||||
parse_mode='Markdown')
|
||||
else:
|
||||
await bot.send_message(user_id, "Ответов нет")
|
||||
|
||||
# Функция для переписывания ответов
|
||||
async def rewrite_answers(user_id):
|
||||
# Очищаем таблицу с ответами
|
||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.")
|
||||
await ask_question(user_id, 1)
|
||||
|
||||
# Функция для обработки сообщений из группы и пересылки их пользователю
|
||||
@dp.message_handler(lambda message: message.chat.id == int(GROUP_ID) and message.reply_to_message)
|
||||
async def handle_group_reply(message: types.Message):
|
||||
# Проверяем, что сообщение является ответом на сообщение бота
|
||||
if message.reply_to_message.from_user.id == bot.id:
|
||||
# Извлекаем ID пользователя из текста сообщения
|
||||
try:
|
||||
user_id = int(message.reply_to_message.text.split()[2].strip(':'))
|
||||
await bot.send_message(user_id, f"Сообщение от администратора:\n\n{message.text}")
|
||||
# Сохраняем ответ пользователя для пересылки обратно в группу
|
||||
cursor.execute("INSERT INTO admin_user_conversation (user_id, admin_message_id) VALUES (?, ?)",
|
||||
(user_id, message.message_id))
|
||||
conn.commit()
|
||||
except (IndexError, ValueError):
|
||||
await message.reply("Не удалось определить ID пользователя для ответа.")
|
||||
|
||||
# Обработчик сообщений от пользователя в ответ на администратора
|
||||
@dp.message_handler(lambda message: message.chat.id != int(GROUP_ID))
|
||||
async def handle_user_reply(message: types.Message):
|
||||
user_id = message.chat.id
|
||||
cursor.execute("SELECT admin_message_id FROM admin_user_conversation WHERE user_id=? ORDER BY admin_message_id DESC LIMIT 1", (user_id,))
|
||||
admin_message_id = cursor.fetchone()
|
||||
if admin_message_id:
|
||||
await bot.send_message(GROUP_ID, f"Сообщение от пользователя {user_id}:\n\n{message.text}", reply_to_message_id=admin_message_id[0])
|
||||
|
||||
if __name__ == '__main__':
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop.create_task(dp.start_polling())
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
conn.close()
|
||||
import logging
|
||||
import sqlite3
|
||||
import asyncio
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.dispatcher.filters import Command
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
# Устанавливаем уровень логов
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
# Подключаемся к базе данных SQLite
|
||||
conn = sqlite3.connect("answers.db")
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Создание таблицы для хранения ответов
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS answers (
|
||||
user_id INTEGER,
|
||||
question_id INTEGER,
|
||||
answer TEXT
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
# Заменить на свой токен
|
||||
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
||||
GROUP_ID = '-1001961537659'
|
||||
|
||||
# Инициализация бота и диспетчера
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Словарь для хранения вопросов и ответов
|
||||
questions = {
|
||||
1: "Как вас зовут?",
|
||||
2: "Укажите номер телефона для связи",
|
||||
3: "Укажите район, улицу, дом",
|
||||
4: "Какая уборка нужна, влажная или сухая?",
|
||||
5: "На какое время?",
|
||||
6: "Оплата наличными или картой?"
|
||||
}
|
||||
|
||||
answer_map = {
|
||||
'wet_cleaning': 'Влажная уборка',
|
||||
'dry_cleaning': 'Сухая уборка',
|
||||
'morning_time': 'Утро',
|
||||
'day_time': 'День',
|
||||
'evening_time': 'Вечер',
|
||||
'cash_payment': 'Наличные',
|
||||
'card_payment': 'Карта'
|
||||
}
|
||||
|
||||
# Обработчик команды /start
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def start(message: types.Message):
|
||||
if message.chat.id != int(GROUP_ID):
|
||||
await message.answer("Привет! Я задам тебе 6 вопросов Чтобы создать заказ. Давай начнем.")
|
||||
await ask_question(message.chat.id, 1)
|
||||
|
||||
@dp.message_handler(Command("start"))
|
||||
async def show_menu(message: types.Message):
|
||||
await message.reply("Выбери действие:", reply_markup=ReplyKeyboardMarkup(
|
||||
resize_keyboard=True).add(KeyboardButton("Создать заказ")))
|
||||
|
||||
# Функция для задания вопроса
|
||||
async def ask_question(user_id, question_id):
|
||||
if question_id in [4, 5, 6]:
|
||||
if question_id == 4:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Влажная", callback_data="wet_cleaning")],
|
||||
[InlineKeyboardButton(text="Сухая", callback_data="dry_cleaning")]
|
||||
])
|
||||
elif question_id == 5:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Утро", callback_data="morning_time")],
|
||||
[InlineKeyboardButton(text="День", callback_data="day_time")],
|
||||
[InlineKeyboardButton(text="Вечер", callback_data="evening_time")]
|
||||
])
|
||||
elif question_id == 6:
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Наличные", callback_data="cash_payment")],
|
||||
[InlineKeyboardButton(text="Карта", callback_data="card_payment")]
|
||||
])
|
||||
await bot.send_message(user_id, text=questions[question_id], reply_markup=keyboard)
|
||||
else:
|
||||
await bot.send_message(user_id, text=questions[question_id])
|
||||
|
||||
# Обработчик текстовых ответов на первые три вопроса
|
||||
@dp.message_handler(lambda message: message.text not in ["Переписать", "Отправить"])
|
||||
async def handle_text_answer(message: types.Message):
|
||||
if message.chat.id != int(GROUP_ID):
|
||||
user_id = message.chat.id
|
||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||
prev_question_id = cursor.fetchone()[0]
|
||||
|
||||
if prev_question_id is None:
|
||||
question_id = 1
|
||||
else:
|
||||
question_id = prev_question_id + 1
|
||||
answer = message.text
|
||||
|
||||
await save_answer(user_id, question_id, answer)
|
||||
|
||||
if question_id < 6:
|
||||
await ask_question(user_id, question_id + 1)
|
||||
else:
|
||||
await show_confirmation_options(user_id)
|
||||
|
||||
# Обработчик inline-кнопок
|
||||
@dp.callback_query_handler(lambda query: query.data in answer_map.keys())
|
||||
async def handle_callback_answer(query: types.CallbackQuery):
|
||||
user_id = query.from_user.id
|
||||
cursor.execute("SELECT MAX(question_id) FROM answers WHERE user_id=?", (user_id,))
|
||||
prev_question_id = cursor.fetchone()[0]
|
||||
|
||||
if prev_question_id is None:
|
||||
question_id = 1
|
||||
else:
|
||||
question_id = prev_question_id + 1
|
||||
|
||||
await save_answer(user_id, question_id, answer_map[query.data])
|
||||
await query.answer()
|
||||
|
||||
if question_id < 6:
|
||||
await ask_question(user_id, question_id + 1)
|
||||
else:
|
||||
await show_confirmation_options(user_id)
|
||||
|
||||
# Функция для сохранения ответа в базе данных
|
||||
async def save_answer(user_id, question_id, answer):
|
||||
cursor.execute("INSERT INTO answers (user_id, question_id, answer) VALUES (?, ?, ?)", (user_id, question_id, answer))
|
||||
conn.commit()
|
||||
|
||||
# Функция для отображения опций подтверждения
|
||||
async def show_confirmation_options(user_id):
|
||||
cursor.execute("SELECT answer FROM answers WHERE user_id=? ORDER BY question_id", (user_id,))
|
||||
answers = cursor.fetchall()
|
||||
answer_text = "\n".join([f"{questions[i+1]}: {answers[i][0]}" for i in range(6)])
|
||||
await bot.send_message(user_id, text=f"Ваши ответы:\n\n{answer_text}")
|
||||
|
||||
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||
[InlineKeyboardButton(text="Отправить", callback_data="send_answers")],
|
||||
[InlineKeyboardButton(text="Переписать", callback_data="rewrite_answers")]
|
||||
])
|
||||
await bot.send_message(user_id, "Проверьте свои ответы. Выберите действие:", reply_markup=keyboard)
|
||||
|
||||
# Обработчик выбора действия подтверждения
|
||||
@dp.callback_query_handler(lambda query: query.data in ['send_answers', 'rewrite_answers'])
|
||||
async def handle_confirmation(query: types.CallbackQuery):
|
||||
user_id = query.from_user.id
|
||||
if query.data == "send_answers":
|
||||
await send_answers_to_group(user_id)
|
||||
elif query.data == "rewrite_answers":
|
||||
await rewrite_answers(user_id)
|
||||
|
||||
# Функция для отправки ответов в группу
|
||||
async def send_answers_to_group(user_id):
|
||||
cursor.execute("SELECT * FROM answers WHERE user_id=?", (user_id,))
|
||||
answers = cursor.fetchall()
|
||||
|
||||
if answers:
|
||||
answer_text = '\n'.join([f"{questions[ans[1]]}: {ans[2]}" for ans in answers])
|
||||
|
||||
# Очищаем таблицу с ответами
|
||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
# Отправляем ответы в группу и добавляем ID пользователя
|
||||
sent_message = await bot.send_message(GROUP_ID, f"Ответы от пользователя {user_id}:\n\n{answer_text}")
|
||||
await bot.send_message(user_id, f"Ваши ответы отправлены. Оператор с вами свяжится)",
|
||||
parse_mode='Markdown')
|
||||
else:
|
||||
await bot.send_message(user_id, "Ответов нет")
|
||||
|
||||
# Функция для переписывания ответов
|
||||
async def rewrite_answers(user_id):
|
||||
# Очищаем таблицу с ответами
|
||||
cursor.execute("DELETE FROM answers WHERE user_id=?", (user_id,))
|
||||
conn.commit()
|
||||
|
||||
await bot.send_message(user_id, "Ваши ответы удалены. Начнем сначала.")
|
||||
await ask_question(user_id, 1)
|
||||
|
||||
# Функция для обработки сообщений из группы и пересылки их пользователю
|
||||
@dp.message_handler(lambda message: message.chat.id == int(GROUP_ID) and message.reply_to_message)
|
||||
async def handle_group_reply(message: types.Message):
|
||||
# Проверяем, что сообщение является ответом на сообщение бота
|
||||
if message.reply_to_message.from_user.id == bot.id:
|
||||
# Извлекаем ID пользователя из текста сообщения
|
||||
try:
|
||||
user_id = int(message.reply_to_message.text.split()[2].strip(':'))
|
||||
await bot.send_message(user_id, f"Сообщение от администратора:\n\n{message.text}")
|
||||
# Сохраняем ответ пользователя для пересылки обратно в группу
|
||||
cursor.execute("INSERT INTO admin_user_conversation (user_id, admin_message_id) VALUES (?, ?)",
|
||||
(user_id, message.message_id))
|
||||
conn.commit()
|
||||
except (IndexError, ValueError):
|
||||
await message.reply("Не удалось определить ID пользователя для ответа.")
|
||||
|
||||
# Обработчик сообщений от пользователя в ответ на администратора
|
||||
@dp.message_handler(lambda message: message.chat.id != int(GROUP_ID))
|
||||
async def handle_user_reply(message: types.Message):
|
||||
user_id = message.chat.id
|
||||
cursor.execute("SELECT admin_message_id FROM admin_user_conversation WHERE user_id=? ORDER BY admin_message_id DESC LIMIT 1", (user_id,))
|
||||
admin_message_id = cursor.fetchone()
|
||||
if admin_message_id:
|
||||
await bot.send_message(GROUP_ID, f"Сообщение от пользователя {user_id}:\n\n{message.text}", reply_to_message_id=admin_message_id[0])
|
||||
|
||||
if __name__ == '__main__':
|
||||
loop = asyncio.get_event_loop()
|
||||
try:
|
||||
loop.create_task(dp.start_polling())
|
||||
loop.run_forever()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
@@ -1,76 +1,76 @@
|
||||
import logging
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.dispatcher.filters import Command
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
||||
GROUP_ID = '-1001961537659'
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Define states
|
||||
class OrderForm(StatesGroup):
|
||||
question_1 = State()
|
||||
question_2 = State()
|
||||
question_3 = State()
|
||||
question_4 = State()
|
||||
question_5 = State()
|
||||
question_6 = State()
|
||||
|
||||
@dp.message_handler(Command("start"))
|
||||
async def cmd_start(message: types.Message):
|
||||
await message.reply("Привет! Чтобы создать заказ, нажми на кнопку 'Создать заказ' внизу.", reply_markup=ReplyKeyboardMarkup(
|
||||
resize_keyboard=True).add(KeyboardButton("Создать заказ")))
|
||||
|
||||
@dp.message_handler(Command("menu"))
|
||||
async def show_menu(message: types.Message):
|
||||
await message.reply("Выбери действие:", reply_markup=ReplyKeyboardMarkup(
|
||||
resize_keyboard=True).add(KeyboardButton("Создать заказ")))
|
||||
|
||||
@dp.message_handler(Command("create_order"))
|
||||
async def cmd_create_order(message: types.Message):
|
||||
await OrderForm.question_1.set()
|
||||
await message.reply("Ответьте на первый вопрос:")
|
||||
|
||||
@dp.message_handler(state=OrderForm.question_1)
|
||||
async def process_question_1(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['question_1'] = message.text
|
||||
await OrderForm.next()
|
||||
await message.reply("Ответьте на второй вопрос:")
|
||||
|
||||
# Аналогично для остальных вопросов
|
||||
|
||||
@dp.message_handler(state=OrderForm.question_6)
|
||||
async def process_question_6(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['question_6'] = message.text
|
||||
await message.reply("Отлично! Ваши ответы:")
|
||||
async with state.proxy() as data:
|
||||
for key, value in data.items():
|
||||
await message.reply(f"{key}: {value}")
|
||||
|
||||
# Предложить отправить или переписать ответы
|
||||
keyboard = InlineKeyboardMarkup().add(InlineKeyboardButton("Отправить", callback_data="send_order")).add(InlineKeyboardButton("Переписать", callback_data="rewrite_order"))
|
||||
await message.reply("Хотите отправить эти ответы или переписать?", reply_markup=keyboard)
|
||||
|
||||
@dp.callback_query_handler(text="send_order", state="*")
|
||||
async def send_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
for key, value in data.items():
|
||||
await bot.send_message(callback_query.from_user.id, f"{key}: {value}")
|
||||
|
||||
@dp.callback_query_handler(text="rewrite_order", state="*")
|
||||
async def rewrite_order(callback_query: types.CallbackQuery):
|
||||
await cmd_create_order(callback_query.message)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from aiogram import executor
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
import logging
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.dispatcher.filters import Command
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
API_TOKEN = '7472030348:AAGI53nX-ON-WBmEhd_qBC6EnZsHOqp_2kE'
|
||||
GROUP_ID = '-1001961537659'
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Define states
|
||||
class OrderForm(StatesGroup):
|
||||
question_1 = State()
|
||||
question_2 = State()
|
||||
question_3 = State()
|
||||
question_4 = State()
|
||||
question_5 = State()
|
||||
question_6 = State()
|
||||
|
||||
@dp.message_handler(Command("start"))
|
||||
async def cmd_start(message: types.Message):
|
||||
await message.reply("Привет! Чтобы создать заказ, нажми на кнопку 'Создать заказ' внизу.", reply_markup=ReplyKeyboardMarkup(
|
||||
resize_keyboard=True).add(KeyboardButton("Создать заказ")))
|
||||
|
||||
@dp.message_handler(Command("menu"))
|
||||
async def show_menu(message: types.Message):
|
||||
await message.reply("Выбери действие:", reply_markup=ReplyKeyboardMarkup(
|
||||
resize_keyboard=True).add(KeyboardButton("Создать заказ")))
|
||||
|
||||
@dp.message_handler(Command("create_order"))
|
||||
async def cmd_create_order(message: types.Message):
|
||||
await OrderForm.question_1.set()
|
||||
await message.reply("Ответьте на первый вопрос:")
|
||||
|
||||
@dp.message_handler(state=OrderForm.question_1)
|
||||
async def process_question_1(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['question_1'] = message.text
|
||||
await OrderForm.next()
|
||||
await message.reply("Ответьте на второй вопрос:")
|
||||
|
||||
# Аналогично для остальных вопросов
|
||||
|
||||
@dp.message_handler(state=OrderForm.question_6)
|
||||
async def process_question_6(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['question_6'] = message.text
|
||||
await message.reply("Отлично! Ваши ответы:")
|
||||
async with state.proxy() as data:
|
||||
for key, value in data.items():
|
||||
await message.reply(f"{key}: {value}")
|
||||
|
||||
# Предложить отправить или переписать ответы
|
||||
keyboard = InlineKeyboardMarkup().add(InlineKeyboardButton("Отправить", callback_data="send_order")).add(InlineKeyboardButton("Переписать", callback_data="rewrite_order"))
|
||||
await message.reply("Хотите отправить эти ответы или переписать?", reply_markup=keyboard)
|
||||
|
||||
@dp.callback_query_handler(text="send_order", state="*")
|
||||
async def send_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
for key, value in data.items():
|
||||
await bot.send_message(callback_query.from_user.id, f"{key}: {value}")
|
||||
|
||||
@dp.callback_query_handler(text="rewrite_order", state="*")
|
||||
async def rewrite_order(callback_query: types.CallbackQuery):
|
||||
await cmd_create_order(callback_query.message)
|
||||
|
||||
if __name__ == '__main__':
|
||||
from aiogram import executor
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
import sqlite3
|
||||
|
||||
def create_tables():
|
||||
conn = sqlite3.connect('bot_data.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT,
|
||||
phone TEXT,
|
||||
address TEXT,
|
||||
cleaning_time TEXT,
|
||||
cleaning_type TEXT,
|
||||
payment_method TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
name TEXT,
|
||||
phone TEXT,
|
||||
address TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def add_order(name, phone, address, cleaning_time, cleaning_type, payment_method):
|
||||
conn = sqlite3.connect('bot_data.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO orders (name, phone, address, cleaning_time, cleaning_type, payment_method)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
''', (name, phone, address, cleaning_time, cleaning_type, payment_method))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def get_order_history(user_id):
|
||||
conn = sqlite3.connect('bot_data.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
SELECT * FROM orders WHERE user_id = ?
|
||||
''', (user_id,))
|
||||
|
||||
orders = cursor.fetchall()
|
||||
conn.close()
|
||||
return orders
|
||||
import sqlite3
|
||||
|
||||
def create_tables():
|
||||
conn = sqlite3.connect('bot_data.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT,
|
||||
phone TEXT,
|
||||
address TEXT,
|
||||
cleaning_time TEXT,
|
||||
cleaning_type TEXT,
|
||||
payment_method TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
user_id INTEGER PRIMARY KEY,
|
||||
name TEXT,
|
||||
phone TEXT,
|
||||
address TEXT
|
||||
)
|
||||
''')
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def add_order(name, phone, address, cleaning_time, cleaning_type, payment_method):
|
||||
conn = sqlite3.connect('bot_data.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
INSERT INTO orders (name, phone, address, cleaning_time, cleaning_type, payment_method)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
''', (name, phone, address, cleaning_time, cleaning_type, payment_method))
|
||||
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def get_order_history(user_id):
|
||||
conn = sqlite3.connect('bot_data.db')
|
||||
cursor = conn.cursor()
|
||||
|
||||
cursor.execute('''
|
||||
SELECT * FROM orders WHERE user_id = ?
|
||||
''', (user_id,))
|
||||
|
||||
orders = cursor.fetchall()
|
||||
conn.close()
|
||||
return orders
|
||||
|
||||
@@ -1,158 +1,158 @@
|
||||
import logging
|
||||
from aiogram import types
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from database import add_order, get_order_history
|
||||
from logger import log_action
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
class OrderForm(StatesGroup):
|
||||
waiting_for_name = State()
|
||||
waiting_for_phone = State()
|
||||
waiting_for_address = State()
|
||||
waiting_for_cleaning_time = State()
|
||||
waiting_for_cleaning_type = State()
|
||||
waiting_for_payment_method = State()
|
||||
confirmation = State()
|
||||
|
||||
def main_menu_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
||||
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
||||
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
||||
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
||||
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
||||
return keyboard
|
||||
|
||||
def account_data_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
||||
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
||||
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
||||
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
||||
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||
return keyboard
|
||||
|
||||
def order_confirmation_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order'))
|
||||
return keyboard
|
||||
|
||||
def register_handlers(dp, OPERATORS_GROUP_ID):
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def send_welcome(message: types.Message):
|
||||
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
||||
log_action(message.from_user.id, 'start')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
||||
async def process_account_data(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer("Учетные данные:", reply_markup=account_data_keyboard())
|
||||
log_action(callback_query.from_user.id, 'account_data')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
||||
async def process_make_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer("Начнем с ваших учетных данных. Введите ваше ФИО:")
|
||||
await OrderForm.waiting_for_name.set()
|
||||
log_action(callback_query.from_user.id, 'make_order')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
||||
async def process_order_history(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
orders = get_order_history(callback_query.from_user.id)
|
||||
if orders:
|
||||
history_message = "\n\n".join([f"Заказ {order['id']}:\nФИО: {order['name']}\nАдрес: {order['address']}\nТип уборки: {order['cleaning_type']}" for order in orders])
|
||||
await callback_query.message.answer(f"История заказов:\n{history_message}")
|
||||
else:
|
||||
await callback_query.message.answer("История заказов пуста.")
|
||||
log_action(callback_query.from_user.id, 'order_history')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
||||
async def process_request_call(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer("Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
||||
log_action(callback_query.from_user.id, 'request_call')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
||||
async def process_talk_operator(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer("Разговор с оператором начат.")
|
||||
log_action(callback_query.from_user.id, 'talk_operator')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
||||
async def process_back_to_main(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer("Главное меню:", reply_markup=main_menu_keyboard())
|
||||
log_action(callback_query.from_user.id, 'back_to_main')
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT)
|
||||
async def process_name(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['name'] = message.text
|
||||
await message.answer("Введите ваш номер телефона:")
|
||||
await OrderForm.waiting_for_phone.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT)
|
||||
async def process_phone(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['phone'] = message.text
|
||||
await message.answer("Введите адрес для уборки:")
|
||||
await OrderForm.waiting_for_address.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||
async def process_order_address(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['address'] = message.text
|
||||
await message.answer("Выберите время для уборки (утро/день/вечер):")
|
||||
await OrderForm.waiting_for_cleaning_time.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT)
|
||||
async def process_cleaning_time(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['cleaning_time'] = message.text
|
||||
await message.answer("Выберите тип уборки (влажная/сухая/генеральная):")
|
||||
await OrderForm.waiting_for_cleaning_type.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT)
|
||||
async def process_cleaning_type(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['cleaning_type'] = message.text
|
||||
await message.answer("Выберите способ оплаты (картой/наличными):")
|
||||
await OrderForm.waiting_for_payment_method.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT)
|
||||
async def process_payment_method(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['payment_method'] = message.text
|
||||
|
||||
await message.answer("Подтвердите заказ:\n"
|
||||
f"ФИО: {data['name']}\n"
|
||||
f"Номер телефона: {data['phone']}\n"
|
||||
f"Адрес: {data['address']}\n"
|
||||
f"Время уборки: {data['cleaning_time']}\n"
|
||||
f"Тип уборки: {data['cleaning_type']}\n"
|
||||
f"Способ оплаты: {data['payment_method']}\n"
|
||||
"Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.",
|
||||
reply_markup=order_confirmation_keyboard())
|
||||
await OrderForm.confirmation.set()
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation)
|
||||
async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await callback_query.answer()
|
||||
async with state.proxy() as data:
|
||||
order_details = (f"Новый заказ:\n"
|
||||
f"ФИО: {data['name']}\n"
|
||||
f"Номер телефона: {data['phone']}\n"
|
||||
f"Адрес: {data['address']}\n"
|
||||
f"Время уборки: {data['cleaning_time']}\n"
|
||||
f"Тип уборки: {data['cleaning_type']}\n"
|
||||
f"Способ оплаты: {data['payment_method']}")
|
||||
await bot.send_message(OPERATORS_GROUP_ID, order_details)
|
||||
add_order(data['name'], data['phone'], data['address'], data['cleaning_time'], data['cleaning_type'], data['payment_method'])
|
||||
|
||||
await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.")
|
||||
await state.finish()
|
||||
import logging
|
||||
from aiogram import types
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from database import add_order, get_order_history
|
||||
from logger import log_action
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
class OrderForm(StatesGroup):
|
||||
waiting_for_name = State()
|
||||
waiting_for_phone = State()
|
||||
waiting_for_address = State()
|
||||
waiting_for_cleaning_time = State()
|
||||
waiting_for_cleaning_type = State()
|
||||
waiting_for_payment_method = State()
|
||||
confirmation = State()
|
||||
|
||||
def main_menu_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Учетные данные', callback_data='account_data'))
|
||||
keyboard.add(InlineKeyboardButton('Сделать заказ', callback_data='make_order'))
|
||||
keyboard.add(InlineKeyboardButton('История заказов', callback_data='order_history'))
|
||||
keyboard.add(InlineKeyboardButton('Заказать звонок', callback_data='request_call'))
|
||||
keyboard.add(InlineKeyboardButton('Начать разговор с оператором', callback_data='talk_operator'))
|
||||
return keyboard
|
||||
|
||||
def account_data_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Изменить ФИО', callback_data='change_name'))
|
||||
keyboard.add(InlineKeyboardButton('Изменить номер телефона', callback_data='change_phone'))
|
||||
keyboard.add(InlineKeyboardButton('Добавить адрес', callback_data='add_address'))
|
||||
keyboard.add(InlineKeyboardButton('Удалить адрес', callback_data='delete_address'))
|
||||
keyboard.add(InlineKeyboardButton('Поделиться контактом', callback_data='share_contact'))
|
||||
keyboard.add(InlineKeyboardButton('Назад', callback_data='back_to_main'))
|
||||
return keyboard
|
||||
|
||||
def order_confirmation_keyboard():
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(InlineKeyboardButton('Подтвердить', callback_data='confirm_order'))
|
||||
return keyboard
|
||||
|
||||
def register_handlers(dp, OPERATORS_GROUP_ID):
|
||||
@dp.message_handler(commands=['start'])
|
||||
async def send_welcome(message: types.Message):
|
||||
await message.answer("Добро пожаловать в BOTKlining!", reply_markup=main_menu_keyboard())
|
||||
log_action(message.from_user.id, 'start')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'account_data')
|
||||
async def process_account_data(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer("Учетные данные:", reply_markup=account_data_keyboard())
|
||||
log_action(callback_query.from_user.id, 'account_data')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'make_order')
|
||||
async def process_make_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer("Начнем с ваших учетных данных. Введите ваше ФИО:")
|
||||
await OrderForm.waiting_for_name.set()
|
||||
log_action(callback_query.from_user.id, 'make_order')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'order_history')
|
||||
async def process_order_history(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
orders = get_order_history(callback_query.from_user.id)
|
||||
if orders:
|
||||
history_message = "\n\n".join([f"Заказ {order['id']}:\nФИО: {order['name']}\nАдрес: {order['address']}\nТип уборки: {order['cleaning_type']}" for order in orders])
|
||||
await callback_query.message.answer(f"История заказов:\n{history_message}")
|
||||
else:
|
||||
await callback_query.message.answer("История заказов пуста.")
|
||||
log_action(callback_query.from_user.id, 'order_history')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'request_call')
|
||||
async def process_request_call(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer("Заказ звонка оформлен. Ожидайте звонок в течение 30 минут.")
|
||||
log_action(callback_query.from_user.id, 'request_call')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'talk_operator')
|
||||
async def process_talk_operator(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer("Разговор с оператором начат.")
|
||||
log_action(callback_query.from_user.id, 'talk_operator')
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'back_to_main')
|
||||
async def process_back_to_main(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
await callback_query.message.answer("Главное меню:", reply_markup=main_menu_keyboard())
|
||||
log_action(callback_query.from_user.id, 'back_to_main')
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_name, content_types=types.ContentTypes.TEXT)
|
||||
async def process_name(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['name'] = message.text
|
||||
await message.answer("Введите ваш номер телефона:")
|
||||
await OrderForm.waiting_for_phone.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_phone, content_types=types.ContentTypes.TEXT)
|
||||
async def process_phone(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['phone'] = message.text
|
||||
await message.answer("Введите адрес для уборки:")
|
||||
await OrderForm.waiting_for_address.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_address, content_types=types.ContentTypes.TEXT)
|
||||
async def process_order_address(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['address'] = message.text
|
||||
await message.answer("Выберите время для уборки (утро/день/вечер):")
|
||||
await OrderForm.waiting_for_cleaning_time.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_time, content_types=types.ContentTypes.TEXT)
|
||||
async def process_cleaning_time(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['cleaning_time'] = message.text
|
||||
await message.answer("Выберите тип уборки (влажная/сухая/генеральная):")
|
||||
await OrderForm.waiting_for_cleaning_type.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_cleaning_type, content_types=types.ContentTypes.TEXT)
|
||||
async def process_cleaning_type(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['cleaning_type'] = message.text
|
||||
await message.answer("Выберите способ оплаты (картой/наличными):")
|
||||
await OrderForm.waiting_for_payment_method.set()
|
||||
|
||||
@dp.message_handler(state=OrderForm.waiting_for_payment_method, content_types=types.ContentTypes.TEXT)
|
||||
async def process_payment_method(message: types.Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['payment_method'] = message.text
|
||||
|
||||
await message.answer("Подтвердите заказ:\n"
|
||||
f"ФИО: {data['name']}\n"
|
||||
f"Номер телефона: {data['phone']}\n"
|
||||
f"Адрес: {data['address']}\n"
|
||||
f"Время уборки: {data['cleaning_time']}\n"
|
||||
f"Тип уборки: {data['cleaning_type']}\n"
|
||||
f"Способ оплаты: {data['payment_method']}\n"
|
||||
"Если все верно, нажмите 'Подтвердить'. В противном случае, измените нужные данные.",
|
||||
reply_markup=order_confirmation_keyboard())
|
||||
await OrderForm.confirmation.set()
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == 'confirm_order', state=OrderForm.confirmation)
|
||||
async def process_confirm_order(callback_query: types.CallbackQuery, state: FSMContext):
|
||||
await callback_query.answer()
|
||||
async with state.proxy() as data:
|
||||
order_details = (f"Новый заказ:\n"
|
||||
f"ФИО: {data['name']}\n"
|
||||
f"Номер телефона: {data['phone']}\n"
|
||||
f"Адрес: {data['address']}\n"
|
||||
f"Время уборки: {data['cleaning_time']}\n"
|
||||
f"Тип уборки: {data['cleaning_type']}\n"
|
||||
f"Способ оплаты: {data['payment_method']}")
|
||||
await bot.send_message(OPERATORS_GROUP_ID, order_details)
|
||||
add_order(data['name'], data['phone'], data['address'], data['cleaning_time'], data['cleaning_type'], data['payment_method'])
|
||||
|
||||
await bot.send_message(callback_query.from_user.id, "Ваш заказ был подтвержден и отправлен операторам.")
|
||||
await state.finish()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
def log_action(user_id, action):
|
||||
logging.info(f"User {user_id} performed action: {action}")
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
def log_action(user_id, action):
|
||||
logging.info(f"User {user_id} performed action: {action}")
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import logging
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.utils import executor
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
from handlers import register_handlers
|
||||
from database import create_tables
|
||||
|
||||
# Загрузка переменных окружения из .env файла
|
||||
load_dotenv()
|
||||
|
||||
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
||||
OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID'))
|
||||
ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID'))
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Основное приложение
|
||||
if __name__ == '__main__':
|
||||
create_tables()
|
||||
register_handlers(dp, OPERATORS_GROUP_ID)
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
import logging
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.middlewares.logging import LoggingMiddleware
|
||||
from aiogram.utils import executor
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.dispatcher.filters.state import State, StatesGroup
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
from handlers import register_handlers
|
||||
from database import create_tables
|
||||
|
||||
# Загрузка переменных окружения из .env файла
|
||||
load_dotenv()
|
||||
|
||||
API_TOKEN = os.getenv('BOT_API_TOKEN')
|
||||
OPERATORS_GROUP_ID = int(os.getenv('OPERATORS_GROUP_ID'))
|
||||
ADMIN_GROUP_ID = int(os.getenv('ADMIN_GROUP_ID'))
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot(token=API_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
# Основное приложение
|
||||
if __name__ == '__main__':
|
||||
create_tables()
|
||||
register_handlers(dp, OPERATORS_GROUP_ID)
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
||||
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`*
|
||||
6
doners/shop-telegram-bot/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
aiogram==2.19
|
||||
captcha==0.3
|
||||
matplotlib==3.5.1
|
||||
phonenumbers==8.12.40
|
||||
pymorphy2==0.9.1
|
||||
pyparsing==3.0.0
|
||||
0
doners/shop-telegram-bot/src/__init__.py
Normal file
51
doners/shop-telegram-bot/src/category.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import sqlite3
|
||||
import item as itm
|
||||
|
||||
conn = sqlite3.connect("data.db")
|
||||
c = conn.cursor()
|
||||
|
||||
class Category:
|
||||
def __init__(self, cat_id):
|
||||
self.id = cat_id
|
||||
|
||||
def __eq__(self, __o: object) -> bool:
|
||||
return self.get_id() == __o.get_id()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.get_name()
|
||||
|
||||
def get_id(self):
|
||||
return self.id
|
||||
|
||||
def __clist(self):
|
||||
c.execute(f"SELECT * FROM cats WHERE id=?", [self.get_id()])
|
||||
return list(c)[0]
|
||||
|
||||
def get_name(self):
|
||||
return self.__clist()[1]
|
||||
|
||||
def set_name(self, value):
|
||||
c.execute(f"UPDATE cats SET name=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def delete(self):
|
||||
c.execute(f"DELETE FROM cats WHERE id=?", [self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_item_list(self):
|
||||
c.execute(f"SELECT * FROM items WHERE cat_id=?", [self.get_id()])
|
||||
return list(map(itm.Item, [item[0] for item in list(c)]))
|
||||
|
||||
|
||||
def get_cat_list():
|
||||
c.execute(f"SELECT * FROM cats")
|
||||
return list(map(Category, [cat[0] for cat in list(c)]))
|
||||
|
||||
|
||||
def create_cat(cat_name):
|
||||
c.execute(f"INSERT INTO cats(name) VALUES(?)", [cat_name])
|
||||
conn.commit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(get_cat_list())
|
||||
61
doners/shop-telegram-bot/src/commands.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import sqlite3
|
||||
|
||||
conn = sqlite3.connect("data.db")
|
||||
c = conn.cursor()
|
||||
|
||||
|
||||
def get_commands():
|
||||
return list(map(Command, [command[0] for command in list(c.execute("SELECT * FROM commands"))]))
|
||||
|
||||
def does_command_exist(command_id=None, command=None):
|
||||
result = False
|
||||
if command_id:
|
||||
c.execute("SELECT * FROM commands WHERE id=?", [command_id])
|
||||
result = len(list(c)) >= 1
|
||||
elif command:
|
||||
c.execute("SELECT * FROM commands WHERE command=?", [command])
|
||||
result = len(list(c)) >= 1
|
||||
return result
|
||||
|
||||
def get_command_by_command(command):
|
||||
c.execute("SELECT * FROM commands WHERE command=?", [command])
|
||||
return Command(list(c)[0][0])
|
||||
|
||||
|
||||
def create_command(command, response):
|
||||
c.execute("INSERT INTO commands(command, response) VALUES(?,?)", [command, response])
|
||||
conn.commit()
|
||||
|
||||
|
||||
# TODO: rework the permission system and implement command permissions
|
||||
class Command:
|
||||
def __init__(self, command_id=None):
|
||||
self.command_id = command_id
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return f"[{self.get_id()}] {self.get_command()}"
|
||||
|
||||
def __clist(self):
|
||||
c.execute("SELECT * FROM commands WHERE id=?", [self.command_id])
|
||||
return list(c)[0]
|
||||
|
||||
def get_id(self):
|
||||
return self.command_id
|
||||
|
||||
def get_command(self):
|
||||
return self.__clist()[1]
|
||||
|
||||
def get_response(self):
|
||||
return self.__clist()[2]
|
||||
|
||||
def delete(self):
|
||||
c.execute("DELETE FROM commands WHERE id=?", [self.command_id])
|
||||
conn.commit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# create_command("test", "response")
|
||||
|
||||
for command in get_commands():
|
||||
print(command)
|
||||
111
doners/shop-telegram-bot/src/item.py
Normal file
@@ -0,0 +1,111 @@
|
||||
import sqlite3
|
||||
from settings import Settings
|
||||
from os.path import exists
|
||||
from doners.old_2.main import notify_admins
|
||||
|
||||
conn = sqlite3.connect("data.db")
|
||||
c = conn.cursor()
|
||||
|
||||
settings = Settings()
|
||||
|
||||
def does_item_exist(item_id):
|
||||
c.execute(f"SELECT * FROM items WHERE id=?", [item_id])
|
||||
return len(list(c)) == 1
|
||||
|
||||
|
||||
class Item:
|
||||
def __init__(self, item_id):
|
||||
self.__item_id = item_id
|
||||
|
||||
def __eq__(self, __o: object):
|
||||
return self.get_id() == __o.get_id()
|
||||
|
||||
def __repr__(self):
|
||||
return f"[{self.get_id()}] {self.get_name()}"
|
||||
|
||||
def get_id(self):
|
||||
return self.__item_id
|
||||
|
||||
def __clist(self):
|
||||
c.execute(f"SELECT * FROM items WHERE id=?", [self.get_id()])
|
||||
return list(c)[0]
|
||||
|
||||
def get_name(self):
|
||||
return self.__clist()[1]
|
||||
|
||||
def set_name(self, value):
|
||||
c.execute(f"UPDATE items SET name=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_price(self):
|
||||
return self.__clist()[2]
|
||||
|
||||
def set_price(self, value):
|
||||
c.execute(f"UPDATE items SET price=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
# TODO: change to get_cat and change to cat.get_id() everywhere
|
||||
def get_cat_id(self):
|
||||
return self.__clist()[3]
|
||||
|
||||
def set_cat_id(self, value):
|
||||
c.execute(f"UPDATE items SET cat_id=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_desc(self):
|
||||
return self.__clist()[4]
|
||||
|
||||
def set_desc(self, value):
|
||||
c.execute(f"UPDATE items SET desc=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def is_active(self):
|
||||
return self.__clist()[5] == 1
|
||||
|
||||
def set_active(self, value):
|
||||
c.execute(f"UPDATE items SET active=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_amount(self):
|
||||
return int(self.__clist()[6])
|
||||
|
||||
def set_amount(self, value):
|
||||
c.execute(f"UPDATE items SET amount=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_image_id(self):
|
||||
return self.__clist()[7]
|
||||
|
||||
def get_image(self):
|
||||
return open("images/" + self.get_image_id(), "rb")
|
||||
|
||||
def set_image_id(self, value):
|
||||
c.execute(f"UPDATE items SET image_id=? WHERE id=?", [value, self.get_id()])
|
||||
|
||||
async def is_hide_image(self):
|
||||
if exists(f"images/{self.get_image_id()}"):
|
||||
return self.__clist()[8] == 1
|
||||
if self.get_image_id() != "None" and settings.is_debug():
|
||||
await notify_admins(f"Файла \"images/{self.get_image_id()}\" для товара \"{self.get_name()}\" не существует!")
|
||||
return True
|
||||
|
||||
def set_hide_image(self, value):
|
||||
c.execute("UPDATE items SET hide_image=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def delete(self):
|
||||
c.execute(f"DELETE FROM items WHERE id=?", [self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
|
||||
def get_item_list():
|
||||
c.execute("SELECT * FROM items")
|
||||
return list(map(Item, [item[0] for item in list(c)]))
|
||||
|
||||
|
||||
def create_item(name, price, cat_id, desc, image_id="None", active=True):
|
||||
c.execute(f"INSERT INTO items(name, price, cat_id, desc, active, amount, image_id, hide_image) VALUES(?, ?, ?, ?, ?, 0, ?, 0)", [name, price, cat_id, desc, 1 if active else 0, image_id])
|
||||
conn.commit()
|
||||
return get_item_list()[-1]
|
||||
|
||||
|
||||
2522
doners/shop-telegram-bot/src/main.py
Normal file
477
doners/shop-telegram-bot/src/markups.py
Normal file
@@ -0,0 +1,477 @@
|
||||
from os import listdir
|
||||
from aiogram import types
|
||||
from datetime import datetime
|
||||
|
||||
import text_templates as tt
|
||||
from settings import Settings
|
||||
import commands
|
||||
|
||||
settings = Settings()
|
||||
|
||||
# Back buttons
|
||||
# Misc
|
||||
btnBackAdmin = types.InlineKeyboardButton(text=tt.back, callback_data="admin_adminPanel")
|
||||
|
||||
# Item management
|
||||
btnBackItemManagement = types.InlineKeyboardButton(text=tt.back, callback_data="admin_itemManagement")
|
||||
btnBackEditCatChooseCategory = types.InlineKeyboardButton(text=tt.back, callback_data="admin_editCatChooseCategory")
|
||||
def btnBackEditCat(cat_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_editCat{cat_id}")
|
||||
btnBackEditItemChooseCategory = types.InlineKeyboardButton(text=tt.back, callback_data="admin_editItemChooseCategory")
|
||||
def btnBackEditItemChooseItem(cat_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_editItemChooseItem{cat_id}")
|
||||
def btnBackEditItem(item_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_editItem{item_id}")
|
||||
|
||||
# User management
|
||||
btnBackUserManagement = types.InlineKeyboardButton(text=tt.back, callback_data="admin_userManagement")
|
||||
def btnBackSeeUserProfile(user_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_seeUserProfile{user_id}")
|
||||
def btnBackSeeUserOrders(user_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_seeUserOrders{user_id}")
|
||||
|
||||
# Stats
|
||||
btnBackShopStats = types.InlineKeyboardButton(text=tt.back, callback_data="admin_shopStats")
|
||||
btnBackRegistratonStats = types.InlineKeyboardButton(text=tt.back, callback_data="admin_registrationStatsBack")
|
||||
btnBackOrderStats = types.InlineKeyboardButton(text=tt.back, callback_data="admin_orderStatsBack")
|
||||
|
||||
# Settings
|
||||
btnBackShopSettingsDel = types.InlineKeyboardButton(text=tt.back, callback_data="admin_shopSettingsDel")
|
||||
btnBackShopSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_shopSettings")
|
||||
btnBackStatsSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_statsSettings")
|
||||
btnBackMainSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_mainSettings")
|
||||
btnBackCheckoutSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_checkoutSettings")
|
||||
btnBackAdditionalSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_additionalSettings")
|
||||
btnBackCustomCommands = types.InlineKeyboardButton(text=tt.back, callback_data="admin_customCommands")
|
||||
btnBackSystemSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_systemSettings")
|
||||
btnBackItemSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_itemSettings")
|
||||
btnBackBackups = types.InlineKeyboardButton(text=tt.back, callback_data="admin_backups")
|
||||
|
||||
# /start menu
|
||||
btnBackFaq = types.InlineKeyboardButton(text=tt.back, callback_data="faq")
|
||||
btnBackProfile = types.InlineKeyboardButton(text=tt.back, callback_data="profile")
|
||||
btnBackMyOrder = types.InlineKeyboardButton(text=tt.back, callback_data="myOrders")
|
||||
btnBackCatalogue = types.InlineKeyboardButton(text=tt.back, callback_data="catalogue")
|
||||
def btnBackViewCat(cat_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"viewCat{cat_id}")
|
||||
def btnBackViewItem(item_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"viewItem{item_id}")
|
||||
btnBackCart = types.InlineKeyboardButton(text=tt.back, callback_data="cart")
|
||||
btnBackCartDel = types.InlineKeyboardButton(text=tt.back, callback_data="cartDel")
|
||||
btnBackOrders = types.InlineKeyboardButton(text=tt.back, callback_data="manager_orders")
|
||||
|
||||
# Single buttons
|
||||
btnAdminPanel = types.KeyboardButton(tt.admin_panel)
|
||||
btnOrders = types.KeyboardButton(tt.orders)
|
||||
|
||||
def single_button(btn):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(btn)
|
||||
return markup
|
||||
|
||||
|
||||
# Markups
|
||||
# /start buttons
|
||||
def get_markup_main():
|
||||
markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
|
||||
markup.add(types.KeyboardButton(tt.catalogue))
|
||||
markup.add(types.KeyboardButton(tt.cart))
|
||||
markup.add(types.KeyboardButton(tt.profile), types.KeyboardButton(tt.faq))
|
||||
return markup
|
||||
|
||||
def get_markup_admin():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.item_management, callback_data="admin_itemManagement"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.user_management, callback_data="admin_userManagement"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.shop_stats, callback_data="admin_shopStats"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.bot_settings, callback_data="admin_shopSettings"))
|
||||
return markup
|
||||
|
||||
def get_markup_faq():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.contacts, callback_data="contacts"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.refund, callback_data="refund"))
|
||||
return markup
|
||||
|
||||
def get_markup_profile(user):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.my_orders, callback_data="myOrders"))
|
||||
# markup.add(types.InlineKeyboardButton(text=tt.my_support_tickets, callback_data="mySupportTickets"))
|
||||
if user.is_admin():
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_notif if user.notif_on() else tt.enable_notif, callback_data="changeEnableNotif"))
|
||||
return markup
|
||||
|
||||
def get_markup_myOrders(order_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for order in order_list:
|
||||
markup.add(types.InlineKeyboardButton(text=order.get_order_id(), callback_data=f"viewMyOrder{order.get_order_id()}"))
|
||||
markup.add(btnBackProfile)
|
||||
return markup
|
||||
|
||||
def get_markup_viewMyOrder(order):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
match order.get_status():
|
||||
case 0:
|
||||
markup.add(types.InlineKeyboardButton(text=tt.cancel_order, callback_data=f"cancelOrder{order.get_order_id()}"))
|
||||
case -1:
|
||||
markup.add(types.InlineKeyboardButton(text=tt.restore_order, callback_data=f"restoreOrder{order.get_order_id()}"))
|
||||
markup.add(btnBackMyOrder)
|
||||
return markup
|
||||
def get_markup_cart(user):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
delivery_price = '{:.2f}'.format(float(settings.get_delivery_price()))
|
||||
for item_and_amount in user.get_cart_amount():
|
||||
markup.add(types.InlineKeyboardButton(text=f"{item_and_amount[0].get_name()[:30-len(f' - {item_and_amount[1]}шт.')-3]}... - {item_and_amount[1]}шт.", callback_data=f"viewItem{item_and_amount[0].get_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=f"{item_and_amount[0].get_price() * item_and_amount[1]}руб.", callback_data="None"), types.InlineKeyboardButton(text=tt.plus, callback_data=f"addToCartFromCart{item_and_amount[0].get_id()}"), types.InlineKeyboardButton(text=tt.minus, callback_data=f"removeFromCartFromCart{item_and_amount[0].get_id()}"))
|
||||
if settings.is_delivery_enabled():
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delivery_on(delivery_price) if user.is_cart_delivery() else tt.delivery_off(delivery_price), callback_data="changeCartDelivery"))
|
||||
else:
|
||||
markup.add(types.InlineKeyboardButton(text=tt.pickup, callback_data="None"))
|
||||
|
||||
markup.add(types.InlineKeyboardButton(text=tt.clear_cart, callback_data="clearCart"))
|
||||
markup.add(types.InlineKeyboardButton(text=f"Всего: {'{:.2f}'.format(user.get_cart_price())}руб.", callback_data="None"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.cart_checkout, callback_data="checkoutCart"))
|
||||
return markup
|
||||
|
||||
def get_markup_captcha():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text="Новая CAPTCHA", callback_data="refreshCaptcha"))
|
||||
markup.add(btnBackCartDel)
|
||||
return markup
|
||||
|
||||
def get_markup_checkoutCartConfirmation():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.confirm, callback_data=f"checkoutCartConfirm"), types.InlineKeyboardButton(text=tt.deny, callback_data="cart"))
|
||||
return markup
|
||||
|
||||
# Catalogue
|
||||
def get_markup_catalogue(cat_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for cat in cat_list:
|
||||
markup.add(types.InlineKeyboardButton(text=cat.get_name(), callback_data=f"viewCat{cat.get_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.search, callback_data="search"))
|
||||
return markup
|
||||
|
||||
def get_markup_search(query):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for item in query:
|
||||
markup.add(types.InlineKeyboardButton(text=item.get_name(), callback_data=f"viewItem{item.get_id()}"))
|
||||
markup.add(btnBackCatalogue)
|
||||
return markup
|
||||
|
||||
|
||||
def get_markup_viewCat(item_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for item in item_list:
|
||||
if item.is_active():
|
||||
markup.add(types.InlineKeyboardButton(text=f"{item.get_name()} - {item.get_price()} руб.", callback_data=f"viewItem{item.get_id()}"))
|
||||
markup.add(btnBackCatalogue)
|
||||
return markup
|
||||
|
||||
def get_markup_viewItem(item):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.add_to_cart, callback_data=f"addToCart{item.get_id()}"))
|
||||
markup.add(btnBackViewCat(item.get_cat_id()))
|
||||
return markup
|
||||
|
||||
# Admin panel tabs
|
||||
# Item management
|
||||
def get_markup_itemManagement():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.add_cat, callback_data="admin_addCat"), types.InlineKeyboardButton(text=tt.add_item, callback_data="admin_addItem"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.edit_cat, callback_data="admin_editCatChooseCategory"), types.InlineKeyboardButton(text=tt.edit_item, callback_data="admin_editItemChooseCategory"))
|
||||
markup.add(btnBackAdmin)
|
||||
return markup
|
||||
|
||||
def get_markup_editCatChooseCategory(cat_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for cat in cat_list:
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{cat.get_id()}] {cat.get_name()}", callback_data=f"admin_editCat{cat.get_id()}"))
|
||||
markup.add(btnBackItemManagement)
|
||||
return markup
|
||||
|
||||
def get_markup_editCat(cat_id):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_name, callback_data=f"admin_editCatName{cat_id}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delete, callback_data=f"admin_editCatDelete{cat_id}"))
|
||||
markup.add(btnBackEditCatChooseCategory)
|
||||
return markup
|
||||
|
||||
def get_markup_addItemSetCat(cat_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for cat in cat_list:
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{cat.get_id()}] {cat.get_name()}", callback_data=f"admin_addItemSetCat{cat.get_id()}"))
|
||||
markup.add(btnBackItemManagement)
|
||||
return markup
|
||||
|
||||
btnSkipAddItemSetImage = types.InlineKeyboardButton(text=tt.skip, callback_data="admin_skipSetAddItemSetImage")
|
||||
|
||||
def get_markup_addItemConfirmation():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.confirm, callback_data="admin_addItemConfirm"), types.InlineKeyboardButton(text=tt.deny, callback_data="admin_itemManagement"))
|
||||
return markup
|
||||
|
||||
def get_markup_editItemChooseCategory(cat_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for cat in cat_list:
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{cat.get_id()}] {cat.get_name()}", callback_data=f"admin_editItemChooseItem{cat.get_id()}"))
|
||||
markup.add(btnBackItemManagement)
|
||||
return markup
|
||||
|
||||
def get_markup_editItemChooseItem(item_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for item in item_list:
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{item.get_id()}] {item.get_name()}", callback_data=f"admin_editItem{item.get_id()}"))
|
||||
markup.add(btnBackEditItemChooseCategory)
|
||||
return markup
|
||||
|
||||
async def get_markup_editItem(item):
|
||||
itemid = item.get_id()
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_name, callback_data=f"admin_editItemName{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_image, callback_data=f"admin_editItemImage{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.show_image if await item.is_hide_image() else tt.hide_image, callback_data=f"admin_editItemHideImage{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_desc, callback_data=f"admin_editItemDesc{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_price, callback_data=f"admin_editItemPrice{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_item_cat, callback_data=f"admin_editItemCat{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_stock, callback_data=f"admin_editItemStock{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=(tt.hide if item.is_active() else tt.show), callback_data=f"admin_editItemHide{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delete, callback_data=f"admin_editItemDelete{itemid}"))
|
||||
markup.add(btnBackEditItemChooseItem(item.get_cat_id()))
|
||||
return markup
|
||||
|
||||
def get_markup_editItemCat(item_id, cat_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for cat in cat_list:
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{cat.get_id()}] {cat.get_name()}", callback_data=f"admin_editItemSetCat{cat.get_id()}"))
|
||||
markup.add(btnBackEditItem(item_id))
|
||||
return markup
|
||||
|
||||
# User management
|
||||
def get_markup_userManagement():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.user_profile, callback_data="admin_seeUserProfile"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.notify_everyone, callback_data="admin_notifyEveryone"))
|
||||
markup.add(btnBackAdmin)
|
||||
return markup
|
||||
|
||||
def get_markup_notifyEveryoneConfirmation():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.confirm, callback_data="admin_notifyEveryoneConfirm"), btnBackUserManagement)
|
||||
return markup
|
||||
|
||||
def get_markup_seeUserProfile(user):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.orders, callback_data=f"admin_seeUserOrders{user.get_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.remove_manager_role if user.is_manager() else tt.add_manager_role, callback_data=f"admin_changeUserManager{user.get_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.remove_admin_role if user.is_admin() else tt.add_admin_role, callback_data=f"admin_changeUserAdmin{user.get_id()}"))
|
||||
markup.add(btnBackUserManagement)
|
||||
return markup
|
||||
|
||||
def get_markup_seeUserOrders(user):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for order in user.get_orders():
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{order.get_order_id()}]", callback_data=f"admin_seeUserOrder{order.get_order_id()}"))
|
||||
markup.add(btnBackSeeUserProfile(user.get_id()))
|
||||
return markup
|
||||
|
||||
def get_markup_seeUserOrder(order):
|
||||
markup = markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.processing), callback_data=f"admin_changeOrderStatusProcessing{order.get_order_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.delivery), callback_data=f"admin_changeOrderStatusDelivery{order.get_order_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.done), callback_data=f"admin_changeOrderStatusDone{order.get_order_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.cancelled), callback_data=f"admin_changeOrderStatusCancel{order.get_order_id()}"))
|
||||
markup.add(btnBackSeeUserOrders(order.get_user_id()))
|
||||
return markup
|
||||
|
||||
# Shop stats
|
||||
def get_markup_shopStats():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.registration_stats, callback_data="admin_registrationStats"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.order_stats, callback_data="admin_orderStats"))
|
||||
markup.add(btnBackAdmin)
|
||||
return markup
|
||||
|
||||
def get_markup_registrationStats():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.all_time, callback_data="admin_registrationStatsAllTime"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.monthly, callback_data="admin_registrationStatsMonthly"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.weekly, callback_data="admin_registrationStatsWeekly"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.daily, callback_data="admin_registrationStatsDaily"))
|
||||
markup.add(btnBackShopStats)
|
||||
return markup
|
||||
|
||||
def get_markup_orderStats():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.all_time, callback_data="admin_orderStatsAllTime"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.monthly, callback_data="admin_orderStatsMonthly"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.weekly, callback_data="admin_orderStatsWeekly"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.daily, callback_data="admin_orderStatsDaily"))
|
||||
markup.add(btnBackShopStats)
|
||||
return markup
|
||||
|
||||
# Shop settings
|
||||
def get_markup_shopSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.main_settings, callback_data="admin_mainSettings"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.item_settings, callback_data="admin_itemSettings"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.checkout_settings, callback_data="admin_checkoutSettings"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.stats_settings, callback_data="admin_statsSettings"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.additional_settings, callback_data="admin_additionalSettings"))
|
||||
markup.add(btnBackAdmin)
|
||||
return markup
|
||||
|
||||
def get_markup_mainSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=f"Название: {settings.get_shop_name()}", callback_data="admin_changeShopName"))
|
||||
markup.add(types.InlineKeyboardButton(text=f"Приветствие: {settings.get_shop_greeting()}", callback_data="admin_changeShopGreeting"))
|
||||
markup.add(types.InlineKeyboardButton(text=f"Политика возврата: {settings.get_refund_policy()}", callback_data="admin_changeShopRefundPolicy"))
|
||||
markup.add(types.InlineKeyboardButton(text=f"Контакты: {settings.get_shop_contacts()}", callback_data="admin_changeShopContacts"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_sticker if settings.is_sticker_enabled() else tt.enable_sticker, callback_data="admin_changeEnableSticker"))
|
||||
markup.add(btnBackShopSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_itemSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_item_image if settings.is_item_image_enabled() else tt.enable_item_image, callback_data="admin_changeEnableItemImage"))
|
||||
markup.add(btnBackShopSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_checkoutSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delivery_price('{:.2f}'.format(settings.get_delivery_price())), callback_data="admin_changeDeliveryPrice"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_delivery if settings.is_delivery_enabled() else tt.enable_delivery, callback_data="admin_changeEnableDelivery"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_phone_number if settings.is_phone_number_enabled() else tt.enable_phone_number, callback_data="admin_changeEnablePhoneNumber"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_captcha if settings.is_captcha_enabled() else tt.enable_captcha, callback_data="admin_changeEnableCaptcha"))
|
||||
markup.add(btnBackShopSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_statsSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.graph_color, callback_data="admin_statsSettingsColor"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.border_width, callback_data="admin_statsSettingsBorderWidth"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.title_font_size, callback_data="admin_statsSettingsTitleFontSize"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.axis_font_size, callback_data="admin_statsSettingsAxisFontSize"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.tick_font_size, callback_data="admin_statsSettingsTickFontSize"))
|
||||
markup.add(btnBackShopSettingsDel)
|
||||
return markup
|
||||
|
||||
def get_markup_statsSettingsColor():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text="⬛️", callback_data="admin_statsSettingsColorBlack"), types.InlineKeyboardButton(text="⬜️", callback_data="admin_statsSettingsColorWhite"), types.InlineKeyboardButton(text="🟥", callback_data="admin_statsSettingsColorRed"))
|
||||
markup.add(types.InlineKeyboardButton(text="🟨", callback_data="admin_statsSettingsColorYellow"), types.InlineKeyboardButton(text="🟪", callback_data="admin_statsSettingsColorPurple"), types.InlineKeyboardButton(text="🟦", callback_data="admin_statsSettingsColorBlue"))
|
||||
markup.add(types.InlineKeyboardButton(text="🟧", callback_data="admin_statsSettingsColorOrange"), types.InlineKeyboardButton(text="🟩", callback_data="admin_statsSettingsColorGreen"), types.InlineKeyboardButton(text="🟫", callback_data="admin_statsSettingsColorBrown"))
|
||||
markup.add(btnBackStatsSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_statsSettingsBorderWidth():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.unavailable if settings.get_borderwidth() == "0" else tt.minus, callback_data="None" if settings.get_borderwidth() == "0" else "admin_statsSettingsBorderWidthReduce"), types.InlineKeyboardButton(text=settings.get_borderwidth(), callback_data="admin_statsSettingsBorderWidthDefault"), types.InlineKeyboardButton(text=tt.plus, callback_data="admin_statsSettingsBorderWidthAdd"))
|
||||
markup.add(btnBackStatsSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_statsSettingsTitleFontSize():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.unavailable if settings.get_titlefontsize() == "2" else tt.minus, callback_data="None" if settings.get_titlefontsize() == "2" else "admin_statsSettingsTitleFontSizeReduce"), types.InlineKeyboardButton(text=settings.get_titlefontsize(), callback_data="admin_statsSettingsTitleFontSizeDefault"), types.InlineKeyboardButton(text=tt.plus, callback_data="admin_statsSettingsTitleFontSizeAdd"))
|
||||
markup.add(btnBackStatsSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_statsSettingsAxisFontSize():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.unavailable if settings.get_axisfontsize() == "2" else tt.minus, callback_data="None" if settings.get_axisfontsize() == "2" else "admin_statsSettingsAxisFontSizeReduce"), types.InlineKeyboardButton(text=settings.get_axisfontsize(), callback_data="admin_statsSettingsAxisFontSizeDefault"), types.InlineKeyboardButton(text=tt.plus, callback_data="admin_statsSettingsAxisFontSizeAdd"))
|
||||
markup.add(btnBackStatsSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_statsSettingsTickFontSize():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.unavailable if settings.get_tickfontsize() == "2" else tt.minus, callback_data="None" if settings.get_tickfontsize() == "2" else "admin_statsSettingsTickFontSizeReduce"), types.InlineKeyboardButton(text=settings.get_tickfontsize(), callback_data="admin_statsSettingsTickFontSizeDefault"), types.InlineKeyboardButton(text=tt.plus, callback_data="admin_statsSettingsTickFontSizeAdd"))
|
||||
markup.add(btnBackStatsSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_systemSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.clean_images, callback_data="admin_cleanImagesMenu"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.reset_settings, callback_data="admin_resetSettingsMenu"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.clean_database, callback_data="admin_cleanDatabaseMenu"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.backups, callback_data="admin_backups"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_debug if settings.is_debug() else tt.enable_debug, callback_data="admin_changeEnableDebug"))
|
||||
markup.add(btnBackAdditionalSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_backups():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.update_backup, callback_data="admin_updateBackup"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.load_backup, callback_data="admin_loadBackupMenu"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.clean_backups, callback_data="admin_cleanBackupsMenu"))
|
||||
markup.add(btnBackSystemSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_loadBackupMenu():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
backups = listdir("backups")
|
||||
backups.sort(key=lambda b: datetime.strptime(b, "%d-%m-%Y"))
|
||||
for backup in backups[:90]:
|
||||
markup.add(types.InlineKeyboardButton(text=backup, callback_data=f"admin_loadBackup{backup}"))
|
||||
markup.add(btnBackBackups)
|
||||
return markup
|
||||
|
||||
def get_markup_cleanBackupsMenu():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for days in ["7", "30", "90"]:
|
||||
markup.add(types.InlineKeyboardButton(text=f"{days} дней", callback_data=f"admin_cleanBackups{days}"))
|
||||
markup.add(types.InlineKeyboardButton(text="Удалить все резервные копии", callback_data="admin_cleanBackupsAll"))
|
||||
markup.add(btnBackBackups)
|
||||
return markup
|
||||
|
||||
def get_markup_cleanImagesMenu():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delete, callback_data="admin_cleanImages"))
|
||||
markup.add(btnBackSystemSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_resetSettingsMenu():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.reset, callback_data="admin_resetSettings"))
|
||||
markup.add(btnBackSystemSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_cleanDatabaseMenu():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delete, callback_data="admin_cleanDatabase"))
|
||||
markup.add(btnBackSystemSettings)
|
||||
return markup
|
||||
|
||||
# Manager tab
|
||||
def get_markup_seeOrder(order, user_id=None):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.processing), callback_data=f"manager_changeOrderStatusProcessing{order.get_order_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.delivery), callback_data=f"manager_changeOrderStatusDelivery{order.get_order_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.done), callback_data=f"manager_changeOrderStatusDone{order.get_order_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.cancelled), callback_data=f"manager_changeOrderStatusCancel{order.get_order_id()}"))
|
||||
markup.add(btnBackSeeUserOrders(user_id) if user_id else btnBackOrders)
|
||||
return markup
|
||||
|
||||
def get_markup_orders():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.processing, callback_data="manager_ordersProcessing"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delivery, callback_data="manager_ordersDelivery"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.done, callback_data="manager_ordersDone"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.cancelled, callback_data="manager_ordersCancelled"))
|
||||
return markup
|
||||
|
||||
def get_markup_ordersByOrderList(order_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for order in order_list:
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{order.get_order_id()}]", callback_data=f"manager_seeOrder{order.get_order_id()}"))
|
||||
markup.add(btnBackOrders)
|
||||
return markup
|
||||
|
||||
def get_markup_additionalSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.custom_commands, callback_data="admin_customCommands"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.system_settings, callback_data="admin_systemSettings"))
|
||||
markup.add(btnBackShopSettings)
|
||||
return markup
|
||||
|
||||
# Custom commands
|
||||
def get_markup_customCommands():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for command in commands.get_commands():
|
||||
markup.add(types.InlineKeyboardButton(text=command.get_command(), callback_data="None"), types.InlineKeyboardButton(text=tt.delete, callback_data=f"admin_deleteCommand{command.get_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.line_separator, callback_data="None"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.add_command, callback_data="admin_addCommand"))
|
||||
markup.add(btnBackAdditionalSettings)
|
||||
return markup
|
||||
108
doners/shop-telegram-bot/src/order.py
Normal file
@@ -0,0 +1,108 @@
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
import item as itm
|
||||
import text_templates as tt
|
||||
from settings import Settings
|
||||
|
||||
settings = Settings()
|
||||
|
||||
conn = sqlite3.connect("data.db")
|
||||
c = conn.cursor()
|
||||
|
||||
class Order:
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.get_order_id()}"
|
||||
|
||||
def get_order_id(self):
|
||||
return self.id
|
||||
|
||||
def __clist(self):
|
||||
c.execute(f"SELECT * FROM orders WHERE order_id=?", [self.get_order_id()])
|
||||
return list(c)[0]
|
||||
|
||||
def get_user_id(self):
|
||||
return self.__clist()[1]
|
||||
|
||||
def get_item_list_raw(self):
|
||||
return self.__clist()[2]
|
||||
|
||||
def get_item_list(self):
|
||||
return list(map(itm.Item, [item_id for item_id in self.get_item_list_raw().split(",")]))
|
||||
|
||||
def get_item_list_amount(self):
|
||||
cart = [item.get_id() for item in self.get_item_list()]
|
||||
return [[itm.Item(item_id), cart.count(item_id)] for item_id in set(cart)]
|
||||
|
||||
def get_item_list_price(self):
|
||||
return sum([item_and_price[0].get_price() * item_and_price[1] for item_and_price in self.get_item_list_amount()]) + (float(settings.get_delivery_price()) if self.get_home_adress() != None else 0)
|
||||
|
||||
def set_item_list(self, value):
|
||||
c.execute(f"UPDATE orders SET item_list=? WHERE order_id=?", [value, self.get_order_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_email_adress(self):
|
||||
return self.__clist()[3]
|
||||
|
||||
def set_email_adress(self, value):
|
||||
c.execute(f"UPDATE orders SET email_adress=? WHERE order_id=?", [value, self.get_order_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_phone_number(self):
|
||||
return self.__clist()[4]
|
||||
|
||||
def set_phone_number(self, value):
|
||||
c.execute(f"UPDATE orders SET phone_number=? WHERE order_id=?", [value, self.get_order_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_home_adress(self):
|
||||
return self.__clist()[5]
|
||||
|
||||
def set_home_adress(self, value):
|
||||
c.execute(f"UPDATE orders SET home_adress=? WHERE order_id=?", [value, self.get_order_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_additional_message(self):
|
||||
return self.__clist()[6]
|
||||
|
||||
def get_date(self):
|
||||
return datetime.strptime(self.__clist()[7], "%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def get_date_string(self):
|
||||
return self.__clist()[7]
|
||||
|
||||
def get_status(self):
|
||||
return self.__clist()[8]
|
||||
|
||||
def get_status_string(self):
|
||||
return get_status_dict()[self.__clist()[8]]
|
||||
|
||||
def set_status(self, value):
|
||||
c.execute(f"UPDATE orders SET status=? WHERE order_id=?", [value, self.get_order_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_status_dict():
|
||||
return {
|
||||
0: tt.processing,
|
||||
1: tt.delivery,
|
||||
2: tt.done,
|
||||
-1: tt.cancelled,
|
||||
}
|
||||
|
||||
def get_order_list(status=None):
|
||||
if status:
|
||||
c.execute(f"SELECT * FROM orders WHERE status=?", [status])
|
||||
else:
|
||||
c.execute(f"SELECT * FROM orders")
|
||||
return list(map(Order, [order[0] for order in list(c)]))
|
||||
|
||||
def does_order_exist(order_id):
|
||||
c.execute(f"SELECT * FROM orders WHERE order_id=?", [order_id])
|
||||
return len(list(c)) == 1
|
||||
|
||||
def create_order(order_id, user_id, item_list, email_adress, additional_message, phone_number="None", home_adress="None"):
|
||||
c.execute(f"INSERT INTO orders VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", [order_id, user_id, item_list, email_adress, phone_number, home_adress, additional_message, datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 0])
|
||||
conn.commit()
|
||||
return Order(order_id)
|
||||
45
doners/shop-telegram-bot/src/search.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import pymorphy2
|
||||
from pyparsing import opAssoc
|
||||
import item as itm
|
||||
|
||||
m = pymorphy2.MorphAnalyzer()
|
||||
|
||||
excluded_words = ["без", "в", "для", "до", "за", "из", "к", "под", "а", "о", "над", "на", "о", "об", "от", "перед", "по", "под", "при", "про", "с", "у"]
|
||||
|
||||
def get_normal_forms(word):
|
||||
return set(p.normal_form for p in m.parse(word))
|
||||
|
||||
|
||||
class Query:
|
||||
def __init__(self, results):
|
||||
self.results = results
|
||||
|
||||
def __get_items(self):
|
||||
return list(map(itm.Item, self.results.keys()))[::-1][:90]
|
||||
|
||||
def match(self):
|
||||
return self.__get_items()
|
||||
|
||||
def price(self):
|
||||
return sorted(list(map(itm.Item, self.results.keys()))[::-1], key=lambda item: item.get_price())
|
||||
|
||||
def popular(self):
|
||||
pass
|
||||
|
||||
# a match in a name is 3 points
|
||||
# a match in a desc is 1 point
|
||||
def search_item(query):
|
||||
points = dict()
|
||||
for item in itm.get_item_list():
|
||||
points[item.get_id()] = 0
|
||||
for word in list(filter(lambda word: word not in excluded_words, query.split())):
|
||||
for p in get_normal_forms(word):
|
||||
for word_title in item.get_name().split():
|
||||
if p in get_normal_forms(word_title):
|
||||
points[item.get_id()] += 3
|
||||
for word_title in item.get_desc().split():
|
||||
if p in get_normal_forms(word_title):
|
||||
points[item.get_id()] += 1
|
||||
if points[item.get_id()] == 0:
|
||||
points.pop(item.get_id())
|
||||
return Query(dict(sorted(points.items(), key=lambda item: item[1])))
|
||||
170
doners/shop-telegram-bot/src/settings.py
Normal file
@@ -0,0 +1,170 @@
|
||||
from configparser import ConfigParser
|
||||
from genericpath import exists
|
||||
from os import remove
|
||||
import sqlite3
|
||||
|
||||
class Settings:
|
||||
def __init__(self, config_path="config.ini"):
|
||||
self.__config_path = config_path
|
||||
|
||||
def __get_config(self):
|
||||
conf = ConfigParser()
|
||||
conf.read(self.__config_path)
|
||||
return conf
|
||||
|
||||
def __set_setting(self, category, subcategory, value):
|
||||
conf = self.__get_config()
|
||||
conf.set(category, subcategory, str(value))
|
||||
with open(self.__config_path, 'w') as config:
|
||||
conf.write(config)
|
||||
|
||||
# main_settings
|
||||
def get_token(self):
|
||||
return self.__get_config()["main_settings"]["token"]
|
||||
|
||||
def set_token(self, value):
|
||||
self.__set_setting("main_settings", "token", value)
|
||||
|
||||
def is_debug(self):
|
||||
return self.__get_config()["main_settings"]["debug"] == "1"
|
||||
|
||||
def set_debug(self, value):
|
||||
self.__set_setting("main_settings", "debug", value)
|
||||
|
||||
def get_main_admin_id(self):
|
||||
return self.__get_config()["main_settings"]["mainadminid"]
|
||||
|
||||
def set_main_admin_id(self, value):
|
||||
self.__set_setting("main_settings", "mainadminid", value)
|
||||
|
||||
# shop_settings
|
||||
def get_shop_name(self):
|
||||
return self.__get_config()["shop_settings"]["name"]
|
||||
|
||||
def set_shop_name(self, value):
|
||||
self.__set_setting("shop_settings", "name", value)
|
||||
|
||||
def get_shop_greeting(self):
|
||||
return self.__get_config()["shop_settings"]["greeting"]
|
||||
|
||||
def set_shop_greeting(self, value):
|
||||
self.__set_setting("shop_settings", "greeting", value)
|
||||
|
||||
def is_sticker_enabled(self):
|
||||
return self.__get_config()["shop_settings"]["enablesticker"] == "1"
|
||||
|
||||
def set_enable_sticker(self, value):
|
||||
self.__set_setting("shop_settings", "enablesticker", value)
|
||||
|
||||
def get_refund_policy(self):
|
||||
return self.__get_config()["shop_settings"]["refundpolicy"]
|
||||
|
||||
def set_refund_policy(self, value):
|
||||
self.__set_setting("shop_settings", "refundpolicy", value)
|
||||
|
||||
def get_shop_contacts(self):
|
||||
return self.__get_config()["shop_settings"]["contacts"]
|
||||
|
||||
def set_shop_contacts(self, value):
|
||||
self.__set_setting("shop_settings", "contacts", value)
|
||||
|
||||
def is_item_image_enabled(self):
|
||||
return self.__get_config()["shop_settings"]["enableimage"] == "1"
|
||||
|
||||
def set_item_image(self, value):
|
||||
self.__set_setting("shop_settings", "enableimage", value)
|
||||
|
||||
def is_phone_number_enabled(self):
|
||||
return self.__get_config()["shop_settings"]["enablephonenumber"] == "1"
|
||||
|
||||
def set_enable_phone_number(self, value):
|
||||
self.__set_setting("shop_settings", "enablephonenumber", value)
|
||||
|
||||
def is_delivery_enabled(self):
|
||||
return self.__get_config()["shop_settings"]["enabledelivery"] == "1"
|
||||
|
||||
def set_delivery(self, value):
|
||||
self.__set_setting("shop_settings", "enabledelivery", value)
|
||||
|
||||
def get_delivery_price(self):
|
||||
return float(self.__get_config()["shop_settings"]["delivery_price"])
|
||||
|
||||
def set_delivery_price(self, value):
|
||||
self.__set_setting("shop_settings", "delivery_price", value)
|
||||
|
||||
def is_captcha_enabled(self):
|
||||
return self.__get_config()["shop_settings"]["enablecaptcha"] == "1"
|
||||
|
||||
def set_enable_captcha(self, value):
|
||||
self.__set_setting("shop_settings", "enablecaptcha", value)
|
||||
|
||||
# stats_settings
|
||||
def get_barcolor(self):
|
||||
return self.__get_config()["stats_settings"]["barcolor"]
|
||||
|
||||
def set_barcolor(self, value):
|
||||
self.__set_setting("stats_settings", "barcolor", value)
|
||||
|
||||
def get_borderwidth(self):
|
||||
return self.__get_config()["stats_settings"]["borderwidth"]
|
||||
|
||||
def set_borderwidth(self, value):
|
||||
self.__set_setting("stats_settings", "borderwidth", value)
|
||||
|
||||
def get_titlefontsize(self):
|
||||
return self.__get_config()["stats_settings"]["titlefontsize"]
|
||||
|
||||
def set_titlefontsize(self, value):
|
||||
self.__set_setting("stats_settings", "titlefontsize", value)
|
||||
|
||||
def get_axisfontsize(self):
|
||||
return self.__get_config()["stats_settings"]["axisfontsize"]
|
||||
|
||||
def set_axisfontsize(self, value):
|
||||
self.__set_setting("stats_settings", "axisfontsize", value)
|
||||
|
||||
def get_tickfontsize(self):
|
||||
return self.__get_config()["stats_settings"]["tickfontsize"]
|
||||
|
||||
def set_tickfontsize(self, value):
|
||||
self.__set_setting("stats_settings", "tickfontsize", value)
|
||||
|
||||
def reset(self):
|
||||
DEFAULT_CONFIG_TEXT = f"""[main_settings]
|
||||
token = {self.get_token()}
|
||||
mainadminid = {self.get_main_admin_id()}
|
||||
debug = 0
|
||||
|
||||
[shop_settings]
|
||||
name = Название магазина
|
||||
greeting = Добро пожаловать!
|
||||
refundpolicy = Текст для вкладки "Политика возврата"
|
||||
contacts = Текст для вкладки "Контакты"
|
||||
enableimage = 1
|
||||
enablesticker = 0
|
||||
enablephonenumber = 0
|
||||
enabledelivery = 0
|
||||
delivery_price = 0.0
|
||||
enablecaptcha = 1
|
||||
|
||||
[stats_settings]
|
||||
barcolor = 3299ff
|
||||
borderwidth = 1
|
||||
titlefontsize = 20
|
||||
axisfontsize = 12
|
||||
tickfontsize = 8
|
||||
"""
|
||||
if exists("config.ini"):
|
||||
remove("config.ini")
|
||||
with open("config.ini", "w") as config:
|
||||
config.write(DEFAULT_CONFIG_TEXT)
|
||||
|
||||
def clean_db(self):
|
||||
conn = sqlite3.connect("data.db")
|
||||
c = conn.cursor()
|
||||
c.execute("DELETE FROM cats")
|
||||
c.execute("DELETE FROM items")
|
||||
c.execute("DELETE FROM orders")
|
||||
c.execute("DELETE FROM commands")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
111
doners/shop-telegram-bot/src/state_handler.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from urllib import response
|
||||
from aiogram.dispatcher.filters.state import StatesGroup, State
|
||||
|
||||
# Item management
|
||||
class addCat(StatesGroup):
|
||||
state_message = State()
|
||||
name = State()
|
||||
|
||||
class changeCatName(StatesGroup):
|
||||
state_message = State()
|
||||
cat_id = State()
|
||||
name = State()
|
||||
|
||||
class addItem(StatesGroup):
|
||||
# Required
|
||||
name = State()
|
||||
price = State()
|
||||
cat_id = State()
|
||||
desc = State()
|
||||
confirmation = State()
|
||||
|
||||
# Additional
|
||||
image = State()
|
||||
|
||||
class changeItemPrice(StatesGroup):
|
||||
state_message = State()
|
||||
item_id = State()
|
||||
price = State()
|
||||
|
||||
class changeItemImage(StatesGroup):
|
||||
state_message = State()
|
||||
item_id = State()
|
||||
image = State()
|
||||
|
||||
class changeItemDesc(StatesGroup):
|
||||
state_message = State()
|
||||
item_id = State()
|
||||
desc = State()
|
||||
|
||||
class changeItemName(StatesGroup):
|
||||
state_message = State()
|
||||
item_id = State()
|
||||
name = State()
|
||||
|
||||
class changeItemCat(StatesGroup):
|
||||
item_id = State()
|
||||
cat = State()
|
||||
|
||||
class changeItemStock(StatesGroup):
|
||||
item_id = State()
|
||||
state_message = State()
|
||||
stock = State()
|
||||
|
||||
# User management
|
||||
class notifyEveryone(StatesGroup):
|
||||
state_message = State()
|
||||
message = State()
|
||||
confirmation = State()
|
||||
|
||||
class seeUserProfile(StatesGroup):
|
||||
state_message = State()
|
||||
user_id = State()
|
||||
|
||||
|
||||
# Checkout
|
||||
class checkoutCart(StatesGroup):
|
||||
# Data
|
||||
user_id = State()
|
||||
item_list_comma = State()
|
||||
order_id = State()
|
||||
|
||||
# Required
|
||||
email = State()
|
||||
additional_message = State()
|
||||
confirmation = State()
|
||||
|
||||
# Additional
|
||||
phone_number = State()
|
||||
home_adress = State()
|
||||
captcha = State()
|
||||
|
||||
# Main settings
|
||||
class changeShopName(StatesGroup):
|
||||
state_message = State()
|
||||
name = State()
|
||||
|
||||
class changeShopGreeting(StatesGroup):
|
||||
state_message = State()
|
||||
greeting = State()
|
||||
|
||||
class changeShopRefundPolicy(StatesGroup):
|
||||
state_message = State()
|
||||
refund_policy = State()
|
||||
|
||||
class changeShopContacts(StatesGroup):
|
||||
state_message = State()
|
||||
contacts = State()
|
||||
|
||||
class changeDeliveryPrice(StatesGroup):
|
||||
state_message = State()
|
||||
price = State()
|
||||
|
||||
class addCustomCommand(StatesGroup):
|
||||
command = State()
|
||||
response = State()
|
||||
|
||||
|
||||
# Misc
|
||||
class search(StatesGroup):
|
||||
state_message = State()
|
||||
query = State()
|
||||
63
doners/shop-telegram-bot/src/stats.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import datetime
|
||||
import matplotlib.pyplot as plt
|
||||
import order as ordr
|
||||
import user as usr
|
||||
from random import randint
|
||||
from settings import Settings
|
||||
|
||||
settings = Settings()
|
||||
|
||||
def saveplot(data, title, ylabel):
|
||||
plt.autoscale()
|
||||
plt.figure(figsize=(10, 10))
|
||||
plt.title(title, fontsize=settings.get_titlefontsize())
|
||||
plt.xlabel("Дата", fontsize=settings.get_axisfontsize())
|
||||
plt.ylabel(ylabel, fontsize=settings.get_axisfontsize())
|
||||
plt.tick_params(labelsize=settings.get_tickfontsize())
|
||||
plt.bar(range(len(data)), list(data.values()), color="#" + settings.get_barcolor(), edgecolor="black", linewidth=settings.get_borderwidth())
|
||||
plt.xticks(range(len(data)), list(data.keys()), rotation=90)
|
||||
plt.savefig(f"images/stats.png")
|
||||
plt.close()
|
||||
return open(f"images/stats.png", "rb")
|
||||
|
||||
|
||||
def get_random_data():
|
||||
return {f"{randint(1, 30):02}.{randint(1, 12):02}.{randint(2010, 2030)}": randint(5, 100) for _ in range(randint(2, 30))}
|
||||
|
||||
|
||||
def get_random_graph():
|
||||
return saveplot(get_random_data(), "Название", "Ось Y")
|
||||
|
||||
|
||||
class RegistrationCharts:
|
||||
def __init__(self):
|
||||
self.user_list = usr.get_user_list()
|
||||
|
||||
def saveplot(self, data, title):
|
||||
return saveplot(data, title, "Количество регистраций")
|
||||
|
||||
def all_time(self):
|
||||
return self.saveplot({f"{date.day:02}.{date.month:02}.{date.year}": [user.get_register_date().date() for user in self.user_list].count(date) for date in dict.fromkeys([user.get_register_date().date() for user in self.user_list])}, "Регистрации за все время")
|
||||
|
||||
def last_x_days(self, days):
|
||||
return self.saveplot({f"{(datetime.date.today() - datetime.timedelta(days=i)).day:02}.{(datetime.date.today() - datetime.timedelta(days=i)).month:02}": len(list(filter(lambda user: user.get_register_date().date() == datetime.date.today() - datetime.timedelta(days=i), self.user_list))) for i in range(30, -1, -1)}, f"Регистрации за последние {days} дней")
|
||||
|
||||
def last_x_hours(self, hours):
|
||||
return self.saveplot({f"{(datetime.datetime.today() - datetime.timedelta(hours=i)).hour:02}:00": len(list(filter(lambda user: user.get_register_date().hour == (datetime.datetime.now() - datetime.timedelta(hours=i)).hour and user.get_register_date() > datetime.datetime.now() - datetime.timedelta(hours=hours), self.user_list))) for i in range(hours, -1, -1)}, f"Регистрации за последние {hours} часов.")
|
||||
|
||||
|
||||
class OrderCharts:
|
||||
def __init__(self):
|
||||
self.order_list = ordr.get_order_list()
|
||||
|
||||
def saveplot(self, data, title):
|
||||
return saveplot(data, title, "Количество заказов")
|
||||
|
||||
def all_time(self):
|
||||
return self.saveplot({f"{date.day:02}.{date.month:02}.{date.year}": [order.get_date().date() for order in self.order_list].count(date) for date in dict.fromkeys([order.get_date().date() for order in self.order_list])}, "Заказы за все время")
|
||||
|
||||
def last_x_days(self, days):
|
||||
return self.saveplot({f"{(datetime.date.today() - datetime.timedelta(days=i)).day:02}.{(datetime.date.today() - datetime.timedelta(days=i)).month:02}": len(list(filter(lambda order: order.get_date().date() == datetime.date.today() - datetime.timedelta(days=i), self.order_list))) for i in range(days, -1, -1)}, f"Заказы за последние {days} дней")
|
||||
|
||||
def last_x_hours(self, hours):
|
||||
return self.saveplot({f"{(datetime.datetime.today() - datetime.timedelta(hours=i)).hour:02}:00": len(list(filter(lambda order: order.get_date() > datetime.datetime.now() - datetime.timedelta(hours=hours) and order.get_date().hour == (datetime.datetime.today() - datetime.timedelta(hours=i)).hour, self.order_list))) for i in range(hours, -1, -1)}, "Заказы за сегодня" if hours == (datetime.datetime.now().hour + 1) else f"Заказы за последние {hours} часов.")
|
||||
177
doners/shop-telegram-bot/src/text_templates.py
Normal file
@@ -0,0 +1,177 @@
|
||||
from settings import Settings
|
||||
|
||||
settings = Settings()
|
||||
|
||||
line_separator = "➖➖➖➖➖"
|
||||
|
||||
|
||||
# Multiple lines
|
||||
def get_profile_template(user):
|
||||
return f"{line_separator}\n📝 id: {user.get_id()}\n📈 Кол-во заказов: {len(user.get_orders())}\n📅 Дата регистрации: {user.get_register_date_string()}\n{line_separator}"
|
||||
|
||||
def get_faq_template(shop_name):
|
||||
return f"{line_separator}\nℹ️ FAQ магазина {shop_name}\n{line_separator}"
|
||||
|
||||
def get_categories_template():
|
||||
return f"{line_separator}\n🛍️ Категории\n{line_separator}"
|
||||
|
||||
def get_category_was_created_successfuly(cat_name):
|
||||
return f"Категория {cat_name} была успешно создана."
|
||||
|
||||
def get_category_data(cat):
|
||||
return f"{line_separator}\nID: {cat.get_id()}\nНазвание: {cat.get_name()}\n{line_separator}"
|
||||
|
||||
def get_item_card(item=None, name=None, price=None, desc=None, amount=None):
|
||||
if item:
|
||||
name = item.get_name()
|
||||
price = item.get_price()
|
||||
desc = item.get_desc()
|
||||
amount = item.get_amount()
|
||||
|
||||
return f"{line_separator}\n{name} - {'{:.2f}'.format(price)} руб.\nВ наличии: {amount} шт.\n{line_separator}\n{desc}"
|
||||
|
||||
def get_order_confirmation_template(item_amount_dict, cart_price, email_adress, additional_message, phone_number=None, home_adress=None):
|
||||
item_amount_dict_formatted = '\n'.join([f'\t· {item[0].get_name()} - {item[1]} шт.' for item in item_amount_dict])
|
||||
phone_number = f"Номер телефона: {phone_number}\n" if phone_number else ""
|
||||
home_adress = f"Адрес доставки: {home_adress}\n" if home_adress else ""
|
||||
return f"{line_separator}\nТовары:\n{item_amount_dict_formatted}\nСумма: {cart_price}руб.\nEmail: {email_adress}\n{phone_number}{home_adress}Комментарий к заказу: {additional_message}\n{line_separator}\nВы уверены, что хотите оформить заказ?"
|
||||
|
||||
def get_order_template(order):
|
||||
item_list_amount_formatted = '\n'.join([f'\t· {item[0].get_name()} - {item[1]} шт.' for item in order.get_item_list_amount()])
|
||||
phone_number = f"Номер телефона: {order.get_phone_number()}\n" if settings.is_phone_number_enabled() else ""
|
||||
home_adress = f"Адрес доставки: {order.get_home_adress()}\n" if settings.is_delivery_enabled() else f"Самовывоз\n"
|
||||
return f"{line_separator}\nID заказа: {order.get_order_id()}\nID пользователя: {order.get_user_id()}\nТовары:\n{item_list_amount_formatted}\nСумма: {order.get_item_list_price()}руб.\nEmail: {order.get_email_adress()}\n{phone_number}{home_adress}Комментарий к заказу: {order.get_additional_message()}\nСтатус заказа: {order.get_status_string()}\nДата: {order.get_date_string()}\n{line_separator}"
|
||||
|
||||
# Single phrases
|
||||
# /start
|
||||
admin_panel = "🔴 Админ панель"
|
||||
faq = "ℹ️ FAQ"
|
||||
profile = "📁 Профиль"
|
||||
catalogue = "🗄️ Каталог"
|
||||
cart = "🛒 Корзина"
|
||||
support_menu = "☎ Меню тех. поддержки"
|
||||
|
||||
# Admin panel tabs
|
||||
item_management = "📦 Управление товаром"
|
||||
user_management = "🧍 Управление пользователями"
|
||||
shop_stats = "📈 Статистика магазина (BETA)"
|
||||
bot_settings = "⚙ Настройки бота"
|
||||
|
||||
# FAQ
|
||||
contacts = "📞 Контакты"
|
||||
refund = "🎫 Политика возврата"
|
||||
|
||||
# Profile
|
||||
my_orders = "📂 Мои заказы"
|
||||
cancel_order = "❌ Отменить заказ"
|
||||
restore_order = "✅ Восстановить заказ"
|
||||
my_support_tickets = "🙋 Мои тикеты в тех. поддержку"
|
||||
enable_notif = "🔔Включить оповещения о заказах"
|
||||
disable_notif = "🔕Выключить оповещения о заказах"
|
||||
|
||||
# Catalogue / Item / Cart
|
||||
search = "🔍 Найти"
|
||||
add_to_cart = "🛒 Добавить в корзину"
|
||||
cart_is_empty = "Корзина пуста."
|
||||
pickup = "✅Самовывоз"
|
||||
def delivery_on(price): return f"✅ Доставка - {price}руб."
|
||||
def delivery_off(price): return f"❌ Доставка - {price}руб."
|
||||
cart_checkout = "Оформить заказ"
|
||||
clear_cart = "Очистить корзину"
|
||||
processing = "Обрабатывается"
|
||||
delivery = "Ожидает доставки"
|
||||
done = "Готов"
|
||||
cancelled = "Отменён"
|
||||
|
||||
# Item management
|
||||
add_cat = "🛍️ Добавить категорию"
|
||||
add_item = "🗃️ Добавить товар"
|
||||
edit_cat = "✏️ Редактировать категорию"
|
||||
edit_item = "✏️ Редактировать товар"
|
||||
change_name = "📋 Изменить название"
|
||||
change_image = "🖼️ Изменить изображение"
|
||||
hide_image = "🙈 Скрыть изображение"
|
||||
show_image = "🐵 Показать изображение"
|
||||
change_desc = "📝 Изменить описание"
|
||||
change_price = "🏷️ Изменить цену"
|
||||
change_item_cat = "🛍️ Изменить категорию"
|
||||
change_stock = "📦 Изменить кол-во"
|
||||
|
||||
# User management
|
||||
user_profile = "📁Профиль пользователя"
|
||||
notify_everyone = "🔔Оповещение всем пользователям"
|
||||
orders = "📁 Заказы"
|
||||
remove_manager_role = "👨💼 Убрать роль менеджера"
|
||||
add_manager_role = "👨💼 Сделать менеджером"
|
||||
remove_admin_role = "🔴 Убрать роль администратора"
|
||||
add_admin_role = "🔴 Сделать администратором"
|
||||
def change_order_status(status): return f"Изменить статус на \"{status}\""
|
||||
|
||||
# Shop stats
|
||||
registration_stats = "👥Статистика регистраций"
|
||||
order_stats = "📦Статистика заказов"
|
||||
all_time = "За всё время"
|
||||
monthly = "За последние 30 дней"
|
||||
weekly = "За последние 7 дней"
|
||||
daily = "За последние 24 часа"
|
||||
|
||||
# Shop settings
|
||||
main_settings = "🛠️ Основные настройки"
|
||||
item_settings = "🗃️ Настройки товаров"
|
||||
additional_settings = "📖 Дополнительные настройки"
|
||||
custom_commands = "📖 Команды"
|
||||
add_command = "📝 Добавить команду"
|
||||
clean_logs = "📖 Очистить логи"
|
||||
clean_logs_text = "⚠️ Вы уверены, что хотите очистить логи? Они будут удалены без возможности восстановления!\n(Логи за сегодняшний день не будут удалены)"
|
||||
backups = "💾 Резервное копирование"
|
||||
update_backup = "🔄 Обновить резервную копию"
|
||||
load_backup = "💿 Загрузить резервную копию"
|
||||
clean_backups = "🧹 Очистка резервных копий"
|
||||
system_settings = "💻 Система"
|
||||
clean_images = "🗑️ Удалить неиспользуемые изображения"
|
||||
clean_images_text = "⚠️ Вы уверены, что хотите удалить неспользуемые изображения? Они будут удалены без возможности восстановления!"
|
||||
clean_database = "📚 Очистить базу данных"
|
||||
clean_database_text = "⚠️ Вы уверены, что хотите очистить базу данных? Все данные будут удалены без возможности восстановления!"
|
||||
reset_settings = "⚙️ Сбросить настройки"
|
||||
resert_settings_text = "⚠️ Вы уверены, что хотите сбросить настройки? Все данные будут удалены без возможности восстановления!"
|
||||
disable_item_image = "✅ Картинки товаров"
|
||||
enable_item_image = "❌ Картинки товаров"
|
||||
checkout_settings = "💳 Настройки оформления заказа"
|
||||
stats_settings = "📈 Настройки статистики"
|
||||
graph_color = "🌈 Цвет графика"
|
||||
border_width = "🔲 Ширина обводки"
|
||||
title_font_size = "ℹ️ Размер названия графика"
|
||||
axis_font_size = "↔️Размер текста для осей"
|
||||
tick_font_size = "🔢Размер текста для делений"
|
||||
unavailable = "⛔️"
|
||||
minus = "➖"
|
||||
plus = "➕"
|
||||
enable_sticker = "❌ Стикер в приветствии"
|
||||
disable_sticker = "✅ Стикер в приветствии"
|
||||
enable_phone_number = "❌ Номер телефона при заказе"
|
||||
disable_phone_number = "✅ Номер телефона при заказе"
|
||||
enable_delivery = "❌ Доставка"
|
||||
disable_delivery = "✅ Доставка"
|
||||
def delivery_price(price): return f"🚚 Стоимость доставки: {price}руб."
|
||||
enable_captcha = "❌ CAPTCHA при заказе"
|
||||
disable_captcha = "✅ CAPTCHA при заказе"
|
||||
enable_debug = "❌ Режим отладки"
|
||||
disable_debug = "✅ Режим отладки"
|
||||
|
||||
# Manager tab
|
||||
view_order = "📂 Посмотреть заказ"
|
||||
|
||||
# Misc buttons
|
||||
skip = "⏭ Пропустить"
|
||||
back = "🔙 Назад"
|
||||
confirm = "✅ Да"
|
||||
deny = "❌ Нет"
|
||||
error = "Произошла ошибка!"
|
||||
or_press_back = "или нажмите на кнопку \"Назад\"."
|
||||
hide = "🙈 Скрыть"
|
||||
show = "🐵 Показать"
|
||||
delete = "❌ Удалить"
|
||||
reset = "❌ Сбросить"
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(delivery_on)
|
||||