This commit is contained in:
2025-06-08 20:55:08 +09:00
parent f7e0d17829
commit 7a75f79413
139 changed files with 10619 additions and 2340 deletions

14
doners/Shop-bot/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
# Cache files
__pycache__/
# Project files
.idea/
*.log
.env
*.session
data/config.py
data/database.db
# vscode
.vscode/
.history/

View 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
View 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
View 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:
![User Menu](data/assets/4.png)
## Catalog
The catalog consists of products sorted by categories. Users can add items to their cart, and the admin has full control over catalog management (addition/removal).
## Cart
The ordering process looks like this: the user goes to the `🛍️ Catalog`, selects the desired category, chooses products, and clicks the `🛒 Cart` button.
![cart](data/assets/5.png)
------
Then, after making sure everything is in place, proceed to checkout by clicking `📦 Place Order`.
![checkout](data/assets/6.png)
## Add a Product
To add a product, select a category and click the ` Add Product` button. Then, fill out the "name-description-image-price" form and confirm.
![add_product](data/assets/1.png)
## Contacting Administration
To ask the admin a question, simply select the `/sos` command. There is a limit on the number of questions.
![sos](data/assets/7.png)

View File

92
doners/Shop-bot/app.py Normal file
View 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)

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 616 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 947 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 647 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View 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])

View 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

View 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

View File

@@ -0,0 +1,4 @@
from .admin import dp
from .user import dp
__all__ = ['dp']

View File

@@ -0,0 +1,5 @@
from .add import dp
from .questions import dp
from .orders import dp
__all__ = ['dp']

View 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)

View 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)

View 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()

View 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']

View 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()

View 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)

View 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)

View 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)

View 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()

View 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('Ваш кошелек пуст! Чтобы его пополнить нужно...')

View File

@@ -0,0 +1,2 @@
from . import inline
from . import default

View File

@@ -0,0 +1 @@
from . import markups

View 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

View File

@@ -0,0 +1,3 @@
from . import products_from_catalog
from . import products_from_cart
from . import categories

View 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

View 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

View 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
View 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')

View File

@@ -0,0 +1 @@
aiogram==2.9.2

View File

@@ -0,0 +1,3 @@
from .checkout_state import CheckoutState
from .product_state import ProductState, CategoryState
from .sos_state import SosState, AnswerState

View 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()

View 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()

View 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()

View File

@@ -0,0 +1 @@
from . import db

View File

@@ -0,0 +1 @@
from .storage import DatabaseManager

View 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
'''