Init commit
This commit is contained in:
commit
64c6aeb002
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Bot files
|
||||||
|
*.sqlite3
|
||||||
|
*.bak
|
||||||
|
*.env
|
||||||
|
!dist.env
|
||||||
|
venv/
|
||||||
|
test.py
|
||||||
|
departed
|
43
bot.py
Executable file
43
bot.py
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from aiogram import executor
|
||||||
|
|
||||||
|
from load import dp, bot
|
||||||
|
import config
|
||||||
|
import filters
|
||||||
|
import handlers
|
||||||
|
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO
|
||||||
|
)
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_startup(dp):
|
||||||
|
await bot.set_webhook(url=config.webhook_url)
|
||||||
|
|
||||||
|
|
||||||
|
async def on_shutdown(dp):
|
||||||
|
await bot.delete_webhook()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
if config.use_webhook:
|
||||||
|
executor.start_webhook(
|
||||||
|
dispatcher=dp,
|
||||||
|
webhook_path=config.webhook_path,
|
||||||
|
on_startup=on_startup,
|
||||||
|
on_shutdown=on_shutdown,
|
||||||
|
skip_updates=config.skip_updates,
|
||||||
|
host=config.webhook_app_host,
|
||||||
|
port=config.webhook_app_port,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
executor.start_polling(dp, skip_updates=config.skip_updates)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
25
config.py
Normal file
25
config.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from environs import Env
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CONFIG_FILE = '.env'
|
||||||
|
|
||||||
|
env = Env()
|
||||||
|
env.read_env(CONFIG_FILE)
|
||||||
|
|
||||||
|
token = env.str("token")
|
||||||
|
db_url = env.str("database_url", "sqlite:///database.sqlite3")
|
||||||
|
permission_file = env.str("permission_file", "permission.json")
|
||||||
|
|
||||||
|
use_webhook = env.bool("use_webhook", False)
|
||||||
|
skip_updates = env.bool("skip_update", True)
|
||||||
|
telegram_api_server = env.str("telegram_api_server", "https://api.telegram.org")
|
||||||
|
|
||||||
|
if use_webhook:
|
||||||
|
webhook_app_host = env.str("app_host")
|
||||||
|
webhook_app_port = env.int("app_port")
|
||||||
|
|
||||||
|
webhook_host = env.str("webhook_host")
|
||||||
|
|
||||||
|
webhook_path = f"/bot{token}"
|
||||||
|
webhook_url = f"{webhook_host}{webhook_path}"
|
16
dist.env
Normal file
16
dist.env
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Установите # в начало строки, чтобы использовать значение по умолчанию
|
||||||
|
# Строки которые имеют значение по умолчанию позначены # в конце строки
|
||||||
|
|
||||||
|
token = "123:adminadmin"
|
||||||
|
|
||||||
|
skip_update = False #
|
||||||
|
use_webhook = True #
|
||||||
|
#telegram_api_server = "" #
|
||||||
|
#permission_file = ""
|
||||||
|
|
||||||
|
app_host = "127.0.0.1"
|
||||||
|
app_port = 2001
|
||||||
|
webhook_host = "http://127.0.0.1:2001"
|
||||||
|
|
||||||
|
|
||||||
|
#database_url = "" #
|
6
filters/__init__.py
Normal file
6
filters/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from load import dp
|
||||||
|
|
||||||
|
from .admin import IsAdmin
|
||||||
|
|
||||||
|
|
||||||
|
dp.filters_factory.bind(IsAdmin)
|
14
filters/admin.py
Normal file
14
filters/admin.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from aiogram.types import Message
|
||||||
|
from aiogram.dispatcher.filters import BoundFilter
|
||||||
|
|
||||||
|
from utils.database.base import get_admins
|
||||||
|
|
||||||
|
|
||||||
|
class IsAdmin(BoundFilter):
|
||||||
|
key = 'is_admin'
|
||||||
|
|
||||||
|
def __init__(self, is_admin):
|
||||||
|
self.is_admin = is_admin
|
||||||
|
|
||||||
|
async def check(self, message: Message):
|
||||||
|
return message.from_user.id in get_admins()
|
3
handlers/__init__.py
Normal file
3
handlers/__init__.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from . import user
|
||||||
|
from . import admin
|
||||||
|
from . import callback
|
4
handlers/admin/__init__.py
Normal file
4
handlers/admin/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from . import panel
|
||||||
|
from . import add_post
|
||||||
|
from . import delete_post
|
||||||
|
from . import user_control
|
85
handlers/admin/add_post.py
Normal file
85
handlers/admin/add_post.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import io
|
||||||
|
|
||||||
|
from aiogram import types
|
||||||
|
from aiogram.types import ContentType
|
||||||
|
from aiogram.dispatcher import FSMContext
|
||||||
|
|
||||||
|
from load import bot, dp, messages
|
||||||
|
from utils.helper import download_file
|
||||||
|
from keyboard.default.admin.main_menu import base_menu, continue_btn, all_right
|
||||||
|
from keyboard.default.main_menu import back_to_main_menu
|
||||||
|
from state.post import Post
|
||||||
|
from utils.database.market import add_item
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.add_post, is_admin=True)
|
||||||
|
async def cmd_add_post(message: types.Message):
|
||||||
|
await bot.send_message(message.chat.id, "Укажите имя товара", reply_markup=base_menu())
|
||||||
|
await Post.name.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(is_admin=True, state=Post.name)
|
||||||
|
async def admin_form_name(message: types.Message, state: FSMContext):
|
||||||
|
await state.update_data(name=message.text)
|
||||||
|
await bot.send_message(message.chat.id, "Укажите описание товара", reply_markup=base_menu(skip=True))
|
||||||
|
await Post.description.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(is_admin=True, state=Post.description)
|
||||||
|
async def admin_form_desk(message: types.Message, state: FSMContext):
|
||||||
|
if message.text == messages.skip_message:
|
||||||
|
await state.update_data(description="")
|
||||||
|
else:
|
||||||
|
await state.update_data(description=message.text)
|
||||||
|
await bot.send_message(message.chat.id, "Укажите цену", reply_markup=base_menu())
|
||||||
|
await Post.price.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(is_admin=True, state=Post.price)
|
||||||
|
async def admin_form_price(message: types.message, state: FSMContext):
|
||||||
|
if not message.text.replace('.','').replace(',', '').isdigit():
|
||||||
|
await Post.price.set()
|
||||||
|
await state.update_data(price=float(message.text.replace(",", ".")))
|
||||||
|
await bot.send_message(message.chat.id, "Отправьте фото товара")
|
||||||
|
await Post.image.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(is_admin=True, state=Post.image, content_types=[ContentType.PHOTO])
|
||||||
|
async def admin_form_image(message:types.Message, state: FSMContext):
|
||||||
|
file_id = message.photo[-1].file_id
|
||||||
|
file_info = await bot.get_file(file_id)
|
||||||
|
photo = await download_file(file_info.file_path)
|
||||||
|
await state.update_data(image=photo.read())
|
||||||
|
await bot.send_message(
|
||||||
|
message.chat.id,
|
||||||
|
"Все данные для поста были заполнены",
|
||||||
|
reply_markup=continue_btn
|
||||||
|
)
|
||||||
|
await Post.finish.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x:x.text == messages.continue_, state=Post.finish, is_admin=True)
|
||||||
|
async def finish(message: types.Message, state: FSMContext):
|
||||||
|
data = await state.get_data()
|
||||||
|
await bot.send_photo(
|
||||||
|
message.chat.id,
|
||||||
|
caption=messages.product_message.format(
|
||||||
|
name=data["name"],
|
||||||
|
description=data["description"],
|
||||||
|
price=data["price"]
|
||||||
|
),
|
||||||
|
photo=data["image"],
|
||||||
|
reply_markup=all_right,
|
||||||
|
parse_mode='Markdown'
|
||||||
|
)
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.all_right_message, is_admin=True, state=Post.finish)
|
||||||
|
async def write_on_db(message: types.Message, state: FSMContext):
|
||||||
|
data = await state.get_data()
|
||||||
|
add_item(**data)
|
||||||
|
await state.finish()
|
||||||
|
await bot.send_message(
|
||||||
|
message.chat.id,
|
||||||
|
"Товар был добавлен",
|
||||||
|
reply_markup=back_to_main_menu
|
||||||
|
)
|
27
handlers/admin/delete_post.py
Normal file
27
handlers/admin/delete_post.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from aiogram import types
|
||||||
|
|
||||||
|
from load import bot, dp, messages
|
||||||
|
from utils.database.market import Catalog
|
||||||
|
from keyboard.default.main_menu import back_to_main_menu
|
||||||
|
from keyboard.inline.admin.catalog import item_list
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.delete_post, is_admin=True)
|
||||||
|
async def delete_post(message: types.Message):
|
||||||
|
items = Catalog.get_catalog()
|
||||||
|
if not items:
|
||||||
|
await bot.send_message(message.chat.id, "Каталог пуст:(")
|
||||||
|
return
|
||||||
|
item = items[0]
|
||||||
|
await bot.send_message(message.chat.id, messages.catalog, reply_markup=back_to_main_menu)
|
||||||
|
await bot.send_photo(
|
||||||
|
chat_id=message.chat.id,
|
||||||
|
photo=item["image"],
|
||||||
|
caption=messages.product_message.format(
|
||||||
|
name=item["name"],
|
||||||
|
description=item["description"],
|
||||||
|
price=item['price']
|
||||||
|
),
|
||||||
|
parse_mode="Markdown",
|
||||||
|
reply_markup=item_list(items=len(items)-1)
|
||||||
|
)
|
10
handlers/admin/panel.py
Normal file
10
handlers/admin/panel.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from aiogram import types
|
||||||
|
|
||||||
|
from load import dp, bot, messages
|
||||||
|
from keyboard.default.admin.main_menu import main_menu
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.admin_panel, is_admin=True)
|
||||||
|
async def cmd_menu(message: types.Message):
|
||||||
|
await bot.send_message(message.chat.id, "Admin panel", reply_markup=main_menu())
|
||||||
|
|
6
handlers/admin/user_control/__init__.py
Normal file
6
handlers/admin/user_control/__init__.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from utils.json import permission
|
||||||
|
|
||||||
|
if permission.can_admin_add_admins:
|
||||||
|
from . import adding
|
||||||
|
if permission.can_admin_del_admins:
|
||||||
|
from . import delete
|
37
handlers/admin/user_control/adding.py
Normal file
37
handlers/admin/user_control/adding.py
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
from aiogram import types
|
||||||
|
|
||||||
|
from load import dp, bot, messages
|
||||||
|
from state.state import AddUser
|
||||||
|
from utils.database.user import Register, User
|
||||||
|
from keyboard.default.admin.main_menu import base_menu
|
||||||
|
from keyboard.default.main_menu import back_to_main_menu
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.add_admin, is_admin=True)
|
||||||
|
async def add_admin(message: types.Message):
|
||||||
|
await bot.send_message(
|
||||||
|
message.chat.id,
|
||||||
|
("Отправьте user_id пользователя\n"
|
||||||
|
"Его можно узнать зайдя в информацию о пользователе"),
|
||||||
|
reply_markup=base_menu()
|
||||||
|
)
|
||||||
|
await AddUser.user_id.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(state=AddUser.user_id)
|
||||||
|
async def add_admin1(message: types.Message, state):
|
||||||
|
await state.finish()
|
||||||
|
user_id = message.text
|
||||||
|
if not user_id.isdigit() or "-100" in user_id:
|
||||||
|
await bot.send_message(message.chat.id, "Данные не правильные!", reply_markup=back_to_main_menu)
|
||||||
|
return
|
||||||
|
|
||||||
|
user_id = int(user_id)
|
||||||
|
user = User.get_user(user_id)
|
||||||
|
if not user:
|
||||||
|
await bot.send_message(message.chat.id, "Пользователь не существует!", reply_markup=back_to_main_menu)
|
||||||
|
return
|
||||||
|
Register.register_admin(user)
|
||||||
|
await bot.send_message(message.chat.id, "Администратор добавлен!", reply_markup=back_to_main_menu)
|
||||||
|
|
17
handlers/admin/user_control/delete.py
Normal file
17
handlers/admin/user_control/delete.py
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
from aiogram import types
|
||||||
|
|
||||||
|
from load import dp, bot, messages
|
||||||
|
from keyboard.inline.admin.user import item_list
|
||||||
|
from utils.database.base import get_full_admin
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text==messages.del_admin, is_admin=True)
|
||||||
|
async def del_admin(message: types.Message):
|
||||||
|
admins = get_full_admin()
|
||||||
|
admin = admins[0]
|
||||||
|
result = messages.admin_user.format(**admin)
|
||||||
|
await bot.send_message(
|
||||||
|
message.chat.id,
|
||||||
|
result,
|
||||||
|
parse_mode="Markdown",
|
||||||
|
reply_markup=item_list(items=len(admins)-1)
|
||||||
|
)
|
4
handlers/callback/__init__.py
Normal file
4
handlers/callback/__init__.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from . import null
|
||||||
|
from . import catalog
|
||||||
|
from . import cart
|
||||||
|
from . import admin
|
2
handlers/callback/admin/__init__.py
Normal file
2
handlers/callback/admin/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from . import catalog
|
||||||
|
from . import user
|
60
handlers/callback/admin/catalog.py
Normal file
60
handlers/callback/admin/catalog.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import io
|
||||||
|
|
||||||
|
from aiogram import types
|
||||||
|
|
||||||
|
from load import dp, messages
|
||||||
|
from utils.database.market import Catalog
|
||||||
|
from keyboard.inline.admin.catalog import item_list
|
||||||
|
from keyboard.default.main_menu import back_to_main_menu
|
||||||
|
|
||||||
|
|
||||||
|
@dp.callback_query_handler(lambda c: c.data.split("|")[0] in ["adm_prev", "adm_next"], is_admin=True)
|
||||||
|
async def next_item_adm(callback: types.CallbackQuery):
|
||||||
|
data = callback.data.split("|")
|
||||||
|
count = int(data[1])
|
||||||
|
items = Catalog.get_catalog()
|
||||||
|
item = items[count]
|
||||||
|
|
||||||
|
await callback.message.edit_media(
|
||||||
|
media=types.InputMediaPhoto(
|
||||||
|
media=io.BytesIO(item["image"]),
|
||||||
|
caption=messages.product_message.format(
|
||||||
|
name=item["name"],
|
||||||
|
description=item["description"],
|
||||||
|
price=item['price']
|
||||||
|
),
|
||||||
|
parse_mode="Markdown",
|
||||||
|
),
|
||||||
|
reply_markup=item_list(count, len(items)-1, int(data[2]))
|
||||||
|
)
|
||||||
|
|
||||||
|
@dp.callback_query_handler(lambda c: c.data.split("|")[0] == 'delete_post', is_admin=True)
|
||||||
|
async def delete_post_adm(callback: types.CallbackQuery):
|
||||||
|
data = callback.data.split("|")
|
||||||
|
item_id = Catalog.get_catalog()[int(data[1])]["id"]
|
||||||
|
Catalog.delete_post(item_id)
|
||||||
|
|
||||||
|
items = Catalog.get_catalog()
|
||||||
|
if items:
|
||||||
|
item = items[0]
|
||||||
|
await callback.message.edit_media(
|
||||||
|
media=types.InputMediaPhoto(
|
||||||
|
media=io.BytesIO(item["image"]),
|
||||||
|
caption=messages.product_message.format(
|
||||||
|
name=item["name"],
|
||||||
|
description=item["description"],
|
||||||
|
price=item['price']
|
||||||
|
),
|
||||||
|
parse_mode="Markdown",
|
||||||
|
),
|
||||||
|
reply_markup=item_list(items=len(items)-1)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await callback.message.delete()
|
||||||
|
await dp.bot.send_message(
|
||||||
|
callback.message.chat.id,
|
||||||
|
messages.catalog + " пуст!",
|
||||||
|
reply_markup=back_to_main_menu
|
||||||
|
)
|
||||||
|
await callback.answer("Товар удалён!")
|
||||||
|
await callback.answer()
|
35
handlers/callback/admin/user.py
Normal file
35
handlers/callback/admin/user.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from subprocess import call
|
||||||
|
from aiogram import types
|
||||||
|
|
||||||
|
from load import dp, messages
|
||||||
|
from keyboard.inline.admin.user import item_list
|
||||||
|
from utils.database.base import get_full_admin, del_admin
|
||||||
|
|
||||||
|
|
||||||
|
@dp.callback_query_handler(lambda c: c.data.split("|")[0] in ["admin_prev", "admin_next"], is_admin=True)
|
||||||
|
async def next_item_adm(callback: types.CallbackQuery):
|
||||||
|
data = callback.data.split("|")
|
||||||
|
count = int(data[1])
|
||||||
|
items = get_full_admin()
|
||||||
|
admin = items[count]
|
||||||
|
|
||||||
|
await callback.message.edit_text(
|
||||||
|
messages.admin_user.format(**admin),
|
||||||
|
reply_markup=item_list(count, len(items)-1, int(data[2])),
|
||||||
|
parse_mode="Markdown"
|
||||||
|
)
|
||||||
|
await callback.answer()
|
||||||
|
|
||||||
|
@dp.callback_query_handler(lambda x: x.data.split("|")[0] == "delete_admin")
|
||||||
|
async def delete_admin(callback: types.CallbackQuery):
|
||||||
|
data = callback.data.split("|")
|
||||||
|
count = int(data[1])
|
||||||
|
items = get_full_admin()
|
||||||
|
admin = items[count]
|
||||||
|
if admin["user_id"] == callback.from_user.id:
|
||||||
|
await dp.bot.send_message(callback.message.chat.id, "Вы не можете удалить сами себя")
|
||||||
|
await callback.answer()
|
||||||
|
return
|
||||||
|
del_admin(admin["user_id"])
|
||||||
|
await dp.bot.send_message(callback.message.chat.id, f"Админ {admin['first_name']} удалён!")
|
||||||
|
await callback.answer()
|
74
handlers/callback/cart.py
Normal file
74
handlers/callback/cart.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import io
|
||||||
|
|
||||||
|
from aiogram import types
|
||||||
|
|
||||||
|
from load import dp, messages
|
||||||
|
from utils.database.cart import add_to_cart, get_user_cart, del_from_cart
|
||||||
|
from utils.database.market import Catalog
|
||||||
|
from keyboard.inline.cart import cart_list
|
||||||
|
from keyboard.default.main_menu import back_to_main_menu
|
||||||
|
|
||||||
|
|
||||||
|
@dp.callback_query_handler(lambda c: c.data.split("|")[0]=="add_to_cart")
|
||||||
|
async def add_cart(callback: types.CallbackQuery):
|
||||||
|
item_id = callback.data.split("|")[1]
|
||||||
|
add_to_cart(callback.from_user.id, item_id)
|
||||||
|
await callback.answer("Товар добавлен!")
|
||||||
|
|
||||||
|
|
||||||
|
@dp.callback_query_handler(lambda c: c.data.split("|")[0]=="del_from_cart")
|
||||||
|
async def del_cart(callback: types.CallbackQuery):
|
||||||
|
c = callback.data.split("|")[1]
|
||||||
|
item_id = get_user_cart(callback.from_user.id)[int(c)][0]
|
||||||
|
|
||||||
|
del_from_cart(callback.from_user.id, item_id)
|
||||||
|
|
||||||
|
items = get_user_cart(callback.from_user.id)
|
||||||
|
|
||||||
|
if items:
|
||||||
|
index, count =items[0]
|
||||||
|
item = Catalog.get_catalog(index)
|
||||||
|
await callback.message.edit_media(
|
||||||
|
media=types.InputMediaPhoto(
|
||||||
|
media=io.BytesIO(item["image"]),
|
||||||
|
caption=messages.cart_message.format(
|
||||||
|
name=item["name"],
|
||||||
|
description=item["description"],
|
||||||
|
count=count,
|
||||||
|
price=item['price']*count
|
||||||
|
),
|
||||||
|
parse_mode="Markdown",
|
||||||
|
),
|
||||||
|
reply_markup=cart_list(items=len(items)-1)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await callback.message.delete()
|
||||||
|
await dp.bot.send_message(
|
||||||
|
callback.message.chat.id,
|
||||||
|
messages.cart + " пустая!",
|
||||||
|
reply_markup=back_to_main_menu
|
||||||
|
)
|
||||||
|
await callback.answer("Товар удалён!")
|
||||||
|
|
||||||
|
|
||||||
|
@dp.callback_query_handler(lambda c: c.data.split("|")[0] in ["cart_prev", "cart_next"])
|
||||||
|
async def next_item(callback: types.CallbackQuery):
|
||||||
|
data = callback.data.split("|")
|
||||||
|
c = int(data[1])
|
||||||
|
items = get_user_cart(callback.from_user.id)
|
||||||
|
index, count = items[c]
|
||||||
|
item = Catalog.get_catalog(index)
|
||||||
|
|
||||||
|
await callback.message.edit_media(
|
||||||
|
media=types.InputMediaPhoto(
|
||||||
|
media=io.BytesIO(item["image"]),
|
||||||
|
caption=messages.cart_message.format(
|
||||||
|
name=item["name"],
|
||||||
|
description=item["description"],
|
||||||
|
count=count,
|
||||||
|
price=item['price']*count
|
||||||
|
),
|
||||||
|
parse_mode="Markdown",
|
||||||
|
),
|
||||||
|
reply_markup=cart_list(c, len(items)-1)
|
||||||
|
)
|
29
handlers/callback/catalog.py
Normal file
29
handlers/callback/catalog.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import io
|
||||||
|
|
||||||
|
from aiogram import types
|
||||||
|
|
||||||
|
from load import dp, messages
|
||||||
|
from utils.database.market import Catalog
|
||||||
|
from keyboard.inline.catalog import item_list
|
||||||
|
|
||||||
|
|
||||||
|
@dp.callback_query_handler(lambda c: c.data.split("|")[0] in ["prev", "next"])
|
||||||
|
async def next_item(callback: types.CallbackQuery):
|
||||||
|
data = callback.data.split("|")
|
||||||
|
count = int(data[1])
|
||||||
|
items = Catalog.get_catalog()
|
||||||
|
item = items[count]
|
||||||
|
|
||||||
|
await callback.message.edit_media(
|
||||||
|
media=types.InputMediaPhoto(
|
||||||
|
media=io.BytesIO(item["image"]),
|
||||||
|
caption=messages.product_message.format(
|
||||||
|
name=item["name"],
|
||||||
|
description=item["description"],
|
||||||
|
price=item['price']
|
||||||
|
),
|
||||||
|
parse_mode="Markdown",
|
||||||
|
),
|
||||||
|
reply_markup=item_list(count, len(items)-1, int(data[2]))
|
||||||
|
)
|
||||||
|
|
7
handlers/callback/null.py
Normal file
7
handlers/callback/null.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from aiogram.types import CallbackQuery
|
||||||
|
|
||||||
|
from load import dp
|
||||||
|
|
||||||
|
@dp.callback_query_handler(lambda c: c.data == "null")
|
||||||
|
async def null(callback: CallbackQuery):
|
||||||
|
await callback.answer()
|
5
handlers/user/__init__.py
Normal file
5
handlers/user/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from . import start
|
||||||
|
from . import catalog
|
||||||
|
from . import cart
|
||||||
|
from . import checkout
|
||||||
|
from . import ordering
|
40
handlers/user/cart.py
Normal file
40
handlers/user/cart.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
from aiogram import types
|
||||||
|
|
||||||
|
from load import dp, bot, messages
|
||||||
|
from keyboard.default.main_menu import cart_btn
|
||||||
|
from keyboard.inline.cart import cart_list
|
||||||
|
from utils.database.cart import get_user_cart, clean_cart
|
||||||
|
from utils.database.market import Catalog
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.cart)
|
||||||
|
async def cmd_cart(message: types.Message):
|
||||||
|
items = get_user_cart(message.from_user.id)
|
||||||
|
|
||||||
|
if not items:
|
||||||
|
await bot.send_message(message.chat.id, messages.cart + " пустая!", reply_markup=cart_btn())
|
||||||
|
return
|
||||||
|
|
||||||
|
index, count = items[0]
|
||||||
|
item = Catalog.get_catalog(index)
|
||||||
|
|
||||||
|
await bot.send_message(message.chat.id, messages.cart, reply_markup=cart_btn(False))
|
||||||
|
|
||||||
|
await bot.send_photo(
|
||||||
|
chat_id=message.chat.id,
|
||||||
|
photo=item["image"],
|
||||||
|
caption=messages.cart_message.format(
|
||||||
|
name=item["name"],
|
||||||
|
description=item["description"],
|
||||||
|
count=count,
|
||||||
|
price=item['price']*count
|
||||||
|
),
|
||||||
|
parse_mode="Markdown",
|
||||||
|
reply_markup=cart_list(items=len(items)-1)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.clean_cart)
|
||||||
|
async def cmd_clean_cart(message: types.Message):
|
||||||
|
clean_cart(message.from_user.id)
|
||||||
|
await bot.send_message(message.chat.id, messages.cart + " очищена", reply_markup=cart_btn())
|
27
handlers/user/catalog.py
Normal file
27
handlers/user/catalog.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from aiogram import types
|
||||||
|
|
||||||
|
from load import dp, bot, messages
|
||||||
|
from utils.database.market import Catalog
|
||||||
|
from keyboard.inline.catalog import item_list
|
||||||
|
from keyboard.default.main_menu import back_to_main_menu
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.catalog)
|
||||||
|
async def catalog(message: types.Message):
|
||||||
|
items = Catalog.get_catalog()
|
||||||
|
if not items:
|
||||||
|
await bot.send_message(message.chat.id, "Каталог пуст:(")
|
||||||
|
return
|
||||||
|
item = items[0]
|
||||||
|
await bot.send_message(message.chat.id, messages.catalog, reply_markup=back_to_main_menu)
|
||||||
|
await bot.send_photo(
|
||||||
|
chat_id=message.chat.id,
|
||||||
|
photo=item["image"],
|
||||||
|
caption=messages.product_message.format(
|
||||||
|
name=item["name"],
|
||||||
|
description=item["description"],
|
||||||
|
price=item['price']
|
||||||
|
),
|
||||||
|
parse_mode="Markdown",
|
||||||
|
reply_markup=item_list(items=len(items))
|
||||||
|
)
|
35
handlers/user/checkout.py
Normal file
35
handlers/user/checkout.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from aiogram import types
|
||||||
|
|
||||||
|
from load import dp, bot, messages
|
||||||
|
from utils.database.cart import get_user_cart
|
||||||
|
from utils.database.market import Catalog
|
||||||
|
from utils.database.ordering import save_info
|
||||||
|
from keyboard.default.checkout import checkout_btn
|
||||||
|
from state.state import UserState
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.checkout)
|
||||||
|
async def checkout(message:types.Message):
|
||||||
|
items = get_user_cart(message.from_user.id)
|
||||||
|
output: str = ""
|
||||||
|
cost = 0
|
||||||
|
for index, count in items:
|
||||||
|
i = Catalog.get_catalog(index)
|
||||||
|
output += f'Имя: {i["name"]} цена: {i["price"]} - {count}шт.\n'
|
||||||
|
cost += i["price"] * count
|
||||||
|
output += f"Общая сумма: {cost}\nВсё верно? Следующий этап: заполнение заявки"
|
||||||
|
await bot.send_message(message.chat.id, output, reply_markup=checkout_btn())
|
||||||
|
await UserState.confirm.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.all_right_message)
|
||||||
|
async def continue_user_form(message: types.Message):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.save_and_continue, state="*")
|
||||||
|
async def save_user_info(message: types.Message):
|
||||||
|
state = dp.current_state(chat=message.chat.id, user=message.from_user.id)
|
||||||
|
info = await state.get_data()
|
||||||
|
save_info(user_id=message.from_user.id, **info)
|
||||||
|
await continue_user_form(message)
|
108
handlers/user/ordering.py
Normal file
108
handlers/user/ordering.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
from aiogram import types
|
||||||
|
from aiogram.dispatcher import FSMContext
|
||||||
|
|
||||||
|
from load import dp, bot, messages
|
||||||
|
from keyboard.default.checkout import confirm_all_info
|
||||||
|
from keyboard.default.main_menu import cancel_btn, get_phone_number, continue_btn
|
||||||
|
from keyboard.default.ordering import load_info
|
||||||
|
from state.state import UserState
|
||||||
|
from state.checkout import Checkout
|
||||||
|
from utils.database.ordering import get_info
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.all_right_message, state=UserState.confirm)
|
||||||
|
async def confirm(message: types.Message, state: FSMContext):
|
||||||
|
await state.finish()
|
||||||
|
info = get_info(message.from_user.id)
|
||||||
|
|
||||||
|
await bot.send_message(
|
||||||
|
message.chat.id,
|
||||||
|
"Может у вас есть сохраненные данные?",
|
||||||
|
reply_markup=load_info(bool(info))
|
||||||
|
)
|
||||||
|
await Checkout.loaded.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.load_data, state=Checkout.loaded)
|
||||||
|
async def load_form(message: types.message, state: FSMContext):
|
||||||
|
info = get_info(message.from_user.id)
|
||||||
|
await state.update_data(
|
||||||
|
loaded=True,
|
||||||
|
first_name = info["first_name"],
|
||||||
|
last_name = info["last_name"],
|
||||||
|
phone_number = info["phone_number"],
|
||||||
|
address = info["address"]
|
||||||
|
)
|
||||||
|
await bot.send_message(message.chat.id, "Ваши данные загружены!", reply_markup=continue_btn)
|
||||||
|
await Checkout.finish.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.rewrite_data, state=Checkout.loaded)
|
||||||
|
async def new_form(message: types.Message, state: FSMContext = None):
|
||||||
|
await state.update_data(loaded=False)
|
||||||
|
await bot.send_message(
|
||||||
|
message.chat.id,
|
||||||
|
"Напишите вашу фамилию",
|
||||||
|
reply_markup=cancel_btn
|
||||||
|
)
|
||||||
|
await Checkout.last_name.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(state=Checkout.last_name)
|
||||||
|
async def form_last_name(message: types.Message, state: FSMContext):
|
||||||
|
await state.update_data(last_name=message.text)
|
||||||
|
await bot.send_message(
|
||||||
|
message.chat.id,
|
||||||
|
"Ваше имя",
|
||||||
|
reply_markup=cancel_btn
|
||||||
|
)
|
||||||
|
await Checkout.first_name.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(state=Checkout.first_name)
|
||||||
|
async def form_first_name(message: types.Message, state: FSMContext):
|
||||||
|
await state.update_data(first_name=message.text)
|
||||||
|
await bot.send_message(
|
||||||
|
message.chat.id,
|
||||||
|
("Номер телефона\n\n"
|
||||||
|
"Можете написать номер телефона вручную"),
|
||||||
|
reply_markup=get_phone_number
|
||||||
|
)
|
||||||
|
await Checkout.phone_number.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(state=Checkout.phone_number, content_types=[types.ContentType.CONTACT, types.ContentType.TEXT])
|
||||||
|
async def form_phone(message: types.Message, state: FSMContext):
|
||||||
|
if message.contact is not None:
|
||||||
|
phone=message.contact.phone_number
|
||||||
|
else: phone=message.text
|
||||||
|
if len(phone) in [10,12,13]:
|
||||||
|
await state.update_data(phone_number=phone)
|
||||||
|
await bot.send_message(message.chat.id, "Отправте адрес доставки", reply_markup=cancel_btn)
|
||||||
|
await Checkout.address.set()
|
||||||
|
else:
|
||||||
|
await bot.send_message(message.chat.id, "Номер телефона не правильный!")
|
||||||
|
await Checkout.phone_number.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(state=Checkout.address)
|
||||||
|
async def form_address(message: types.Message, state: FSMContext):
|
||||||
|
await state.update_data(address=message.text)
|
||||||
|
await bot.send_message(message.chat.id, "Отлично заявка заполнена!", reply_markup=continue_btn)
|
||||||
|
await Checkout.finish.set()
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(state=Checkout.finish)
|
||||||
|
async def finish_form(message: types.Message, state: FSMContext):
|
||||||
|
data = await state.get_data()
|
||||||
|
|
||||||
|
text = (f"Имя: {data['first_name']}\n"
|
||||||
|
f"Фамилия: {data['last_name']}\n"
|
||||||
|
f"Номер телефона: {data['phone_number']}\n"
|
||||||
|
f"Адрес: {data['address']}\n"
|
||||||
|
)
|
||||||
|
await bot.send_message(
|
||||||
|
message.chat.id,
|
||||||
|
text,
|
||||||
|
reply_markup=confirm_all_info()
|
||||||
|
)
|
50
handlers/user/start.py
Normal file
50
handlers/user/start.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from aiogram import types
|
||||||
|
from aiogram.dispatcher.filters import CommandStart
|
||||||
|
|
||||||
|
from load import dp, bot, messages
|
||||||
|
from keyboard.default.main_menu import main_menu
|
||||||
|
from utils.database.base import get_admins, get_operator
|
||||||
|
from utils.database.user import Register
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(CommandStart())
|
||||||
|
async def start(message: types.Message):
|
||||||
|
Register.register_user(message.from_user)
|
||||||
|
is_bot_admin = message.from_user.id in get_admins()
|
||||||
|
is_operator = message.from_user.id in get_operator()
|
||||||
|
await bot.send_message(
|
||||||
|
message.chat.id,
|
||||||
|
messages.welcome.format(user=message.from_user.first_name),
|
||||||
|
reply_markup=main_menu(admin=is_bot_admin, operator=is_operator)
|
||||||
|
)
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.back)
|
||||||
|
async def back_to_menu(message: types.Message):
|
||||||
|
is_bot_admin = message.from_user.id in get_admins()
|
||||||
|
is_operator = message.from_user.id in get_operator()
|
||||||
|
await bot.send_message(
|
||||||
|
message.chat.id,
|
||||||
|
"Главное меню",
|
||||||
|
reply_markup=main_menu(admin=is_bot_admin, operator=is_operator)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x: x.text == messages.cancel_message, state="*")
|
||||||
|
async def cancel(message: types.Message):
|
||||||
|
state = dp.current_state(chat=message.chat.id, user=message.from_user.id)
|
||||||
|
await state.finish()
|
||||||
|
await back_to_menu(message)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(lambda x:x.text == messages.info)
|
||||||
|
async def userinfo(message: types.Message):
|
||||||
|
await bot.send_message(
|
||||||
|
message.chat.id,
|
||||||
|
("User_id: <code>{user_id}</code>").format(user_id=message.from_user.id),
|
||||||
|
parse_mode="HTML"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@dp.message_handler(commands=['test'])
|
||||||
|
async def test(message: types.Message):
|
||||||
|
Register.register_admin(message.from_user)
|
2
keyboard/__init__.py
Normal file
2
keyboard/__init__.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
from . import default
|
||||||
|
from . import inline
|
0
keyboard/default/__init__.py
Normal file
0
keyboard/default/__init__.py
Normal file
0
keyboard/default/admin/__init__.py
Normal file
0
keyboard/default/admin/__init__.py
Normal file
41
keyboard/default/admin/main_menu.py
Normal file
41
keyboard/default/admin/main_menu.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from aiogram.types.reply_keyboard import ReplyKeyboardMarkup, KeyboardButton
|
||||||
|
|
||||||
|
from load import messages
|
||||||
|
from utils.json import permission
|
||||||
|
|
||||||
|
|
||||||
|
def main_menu():
|
||||||
|
markup = ReplyKeyboardMarkup(resize_keyboard=True)
|
||||||
|
|
||||||
|
markup.add(KeyboardButton(messages.add_post), KeyboardButton(messages.delete_post))
|
||||||
|
admin_control = []
|
||||||
|
if permission.can_admin_add_admins:
|
||||||
|
admin_control.append(KeyboardButton(messages.add_admin))
|
||||||
|
if permission.can_admin_del_admins:
|
||||||
|
admin_control.append(KeyboardButton(messages.del_admin))
|
||||||
|
if admin_control:
|
||||||
|
markup.add(*admin_control)
|
||||||
|
markup.add(KeyboardButton(messages.back))
|
||||||
|
|
||||||
|
return markup
|
||||||
|
|
||||||
|
|
||||||
|
def base_menu(cancel=True, skip=False):
|
||||||
|
markup = ReplyKeyboardMarkup(resize_keyboard=True)
|
||||||
|
if cancel:
|
||||||
|
markup.row(KeyboardButton(messages.cancel_message))
|
||||||
|
if skip:
|
||||||
|
markup.row(KeyboardButton(messages.skip_message))
|
||||||
|
return markup
|
||||||
|
|
||||||
|
|
||||||
|
continue_btn = ReplyKeyboardMarkup(
|
||||||
|
[[KeyboardButton(messages.continue_),
|
||||||
|
KeyboardButton(messages.cancel_message)]],
|
||||||
|
resize_keyboard=True
|
||||||
|
)
|
||||||
|
all_right = ReplyKeyboardMarkup(
|
||||||
|
[[KeyboardButton(messages.all_right_message),
|
||||||
|
KeyboardButton(messages.cancel_message)]],
|
||||||
|
resize_keyboard=True
|
||||||
|
)
|
16
keyboard/default/checkout.py
Normal file
16
keyboard/default/checkout.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from aiogram.types.reply_keyboard import ReplyKeyboardMarkup, KeyboardButton
|
||||||
|
|
||||||
|
from load import messages
|
||||||
|
|
||||||
|
def checkout_btn() -> ReplyKeyboardMarkup:
|
||||||
|
return ReplyKeyboardMarkup([[
|
||||||
|
KeyboardButton(messages.all_right_message),
|
||||||
|
KeyboardButton(messages.cancel_message)
|
||||||
|
]], resize_keyboard=True)
|
||||||
|
|
||||||
|
def confirm_all_info():
|
||||||
|
markup = ReplyKeyboardMarkup(resize_keyboard=True)
|
||||||
|
markup.add(KeyboardButton(messages.all_right_message))
|
||||||
|
markup.row(KeyboardButton(messages.save_and_continue))
|
||||||
|
markup.row(KeyboardButton(messages.cancel_message))
|
||||||
|
return markup
|
41
keyboard/default/main_menu.py
Normal file
41
keyboard/default/main_menu.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
from aiogram.types.reply_keyboard import ReplyKeyboardMarkup, KeyboardButton
|
||||||
|
|
||||||
|
from load import messages
|
||||||
|
|
||||||
|
|
||||||
|
def main_menu(admin=False, operator=False):
|
||||||
|
markup = ReplyKeyboardMarkup(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
KeyboardButton(messages.catalog), # Go to catalog
|
||||||
|
KeyboardButton(messages.cart) # Go to cart
|
||||||
|
]
|
||||||
|
],
|
||||||
|
resize_keyboard=True
|
||||||
|
).add(KeyboardButton(messages.info))
|
||||||
|
if admin == True:
|
||||||
|
markup.add(KeyboardButton(messages.admin_panel))
|
||||||
|
|
||||||
|
if operator == True:
|
||||||
|
markup.add(KeyboardButton(messages.operator_panel))
|
||||||
|
|
||||||
|
return markup
|
||||||
|
|
||||||
|
|
||||||
|
def cart_btn(is_empty=True):
|
||||||
|
markup = ReplyKeyboardMarkup(resize_keyboard=True)
|
||||||
|
if not is_empty:
|
||||||
|
markup.add(KeyboardButton(messages.checkout))
|
||||||
|
markup.add(KeyboardButton(messages.clean_cart))
|
||||||
|
markup.add(KeyboardButton(messages.back))
|
||||||
|
return markup
|
||||||
|
|
||||||
|
back_to_main_menu = ReplyKeyboardMarkup(keyboard=[[KeyboardButton(messages.back)]],resize_keyboard=True)
|
||||||
|
cancel_btn = ReplyKeyboardMarkup([[KeyboardButton(messages.cancel_message)]], resize_keyboard=True)
|
||||||
|
get_phone_number = ReplyKeyboardMarkup(
|
||||||
|
keyboard=[
|
||||||
|
[KeyboardButton(messages.contact, request_contact=True)]
|
||||||
|
],
|
||||||
|
resize_keyboard=True
|
||||||
|
).row(KeyboardButton(messages.cancel_message))
|
||||||
|
continue_btn = ReplyKeyboardMarkup([[KeyboardButton(messages.continue_)]], resize_keyboard=True)
|
10
keyboard/default/ordering.py
Normal file
10
keyboard/default/ordering.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from aiogram.types.reply_keyboard import ReplyKeyboardMarkup, KeyboardButton
|
||||||
|
|
||||||
|
from load import messages
|
||||||
|
|
||||||
|
def load_info(data:bool = False):
|
||||||
|
markup = ReplyKeyboardMarkup(resize_keyboard=True)
|
||||||
|
if data:
|
||||||
|
markup.row(KeyboardButton(messages.load_data))
|
||||||
|
markup.row(KeyboardButton(messages.rewrite_data))
|
||||||
|
return markup
|
0
keyboard/inline/__init__.py
Normal file
0
keyboard/inline/__init__.py
Normal file
26
keyboard/inline/admin/catalog.py
Normal file
26
keyboard/inline/admin/catalog.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from aiogram.types.inline_keyboard import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
|
from load import messages
|
||||||
|
|
||||||
|
|
||||||
|
def item_list(item=0, items:int=1, user_count:int=1) -> InlineKeyboardMarkup:
|
||||||
|
markup = InlineKeyboardMarkup()
|
||||||
|
default = [
|
||||||
|
InlineKeyboardButton(user_count, callback_data='null')
|
||||||
|
]
|
||||||
|
back = ['⬅️', f"adm_prev|{item-1}|{user_count-1}"]
|
||||||
|
next = ['➡️', f"adm_next|{item+1}|{user_count+1}"]
|
||||||
|
|
||||||
|
|
||||||
|
if items < 1 or item >= items:
|
||||||
|
next = [" ", "null"]
|
||||||
|
|
||||||
|
if item == 0:
|
||||||
|
back = [" ", "null"]
|
||||||
|
|
||||||
|
|
||||||
|
default.append(InlineKeyboardButton(next[0], callback_data=next[1]))
|
||||||
|
|
||||||
|
markup.add(InlineKeyboardButton(back[0], callback_data=back[1]), *default)
|
||||||
|
markup.row(InlineKeyboardButton(messages.delete_post, callback_data=f"delete_post|{item}"))
|
||||||
|
return markup
|
24
keyboard/inline/admin/user.py
Normal file
24
keyboard/inline/admin/user.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from aiogram.types.inline_keyboard import InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
|
from load import messages
|
||||||
|
|
||||||
|
def item_list(item=0, items:int=1, user_count:int=1) -> InlineKeyboardMarkup:
|
||||||
|
markup = InlineKeyboardMarkup()
|
||||||
|
default = [
|
||||||
|
InlineKeyboardButton(user_count, callback_data='null')
|
||||||
|
]
|
||||||
|
back = ['⬅️', f"admin_prev|{item-1}|{user_count-1}"]
|
||||||
|
next = ['➡️', f"admin_next|{item+1}|{user_count+1}"]
|
||||||
|
|
||||||
|
|
||||||
|
if items < 1 or item >= items:
|
||||||
|
next = [" ", "null"]
|
||||||
|
|
||||||
|
if item == 0:
|
||||||
|
back = [" ", "null"]
|
||||||
|
|
||||||
|
|
||||||
|
default.append(InlineKeyboardButton(next[0], callback_data=next[1]))
|
||||||
|
|
||||||
|
markup.add(InlineKeyboardButton(back[0], callback_data=back[1]), *default)
|
||||||
|
markup.row(InlineKeyboardButton(messages.del_admin, callback_data=f"delete_admin|{item}"))
|
||||||
|
return markup
|
24
keyboard/inline/cart.py
Normal file
24
keyboard/inline/cart.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from aiogram.types.inline_keyboard import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
|
from load import messages
|
||||||
|
|
||||||
|
def cart_list(item=0, items:int=1) -> InlineKeyboardMarkup:
|
||||||
|
markup = InlineKeyboardMarkup()
|
||||||
|
default = [
|
||||||
|
InlineKeyboardButton(item+1, callback_data='null')
|
||||||
|
]
|
||||||
|
back = ['⬅️', f"cart_prev|{item-1}"]
|
||||||
|
next = ['➡️', f"cart_next|{item+1}"]
|
||||||
|
|
||||||
|
|
||||||
|
if items < 1 or item >= items:
|
||||||
|
next = [" ", "null"]
|
||||||
|
|
||||||
|
if item <= 0:
|
||||||
|
back = [" ", "null"]
|
||||||
|
|
||||||
|
default.append(InlineKeyboardButton(next[0], callback_data=next[1]))
|
||||||
|
|
||||||
|
markup.add(InlineKeyboardButton(back[0], callback_data=back[1]), *default)
|
||||||
|
markup.row(InlineKeyboardButton(messages.delete, callback_data=f"del_from_cart|{item}"))
|
||||||
|
return markup
|
25
keyboard/inline/catalog.py
Normal file
25
keyboard/inline/catalog.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
from aiogram.types.inline_keyboard import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
|
from load import messages
|
||||||
|
|
||||||
|
|
||||||
|
def item_list(item=0, items:int=1, user_count:int=1) -> InlineKeyboardMarkup:
|
||||||
|
markup = InlineKeyboardMarkup()
|
||||||
|
default = [
|
||||||
|
InlineKeyboardButton(user_count, callback_data='null')
|
||||||
|
]
|
||||||
|
back = ['⬅️', f"prev|{item-1}|{user_count-1}"]
|
||||||
|
next = ['➡️', f"next|{item+1}|{user_count+1}"]
|
||||||
|
|
||||||
|
|
||||||
|
if items < 1 or item >= items:
|
||||||
|
next = [" ", "null"]
|
||||||
|
|
||||||
|
if item == 0:
|
||||||
|
back = [" ", "null"]
|
||||||
|
|
||||||
|
default.append(InlineKeyboardButton(next[0], callback_data=next[1]))
|
||||||
|
|
||||||
|
markup.add(InlineKeyboardButton(back[0], callback_data=back[1]), *default)
|
||||||
|
markup.row(InlineKeyboardButton(messages.add_to_cart, callback_data=f"add_to_cart|{item}"))
|
||||||
|
return markup
|
22
keyboard/inline/checkout.py
Normal file
22
keyboard/inline/checkout.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from aiogram.types.inline_keyboard import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
|
|
||||||
|
def cart_list(item=0, items:int=1) -> InlineKeyboardMarkup:
|
||||||
|
markup = InlineKeyboardMarkup()
|
||||||
|
default = [
|
||||||
|
InlineKeyboardButton(item+1, callback_data='null')
|
||||||
|
]
|
||||||
|
back = ['⬅️', f"cart_prev|{item-1}"]
|
||||||
|
next = ['➡️', f"cart_next|{item+1}"]
|
||||||
|
|
||||||
|
|
||||||
|
if items < 1 or item >= items:
|
||||||
|
next = [" ", "null"]
|
||||||
|
|
||||||
|
if item <= 0:
|
||||||
|
back = [" ", "null"]
|
||||||
|
|
||||||
|
default.append(InlineKeyboardButton(next[0], callback_data=next[1]))
|
||||||
|
|
||||||
|
markup.add(InlineKeyboardButton(back[0], callback_data=back[1]), *default)
|
||||||
|
return markup
|
29
load.py
Normal file
29
load.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from aiogram import Dispatcher, Bot
|
||||||
|
from aiogram.bot.api import TelegramAPIServer
|
||||||
|
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||||
|
|
||||||
|
from peewee import SqliteDatabase
|
||||||
|
from playhouse.db_url import connect
|
||||||
|
|
||||||
|
import config
|
||||||
|
from utils import messages # used, do not delete!
|
||||||
|
|
||||||
|
|
||||||
|
# Bot init
|
||||||
|
bot = Bot(
|
||||||
|
token=config.token,
|
||||||
|
server=TelegramAPIServer.from_base(config.telegram_api_server)
|
||||||
|
)
|
||||||
|
|
||||||
|
storage = MemoryStorage()
|
||||||
|
|
||||||
|
dp = Dispatcher(
|
||||||
|
bot=bot,
|
||||||
|
storage=storage
|
||||||
|
)
|
||||||
|
|
||||||
|
db:SqliteDatabase = connect(config.db_url)
|
||||||
|
|
||||||
|
permissions = json.load(open(config.permission_file))
|
4
permission.json
Normal file
4
permission.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"can_admin_add_admins": true,
|
||||||
|
"can_admin_del_admins": true
|
||||||
|
}
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
environs==9.5.0
|
||||||
|
aiogram==2.20
|
||||||
|
peewee==3.14.10
|
||||||
|
pydantic==1.9.1
|
0
state/__init__.py
Normal file
0
state/__init__.py
Normal file
11
state/checkout.py
Normal file
11
state/checkout.py
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
from aiogram.dispatcher.filters.state import StatesGroup, State
|
||||||
|
|
||||||
|
class Checkout(StatesGroup):
|
||||||
|
first_name = State()
|
||||||
|
last_name = State()
|
||||||
|
phone_number = State()
|
||||||
|
address = State()
|
||||||
|
|
||||||
|
loaded = State()
|
||||||
|
finish = State()
|
||||||
|
|
9
state/post.py
Normal file
9
state/post.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from aiogram.dispatcher.filters.state import StatesGroup, State
|
||||||
|
|
||||||
|
class Post(StatesGroup):
|
||||||
|
name = State()
|
||||||
|
description = State()
|
||||||
|
price = State()
|
||||||
|
image = State()
|
||||||
|
|
||||||
|
finish = State()
|
8
state/state.py
Normal file
8
state/state.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from aiogram.dispatcher.filters.state import StatesGroup, State
|
||||||
|
|
||||||
|
class UserState(StatesGroup):
|
||||||
|
confirm = State()
|
||||||
|
|
||||||
|
|
||||||
|
class AddUser(StatesGroup):
|
||||||
|
user_id = State()
|
0
utils/__init__.py
Normal file
0
utils/__init__.py
Normal file
30
utils/database/base.py
Normal file
30
utils/database/base.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
from .model import Admin, Operator
|
||||||
|
|
||||||
|
|
||||||
|
def get_admins():
|
||||||
|
users = []
|
||||||
|
for i in Admin.select():
|
||||||
|
users.append(i.user_id)
|
||||||
|
return users
|
||||||
|
|
||||||
|
|
||||||
|
def get_operator():
|
||||||
|
users = []
|
||||||
|
for i in Operator.select():
|
||||||
|
users.append(i.user_id)
|
||||||
|
return users
|
||||||
|
|
||||||
|
def get_full_admin():
|
||||||
|
usr = []
|
||||||
|
for i in Admin.select():
|
||||||
|
usr.append({
|
||||||
|
"user_id": i.user_id,
|
||||||
|
"first_name": i.first_name,
|
||||||
|
"last_name": i.last_name,
|
||||||
|
"username": i.username
|
||||||
|
})
|
||||||
|
return usr
|
||||||
|
|
||||||
|
|
||||||
|
def del_admin(user_id:int):
|
||||||
|
Admin.delete().where(Admin.user_id == user_id).execute()
|
23
utils/database/cart.py
Normal file
23
utils/database/cart.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from .model import Cart
|
||||||
|
|
||||||
|
|
||||||
|
def add_to_cart(user_id:int, item_id: int):
|
||||||
|
if Cart.select().where(Cart.user_id==user_id, Cart.product_id==item_id).exists():
|
||||||
|
count = Cart.get(Cart.user_id==user_id, Cart.product_id==item_id).count
|
||||||
|
Cart.update(count=count+1).where(Cart.user_id==user_id, Cart.product_id==item_id).execute()
|
||||||
|
return
|
||||||
|
Cart.insert(user_id=user_id, product_id=item_id, count=1).execute()
|
||||||
|
|
||||||
|
|
||||||
|
def del_from_cart(user_id: int, item_id: int):
|
||||||
|
Cart.delete().where(Cart.user_id==user_id, Cart.product_id==item_id).execute()
|
||||||
|
|
||||||
|
|
||||||
|
def clean_cart(user_id: int):
|
||||||
|
Cart.delete().where(Cart.user_id==user_id).execute()
|
||||||
|
|
||||||
|
def get_user_cart(user_id:int):
|
||||||
|
cart = []
|
||||||
|
for i in Cart.select().where(Cart.user_id == user_id):
|
||||||
|
cart.append((i.product_id, i.count))
|
||||||
|
return cart
|
49
utils/database/market.py
Normal file
49
utils/database/market.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
from ast import Mod
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
from .model import Catalog as Model
|
||||||
|
|
||||||
|
|
||||||
|
class Catalog():
|
||||||
|
@staticmethod
|
||||||
|
def __get_item(item_id: int) -> dict:
|
||||||
|
item = {}
|
||||||
|
if item_id is not None:
|
||||||
|
i = Model.get_or_none(Model.id == item_id)
|
||||||
|
if i is not None:
|
||||||
|
item.update(
|
||||||
|
{
|
||||||
|
"id": i.id,
|
||||||
|
"name": i.name,
|
||||||
|
"description": i.description,
|
||||||
|
"price": i.price,
|
||||||
|
"image": i.image
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return item
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_catalog(self, item_id:int = None, get_count:bool = False) -> t.Union[list, dict]:
|
||||||
|
if item_id:
|
||||||
|
if get_count:
|
||||||
|
return self.__get_item(item_id), Model.select().count()
|
||||||
|
return self.__get_item(item_id)
|
||||||
|
|
||||||
|
items = []
|
||||||
|
for i in Model.select():
|
||||||
|
items.append(self.__get_item(i.id))
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def delete_post(item_id: int):
|
||||||
|
return Model.delete_by_id(item_id)
|
||||||
|
|
||||||
|
|
||||||
|
def add_item(name: str, description: str, price:t.Union[int, float], image:bytes, **kw):
|
||||||
|
Model.insert(
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
price=price,
|
||||||
|
image=image
|
||||||
|
).execute()
|
48
utils/database/model.py
Normal file
48
utils/database/model.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
from peewee import (Model, BigIntegerField, TextField, BlobField,
|
||||||
|
IntegerField, CharField, FloatField, ForeignKeyField)
|
||||||
|
|
||||||
|
from load import db
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModel(Model):
|
||||||
|
'''Base model. Abstract Class'''
|
||||||
|
class Meta:
|
||||||
|
database = db
|
||||||
|
|
||||||
|
|
||||||
|
class User(BaseModel):
|
||||||
|
user_id = BigIntegerField(null=False, unique=True)
|
||||||
|
first_name = CharField(null=False, max_length=64)
|
||||||
|
last_name = CharField(null=True, max_length=64)
|
||||||
|
username = CharField(null=True, max_length=32)
|
||||||
|
|
||||||
|
|
||||||
|
class Admin(User):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Operator(User):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Catalog(BaseModel):
|
||||||
|
name = TextField()
|
||||||
|
description = TextField()
|
||||||
|
image = BlobField()
|
||||||
|
price = FloatField()
|
||||||
|
|
||||||
|
|
||||||
|
class Cart(BaseModel):
|
||||||
|
user_id = BigIntegerField()
|
||||||
|
product_id = IntegerField()
|
||||||
|
count = IntegerField(default=1)
|
||||||
|
|
||||||
|
|
||||||
|
class UserInfo(BaseModel):
|
||||||
|
user_id = BigIntegerField(null=False, unique=True)
|
||||||
|
first_name = TextField()
|
||||||
|
last_name = TextField()
|
||||||
|
phone_number = CharField(15)
|
||||||
|
address = TextField()
|
||||||
|
|
||||||
|
db.create_tables([Cart, Catalog, Operator, Admin, User, UserInfo])
|
23
utils/database/ordering.py
Normal file
23
utils/database/ordering.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from .model import UserInfo
|
||||||
|
|
||||||
|
|
||||||
|
def save_info(user_id: int, last_name:str, first_name:str, phone_number:str, address:str, **kw):
|
||||||
|
UserInfo.insert(
|
||||||
|
user_id=user_id,
|
||||||
|
first_name=first_name,
|
||||||
|
last_name=last_name,
|
||||||
|
phone_number=phone_number,
|
||||||
|
address=address
|
||||||
|
).on_conflict_replace().execute()
|
||||||
|
|
||||||
|
def get_info(user_id: int):
|
||||||
|
output = None
|
||||||
|
if UserInfo.select().where(UserInfo.user_id == user_id).exists():
|
||||||
|
user = UserInfo.get(UserInfo.user_id == user_id)
|
||||||
|
output = {
|
||||||
|
"last_name": user.last_name,
|
||||||
|
"first_name": user.first_name,
|
||||||
|
"phone_number": user.phone_number,
|
||||||
|
"address": user.address
|
||||||
|
}
|
||||||
|
return output
|
45
utils/database/user.py
Normal file
45
utils/database/user.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from aiogram.types import User as UserType
|
||||||
|
from peewee import Model
|
||||||
|
|
||||||
|
from .model import User as UserModel
|
||||||
|
from .model import Admin, Operator
|
||||||
|
from utils import types
|
||||||
|
|
||||||
|
|
||||||
|
class Register:
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __register_user(self, model: Model, user: UserType):
|
||||||
|
model.insert(
|
||||||
|
user_id=user.id,
|
||||||
|
first_name=user.first_name,
|
||||||
|
last_name=user.last_name,
|
||||||
|
username=user.username
|
||||||
|
).on_conflict_replace().execute()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register_user(self, user: UserType):
|
||||||
|
return self.__register_user(UserModel, user)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register_admin(self, user: UserType):
|
||||||
|
return self.__register_user(Admin, user)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register_operator(self, user: UserType):
|
||||||
|
return self.__register_user(Operator, user)
|
||||||
|
|
||||||
|
|
||||||
|
class User:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_user(user_id: int):
|
||||||
|
if not UserModel.select().where(UserModel.user_id==user_id).exists():
|
||||||
|
return None
|
||||||
|
data = UserModel.get(UserModel.user_id==user_id)
|
||||||
|
return types.User(
|
||||||
|
user_id=data.user_id,
|
||||||
|
first_name=data.first_name,
|
||||||
|
last_name=data.last_name,
|
||||||
|
username=data.username
|
||||||
|
)
|
13
utils/helper.py
Normal file
13
utils/helper.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import io
|
||||||
|
import typing as t
|
||||||
|
|
||||||
|
from load import bot, config
|
||||||
|
|
||||||
|
async def download_file(file_path, *args, **kw) -> t.Union[io.BytesIO, t.Any]:
|
||||||
|
if config.telegram_api_server == "https://api.telegram.org":
|
||||||
|
return await bot.download_file(file_path, *args, **kw)
|
||||||
|
else:
|
||||||
|
with open(file_path, 'rb') as f:
|
||||||
|
file = f.read()
|
||||||
|
f.close()
|
||||||
|
return io.BytesIO(file)
|
5
utils/json/__init__.py
Normal file
5
utils/json/__init__.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from load import permissions as __permissions
|
||||||
|
|
||||||
|
from .model import Permission
|
||||||
|
|
||||||
|
permission = Permission(**__permissions)
|
5
utils/json/model.py
Normal file
5
utils/json/model.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
class Permission(BaseModel):
|
||||||
|
can_admin_add_admins: bool
|
||||||
|
can_admin_del_admins: bool
|
54
utils/messages.py
Normal file
54
utils/messages.py
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
catalog = '🛍️ Каталог'
|
||||||
|
cart = '🛒 Корзина'
|
||||||
|
back = "👈 Главное меню" #Назад
|
||||||
|
checkout = "📦 Оформить заказ"
|
||||||
|
add_to_cart = "➕ Добавить в корзину"
|
||||||
|
delete = "🗑️ Удалить"
|
||||||
|
clean_cart = "🗑️ Очистить корзину"
|
||||||
|
info = "ℹ️ Информация"
|
||||||
|
|
||||||
|
confirm_message = '✅ Подтвердить заказ'
|
||||||
|
all_right_message = '✅ Все верно'
|
||||||
|
cancel_message = '🚫 Отменить'
|
||||||
|
skip_message = '⏩ Пропустить'
|
||||||
|
contact = "☎️ Предоставить номер телефона"
|
||||||
|
|
||||||
|
save_and_continue = "Сохранить данные и продолжить"
|
||||||
|
continue_ = "Продолжить"
|
||||||
|
|
||||||
|
rewrite_data = "Ввести заново"
|
||||||
|
load_data = "Загрузить данные"
|
||||||
|
|
||||||
|
|
||||||
|
#Admins \ Moder
|
||||||
|
admin_panel = "Админ панель"
|
||||||
|
operator_panel = "Панель оператора"
|
||||||
|
add_post = "Добавить пост"
|
||||||
|
delete_post = "🗑️ Удалить пост"
|
||||||
|
add_admin = "Добавить админа"
|
||||||
|
del_admin = "Удалить админа"
|
||||||
|
admin_user = (
|
||||||
|
'Имя: [{first_name}](tg://user?id={user_id})\n'
|
||||||
|
'Фамилия: [{last_name}](tg://user?id={user_id})\n'
|
||||||
|
'Имя пользователя: @{username}\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
#balance = '💰 Баланс'
|
||||||
|
#delivery_status = '🚚 Статус заказа'
|
||||||
|
|
||||||
|
product_message = (
|
||||||
|
"*{name}*\n\n"
|
||||||
|
"{description}\n\n\n"
|
||||||
|
"Цена: {price}"
|
||||||
|
)
|
||||||
|
cart_message = (
|
||||||
|
"*{name}*\n\n"
|
||||||
|
"{description}\n\n\n"
|
||||||
|
"Количество: {count}\n"
|
||||||
|
"Цена: {price}"
|
||||||
|
)
|
||||||
|
welcome = (
|
||||||
|
"Привет {user}\n"
|
||||||
|
"Добро пожаловать в магазинчик"
|
||||||
|
)
|
23
utils/types.py
Normal file
23
utils/types.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import typing as t
|
||||||
|
from abc import ABC
|
||||||
|
|
||||||
|
|
||||||
|
class BaseUserFileds(ABC):
|
||||||
|
id: int
|
||||||
|
first_name: str
|
||||||
|
last_name: t.Union[str, None] = None
|
||||||
|
username: t.Union[str, None] = None
|
||||||
|
|
||||||
|
|
||||||
|
class User(BaseUserFileds):
|
||||||
|
|
||||||
|
def __new__(self, **kw):
|
||||||
|
if "user_id" in kw:
|
||||||
|
self.id = kw["user_id"]
|
||||||
|
if "first_name" in kw:
|
||||||
|
self.first_name = kw["first_name"]
|
||||||
|
if "last_name" in kw:
|
||||||
|
self.last_name = kw["last_name"]
|
||||||
|
if "username" in kw:
|
||||||
|
self.username = kw["username"]
|
||||||
|
return self
|
Loading…
Reference in New Issue
Block a user