old_01
3
.idea/encodings.xml
generated
@@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding">
|
||||
<file url="file://$PROJECT_DIR$/old/bot.log" charset="windows-1251" />
|
||||
<file url="file://$PROJECT_DIR$/old/bot.log" charset="windows-1251" />
|
||||
<file url="file://$PROJECT_DIR$/doners/old/bot.log" charset="windows-1251" />
|
||||
</component>
|
||||
</project>
|
||||
218
README.md
@@ -155,3 +155,221 @@ ChatGPT
|
||||
Использовать базу данных для хранения информации о пользователях, заказах и истории коммуникации с операторами.
|
||||
Реализовать механизм автоматического анализа данных о заказах для предложения персонализированных услуг или скидок.
|
||||
Эти функции помогут сделать бота более удобным и эффективным для пользователей и операторов.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Конечно, вот структура файлов для Telegram бота, который использует aiogram, PostgreSQL, logging, inline-кнопки и .env файл для хранения информации о группах админов и операторов. Бот будет делать опросы.
|
||||
|
||||
markdown
|
||||
Копировать код
|
||||
my_telegram_bot/
|
||||
├── .env
|
||||
├── bot.py
|
||||
├── config.py
|
||||
├── database.py
|
||||
├── handlers/
|
||||
│ ├── __init__.py
|
||||
│ ├── admin.py
|
||||
│ ├── polls.py
|
||||
│ ├── start.py
|
||||
├── keyboards/
|
||||
│ ├── __init__.py
|
||||
│ └── inline.py
|
||||
├── middlewares/
|
||||
│ ├── __init__.py
|
||||
│ └── logging.py
|
||||
├── models/
|
||||
│ ├── __init__.py
|
||||
│ └── poll.py
|
||||
├── utils/
|
||||
│ ├── __init__.py
|
||||
│ └── misc.py
|
||||
└── requirements.txt
|
||||
Описание файлов и директорий
|
||||
.env: файл с переменными окружения. Пример содержания:
|
||||
|
||||
env
|
||||
Копировать код
|
||||
BOT_TOKEN=<your_bot_token>
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/mydatabase
|
||||
ADMINS=123456789,987654321
|
||||
OPERATORS=123123123,321321321
|
||||
bot.py: основной файл для запуска бота.
|
||||
|
||||
python
|
||||
Копировать код
|
||||
import logging
|
||||
from aiogram import Bot, Dispatcher, executor
|
||||
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||
from config import load_config
|
||||
from database import setup_database
|
||||
from middlewares.logging import LoggingMiddleware
|
||||
from handlers import start, admin, polls
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
config = load_config()
|
||||
bot = Bot(token=config['BOT_TOKEN'])
|
||||
dp = Dispatcher(bot, storage=MemoryStorage())
|
||||
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
start.register_handlers(dp)
|
||||
admin.register_handlers(dp)
|
||||
polls.register_handlers(dp)
|
||||
|
||||
setup_database()
|
||||
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
config.py: файл для загрузки конфигурации из .env.
|
||||
|
||||
python
|
||||
Копировать код
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
load_dotenv()
|
||||
|
||||
def load_config():
|
||||
return {
|
||||
'BOT_TOKEN': os.getenv('BOT_TOKEN'),
|
||||
'DATABASE_URL': os.getenv('DATABASE_URL'),
|
||||
'ADMINS': list(map(int, os.getenv('ADMINS').split(','))),
|
||||
'OPERATORS': list(map(int, os.getenv('OPERATORS').split(','))),
|
||||
}
|
||||
database.py: файл для настройки базы данных.
|
||||
|
||||
python
|
||||
Копировать код
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from config import load_config
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
config = load_config()
|
||||
engine = sa.create_engine(config['DATABASE_URL'])
|
||||
Session = sessionmaker(bind=engine)
|
||||
|
||||
def setup_database():
|
||||
Base.metadata.create_all(engine)
|
||||
handlers/__init__.py: инициализация пакета хэндлеров.
|
||||
|
||||
python
|
||||
Копировать код
|
||||
from aiogram import Dispatcher
|
||||
|
||||
def register_handlers(dp: Dispatcher):
|
||||
from . import start, admin, polls
|
||||
start.register_handlers(dp)
|
||||
admin.register_handlers(dp)
|
||||
polls.register_handlers(dp)
|
||||
handlers/admin.py: хэндлеры для админов.
|
||||
|
||||
python
|
||||
Копировать код
|
||||
from aiogram import types, Dispatcher
|
||||
from config import load_config
|
||||
|
||||
config = load_config()
|
||||
|
||||
async def admin_command(message: types.Message):
|
||||
if message.from_user.id not in config['ADMINS']:
|
||||
await message.reply("You are not an admin!")
|
||||
return
|
||||
await message.reply("Hello, Admin!")
|
||||
|
||||
def register_handlers(dp: Dispatcher):
|
||||
dp.register_message_handler(admin_command, commands=['admin'])
|
||||
handlers/polls.py: хэндлеры для опросов.
|
||||
|
||||
python
|
||||
Копировать код
|
||||
from aiogram import types, Dispatcher
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from models.poll import Poll
|
||||
from database import Session
|
||||
|
||||
async def create_poll(message: types.Message):
|
||||
markup = InlineKeyboardMarkup()
|
||||
markup.add(InlineKeyboardButton('Option 1', callback_data='vote_1'))
|
||||
markup.add(InlineKeyboardButton('Option 2', callback_data='vote_2'))
|
||||
await message.reply("Choose an option:", reply_markup=markup)
|
||||
|
||||
async def handle_vote(callback_query: types.CallbackQuery):
|
||||
option = callback_query.data.split('_')[1]
|
||||
with Session() as session:
|
||||
poll = Poll(option=option)
|
||||
session.add(poll)
|
||||
session.commit()
|
||||
await callback_query.answer(f"You voted for option {option}")
|
||||
|
||||
def register_handlers(dp: Dispatcher):
|
||||
dp.register_message_handler(create_poll, commands=['poll'])
|
||||
dp.register_callback_query_handler(handle_vote, lambda c: c.data.startswith('vote_'))
|
||||
handlers/start.py: хэндлер для команды /start.
|
||||
|
||||
python
|
||||
Копировать код
|
||||
from aiogram import types, Dispatcher
|
||||
|
||||
async def start_command(message: types.Message):
|
||||
await message.reply("Hello! I am your polling bot.")
|
||||
|
||||
def register_handlers(dp: Dispatcher):
|
||||
dp.register_message_handler(start_command, commands=['start'])
|
||||
keyboards/inline.py: файл для создания inline-клавиатур.
|
||||
|
||||
python
|
||||
Копировать код
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
|
||||
def create_poll_keyboard():
|
||||
markup = InlineKeyboardMarkup()
|
||||
markup.add(InlineKeyboardButton('Option 1', callback_data='vote_1'))
|
||||
markup.add(InlineKeyboardButton('Option 2', callback_data='vote_2'))
|
||||
return markup
|
||||
middlewares/logging.py: файл для логирования.
|
||||
|
||||
python
|
||||
Копировать код
|
||||
from aiogram.dispatcher.middlewares import BaseMiddleware
|
||||
import logging
|
||||
|
||||
class LoggingMiddleware(BaseMiddleware):
|
||||
async def on_process_message(self, message, data):
|
||||
logging.info(f"Received message from {message.from_user.id}: {message.text}")
|
||||
models/poll.py: модель для хранения данных опросов.
|
||||
|
||||
python
|
||||
Копировать код
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from database import Base
|
||||
|
||||
class Poll(Base):
|
||||
__tablename__ = 'polls'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
option = Column(String, nullable=False)
|
||||
utils/misc.py: утилитарные функции.
|
||||
|
||||
python
|
||||
Копировать код
|
||||
# В этом файле можно разместить различные вспомогательные функции.
|
||||
requirements.txt: зависимости проекта.
|
||||
|
||||
Копировать код
|
||||
aiogram
|
||||
python-dotenv
|
||||
sqlalchemy
|
||||
Эта структура предоставляет базу для дальнейшего расширения функциональности бота. Вы можете добавлять дополнительные обработчики, модели и утилиты по мере необходимости.
|
||||
24
bot.py
@@ -0,0 +1,24 @@
|
||||
import logging
|
||||
from aiogram import Bot, Dispatcher, executor
|
||||
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||
from config import load_config
|
||||
from database import setup_database
|
||||
from middlewares.logging import LoggingMiddleware
|
||||
from handlers import start, admin, polls
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
config = load_config()
|
||||
bot = Bot(token=config['BOT_TOKEN'])
|
||||
dp = Dispatcher(bot, storage=MemoryStorage())
|
||||
|
||||
dp.middleware.setup(LoggingMiddleware())
|
||||
|
||||
start.register_handlers(dp)
|
||||
admin.register_handlers(dp)
|
||||
polls.register_handlers(dp)
|
||||
|
||||
setup_database()
|
||||
|
||||
if __name__ == '__main__':
|
||||
executor.start_polling(dp, skip_updates=True)
|
||||
|
||||
12
config.py
@@ -0,0 +1,12 @@
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
|
||||
load_dotenv()
|
||||
|
||||
def load_config():
|
||||
return {
|
||||
'BOT_TOKEN': os.getenv('BOT_TOKEN'),
|
||||
'DATABASE_URL': os.getenv('DATABASE_URL'),
|
||||
'ADMINS': list(map(int, os.getenv('ADMINS').split(','))),
|
||||
'OPERATORS': list(map(int, os.getenv('OPERATORS').split(','))),
|
||||
}
|
||||
|
||||
13
database.py
@@ -0,0 +1,13 @@
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from config import load_config
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
config = load_config()
|
||||
engine = sa.create_engine(config['DATABASE_URL'])
|
||||
Session = sessionmaker(bind=engine)
|
||||
|
||||
def setup_database():
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
14
doners/Shop-bot/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# Cache files
|
||||
__pycache__/
|
||||
|
||||
# Project files
|
||||
.idea/
|
||||
*.log
|
||||
.env
|
||||
*.session
|
||||
data/config.py
|
||||
data/database.db
|
||||
|
||||
# vscode
|
||||
.vscode/
|
||||
.history/
|
||||
9
doners/Shop-bot/Dockerfile
Normal file
@@ -0,0 +1,9 @@
|
||||
FROM python:3.7-slim
|
||||
|
||||
WORKDIR /botname
|
||||
|
||||
COPY requirements.txt /botname/
|
||||
RUN pip install -r /botname/requirements.txt
|
||||
COPY . /botname/
|
||||
|
||||
CMD python3 /botname/app.py
|
||||
21
doners/Shop-bot/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Hagai
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
47
doners/Shop-bot/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
<p align="center">
|
||||
<a href="https://t.me/example_store_bot"><img src="data/assets/logo.png" alt="ShopBot"></a>
|
||||
</p>
|
||||
|
||||
This is an example Telegram shop bot. It's a simple and, most importantly, efficient way to place an order without leaving your favorite messenger.
|
||||
|
||||
## What can it do?
|
||||
|
||||
1. `/start` - needed to start the bot and choose the mode (user/admin).
|
||||
|
||||
2. `/menu` - go to the menu.
|
||||
|
||||
3. `/sos` - ask the administrator a question.
|
||||
|
||||
## Menu
|
||||
|
||||
The user menu looks like this:
|
||||
|
||||

|
||||
|
||||
## Catalog
|
||||
|
||||
The catalog consists of products sorted by categories. Users can add items to their cart, and the admin has full control over catalog management (addition/removal).
|
||||
|
||||
## Cart
|
||||
|
||||
The ordering process looks like this: the user goes to the `🛍️ Catalog`, selects the desired category, chooses products, and clicks the `🛒 Cart` button.
|
||||
|
||||

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

|
||||
|
||||
## Add a Product
|
||||
|
||||
To add a product, select a category and click the `➕ Add Product` button. Then, fill out the "name-description-image-price" form and confirm.
|
||||
|
||||

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

|
||||
0
doners/Shop-bot/__init__.py
Normal file
92
doners/Shop-bot/app.py
Normal file
@@ -0,0 +1,92 @@
|
||||
|
||||
import os
|
||||
import handlers
|
||||
from aiogram import executor, types
|
||||
from aiogram.types import ReplyKeyboardMarkup, ReplyKeyboardRemove
|
||||
from data import config
|
||||
from loader import dp, db, bot
|
||||
import filters
|
||||
import logging
|
||||
|
||||
filters.setup(dp)
|
||||
|
||||
WEBAPP_HOST = "0.0.0.0"
|
||||
WEBAPP_PORT = int(os.environ.get("PORT", 5000))
|
||||
user_message = 'Пользователь'
|
||||
admin_message = 'Админ'
|
||||
|
||||
|
||||
@dp.message_handler(commands='start')
|
||||
async def cmd_start(message: types.Message):
|
||||
|
||||
markup = ReplyKeyboardMarkup(resize_keyboard=True)
|
||||
|
||||
markup.row(user_message, admin_message)
|
||||
|
||||
await message.answer('''Привет! 👋
|
||||
|
||||
🤖 Я бот-магазин по подаже товаров любой категории.
|
||||
|
||||
🛍️ Чтобы перейти в каталог и выбрать приглянувшиеся товары возпользуйтесь командой /menu.
|
||||
|
||||
💰 Пополнить счет можно через Яндекс.кассу, Сбербанк или Qiwi.
|
||||
|
||||
❓ Возникли вопросы? Не проблема! Команда /sos поможет связаться с админами, которые постараются как можно быстрее откликнуться.
|
||||
|
||||
🤝 Заказать похожего бота? Свяжитесь с разработчиком <a href="https://t.me/NikolaySimakov">Nikolay Simakov</a>, он не кусается)))
|
||||
''', reply_markup=markup)
|
||||
|
||||
|
||||
@dp.message_handler(text=user_message)
|
||||
async def user_mode(message: types.Message):
|
||||
|
||||
cid = message.chat.id
|
||||
if cid in config.ADMINS:
|
||||
config.ADMINS.remove(cid)
|
||||
|
||||
await message.answer('Включен пользовательский режим.', reply_markup=ReplyKeyboardRemove())
|
||||
|
||||
|
||||
@dp.message_handler(text=admin_message)
|
||||
async def admin_mode(message: types.Message):
|
||||
|
||||
cid = message.chat.id
|
||||
if cid not in config.ADMINS:
|
||||
config.ADMINS.append(cid)
|
||||
|
||||
await message.answer('Включен админский режим.', reply_markup=ReplyKeyboardRemove())
|
||||
|
||||
|
||||
async def on_startup(dp):
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
db.create_tables()
|
||||
|
||||
await bot.delete_webhook()
|
||||
await bot.set_webhook(config.WEBHOOK_URL)
|
||||
|
||||
|
||||
async def on_shutdown():
|
||||
logging.warning("Shutting down..")
|
||||
await bot.delete_webhook()
|
||||
await dp.storage.close()
|
||||
await dp.storage.wait_closed()
|
||||
logging.warning("Bot down")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if "HEROKU" in list(os.environ.keys()):
|
||||
|
||||
executor.start_webhook(
|
||||
dispatcher=dp,
|
||||
webhook_path=config.WEBHOOK_PATH,
|
||||
on_startup=on_startup,
|
||||
on_shutdown=on_shutdown,
|
||||
skip_updates=True,
|
||||
host=WEBAPP_HOST,
|
||||
port=WEBAPP_PORT,
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
executor.start_polling(dp, on_startup=on_startup, skip_updates=False)
|
||||
0
doners/Shop-bot/data/__init__.py
Normal file
BIN
doners/Shop-bot/data/assets/1.png
Normal file
|
After Width: | Height: | Size: 616 KiB |
BIN
doners/Shop-bot/data/assets/2.png
Normal file
|
After Width: | Height: | Size: 947 KiB |
BIN
doners/Shop-bot/data/assets/3.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
doners/Shop-bot/data/assets/4.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
doners/Shop-bot/data/assets/5.png
Normal file
|
After Width: | Height: | Size: 647 KiB |
BIN
doners/Shop-bot/data/assets/6.png
Normal file
|
After Width: | Height: | Size: 399 KiB |
BIN
doners/Shop-bot/data/assets/7.png
Normal file
|
After Width: | Height: | Size: 157 KiB |
BIN
doners/Shop-bot/data/assets/logo.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
doners/Shop-bot/data/assets/logo_mini.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
7
doners/Shop-bot/filters/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from aiogram import Dispatcher
|
||||
from .is_admin import IsAdmin
|
||||
from .is_user import IsUser
|
||||
|
||||
def setup(dp: Dispatcher):
|
||||
dp.filters_factory.bind(IsAdmin, event_handlers=[dp.message_handlers])
|
||||
dp.filters_factory.bind(IsUser, event_handlers=[dp.message_handlers])
|
||||
9
doners/Shop-bot/filters/is_admin.py
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
from aiogram.types import Message
|
||||
from aiogram.dispatcher.filters import BoundFilter
|
||||
from data.config import ADMINS
|
||||
|
||||
class IsAdmin(BoundFilter):
|
||||
|
||||
async def check(self, message: Message):
|
||||
return message.from_user.id in ADMINS
|
||||
9
doners/Shop-bot/filters/is_user.py
Normal file
@@ -0,0 +1,9 @@
|
||||
|
||||
from aiogram.types import Message
|
||||
from aiogram.dispatcher.filters import BoundFilter
|
||||
from data.config import ADMINS
|
||||
|
||||
class IsUser(BoundFilter):
|
||||
|
||||
async def check(self, message: Message):
|
||||
return message.from_user.id not in ADMINS
|
||||
4
doners/Shop-bot/handlers/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .admin import dp
|
||||
from .user import dp
|
||||
|
||||
__all__ = ['dp']
|
||||
5
doners/Shop-bot/handlers/admin/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .add import dp
|
||||
from .questions import dp
|
||||
from .orders import dp
|
||||
|
||||
__all__ = ['dp']
|
||||
285
doners/Shop-bot/handlers/admin/add.py
Normal file
@@ -0,0 +1,285 @@
|
||||
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton, ContentType, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
||||
from aiogram.utils.callback_data import CallbackData
|
||||
from keyboards.default.markups import *
|
||||
from states import ProductState, CategoryState
|
||||
from aiogram.types.chat import ChatActions
|
||||
from doners.old_2.handlers import settings
|
||||
from loader import dp, db, bot
|
||||
from filters import IsAdmin
|
||||
from hashlib import md5
|
||||
|
||||
|
||||
category_cb = CallbackData('category', 'id', 'action')
|
||||
product_cb = CallbackData('product', 'id', 'action')
|
||||
|
||||
add_product = '➕ Добавить товар'
|
||||
delete_category = '🗑️ Удалить категорию'
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), text=settings)
|
||||
async def process_settings(message: Message):
|
||||
|
||||
markup = InlineKeyboardMarkup()
|
||||
|
||||
for idx, title in db.fetchall('SELECT * FROM categories'):
|
||||
|
||||
markup.add(InlineKeyboardButton(
|
||||
title, callback_data=category_cb.new(id=idx, action='view')))
|
||||
|
||||
markup.add(InlineKeyboardButton(
|
||||
'+ Добавить категорию', callback_data='add_category'))
|
||||
|
||||
await message.answer('Настройка категорий:', reply_markup=markup)
|
||||
|
||||
|
||||
@dp.callback_query_handler(IsAdmin(), category_cb.filter(action='view'))
|
||||
async def category_callback_handler(query: CallbackQuery, callback_data: dict, state: FSMContext):
|
||||
|
||||
category_idx = callback_data['id']
|
||||
|
||||
products = db.fetchall('''SELECT * FROM products product
|
||||
WHERE product.tag = (SELECT title FROM categories WHERE idx=?)''',
|
||||
(category_idx,))
|
||||
|
||||
await query.message.delete()
|
||||
await query.answer('Все добавленные товары в эту категорию.')
|
||||
await state.update_data(category_index=category_idx)
|
||||
await show_products(query.message, products, category_idx)
|
||||
|
||||
|
||||
# category
|
||||
|
||||
|
||||
@dp.callback_query_handler(IsAdmin(), text='add_category')
|
||||
async def add_category_callback_handler(query: CallbackQuery):
|
||||
await query.message.delete()
|
||||
await query.message.answer('Название категории?')
|
||||
await CategoryState.title.set()
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), state=CategoryState.title)
|
||||
async def set_category_title_handler(message: Message, state: FSMContext):
|
||||
|
||||
category = message.text
|
||||
idx = md5(category.encode('utf-8')).hexdigest()
|
||||
db.query('INSERT INTO categories VALUES (?, ?)', (idx, category))
|
||||
|
||||
await state.finish()
|
||||
await process_settings(message)
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), text=delete_category)
|
||||
async def delete_category_handler(message: Message, state: FSMContext):
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
if 'category_index' in data.keys():
|
||||
|
||||
idx = data['category_index']
|
||||
|
||||
db.query(
|
||||
'DELETE FROM products WHERE tag IN (SELECT title FROM categories WHERE idx=?)', (idx,))
|
||||
db.query('DELETE FROM categories WHERE idx=?', (idx,))
|
||||
|
||||
await message.answer('Готово!', reply_markup=ReplyKeyboardRemove())
|
||||
await process_settings(message)
|
||||
|
||||
|
||||
# add product
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), text=add_product)
|
||||
async def process_add_product(message: Message):
|
||||
|
||||
await ProductState.title.set()
|
||||
|
||||
markup = ReplyKeyboardMarkup(resize_keyboard=True)
|
||||
markup.add(cancel_message)
|
||||
|
||||
await message.answer('Название?', reply_markup=markup)
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), text=cancel_message, state=ProductState.title)
|
||||
async def process_cancel(message: Message, state: FSMContext):
|
||||
|
||||
await message.answer('Ок, отменено!', reply_markup=ReplyKeyboardRemove())
|
||||
await state.finish()
|
||||
|
||||
await process_settings(message)
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), text=back_message, state=ProductState.title)
|
||||
async def process_title_back(message: Message, state: FSMContext):
|
||||
await process_add_product(message)
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), state=ProductState.title)
|
||||
async def process_title(message: Message, state: FSMContext):
|
||||
|
||||
async with state.proxy() as data:
|
||||
data['title'] = message.text
|
||||
|
||||
await ProductState.next()
|
||||
await message.answer('Описание?', reply_markup=back_markup())
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), text=back_message, state=ProductState.body)
|
||||
async def process_body_back(message: Message, state: FSMContext):
|
||||
|
||||
await ProductState.title.set()
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
await message.answer(f"Изменить название с <b>{data['title']}</b>?", reply_markup=back_markup())
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), state=ProductState.body)
|
||||
async def process_body(message: Message, state: FSMContext):
|
||||
|
||||
async with state.proxy() as data:
|
||||
data['body'] = message.text
|
||||
|
||||
await ProductState.next()
|
||||
await message.answer('Фото?', reply_markup=back_markup())
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), content_types=ContentType.PHOTO, state=ProductState.image)
|
||||
async def process_image_photo(message: Message, state: FSMContext):
|
||||
|
||||
fileID = message.photo[-1].file_id
|
||||
file_info = await bot.get_file(fileID)
|
||||
downloaded_file = (await bot.download_file(file_info.file_path)).read()
|
||||
|
||||
async with state.proxy() as data:
|
||||
data['image'] = downloaded_file
|
||||
|
||||
await ProductState.next()
|
||||
await message.answer('Цена?', reply_markup=back_markup())
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), content_types=ContentType.TEXT, state=ProductState.image)
|
||||
async def process_image_url(message: Message, state: FSMContext):
|
||||
|
||||
if message.text == back_message:
|
||||
|
||||
await ProductState.body.set()
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
await message.answer(f"Изменить описание с <b>{data['body']}</b>?", reply_markup=back_markup())
|
||||
|
||||
else:
|
||||
|
||||
await message.answer('Вам нужно прислать фото товара.')
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), lambda message: not message.text.isdigit(), state=ProductState.price)
|
||||
async def process_price_invalid(message: Message, state: FSMContext):
|
||||
|
||||
if message.text == back_message:
|
||||
|
||||
await ProductState.image.set()
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
await message.answer("Другое изображение?", reply_markup=back_markup())
|
||||
|
||||
else:
|
||||
|
||||
await message.answer('Укажите цену в виде числа!')
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), lambda message: message.text.isdigit(), state=ProductState.price)
|
||||
async def process_price(message: Message, state: FSMContext):
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
data['price'] = message.text
|
||||
|
||||
title = data['title']
|
||||
body = data['body']
|
||||
price = data['price']
|
||||
|
||||
await ProductState.next()
|
||||
text = f'<b>{title}</b>\n\n{body}\n\nЦена: {price} рублей.'
|
||||
|
||||
markup = check_markup()
|
||||
|
||||
await message.answer_photo(photo=data['image'],
|
||||
caption=text,
|
||||
reply_markup=markup)
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), lambda message: message.text not in [back_message, all_right_message], state=ProductState.confirm)
|
||||
async def process_confirm_invalid(message: Message, state: FSMContext):
|
||||
await message.answer('Такого варианта не было.')
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), text=back_message, state=ProductState.confirm)
|
||||
async def process_confirm_back(message: Message, state: FSMContext):
|
||||
|
||||
await ProductState.price.set()
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
await message.answer(f"Изменить цену с <b>{data['price']}</b>?", reply_markup=back_markup())
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), text=all_right_message, state=ProductState.confirm)
|
||||
async def process_confirm(message: Message, state: FSMContext):
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
title = data['title']
|
||||
body = data['body']
|
||||
image = data['image']
|
||||
price = data['price']
|
||||
|
||||
tag = db.fetchone(
|
||||
'SELECT title FROM categories WHERE idx=?', (data['category_index'],))[0]
|
||||
idx = md5(' '.join([title, body, price, tag]
|
||||
).encode('utf-8')).hexdigest()
|
||||
|
||||
db.query('INSERT INTO products VALUES (?, ?, ?, ?, ?, ?)',
|
||||
(idx, title, body, image, int(price), tag))
|
||||
|
||||
await state.finish()
|
||||
await message.answer('Готово!', reply_markup=ReplyKeyboardRemove())
|
||||
await process_settings(message)
|
||||
|
||||
|
||||
# delete product
|
||||
|
||||
|
||||
@dp.callback_query_handler(IsAdmin(), product_cb.filter(action='delete'))
|
||||
async def delete_product_callback_handler(query: CallbackQuery, callback_data: dict):
|
||||
|
||||
product_idx = callback_data['id']
|
||||
db.query('DELETE FROM products WHERE idx=?', (product_idx,))
|
||||
await query.answer('Удалено!')
|
||||
await query.message.delete()
|
||||
|
||||
|
||||
async def show_products(m, products, category_idx):
|
||||
|
||||
await bot.send_chat_action(m.chat.id, ChatActions.TYPING)
|
||||
|
||||
for idx, title, body, image, price, tag in products:
|
||||
|
||||
text = f'<b>{title}</b>\n\n{body}\n\nЦена: {price} рублей.'
|
||||
|
||||
markup = InlineKeyboardMarkup()
|
||||
markup.add(InlineKeyboardButton(
|
||||
'🗑️ Удалить', callback_data=product_cb.new(id=idx, action='delete')))
|
||||
|
||||
await m.answer_photo(photo=image,
|
||||
caption=text,
|
||||
reply_markup=markup)
|
||||
|
||||
markup = ReplyKeyboardMarkup()
|
||||
markup.add(add_product)
|
||||
markup.add(delete_category)
|
||||
|
||||
await m.answer('Хотите что-нибудь добавить или удалить?', reply_markup=markup)
|
||||
22
doners/Shop-bot/handlers/admin/orders.py
Normal file
@@ -0,0 +1,22 @@
|
||||
|
||||
from aiogram.types import Message
|
||||
from loader import dp, db
|
||||
from doners.old_2.handlers import orders
|
||||
from filters import IsAdmin
|
||||
|
||||
@dp.message_handler(IsAdmin(), text=orders)
|
||||
async def process_orders(message: Message):
|
||||
|
||||
orders = db.fetchall('SELECT * FROM orders')
|
||||
|
||||
if len(orders) == 0: await message.answer('У вас нет заказов.')
|
||||
else: await order_answer(message, orders)
|
||||
|
||||
async def order_answer(message, orders):
|
||||
|
||||
res = ''
|
||||
|
||||
for order in orders:
|
||||
res += f'Заказ <b>№{order[3]}</b>\n\n'
|
||||
|
||||
await message.answer(res)
|
||||
78
doners/Shop-bot/handlers/admin/questions.py
Normal file
@@ -0,0 +1,78 @@
|
||||
|
||||
from doners.old_2.handlers import questions
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.utils.callback_data import CallbackData
|
||||
from keyboards.default.markups import all_right_message, cancel_message, submit_markup
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton, ReplyKeyboardRemove
|
||||
from aiogram.types.chat import ChatActions
|
||||
from states import AnswerState
|
||||
from loader import dp, db, bot
|
||||
from filters import IsAdmin
|
||||
|
||||
question_cb = CallbackData('question', 'cid', 'action')
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), text=questions)
|
||||
async def process_questions(message: Message):
|
||||
|
||||
await bot.send_chat_action(message.chat.id, ChatActions.TYPING)
|
||||
questions = db.fetchall('SELECT * FROM questions')
|
||||
|
||||
if len(questions) == 0:
|
||||
|
||||
await message.answer('Нет вопросов.')
|
||||
|
||||
else:
|
||||
|
||||
for cid, question in questions:
|
||||
|
||||
markup = InlineKeyboardMarkup()
|
||||
markup.add(InlineKeyboardButton(
|
||||
'Ответить', callback_data=question_cb.new(cid=cid, action='answer')))
|
||||
|
||||
await message.answer(question, reply_markup=markup)
|
||||
|
||||
|
||||
@dp.callback_query_handler(IsAdmin(), question_cb.filter(action='answer'))
|
||||
async def process_answer(query: CallbackQuery, callback_data: dict, state: FSMContext):
|
||||
|
||||
async with state.proxy() as data:
|
||||
data['cid'] = callback_data['cid']
|
||||
|
||||
await query.message.answer('Напиши ответ.', reply_markup=ReplyKeyboardRemove())
|
||||
await AnswerState.answer.set()
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), state=AnswerState.answer)
|
||||
async def process_submit(message: Message, state: FSMContext):
|
||||
|
||||
async with state.proxy() as data:
|
||||
data['answer'] = message.text
|
||||
|
||||
await AnswerState.next()
|
||||
await message.answer('Убедитесь, что не ошиблись в ответе.', reply_markup=submit_markup())
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), text=cancel_message, state=AnswerState.submit)
|
||||
async def process_send_answer(message: Message, state: FSMContext):
|
||||
await message.answer('Отменено!', reply_markup=ReplyKeyboardRemove())
|
||||
await state.finish()
|
||||
|
||||
|
||||
@dp.message_handler(IsAdmin(), text=all_right_message, state=AnswerState.submit)
|
||||
async def process_send_answer(message: Message, state: FSMContext):
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
answer = data['answer']
|
||||
cid = data['cid']
|
||||
|
||||
question = db.fetchone(
|
||||
'SELECT question FROM questions WHERE cid=?', (cid,))[0]
|
||||
db.query('DELETE FROM questions WHERE cid=?', (cid,))
|
||||
text = f'Вопрос: <b>{question}</b>\n\nОтвет: <b>{answer}</b>'
|
||||
|
||||
await message.answer('Отправлено!', reply_markup=ReplyKeyboardRemove())
|
||||
await bot.send_message(cid, text)
|
||||
|
||||
await state.finish()
|
||||
8
doners/Shop-bot/handlers/user/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .menu import dp
|
||||
from .cart import dp
|
||||
from .wallet import dp
|
||||
from .catalog import dp
|
||||
from .delivery_status import dp
|
||||
from .sos import dp
|
||||
|
||||
__all__ = ['dp']
|
||||
247
doners/Shop-bot/handlers/user/cart.py
Normal file
@@ -0,0 +1,247 @@
|
||||
import logging
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.types import Message, CallbackQuery, ReplyKeyboardMarkup, ReplyKeyboardRemove, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from keyboards.inline.products_from_cart import product_markup, product_cb
|
||||
from aiogram.utils.callback_data import CallbackData
|
||||
from keyboards.default.markups import *
|
||||
from aiogram.types.chat import ChatActions
|
||||
from states import CheckoutState
|
||||
from loader import dp, db, bot
|
||||
from filters import IsUser
|
||||
from .menu import cart
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), text=cart)
|
||||
async def process_cart(message: Message, state: FSMContext):
|
||||
|
||||
cart_data = db.fetchall(
|
||||
'SELECT * FROM cart WHERE cid=?', (message.chat.id,))
|
||||
|
||||
if len(cart_data) == 0:
|
||||
|
||||
await message.answer('Ваша корзина пуста.')
|
||||
|
||||
else:
|
||||
|
||||
await bot.send_chat_action(message.chat.id, ChatActions.TYPING)
|
||||
async with state.proxy() as data:
|
||||
data['products'] = {}
|
||||
|
||||
order_cost = 0
|
||||
|
||||
for _, idx, count_in_cart in cart_data:
|
||||
|
||||
product = db.fetchone('SELECT * FROM products WHERE idx=?', (idx,))
|
||||
|
||||
if product == None:
|
||||
|
||||
db.query('DELETE FROM cart WHERE idx=?', (idx,))
|
||||
|
||||
else:
|
||||
_, title, body, image, price, _ = product
|
||||
order_cost += price
|
||||
|
||||
async with state.proxy() as data:
|
||||
data['products'][idx] = [title, price, count_in_cart]
|
||||
|
||||
markup = product_markup(idx, count_in_cart)
|
||||
text = f'<b>{title}</b>\n\n{body}\n\nЦена: {price}₽.'
|
||||
|
||||
await message.answer_photo(photo=image,
|
||||
caption=text,
|
||||
reply_markup=markup)
|
||||
|
||||
if order_cost != 0:
|
||||
markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True)
|
||||
markup.add('📦 Оформить заказ')
|
||||
|
||||
await message.answer('Перейти к оформлению?',
|
||||
reply_markup=markup)
|
||||
|
||||
|
||||
@dp.callback_query_handler(IsUser(), product_cb.filter(action='count'))
|
||||
@dp.callback_query_handler(IsUser(), product_cb.filter(action='increase'))
|
||||
@dp.callback_query_handler(IsUser(), product_cb.filter(action='decrease'))
|
||||
async def product_callback_handler(query: CallbackQuery, callback_data: dict, state: FSMContext):
|
||||
|
||||
idx = callback_data['id']
|
||||
action = callback_data['action']
|
||||
|
||||
if 'count' == action:
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
if 'products' not in data.keys():
|
||||
|
||||
await process_cart(query.message, state)
|
||||
|
||||
else:
|
||||
|
||||
await query.answer('Количество - ' + data['products'][idx][2])
|
||||
|
||||
else:
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
if 'products' not in data.keys():
|
||||
|
||||
await process_cart(query.message, state)
|
||||
|
||||
else:
|
||||
|
||||
data['products'][idx][2] += 1 if 'increase' == action else -1
|
||||
count_in_cart = data['products'][idx][2]
|
||||
|
||||
if count_in_cart == 0:
|
||||
|
||||
db.query('''DELETE FROM cart
|
||||
WHERE cid = ? AND idx = ?''', (query.message.chat.id, idx))
|
||||
|
||||
await query.message.delete()
|
||||
else:
|
||||
|
||||
db.query('''UPDATE cart
|
||||
SET quantity = ?
|
||||
WHERE cid = ? AND idx = ?''', (count_in_cart, query.message.chat.id, idx))
|
||||
|
||||
await query.message.edit_reply_markup(product_markup(idx, count_in_cart))
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), text='📦 Оформить заказ')
|
||||
async def process_checkout(message: Message, state: FSMContext):
|
||||
|
||||
await CheckoutState.check_cart.set()
|
||||
await checkout(message, state)
|
||||
|
||||
|
||||
async def checkout(message, state):
|
||||
answer = ''
|
||||
total_price = 0
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
for title, price, count_in_cart in data['products'].values():
|
||||
|
||||
tp = count_in_cart * price
|
||||
answer += f'<b>{title}</b> * {count_in_cart}шт. = {tp}₽\n'
|
||||
total_price += tp
|
||||
|
||||
await message.answer(f'{answer}\nОбщая сумма заказа: {total_price}₽.',
|
||||
reply_markup=check_markup())
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), lambda message: message.text not in [all_right_message, back_message], state=CheckoutState.check_cart)
|
||||
async def process_check_cart_invalid(message: Message):
|
||||
await message.reply('Такого варианта не было.')
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), text=back_message, state=CheckoutState.check_cart)
|
||||
async def process_check_cart_back(message: Message, state: FSMContext):
|
||||
await state.finish()
|
||||
await process_cart(message, state)
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), text=all_right_message, state=CheckoutState.check_cart)
|
||||
async def process_check_cart_all_right(message: Message, state: FSMContext):
|
||||
await CheckoutState.next()
|
||||
await message.answer('Укажите свое имя.',
|
||||
reply_markup=back_markup())
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), text=back_message, state=CheckoutState.name)
|
||||
async def process_name_back(message: Message, state: FSMContext):
|
||||
await CheckoutState.check_cart.set()
|
||||
await checkout(message, state)
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), state=CheckoutState.name)
|
||||
async def process_name(message: Message, state: FSMContext):
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
data['name'] = message.text
|
||||
|
||||
if 'address' in data.keys():
|
||||
|
||||
await confirm(message)
|
||||
await CheckoutState.confirm.set()
|
||||
|
||||
else:
|
||||
|
||||
await CheckoutState.next()
|
||||
await message.answer('Укажите свой адрес места жительства.',
|
||||
reply_markup=back_markup())
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), text=back_message, state=CheckoutState.address)
|
||||
async def process_address_back(message: Message, state: FSMContext):
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
await message.answer('Изменить имя с <b>' + data['name'] + '</b>?',
|
||||
reply_markup=back_markup())
|
||||
|
||||
await CheckoutState.name.set()
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), state=CheckoutState.address)
|
||||
async def process_address(message: Message, state: FSMContext):
|
||||
|
||||
async with state.proxy() as data:
|
||||
data['address'] = message.text
|
||||
|
||||
await confirm(message)
|
||||
await CheckoutState.next()
|
||||
|
||||
|
||||
async def confirm(message):
|
||||
|
||||
await message.answer('Убедитесь, что все правильно оформлено и подтвердите заказ.',
|
||||
reply_markup=confirm_markup())
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), lambda message: message.text not in [confirm_message, back_message], state=CheckoutState.confirm)
|
||||
async def process_confirm_invalid(message: Message):
|
||||
await message.reply('Такого варианта не было.')
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), text=back_message, state=CheckoutState.confirm)
|
||||
async def process_confirm(message: Message, state: FSMContext):
|
||||
|
||||
await CheckoutState.address.set()
|
||||
|
||||
async with state.proxy() as data:
|
||||
await message.answer('Изменить адрес с <b>' + data['address'] + '</b>?',
|
||||
reply_markup=back_markup())
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), text=confirm_message, state=CheckoutState.confirm)
|
||||
async def process_confirm(message: Message, state: FSMContext):
|
||||
|
||||
enough_money = True # enough money on the balance sheet
|
||||
markup = ReplyKeyboardRemove()
|
||||
|
||||
if enough_money:
|
||||
|
||||
logging.info('Deal was made.')
|
||||
|
||||
async with state.proxy() as data:
|
||||
|
||||
cid = message.chat.id
|
||||
products = [idx + '=' + str(quantity)
|
||||
for idx, quantity in db.fetchall('''SELECT idx, quantity FROM cart
|
||||
WHERE cid=?''', (cid,))] # idx=quantity
|
||||
|
||||
db.query('INSERT INTO orders VALUES (?, ?, ?, ?)',
|
||||
(cid, data['name'], data['address'], ' '.join(products)))
|
||||
|
||||
db.query('DELETE FROM cart WHERE cid=?', (cid,))
|
||||
|
||||
await message.answer('Ок! Ваш заказ уже в пути 🚀\nИмя: <b>' + data['name'] + '</b>\nАдрес: <b>' + data['address'] + '</b>',
|
||||
reply_markup=markup)
|
||||
else:
|
||||
|
||||
await message.answer('У вас недостаточно денег на счете. Пополните баланс!',
|
||||
reply_markup=markup)
|
||||
|
||||
await state.finish()
|
||||
58
doners/Shop-bot/handlers/user/catalog.py
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
import logging
|
||||
from aiogram.types import Message, CallbackQuery
|
||||
from keyboards.inline.categories import categories_markup, category_cb
|
||||
from keyboards.inline.products_from_catalog import product_markup, product_cb
|
||||
from aiogram.utils.callback_data import CallbackData
|
||||
from aiogram.types.chat import ChatActions
|
||||
from loader import dp, db, bot
|
||||
from .menu import catalog
|
||||
from filters import IsUser
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), text=catalog)
|
||||
async def process_catalog(message: Message):
|
||||
await message.answer('Выберите раздел, чтобы вывести список товаров:',
|
||||
reply_markup=categories_markup())
|
||||
|
||||
|
||||
@dp.callback_query_handler(IsUser(), category_cb.filter(action='view'))
|
||||
async def category_callback_handler(query: CallbackQuery, callback_data: dict):
|
||||
|
||||
products = db.fetchall('''SELECT * FROM products product
|
||||
WHERE product.tag = (SELECT title FROM categories WHERE idx=?)
|
||||
AND product.idx NOT IN (SELECT idx FROM cart WHERE cid = ?)''',
|
||||
(callback_data['id'], query.message.chat.id))
|
||||
|
||||
await query.answer('Все доступные товары.')
|
||||
await show_products(query.message, products)
|
||||
|
||||
|
||||
@dp.callback_query_handler(IsUser(), product_cb.filter(action='add'))
|
||||
async def add_product_callback_handler(query: CallbackQuery, callback_data: dict):
|
||||
|
||||
db.query('INSERT INTO cart VALUES (?, ?, 1)',
|
||||
(query.message.chat.id, callback_data['id']))
|
||||
|
||||
await query.answer('Товар добавлен в корзину!')
|
||||
await query.message.delete()
|
||||
|
||||
|
||||
async def show_products(m, products):
|
||||
|
||||
if len(products) == 0:
|
||||
|
||||
await m.answer('Здесь ничего нет 😢')
|
||||
|
||||
else:
|
||||
|
||||
await bot.send_chat_action(m.chat.id, ChatActions.TYPING)
|
||||
|
||||
for idx, title, body, image, price, _ in products:
|
||||
|
||||
markup = product_markup(idx, price)
|
||||
text = f'<b>{title}</b>\n\n{body}'
|
||||
|
||||
await m.answer_photo(photo=image,
|
||||
caption=text,
|
||||
reply_markup=markup)
|
||||
31
doners/Shop-bot/handlers/user/delivery_status.py
Normal file
@@ -0,0 +1,31 @@
|
||||
|
||||
from aiogram.types import Message
|
||||
from loader import dp, db
|
||||
from .menu import delivery_status
|
||||
from filters import IsUser
|
||||
|
||||
@dp.message_handler(IsUser(), text=delivery_status)
|
||||
async def process_delivery_status(message: Message):
|
||||
|
||||
orders = db.fetchall('SELECT * FROM orders WHERE cid=?', (message.chat.id,))
|
||||
|
||||
if len(orders) == 0: await message.answer('У вас нет активных заказов.')
|
||||
else: await delivery_status_answer(message, orders)
|
||||
|
||||
async def delivery_status_answer(message, orders):
|
||||
|
||||
res = ''
|
||||
|
||||
for order in orders:
|
||||
|
||||
res += f'Заказ <b>№{order[3]}</b>'
|
||||
answer = [
|
||||
' лежит на складе.',
|
||||
' уже в пути!',
|
||||
' прибыл и ждет вас на почте!'
|
||||
]
|
||||
|
||||
res += answer[0]
|
||||
res += '\n\n'
|
||||
|
||||
await message.answer(res)
|
||||
30
doners/Shop-bot/handlers/user/menu.py
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
from aiogram.types import Message, CallbackQuery, ReplyKeyboardMarkup
|
||||
from loader import dp
|
||||
from filters import IsAdmin, IsUser
|
||||
|
||||
catalog = '🛍️ Каталог'
|
||||
balance = '💰 Баланс'
|
||||
cart = '🛒 Корзина'
|
||||
delivery_status = '🚚 Статус заказа'
|
||||
|
||||
settings = '⚙️ Настройка каталога'
|
||||
orders = '🚚 Заказы'
|
||||
questions = '❓ Вопросы'
|
||||
|
||||
@dp.message_handler(IsAdmin(), commands='menu')
|
||||
async def admin_menu(message: Message):
|
||||
markup = ReplyKeyboardMarkup(selective=True)
|
||||
markup.add(settings)
|
||||
markup.add(questions, orders)
|
||||
|
||||
await message.answer('Меню', reply_markup=markup)
|
||||
|
||||
@dp.message_handler(IsUser(), commands='menu')
|
||||
async def user_menu(message: Message):
|
||||
markup = ReplyKeyboardMarkup(selective=True)
|
||||
markup.add(catalog)
|
||||
markup.add(balance, cart)
|
||||
markup.add(delivery_status)
|
||||
|
||||
await message.answer('Меню', reply_markup=markup)
|
||||
54
doners/Shop-bot/handlers/user/sos.py
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.types import ReplyKeyboardMarkup, ReplyKeyboardRemove
|
||||
from keyboards.default.markups import all_right_message, cancel_message, submit_markup
|
||||
from aiogram.types import Message
|
||||
from states import SosState
|
||||
from filters import IsUser
|
||||
from loader import dp, db
|
||||
|
||||
|
||||
@dp.message_handler(commands='sos')
|
||||
async def cmd_sos(message: Message):
|
||||
await SosState.question.set()
|
||||
await message.answer('В чем суть проблемы? Опишите как можно детальнее и администратор обязательно вам ответит.', reply_markup=ReplyKeyboardRemove())
|
||||
|
||||
|
||||
@dp.message_handler(state=SosState.question)
|
||||
async def process_question(message: Message, state: FSMContext):
|
||||
async with state.proxy() as data:
|
||||
data['question'] = message.text
|
||||
|
||||
await message.answer('Убедитесь, что все верно.', reply_markup=submit_markup())
|
||||
await SosState.next()
|
||||
|
||||
|
||||
@dp.message_handler(lambda message: message.text not in [cancel_message, all_right_message], state=SosState.submit)
|
||||
async def process_price_invalid(message: Message):
|
||||
await message.answer('Такого варианта не было.')
|
||||
|
||||
|
||||
@dp.message_handler(text=cancel_message, state=SosState.submit)
|
||||
async def process_cancel(message: Message, state: FSMContext):
|
||||
await message.answer('Отменено!', reply_markup=ReplyKeyboardRemove())
|
||||
await state.finish()
|
||||
|
||||
|
||||
@dp.message_handler(text=all_right_message, state=SosState.submit)
|
||||
async def process_submit(message: Message, state: FSMContext):
|
||||
|
||||
cid = message.chat.id
|
||||
|
||||
if db.fetchone('SELECT * FROM questions WHERE cid=?', (cid,)) == None:
|
||||
|
||||
async with state.proxy() as data:
|
||||
db.query('INSERT INTO questions VALUES (?, ?)',
|
||||
(cid, data['question']))
|
||||
|
||||
await message.answer('Отправлено!', reply_markup=ReplyKeyboardRemove())
|
||||
|
||||
else:
|
||||
|
||||
await message.answer('Превышен лимит на количество задаваемых вопросов.', reply_markup=ReplyKeyboardRemove())
|
||||
|
||||
await state.finish()
|
||||
18
doners/Shop-bot/handlers/user/wallet.py
Normal file
@@ -0,0 +1,18 @@
|
||||
|
||||
from loader import dp
|
||||
from aiogram.dispatcher import FSMContext
|
||||
from aiogram.types import Message
|
||||
from filters import IsUser
|
||||
from .menu import balance
|
||||
|
||||
# test card ==> 1111 1111 1111 1026, 12/22, CVC 000
|
||||
|
||||
# shopId 506751
|
||||
|
||||
# shopArticleId 538350
|
||||
|
||||
|
||||
@dp.message_handler(IsUser(), text=balance)
|
||||
async def process_balance(message: Message, state: FSMContext):
|
||||
await message.answer('Ваш кошелек пуст! Чтобы его пополнить нужно...')
|
||||
|
||||
2
doners/Shop-bot/keyboards/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from . import inline
|
||||
from . import default
|
||||
1
doners/Shop-bot/keyboards/default/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import markups
|
||||
31
doners/Shop-bot/keyboards/default/markups.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from aiogram.types import ReplyKeyboardMarkup
|
||||
|
||||
back_message = '👈 Назад'
|
||||
confirm_message = '✅ Подтвердить заказ'
|
||||
all_right_message = '✅ Все верно'
|
||||
cancel_message = '🚫 Отменить'
|
||||
|
||||
def confirm_markup():
|
||||
markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True)
|
||||
markup.add(confirm_message)
|
||||
markup.add(back_message)
|
||||
|
||||
return markup
|
||||
|
||||
def back_markup():
|
||||
markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True)
|
||||
markup.add(back_message)
|
||||
|
||||
return markup
|
||||
|
||||
def check_markup():
|
||||
markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True)
|
||||
markup.row(back_message, all_right_message)
|
||||
|
||||
return markup
|
||||
|
||||
def submit_markup():
|
||||
markup = ReplyKeyboardMarkup(resize_keyboard=True, selective=True)
|
||||
markup.row(cancel_message, all_right_message)
|
||||
|
||||
return markup
|
||||
3
doners/Shop-bot/keyboards/inline/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from . import products_from_catalog
|
||||
from . import products_from_cart
|
||||
from . import categories
|
||||
16
doners/Shop-bot/keyboards/inline/categories.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.utils.callback_data import CallbackData
|
||||
from loader import db
|
||||
|
||||
category_cb = CallbackData('category', 'id', 'action')
|
||||
|
||||
|
||||
def categories_markup():
|
||||
|
||||
global category_cb
|
||||
|
||||
markup = InlineKeyboardMarkup()
|
||||
for idx, title in db.fetchall('SELECT * FROM categories'):
|
||||
markup.add(InlineKeyboardButton(title, callback_data=category_cb.new(id=idx, action='view')))
|
||||
|
||||
return markup
|
||||
16
doners/Shop-bot/keyboards/inline/products_from_cart.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.utils.callback_data import CallbackData
|
||||
|
||||
product_cb = CallbackData('product', 'id', 'action')
|
||||
|
||||
def product_markup(idx, count):
|
||||
|
||||
global product_cb
|
||||
|
||||
markup = InlineKeyboardMarkup()
|
||||
back_btn = InlineKeyboardButton('⬅️', callback_data=product_cb.new(id=idx, action='decrease'))
|
||||
count_btn = InlineKeyboardButton(count, callback_data=product_cb.new(id=idx, action='count'))
|
||||
next_btn = InlineKeyboardButton('➡️', callback_data=product_cb.new(id=idx, action='increase'))
|
||||
markup.row(back_btn, count_btn, next_btn)
|
||||
|
||||
return markup
|
||||
15
doners/Shop-bot/keyboards/inline/products_from_catalog.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.utils.callback_data import CallbackData
|
||||
from loader import db
|
||||
|
||||
product_cb = CallbackData('product', 'id', 'action')
|
||||
|
||||
|
||||
def product_markup(idx='', price=0):
|
||||
|
||||
global product_cb
|
||||
|
||||
markup = InlineKeyboardMarkup()
|
||||
markup.add(InlineKeyboardButton(f'Добавить в корзину - {price}₽', callback_data=product_cb.new(id=idx, action='add')))
|
||||
|
||||
return markup
|
||||
10
doners/Shop-bot/loader.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.contrib.fsm_storage.memory import MemoryStorage
|
||||
from utils.db.storage import DatabaseManager
|
||||
|
||||
from data import config
|
||||
|
||||
bot = Bot(token=config.BOT_TOKEN, parse_mode=types.ParseMode.HTML)
|
||||
storage = MemoryStorage()
|
||||
dp = Dispatcher(bot, storage=storage)
|
||||
db = DatabaseManager('data/database.db')
|
||||
1
doners/Shop-bot/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
aiogram==2.9.2
|
||||
3
doners/Shop-bot/states/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .checkout_state import CheckoutState
|
||||
from .product_state import ProductState, CategoryState
|
||||
from .sos_state import SosState, AnswerState
|
||||
7
doners/Shop-bot/states/checkout_state.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from aiogram.dispatcher.filters.state import StatesGroup, State
|
||||
|
||||
class CheckoutState(StatesGroup):
|
||||
check_cart = State()
|
||||
name = State()
|
||||
address = State()
|
||||
confirm = State()
|
||||
11
doners/Shop-bot/states/product_state.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from aiogram.dispatcher.filters.state import StatesGroup, State
|
||||
|
||||
class ProductState(StatesGroup):
|
||||
title = State()
|
||||
body = State()
|
||||
image = State()
|
||||
price = State()
|
||||
confirm = State()
|
||||
|
||||
class CategoryState(StatesGroup):
|
||||
title = State()
|
||||
9
doners/Shop-bot/states/sos_state.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from aiogram.dispatcher.filters.state import StatesGroup, State
|
||||
|
||||
class SosState(StatesGroup):
|
||||
question = State()
|
||||
submit = State()
|
||||
|
||||
class AnswerState(StatesGroup):
|
||||
answer = State()
|
||||
submit = State()
|
||||
1
doners/Shop-bot/utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import db
|
||||
1
doners/Shop-bot/utils/db/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .storage import DatabaseManager
|
||||
59
doners/Shop-bot/utils/db/storage.py
Normal file
@@ -0,0 +1,59 @@
|
||||
|
||||
import sqlite3 as lite
|
||||
|
||||
class DatabaseManager(object):
|
||||
|
||||
def __init__(self, path):
|
||||
self.conn = lite.connect(path)
|
||||
self.conn.execute('pragma foreign_keys = on')
|
||||
self.conn.commit()
|
||||
self.cur = self.conn.cursor()
|
||||
|
||||
def create_tables(self):
|
||||
self.query('CREATE TABLE IF NOT EXISTS products (idx text, title text, body text, photo blob, price int, tag text)')
|
||||
self.query('CREATE TABLE IF NOT EXISTS orders (cid int, usr_name text, usr_address text, products text)')
|
||||
self.query('CREATE TABLE IF NOT EXISTS cart (cid int, idx text, quantity int)')
|
||||
self.query('CREATE TABLE IF NOT EXISTS categories (idx text, title text)')
|
||||
self.query('CREATE TABLE IF NOT EXISTS wallet (cid int, balance real)')
|
||||
self.query('CREATE TABLE IF NOT EXISTS questions (cid int, question text)')
|
||||
|
||||
def query(self, arg, values=None):
|
||||
if values == None:
|
||||
self.cur.execute(arg)
|
||||
else:
|
||||
self.cur.execute(arg, values)
|
||||
self.conn.commit()
|
||||
|
||||
def fetchone(self, arg, values=None):
|
||||
if values == None:
|
||||
self.cur.execute(arg)
|
||||
else:
|
||||
self.cur.execute(arg, values)
|
||||
return self.cur.fetchone()
|
||||
|
||||
def fetchall(self, arg, values=None):
|
||||
if values == None:
|
||||
self.cur.execute(arg)
|
||||
else:
|
||||
self.cur.execute(arg, values)
|
||||
return self.cur.fetchall()
|
||||
|
||||
def __del__(self):
|
||||
self.conn.close()
|
||||
|
||||
|
||||
'''
|
||||
|
||||
products: idx text, title text, body text, photo blob, price int, tag text
|
||||
|
||||
orders: cid int, usr_name text, usr_address text, products text
|
||||
|
||||
cart: cid int, idx text, quantity int ==> product_idx
|
||||
|
||||
categories: idx text, title text
|
||||
|
||||
wallet: cid int, balance real
|
||||
|
||||
questions: cid int, question text
|
||||
|
||||
'''
|
||||
0
doners/__init__.py
Normal file
0
doners/bot-market/__init__.py
Normal file
0
doners/bot-market/app/__init__.py
Normal file
0
doners/bot-market/app/database/__init__.py
Normal file
0
doners/old/__init__.py
Normal file
23
doners/shop-telegram-bot/.dockerignore
Normal file
@@ -0,0 +1,23 @@
|
||||
/images
|
||||
data.db
|
||||
*.pyc
|
||||
*.pyd
|
||||
sticker.tgs
|
||||
config.ini
|
||||
src/test.py
|
||||
aws.sh
|
||||
KP.pem
|
||||
ideas.txt
|
||||
/tmp
|
||||
DOCS/overview.md
|
||||
src/modules/
|
||||
backups/
|
||||
logs/
|
||||
DOCS/
|
||||
.github/
|
||||
.git/
|
||||
tests/
|
||||
LICENCE
|
||||
readme.md
|
||||
start.cmd
|
||||
start.sh
|
||||
2
doners/shop-telegram-bot/.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
Monero: 43fxouHQFZ5guiyYSUWh6eL1ZQ7pDtVV8D1kKUzp4aYwQBgLjyY8q7KjeEbDvvTYCDPFEdDxz9duBdRrZPnjiSMwVGV4jZj
|
||||
Bitcoin: bc1qnzft2cfty0hqptxjx9ajk4m2q9x3a30gvpylh2
|
||||
27
doners/shop-telegram-bot/.github/ISSUE_TEMPLATE/bug-report.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Помогите нам стать лучше
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
assignees: w1png
|
||||
|
||||
---
|
||||
|
||||
**Опишите ошибку**
|
||||
Краткое и понятное описание проблемы.
|
||||
|
||||
**Как воспроизвести ошибку**
|
||||
1. Откройте '...'
|
||||
2. Нажмите на '....'
|
||||
3. Пролистайте до '....'
|
||||
4. Ошибка
|
||||
|
||||
**Ожидаемое поведение**
|
||||
Краткое и понятное описание ожидаемого поведения.
|
||||
|
||||
**Скриншоты**
|
||||
Прикрепите скриншот с проблемой.
|
||||
|
||||
**Информация о системе:**
|
||||
- Операционная система
|
||||
- Версия Python
|
||||
15
doners/shop-telegram-bot/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
/images
|
||||
data.db
|
||||
*.pyc
|
||||
*.pyd
|
||||
sticker.tgs
|
||||
config.ini
|
||||
src/test.py
|
||||
aws.sh
|
||||
KP.pem
|
||||
ideas.txt
|
||||
/tmp
|
||||
DOCS/overview.md
|
||||
src/modules/
|
||||
backups/
|
||||
logs/
|
||||
BIN
doners/shop-telegram-bot/DOCS/bot_overview.gif
Normal file
|
After Width: | Height: | Size: 11 MiB |
7
doners/shop-telegram-bot/Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
||||
FROM python:3.10-slim-buster
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
RUN python3 -m pip install -r requirements.txt
|
||||
CMD python3 installer.py --nointeract && python3 src/main.py
|
||||
674
doners/shop-telegram-bot/LICENSE
Normal file
@@ -0,0 +1,674 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
0
doners/shop-telegram-bot/__init__.py
Normal file
174
doners/shop-telegram-bot/installer.py
Normal file
@@ -0,0 +1,174 @@
|
||||
from os import system, name, remove, mkdir, rmdir, listdir, environ
|
||||
from os.path import exists
|
||||
from sys import argv
|
||||
|
||||
import sqlite3
|
||||
|
||||
|
||||
def clearConsole():
|
||||
system("cls" if name in ("nt", "dos") else "clear")
|
||||
|
||||
def create_config(token, main_admin_id, config_path="config.ini"):
|
||||
DEFAULT_CONFIG_TEXT = f"""[main_settings]
|
||||
token = {token}
|
||||
mainadminid = {main_admin_id}
|
||||
debug = 0
|
||||
|
||||
[shop_settings]
|
||||
name = Название магазина
|
||||
greeting = Добро пожаловать!
|
||||
refundpolicy = Текст для вкладки "Политика возврата"
|
||||
contacts = Текст для вкладки "Контакты"
|
||||
enableimage = 1
|
||||
enablesticker = 0
|
||||
enablephonenumber = 0
|
||||
enabledelivery = 0
|
||||
delivery_price = 0.0
|
||||
enablecaptcha = 1
|
||||
|
||||
[stats_settings]
|
||||
barcolor = 3299ff
|
||||
borderwidth = 1
|
||||
titlefontsize = 20
|
||||
axisfontsize = 12
|
||||
tickfontsize = 8
|
||||
"""
|
||||
with open(config_path, "w") as config:
|
||||
config.write(DEFAULT_CONFIG_TEXT)
|
||||
|
||||
|
||||
CREATE_CATS_TEXT = """
|
||||
CREATE TABLE "cats" (
|
||||
"id" INTEGER,
|
||||
"name" TEXT NOT NULL,
|
||||
PRIMARY KEY("id")
|
||||
)
|
||||
"""
|
||||
CREATE_ITEMS_TEXT = """
|
||||
CREATE TABLE "items" (
|
||||
"id" INTEGER,
|
||||
"name" TEXT NOT NULL,
|
||||
"price" FLOAT NOT NULL,
|
||||
"cat_id" INTEGER NOT NULL,
|
||||
"desc" TEXT,
|
||||
"active" INTEGER,
|
||||
"amount" INTEGER,
|
||||
"image_id" INTEGER,
|
||||
"hide_image" INTEGER,
|
||||
PRIMARY KEY("id")
|
||||
)
|
||||
"""
|
||||
CREATE_ORDERS_TEXT = """
|
||||
CREATE TABLE "orders" (
|
||||
"order_id" INTEGER,
|
||||
"user_id" INTEGER,
|
||||
"item_list" TEXT,
|
||||
"email_adress" TEXT,
|
||||
"phone_number" TEXT,
|
||||
"home_adress" TEXT,
|
||||
"additional_message" TEXT,
|
||||
"date" TEXT,
|
||||
"status" INTEGER
|
||||
)
|
||||
"""
|
||||
CREATE_USERS_TEXT = """
|
||||
CREATE TABLE "users" (
|
||||
"user_id" INTEGER NOT NULL,
|
||||
"is_admin" INTEGER,
|
||||
"is_manager" INTEGER,
|
||||
"notification" INTEGER,
|
||||
"date_created" TEXT,
|
||||
"cart" TEXT,
|
||||
"cart_delivery" INTEGER
|
||||
)
|
||||
"""
|
||||
CREATE_COMMANDS_TEXT = """
|
||||
CREATE TABLE "commands" (
|
||||
"id" INTEGER NOT NULL,
|
||||
"command" TEXT,
|
||||
"response" TEXT,
|
||||
PRIMARY KEY("id")
|
||||
)
|
||||
"""
|
||||
|
||||
def create_db():
|
||||
conn = sqlite3.connect("data.db")
|
||||
c = conn.cursor()
|
||||
c.execute(CREATE_CATS_TEXT)
|
||||
c.execute(CREATE_ITEMS_TEXT)
|
||||
c.execute(CREATE_ORDERS_TEXT)
|
||||
c.execute(CREATE_USERS_TEXT)
|
||||
c.execute(CREATE_COMMANDS_TEXT)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def do_files_exist():
|
||||
return any(list(map(exists, ["config.ini", "images", "data.db"])))
|
||||
|
||||
if __name__ == "__main__":
|
||||
if "--nointeract" in argv:
|
||||
if do_files_exist():
|
||||
exit(0)
|
||||
|
||||
token = environ.get("TELEGRAM_TOKEN")
|
||||
main_admin_id = environ.get("MAIN_ADMIN_ID")
|
||||
if token is None or main_admin_id is None:
|
||||
print("Не указаны переменные окружения TELEGRAM_TOKEN или MAIN_ADMIN_ID")
|
||||
exit(1)
|
||||
create_config(token, main_admin_id)
|
||||
|
||||
create_db()
|
||||
[mkdir(name) for name in ["backups", "images"]]
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
clearConsole()
|
||||
if do_files_exist():
|
||||
while True:
|
||||
confirmation = input("Вы уверены, что хотите повторно запустить процесс установки? Все данные будут утеряны! (y/N) ")
|
||||
if confirmation.lower() in ["y", "yes", "n", "no", ""]:
|
||||
break
|
||||
else:
|
||||
confirmation = "y"
|
||||
|
||||
|
||||
if confirmation.lower() in ["y", "yes"]:
|
||||
print("Вы можете узнать как получить токен бота, перейдя по ссылке: https://youtu.be/fyISLEvzIec")
|
||||
token = input("Введите токен бота: ")
|
||||
print("Вы можете получить ваш ID, написав \"/start\" боту @userinfobot")
|
||||
main_admin_id = input("Введите ID главного администратора: ")
|
||||
if main_admin_id.isalnum():
|
||||
if exists("data.db"):
|
||||
remove("data.db")
|
||||
print("База данных была удалена.")
|
||||
create_db()
|
||||
print("База данных была создана.")
|
||||
if exists("config.ini"):
|
||||
remove("config.ini")
|
||||
print("Файл настроек был удален.")
|
||||
create_config(token, main_admin_id)
|
||||
print("Файл настроек был создан.")
|
||||
if exists("images"):
|
||||
for file in listdir("images"):
|
||||
remove("images/" + file)
|
||||
rmdir("images")
|
||||
print("Папка \"images\" была удалена.")
|
||||
mkdir("images")
|
||||
print("Папка \"images\" была создана.")
|
||||
if exists("backups"):
|
||||
for folder in listdir("backups"):
|
||||
for file in listdir("backups/" + folder):
|
||||
remove(f"backups/{folder}/{file}")
|
||||
rmdir(f"backups/{folder}")
|
||||
rmdir("backups")
|
||||
print("Папка \"backups\" была удалена.")
|
||||
mkdir("backups")
|
||||
print("Папка \"backups\" была создана.")
|
||||
else:
|
||||
print("Неверный ID главного администратора.")
|
||||
else:
|
||||
print("Установка была отменена.")
|
||||
|
||||
|
||||
input("Нажмите ENTER, чтобы продолжить...")
|
||||
83
doners/shop-telegram-bot/readme.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# ⚠️ Эта версия бота больше не обновляется!
|
||||
Сейчас я переписываю бота, за обновлениями следите на ветке rewrite :)
|
||||
<br>
|
||||
|
||||
## 🪲 В случае возникновения ошибок открывайте тикет во вкладке Issues :)
|
||||
|
||||
# Навигация
|
||||
|
||||
- [Навигация](#навигация)
|
||||
- [Зачем нужен этот бот?](#зачем-нужен-этот-бот)
|
||||
- [Установка](#установка)
|
||||
- [Docker](#docker)
|
||||
- [Python](#python)
|
||||
- [Установка необходимых Python-пакетов](#установка-необходимых-python-пакетов)
|
||||
- [Запуск установщика](#запуск-установщика)
|
||||
- [Запуск бота](#запуск-бота)
|
||||
- [Запуск с помощью скрипта](#запуск-с-помощью-скрипта)
|
||||
- [Linux](#linux)
|
||||
- [Macos](#macos)
|
||||
- [Запуск вручную](#запуск-вручную)
|
||||
- [Режим отладки](#режим-отладки)
|
||||
|
||||
# Зачем нужен этот бот?
|
||||
|
||||
Зачастую люди, желающие открыть маленький интернет-бизнес, делают это с помощью профиля в социальных сетях, что требует вручную обрабатывать каждую заявку. Этот бот позволит каждому быстро открыть автоматизированный магазин на базе телеграм бота, что значительно уменьшит время обработки заказов.
|
||||
|
||||

|
||||
|
||||
# Установка
|
||||
|
||||
## Docker
|
||||
Этот способ установки является основным и рекомендуемым. Он позволяет установить бота в репродуцируемом окружении, что упрощает его обновление и установку на сервер.
|
||||
|
||||
Для работы бота необходимо установить [Docker](https://docs.docker.com/get-docker/) и выполнить следущую команду:
|
||||
|
||||
```
|
||||
docker run -d \
|
||||
-e MAIN_ADMIN_ID='Ваш Telegram ID' \
|
||||
-e TELEGRAM_TOKEN='token' \
|
||||
w1png/shop-telegram-bot
|
||||
```
|
||||
|
||||
## Python
|
||||
|
||||
Для работы бота необходимо установить [Python версии 3.10 и выше](https://www.python.org/downloads/).
|
||||
|
||||
### Установка необходимых Python-пакетов
|
||||
|
||||
python3 -m pip install -r requirements.txt
|
||||
|
||||
### Запуск установщика
|
||||
|
||||
Перед запуском установщика требуется [создать токен](https://youtu.be/fyISLEvzIec) для телеграм бота и [получить ваш ID](https://badcode.ru/kak-v-telegram-uznat-svoi-id/).
|
||||
|
||||
Установщик запускается с помощью команды:
|
||||
|
||||
python3 installer.py
|
||||
|
||||
## Запуск бота
|
||||
|
||||
### Запуск с помощью скрипта
|
||||
|
||||
#### Linux/MacOS
|
||||
|
||||
$ chmod +x start.sh
|
||||
$ ./start.sh
|
||||
|
||||
#### Windows
|
||||
|
||||
$ start.cmd
|
||||
|
||||
### Запуск вручную
|
||||
|
||||
python3 main.py
|
||||
|
||||
# Режим отладки
|
||||
|
||||
Режим отладки можно активировать во вкладке "Основные настройки".
|
||||
После активации в терминале начнут отображаться все сообщения и вызовы в формате:
|
||||
|
||||
DEBUG: <MESSAGE/CALL> [<user_id>] <Сообщение/вызов>
|
||||
|
||||
*Пример: `DEBUG CALL [462741] admin_itemManagement`*
|
||||
6
doners/shop-telegram-bot/requirements.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
aiogram==2.19
|
||||
captcha==0.3
|
||||
matplotlib==3.5.1
|
||||
phonenumbers==8.12.40
|
||||
pymorphy2==0.9.1
|
||||
pyparsing==3.0.0
|
||||
0
doners/shop-telegram-bot/src/__init__.py
Normal file
51
doners/shop-telegram-bot/src/category.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import sqlite3
|
||||
import item as itm
|
||||
|
||||
conn = sqlite3.connect("data.db")
|
||||
c = conn.cursor()
|
||||
|
||||
class Category:
|
||||
def __init__(self, cat_id):
|
||||
self.id = cat_id
|
||||
|
||||
def __eq__(self, __o: object) -> bool:
|
||||
return self.get_id() == __o.get_id()
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.get_name()
|
||||
|
||||
def get_id(self):
|
||||
return self.id
|
||||
|
||||
def __clist(self):
|
||||
c.execute(f"SELECT * FROM cats WHERE id=?", [self.get_id()])
|
||||
return list(c)[0]
|
||||
|
||||
def get_name(self):
|
||||
return self.__clist()[1]
|
||||
|
||||
def set_name(self, value):
|
||||
c.execute(f"UPDATE cats SET name=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def delete(self):
|
||||
c.execute(f"DELETE FROM cats WHERE id=?", [self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_item_list(self):
|
||||
c.execute(f"SELECT * FROM items WHERE cat_id=?", [self.get_id()])
|
||||
return list(map(itm.Item, [item[0] for item in list(c)]))
|
||||
|
||||
|
||||
def get_cat_list():
|
||||
c.execute(f"SELECT * FROM cats")
|
||||
return list(map(Category, [cat[0] for cat in list(c)]))
|
||||
|
||||
|
||||
def create_cat(cat_name):
|
||||
c.execute(f"INSERT INTO cats(name) VALUES(?)", [cat_name])
|
||||
conn.commit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(get_cat_list())
|
||||
61
doners/shop-telegram-bot/src/commands.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import sqlite3
|
||||
|
||||
conn = sqlite3.connect("data.db")
|
||||
c = conn.cursor()
|
||||
|
||||
|
||||
def get_commands():
|
||||
return list(map(Command, [command[0] for command in list(c.execute("SELECT * FROM commands"))]))
|
||||
|
||||
def does_command_exist(command_id=None, command=None):
|
||||
result = False
|
||||
if command_id:
|
||||
c.execute("SELECT * FROM commands WHERE id=?", [command_id])
|
||||
result = len(list(c)) >= 1
|
||||
elif command:
|
||||
c.execute("SELECT * FROM commands WHERE command=?", [command])
|
||||
result = len(list(c)) >= 1
|
||||
return result
|
||||
|
||||
def get_command_by_command(command):
|
||||
c.execute("SELECT * FROM commands WHERE command=?", [command])
|
||||
return Command(list(c)[0][0])
|
||||
|
||||
|
||||
def create_command(command, response):
|
||||
c.execute("INSERT INTO commands(command, response) VALUES(?,?)", [command, response])
|
||||
conn.commit()
|
||||
|
||||
|
||||
# TODO: rework the permission system and implement command permissions
|
||||
class Command:
|
||||
def __init__(self, command_id=None):
|
||||
self.command_id = command_id
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return f"[{self.get_id()}] {self.get_command()}"
|
||||
|
||||
def __clist(self):
|
||||
c.execute("SELECT * FROM commands WHERE id=?", [self.command_id])
|
||||
return list(c)[0]
|
||||
|
||||
def get_id(self):
|
||||
return self.command_id
|
||||
|
||||
def get_command(self):
|
||||
return self.__clist()[1]
|
||||
|
||||
def get_response(self):
|
||||
return self.__clist()[2]
|
||||
|
||||
def delete(self):
|
||||
c.execute("DELETE FROM commands WHERE id=?", [self.command_id])
|
||||
conn.commit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# create_command("test", "response")
|
||||
|
||||
for command in get_commands():
|
||||
print(command)
|
||||
111
doners/shop-telegram-bot/src/item.py
Normal file
@@ -0,0 +1,111 @@
|
||||
import sqlite3
|
||||
from settings import Settings
|
||||
from os.path import exists
|
||||
from doners.old_2.main import notify_admins
|
||||
|
||||
conn = sqlite3.connect("data.db")
|
||||
c = conn.cursor()
|
||||
|
||||
settings = Settings()
|
||||
|
||||
def does_item_exist(item_id):
|
||||
c.execute(f"SELECT * FROM items WHERE id=?", [item_id])
|
||||
return len(list(c)) == 1
|
||||
|
||||
|
||||
class Item:
|
||||
def __init__(self, item_id):
|
||||
self.__item_id = item_id
|
||||
|
||||
def __eq__(self, __o: object):
|
||||
return self.get_id() == __o.get_id()
|
||||
|
||||
def __repr__(self):
|
||||
return f"[{self.get_id()}] {self.get_name()}"
|
||||
|
||||
def get_id(self):
|
||||
return self.__item_id
|
||||
|
||||
def __clist(self):
|
||||
c.execute(f"SELECT * FROM items WHERE id=?", [self.get_id()])
|
||||
return list(c)[0]
|
||||
|
||||
def get_name(self):
|
||||
return self.__clist()[1]
|
||||
|
||||
def set_name(self, value):
|
||||
c.execute(f"UPDATE items SET name=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_price(self):
|
||||
return self.__clist()[2]
|
||||
|
||||
def set_price(self, value):
|
||||
c.execute(f"UPDATE items SET price=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
# TODO: change to get_cat and change to cat.get_id() everywhere
|
||||
def get_cat_id(self):
|
||||
return self.__clist()[3]
|
||||
|
||||
def set_cat_id(self, value):
|
||||
c.execute(f"UPDATE items SET cat_id=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_desc(self):
|
||||
return self.__clist()[4]
|
||||
|
||||
def set_desc(self, value):
|
||||
c.execute(f"UPDATE items SET desc=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def is_active(self):
|
||||
return self.__clist()[5] == 1
|
||||
|
||||
def set_active(self, value):
|
||||
c.execute(f"UPDATE items SET active=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_amount(self):
|
||||
return int(self.__clist()[6])
|
||||
|
||||
def set_amount(self, value):
|
||||
c.execute(f"UPDATE items SET amount=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_image_id(self):
|
||||
return self.__clist()[7]
|
||||
|
||||
def get_image(self):
|
||||
return open("images/" + self.get_image_id(), "rb")
|
||||
|
||||
def set_image_id(self, value):
|
||||
c.execute(f"UPDATE items SET image_id=? WHERE id=?", [value, self.get_id()])
|
||||
|
||||
async def is_hide_image(self):
|
||||
if exists(f"images/{self.get_image_id()}"):
|
||||
return self.__clist()[8] == 1
|
||||
if self.get_image_id() != "None" and settings.is_debug():
|
||||
await notify_admins(f"Файла \"images/{self.get_image_id()}\" для товара \"{self.get_name()}\" не существует!")
|
||||
return True
|
||||
|
||||
def set_hide_image(self, value):
|
||||
c.execute("UPDATE items SET hide_image=? WHERE id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def delete(self):
|
||||
c.execute(f"DELETE FROM items WHERE id=?", [self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
|
||||
def get_item_list():
|
||||
c.execute("SELECT * FROM items")
|
||||
return list(map(Item, [item[0] for item in list(c)]))
|
||||
|
||||
|
||||
def create_item(name, price, cat_id, desc, image_id="None", active=True):
|
||||
c.execute(f"INSERT INTO items(name, price, cat_id, desc, active, amount, image_id, hide_image) VALUES(?, ?, ?, ?, ?, 0, ?, 0)", [name, price, cat_id, desc, 1 if active else 0, image_id])
|
||||
conn.commit()
|
||||
return get_item_list()[-1]
|
||||
|
||||
|
||||
2522
doners/shop-telegram-bot/src/main.py
Normal file
477
doners/shop-telegram-bot/src/markups.py
Normal file
@@ -0,0 +1,477 @@
|
||||
from os import listdir
|
||||
from aiogram import types
|
||||
from datetime import datetime
|
||||
|
||||
import text_templates as tt
|
||||
from settings import Settings
|
||||
import commands
|
||||
|
||||
settings = Settings()
|
||||
|
||||
# Back buttons
|
||||
# Misc
|
||||
btnBackAdmin = types.InlineKeyboardButton(text=tt.back, callback_data="admin_adminPanel")
|
||||
|
||||
# Item management
|
||||
btnBackItemManagement = types.InlineKeyboardButton(text=tt.back, callback_data="admin_itemManagement")
|
||||
btnBackEditCatChooseCategory = types.InlineKeyboardButton(text=tt.back, callback_data="admin_editCatChooseCategory")
|
||||
def btnBackEditCat(cat_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_editCat{cat_id}")
|
||||
btnBackEditItemChooseCategory = types.InlineKeyboardButton(text=tt.back, callback_data="admin_editItemChooseCategory")
|
||||
def btnBackEditItemChooseItem(cat_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_editItemChooseItem{cat_id}")
|
||||
def btnBackEditItem(item_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_editItem{item_id}")
|
||||
|
||||
# User management
|
||||
btnBackUserManagement = types.InlineKeyboardButton(text=tt.back, callback_data="admin_userManagement")
|
||||
def btnBackSeeUserProfile(user_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_seeUserProfile{user_id}")
|
||||
def btnBackSeeUserOrders(user_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"admin_seeUserOrders{user_id}")
|
||||
|
||||
# Stats
|
||||
btnBackShopStats = types.InlineKeyboardButton(text=tt.back, callback_data="admin_shopStats")
|
||||
btnBackRegistratonStats = types.InlineKeyboardButton(text=tt.back, callback_data="admin_registrationStatsBack")
|
||||
btnBackOrderStats = types.InlineKeyboardButton(text=tt.back, callback_data="admin_orderStatsBack")
|
||||
|
||||
# Settings
|
||||
btnBackShopSettingsDel = types.InlineKeyboardButton(text=tt.back, callback_data="admin_shopSettingsDel")
|
||||
btnBackShopSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_shopSettings")
|
||||
btnBackStatsSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_statsSettings")
|
||||
btnBackMainSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_mainSettings")
|
||||
btnBackCheckoutSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_checkoutSettings")
|
||||
btnBackAdditionalSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_additionalSettings")
|
||||
btnBackCustomCommands = types.InlineKeyboardButton(text=tt.back, callback_data="admin_customCommands")
|
||||
btnBackSystemSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_systemSettings")
|
||||
btnBackItemSettings = types.InlineKeyboardButton(text=tt.back, callback_data="admin_itemSettings")
|
||||
btnBackBackups = types.InlineKeyboardButton(text=tt.back, callback_data="admin_backups")
|
||||
|
||||
# /start menu
|
||||
btnBackFaq = types.InlineKeyboardButton(text=tt.back, callback_data="faq")
|
||||
btnBackProfile = types.InlineKeyboardButton(text=tt.back, callback_data="profile")
|
||||
btnBackMyOrder = types.InlineKeyboardButton(text=tt.back, callback_data="myOrders")
|
||||
btnBackCatalogue = types.InlineKeyboardButton(text=tt.back, callback_data="catalogue")
|
||||
def btnBackViewCat(cat_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"viewCat{cat_id}")
|
||||
def btnBackViewItem(item_id): return types.InlineKeyboardButton(text=tt.back, callback_data=f"viewItem{item_id}")
|
||||
btnBackCart = types.InlineKeyboardButton(text=tt.back, callback_data="cart")
|
||||
btnBackCartDel = types.InlineKeyboardButton(text=tt.back, callback_data="cartDel")
|
||||
btnBackOrders = types.InlineKeyboardButton(text=tt.back, callback_data="manager_orders")
|
||||
|
||||
# Single buttons
|
||||
btnAdminPanel = types.KeyboardButton(tt.admin_panel)
|
||||
btnOrders = types.KeyboardButton(tt.orders)
|
||||
|
||||
def single_button(btn):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(btn)
|
||||
return markup
|
||||
|
||||
|
||||
# Markups
|
||||
# /start buttons
|
||||
def get_markup_main():
|
||||
markup = types.ReplyKeyboardMarkup(resize_keyboard=True)
|
||||
markup.add(types.KeyboardButton(tt.catalogue))
|
||||
markup.add(types.KeyboardButton(tt.cart))
|
||||
markup.add(types.KeyboardButton(tt.profile), types.KeyboardButton(tt.faq))
|
||||
return markup
|
||||
|
||||
def get_markup_admin():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.item_management, callback_data="admin_itemManagement"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.user_management, callback_data="admin_userManagement"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.shop_stats, callback_data="admin_shopStats"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.bot_settings, callback_data="admin_shopSettings"))
|
||||
return markup
|
||||
|
||||
def get_markup_faq():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.contacts, callback_data="contacts"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.refund, callback_data="refund"))
|
||||
return markup
|
||||
|
||||
def get_markup_profile(user):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.my_orders, callback_data="myOrders"))
|
||||
# markup.add(types.InlineKeyboardButton(text=tt.my_support_tickets, callback_data="mySupportTickets"))
|
||||
if user.is_admin():
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_notif if user.notif_on() else tt.enable_notif, callback_data="changeEnableNotif"))
|
||||
return markup
|
||||
|
||||
def get_markup_myOrders(order_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for order in order_list:
|
||||
markup.add(types.InlineKeyboardButton(text=order.get_order_id(), callback_data=f"viewMyOrder{order.get_order_id()}"))
|
||||
markup.add(btnBackProfile)
|
||||
return markup
|
||||
|
||||
def get_markup_viewMyOrder(order):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
match order.get_status():
|
||||
case 0:
|
||||
markup.add(types.InlineKeyboardButton(text=tt.cancel_order, callback_data=f"cancelOrder{order.get_order_id()}"))
|
||||
case -1:
|
||||
markup.add(types.InlineKeyboardButton(text=tt.restore_order, callback_data=f"restoreOrder{order.get_order_id()}"))
|
||||
markup.add(btnBackMyOrder)
|
||||
return markup
|
||||
def get_markup_cart(user):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
delivery_price = '{:.2f}'.format(float(settings.get_delivery_price()))
|
||||
for item_and_amount in user.get_cart_amount():
|
||||
markup.add(types.InlineKeyboardButton(text=f"{item_and_amount[0].get_name()[:30-len(f' - {item_and_amount[1]}шт.')-3]}... - {item_and_amount[1]}шт.", callback_data=f"viewItem{item_and_amount[0].get_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=f"{item_and_amount[0].get_price() * item_and_amount[1]}руб.", callback_data="None"), types.InlineKeyboardButton(text=tt.plus, callback_data=f"addToCartFromCart{item_and_amount[0].get_id()}"), types.InlineKeyboardButton(text=tt.minus, callback_data=f"removeFromCartFromCart{item_and_amount[0].get_id()}"))
|
||||
if settings.is_delivery_enabled():
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delivery_on(delivery_price) if user.is_cart_delivery() else tt.delivery_off(delivery_price), callback_data="changeCartDelivery"))
|
||||
else:
|
||||
markup.add(types.InlineKeyboardButton(text=tt.pickup, callback_data="None"))
|
||||
|
||||
markup.add(types.InlineKeyboardButton(text=tt.clear_cart, callback_data="clearCart"))
|
||||
markup.add(types.InlineKeyboardButton(text=f"Всего: {'{:.2f}'.format(user.get_cart_price())}руб.", callback_data="None"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.cart_checkout, callback_data="checkoutCart"))
|
||||
return markup
|
||||
|
||||
def get_markup_captcha():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text="Новая CAPTCHA", callback_data="refreshCaptcha"))
|
||||
markup.add(btnBackCartDel)
|
||||
return markup
|
||||
|
||||
def get_markup_checkoutCartConfirmation():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.confirm, callback_data=f"checkoutCartConfirm"), types.InlineKeyboardButton(text=tt.deny, callback_data="cart"))
|
||||
return markup
|
||||
|
||||
# Catalogue
|
||||
def get_markup_catalogue(cat_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for cat in cat_list:
|
||||
markup.add(types.InlineKeyboardButton(text=cat.get_name(), callback_data=f"viewCat{cat.get_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.search, callback_data="search"))
|
||||
return markup
|
||||
|
||||
def get_markup_search(query):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for item in query:
|
||||
markup.add(types.InlineKeyboardButton(text=item.get_name(), callback_data=f"viewItem{item.get_id()}"))
|
||||
markup.add(btnBackCatalogue)
|
||||
return markup
|
||||
|
||||
|
||||
def get_markup_viewCat(item_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for item in item_list:
|
||||
if item.is_active():
|
||||
markup.add(types.InlineKeyboardButton(text=f"{item.get_name()} - {item.get_price()} руб.", callback_data=f"viewItem{item.get_id()}"))
|
||||
markup.add(btnBackCatalogue)
|
||||
return markup
|
||||
|
||||
def get_markup_viewItem(item):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.add_to_cart, callback_data=f"addToCart{item.get_id()}"))
|
||||
markup.add(btnBackViewCat(item.get_cat_id()))
|
||||
return markup
|
||||
|
||||
# Admin panel tabs
|
||||
# Item management
|
||||
def get_markup_itemManagement():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.add_cat, callback_data="admin_addCat"), types.InlineKeyboardButton(text=tt.add_item, callback_data="admin_addItem"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.edit_cat, callback_data="admin_editCatChooseCategory"), types.InlineKeyboardButton(text=tt.edit_item, callback_data="admin_editItemChooseCategory"))
|
||||
markup.add(btnBackAdmin)
|
||||
return markup
|
||||
|
||||
def get_markup_editCatChooseCategory(cat_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for cat in cat_list:
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{cat.get_id()}] {cat.get_name()}", callback_data=f"admin_editCat{cat.get_id()}"))
|
||||
markup.add(btnBackItemManagement)
|
||||
return markup
|
||||
|
||||
def get_markup_editCat(cat_id):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_name, callback_data=f"admin_editCatName{cat_id}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delete, callback_data=f"admin_editCatDelete{cat_id}"))
|
||||
markup.add(btnBackEditCatChooseCategory)
|
||||
return markup
|
||||
|
||||
def get_markup_addItemSetCat(cat_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for cat in cat_list:
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{cat.get_id()}] {cat.get_name()}", callback_data=f"admin_addItemSetCat{cat.get_id()}"))
|
||||
markup.add(btnBackItemManagement)
|
||||
return markup
|
||||
|
||||
btnSkipAddItemSetImage = types.InlineKeyboardButton(text=tt.skip, callback_data="admin_skipSetAddItemSetImage")
|
||||
|
||||
def get_markup_addItemConfirmation():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.confirm, callback_data="admin_addItemConfirm"), types.InlineKeyboardButton(text=tt.deny, callback_data="admin_itemManagement"))
|
||||
return markup
|
||||
|
||||
def get_markup_editItemChooseCategory(cat_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for cat in cat_list:
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{cat.get_id()}] {cat.get_name()}", callback_data=f"admin_editItemChooseItem{cat.get_id()}"))
|
||||
markup.add(btnBackItemManagement)
|
||||
return markup
|
||||
|
||||
def get_markup_editItemChooseItem(item_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for item in item_list:
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{item.get_id()}] {item.get_name()}", callback_data=f"admin_editItem{item.get_id()}"))
|
||||
markup.add(btnBackEditItemChooseCategory)
|
||||
return markup
|
||||
|
||||
async def get_markup_editItem(item):
|
||||
itemid = item.get_id()
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_name, callback_data=f"admin_editItemName{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_image, callback_data=f"admin_editItemImage{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.show_image if await item.is_hide_image() else tt.hide_image, callback_data=f"admin_editItemHideImage{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_desc, callback_data=f"admin_editItemDesc{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_price, callback_data=f"admin_editItemPrice{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_item_cat, callback_data=f"admin_editItemCat{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_stock, callback_data=f"admin_editItemStock{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=(tt.hide if item.is_active() else tt.show), callback_data=f"admin_editItemHide{itemid}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delete, callback_data=f"admin_editItemDelete{itemid}"))
|
||||
markup.add(btnBackEditItemChooseItem(item.get_cat_id()))
|
||||
return markup
|
||||
|
||||
def get_markup_editItemCat(item_id, cat_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for cat in cat_list:
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{cat.get_id()}] {cat.get_name()}", callback_data=f"admin_editItemSetCat{cat.get_id()}"))
|
||||
markup.add(btnBackEditItem(item_id))
|
||||
return markup
|
||||
|
||||
# User management
|
||||
def get_markup_userManagement():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.user_profile, callback_data="admin_seeUserProfile"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.notify_everyone, callback_data="admin_notifyEveryone"))
|
||||
markup.add(btnBackAdmin)
|
||||
return markup
|
||||
|
||||
def get_markup_notifyEveryoneConfirmation():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.confirm, callback_data="admin_notifyEveryoneConfirm"), btnBackUserManagement)
|
||||
return markup
|
||||
|
||||
def get_markup_seeUserProfile(user):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.orders, callback_data=f"admin_seeUserOrders{user.get_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.remove_manager_role if user.is_manager() else tt.add_manager_role, callback_data=f"admin_changeUserManager{user.get_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.remove_admin_role if user.is_admin() else tt.add_admin_role, callback_data=f"admin_changeUserAdmin{user.get_id()}"))
|
||||
markup.add(btnBackUserManagement)
|
||||
return markup
|
||||
|
||||
def get_markup_seeUserOrders(user):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for order in user.get_orders():
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{order.get_order_id()}]", callback_data=f"admin_seeUserOrder{order.get_order_id()}"))
|
||||
markup.add(btnBackSeeUserProfile(user.get_id()))
|
||||
return markup
|
||||
|
||||
def get_markup_seeUserOrder(order):
|
||||
markup = markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.processing), callback_data=f"admin_changeOrderStatusProcessing{order.get_order_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.delivery), callback_data=f"admin_changeOrderStatusDelivery{order.get_order_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.done), callback_data=f"admin_changeOrderStatusDone{order.get_order_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.cancelled), callback_data=f"admin_changeOrderStatusCancel{order.get_order_id()}"))
|
||||
markup.add(btnBackSeeUserOrders(order.get_user_id()))
|
||||
return markup
|
||||
|
||||
# Shop stats
|
||||
def get_markup_shopStats():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.registration_stats, callback_data="admin_registrationStats"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.order_stats, callback_data="admin_orderStats"))
|
||||
markup.add(btnBackAdmin)
|
||||
return markup
|
||||
|
||||
def get_markup_registrationStats():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.all_time, callback_data="admin_registrationStatsAllTime"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.monthly, callback_data="admin_registrationStatsMonthly"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.weekly, callback_data="admin_registrationStatsWeekly"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.daily, callback_data="admin_registrationStatsDaily"))
|
||||
markup.add(btnBackShopStats)
|
||||
return markup
|
||||
|
||||
def get_markup_orderStats():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.all_time, callback_data="admin_orderStatsAllTime"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.monthly, callback_data="admin_orderStatsMonthly"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.weekly, callback_data="admin_orderStatsWeekly"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.daily, callback_data="admin_orderStatsDaily"))
|
||||
markup.add(btnBackShopStats)
|
||||
return markup
|
||||
|
||||
# Shop settings
|
||||
def get_markup_shopSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.main_settings, callback_data="admin_mainSettings"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.item_settings, callback_data="admin_itemSettings"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.checkout_settings, callback_data="admin_checkoutSettings"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.stats_settings, callback_data="admin_statsSettings"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.additional_settings, callback_data="admin_additionalSettings"))
|
||||
markup.add(btnBackAdmin)
|
||||
return markup
|
||||
|
||||
def get_markup_mainSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=f"Название: {settings.get_shop_name()}", callback_data="admin_changeShopName"))
|
||||
markup.add(types.InlineKeyboardButton(text=f"Приветствие: {settings.get_shop_greeting()}", callback_data="admin_changeShopGreeting"))
|
||||
markup.add(types.InlineKeyboardButton(text=f"Политика возврата: {settings.get_refund_policy()}", callback_data="admin_changeShopRefundPolicy"))
|
||||
markup.add(types.InlineKeyboardButton(text=f"Контакты: {settings.get_shop_contacts()}", callback_data="admin_changeShopContacts"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_sticker if settings.is_sticker_enabled() else tt.enable_sticker, callback_data="admin_changeEnableSticker"))
|
||||
markup.add(btnBackShopSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_itemSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_item_image if settings.is_item_image_enabled() else tt.enable_item_image, callback_data="admin_changeEnableItemImage"))
|
||||
markup.add(btnBackShopSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_checkoutSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delivery_price('{:.2f}'.format(settings.get_delivery_price())), callback_data="admin_changeDeliveryPrice"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_delivery if settings.is_delivery_enabled() else tt.enable_delivery, callback_data="admin_changeEnableDelivery"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_phone_number if settings.is_phone_number_enabled() else tt.enable_phone_number, callback_data="admin_changeEnablePhoneNumber"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_captcha if settings.is_captcha_enabled() else tt.enable_captcha, callback_data="admin_changeEnableCaptcha"))
|
||||
markup.add(btnBackShopSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_statsSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.graph_color, callback_data="admin_statsSettingsColor"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.border_width, callback_data="admin_statsSettingsBorderWidth"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.title_font_size, callback_data="admin_statsSettingsTitleFontSize"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.axis_font_size, callback_data="admin_statsSettingsAxisFontSize"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.tick_font_size, callback_data="admin_statsSettingsTickFontSize"))
|
||||
markup.add(btnBackShopSettingsDel)
|
||||
return markup
|
||||
|
||||
def get_markup_statsSettingsColor():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text="⬛️", callback_data="admin_statsSettingsColorBlack"), types.InlineKeyboardButton(text="⬜️", callback_data="admin_statsSettingsColorWhite"), types.InlineKeyboardButton(text="🟥", callback_data="admin_statsSettingsColorRed"))
|
||||
markup.add(types.InlineKeyboardButton(text="🟨", callback_data="admin_statsSettingsColorYellow"), types.InlineKeyboardButton(text="🟪", callback_data="admin_statsSettingsColorPurple"), types.InlineKeyboardButton(text="🟦", callback_data="admin_statsSettingsColorBlue"))
|
||||
markup.add(types.InlineKeyboardButton(text="🟧", callback_data="admin_statsSettingsColorOrange"), types.InlineKeyboardButton(text="🟩", callback_data="admin_statsSettingsColorGreen"), types.InlineKeyboardButton(text="🟫", callback_data="admin_statsSettingsColorBrown"))
|
||||
markup.add(btnBackStatsSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_statsSettingsBorderWidth():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.unavailable if settings.get_borderwidth() == "0" else tt.minus, callback_data="None" if settings.get_borderwidth() == "0" else "admin_statsSettingsBorderWidthReduce"), types.InlineKeyboardButton(text=settings.get_borderwidth(), callback_data="admin_statsSettingsBorderWidthDefault"), types.InlineKeyboardButton(text=tt.plus, callback_data="admin_statsSettingsBorderWidthAdd"))
|
||||
markup.add(btnBackStatsSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_statsSettingsTitleFontSize():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.unavailable if settings.get_titlefontsize() == "2" else tt.minus, callback_data="None" if settings.get_titlefontsize() == "2" else "admin_statsSettingsTitleFontSizeReduce"), types.InlineKeyboardButton(text=settings.get_titlefontsize(), callback_data="admin_statsSettingsTitleFontSizeDefault"), types.InlineKeyboardButton(text=tt.plus, callback_data="admin_statsSettingsTitleFontSizeAdd"))
|
||||
markup.add(btnBackStatsSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_statsSettingsAxisFontSize():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.unavailable if settings.get_axisfontsize() == "2" else tt.minus, callback_data="None" if settings.get_axisfontsize() == "2" else "admin_statsSettingsAxisFontSizeReduce"), types.InlineKeyboardButton(text=settings.get_axisfontsize(), callback_data="admin_statsSettingsAxisFontSizeDefault"), types.InlineKeyboardButton(text=tt.plus, callback_data="admin_statsSettingsAxisFontSizeAdd"))
|
||||
markup.add(btnBackStatsSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_statsSettingsTickFontSize():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.unavailable if settings.get_tickfontsize() == "2" else tt.minus, callback_data="None" if settings.get_tickfontsize() == "2" else "admin_statsSettingsTickFontSizeReduce"), types.InlineKeyboardButton(text=settings.get_tickfontsize(), callback_data="admin_statsSettingsTickFontSizeDefault"), types.InlineKeyboardButton(text=tt.plus, callback_data="admin_statsSettingsTickFontSizeAdd"))
|
||||
markup.add(btnBackStatsSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_systemSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.clean_images, callback_data="admin_cleanImagesMenu"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.reset_settings, callback_data="admin_resetSettingsMenu"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.clean_database, callback_data="admin_cleanDatabaseMenu"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.backups, callback_data="admin_backups"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.disable_debug if settings.is_debug() else tt.enable_debug, callback_data="admin_changeEnableDebug"))
|
||||
markup.add(btnBackAdditionalSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_backups():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.update_backup, callback_data="admin_updateBackup"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.load_backup, callback_data="admin_loadBackupMenu"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.clean_backups, callback_data="admin_cleanBackupsMenu"))
|
||||
markup.add(btnBackSystemSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_loadBackupMenu():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
backups = listdir("backups")
|
||||
backups.sort(key=lambda b: datetime.strptime(b, "%d-%m-%Y"))
|
||||
for backup in backups[:90]:
|
||||
markup.add(types.InlineKeyboardButton(text=backup, callback_data=f"admin_loadBackup{backup}"))
|
||||
markup.add(btnBackBackups)
|
||||
return markup
|
||||
|
||||
def get_markup_cleanBackupsMenu():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for days in ["7", "30", "90"]:
|
||||
markup.add(types.InlineKeyboardButton(text=f"{days} дней", callback_data=f"admin_cleanBackups{days}"))
|
||||
markup.add(types.InlineKeyboardButton(text="Удалить все резервные копии", callback_data="admin_cleanBackupsAll"))
|
||||
markup.add(btnBackBackups)
|
||||
return markup
|
||||
|
||||
def get_markup_cleanImagesMenu():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delete, callback_data="admin_cleanImages"))
|
||||
markup.add(btnBackSystemSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_resetSettingsMenu():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.reset, callback_data="admin_resetSettings"))
|
||||
markup.add(btnBackSystemSettings)
|
||||
return markup
|
||||
|
||||
def get_markup_cleanDatabaseMenu():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delete, callback_data="admin_cleanDatabase"))
|
||||
markup.add(btnBackSystemSettings)
|
||||
return markup
|
||||
|
||||
# Manager tab
|
||||
def get_markup_seeOrder(order, user_id=None):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.processing), callback_data=f"manager_changeOrderStatusProcessing{order.get_order_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.delivery), callback_data=f"manager_changeOrderStatusDelivery{order.get_order_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.done), callback_data=f"manager_changeOrderStatusDone{order.get_order_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.change_order_status(tt.cancelled), callback_data=f"manager_changeOrderStatusCancel{order.get_order_id()}"))
|
||||
markup.add(btnBackSeeUserOrders(user_id) if user_id else btnBackOrders)
|
||||
return markup
|
||||
|
||||
def get_markup_orders():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.processing, callback_data="manager_ordersProcessing"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.delivery, callback_data="manager_ordersDelivery"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.done, callback_data="manager_ordersDone"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.cancelled, callback_data="manager_ordersCancelled"))
|
||||
return markup
|
||||
|
||||
def get_markup_ordersByOrderList(order_list):
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for order in order_list:
|
||||
markup.add(types.InlineKeyboardButton(text=f"[{order.get_order_id()}]", callback_data=f"manager_seeOrder{order.get_order_id()}"))
|
||||
markup.add(btnBackOrders)
|
||||
return markup
|
||||
|
||||
def get_markup_additionalSettings():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
markup.add(types.InlineKeyboardButton(text=tt.custom_commands, callback_data="admin_customCommands"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.system_settings, callback_data="admin_systemSettings"))
|
||||
markup.add(btnBackShopSettings)
|
||||
return markup
|
||||
|
||||
# Custom commands
|
||||
def get_markup_customCommands():
|
||||
markup = types.InlineKeyboardMarkup()
|
||||
for command in commands.get_commands():
|
||||
markup.add(types.InlineKeyboardButton(text=command.get_command(), callback_data="None"), types.InlineKeyboardButton(text=tt.delete, callback_data=f"admin_deleteCommand{command.get_id()}"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.line_separator, callback_data="None"))
|
||||
markup.add(types.InlineKeyboardButton(text=tt.add_command, callback_data="admin_addCommand"))
|
||||
markup.add(btnBackAdditionalSettings)
|
||||
return markup
|
||||
108
doners/shop-telegram-bot/src/order.py
Normal file
@@ -0,0 +1,108 @@
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
import item as itm
|
||||
import text_templates as tt
|
||||
from settings import Settings
|
||||
|
||||
settings = Settings()
|
||||
|
||||
conn = sqlite3.connect("data.db")
|
||||
c = conn.cursor()
|
||||
|
||||
class Order:
|
||||
def __init__(self, id):
|
||||
self.id = id
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.get_order_id()}"
|
||||
|
||||
def get_order_id(self):
|
||||
return self.id
|
||||
|
||||
def __clist(self):
|
||||
c.execute(f"SELECT * FROM orders WHERE order_id=?", [self.get_order_id()])
|
||||
return list(c)[0]
|
||||
|
||||
def get_user_id(self):
|
||||
return self.__clist()[1]
|
||||
|
||||
def get_item_list_raw(self):
|
||||
return self.__clist()[2]
|
||||
|
||||
def get_item_list(self):
|
||||
return list(map(itm.Item, [item_id for item_id in self.get_item_list_raw().split(",")]))
|
||||
|
||||
def get_item_list_amount(self):
|
||||
cart = [item.get_id() for item in self.get_item_list()]
|
||||
return [[itm.Item(item_id), cart.count(item_id)] for item_id in set(cart)]
|
||||
|
||||
def get_item_list_price(self):
|
||||
return sum([item_and_price[0].get_price() * item_and_price[1] for item_and_price in self.get_item_list_amount()]) + (float(settings.get_delivery_price()) if self.get_home_adress() != None else 0)
|
||||
|
||||
def set_item_list(self, value):
|
||||
c.execute(f"UPDATE orders SET item_list=? WHERE order_id=?", [value, self.get_order_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_email_adress(self):
|
||||
return self.__clist()[3]
|
||||
|
||||
def set_email_adress(self, value):
|
||||
c.execute(f"UPDATE orders SET email_adress=? WHERE order_id=?", [value, self.get_order_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_phone_number(self):
|
||||
return self.__clist()[4]
|
||||
|
||||
def set_phone_number(self, value):
|
||||
c.execute(f"UPDATE orders SET phone_number=? WHERE order_id=?", [value, self.get_order_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_home_adress(self):
|
||||
return self.__clist()[5]
|
||||
|
||||
def set_home_adress(self, value):
|
||||
c.execute(f"UPDATE orders SET home_adress=? WHERE order_id=?", [value, self.get_order_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_additional_message(self):
|
||||
return self.__clist()[6]
|
||||
|
||||
def get_date(self):
|
||||
return datetime.strptime(self.__clist()[7], "%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def get_date_string(self):
|
||||
return self.__clist()[7]
|
||||
|
||||
def get_status(self):
|
||||
return self.__clist()[8]
|
||||
|
||||
def get_status_string(self):
|
||||
return get_status_dict()[self.__clist()[8]]
|
||||
|
||||
def set_status(self, value):
|
||||
c.execute(f"UPDATE orders SET status=? WHERE order_id=?", [value, self.get_order_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_status_dict():
|
||||
return {
|
||||
0: tt.processing,
|
||||
1: tt.delivery,
|
||||
2: tt.done,
|
||||
-1: tt.cancelled,
|
||||
}
|
||||
|
||||
def get_order_list(status=None):
|
||||
if status:
|
||||
c.execute(f"SELECT * FROM orders WHERE status=?", [status])
|
||||
else:
|
||||
c.execute(f"SELECT * FROM orders")
|
||||
return list(map(Order, [order[0] for order in list(c)]))
|
||||
|
||||
def does_order_exist(order_id):
|
||||
c.execute(f"SELECT * FROM orders WHERE order_id=?", [order_id])
|
||||
return len(list(c)) == 1
|
||||
|
||||
def create_order(order_id, user_id, item_list, email_adress, additional_message, phone_number="None", home_adress="None"):
|
||||
c.execute(f"INSERT INTO orders VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)", [order_id, user_id, item_list, email_adress, phone_number, home_adress, additional_message, datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 0])
|
||||
conn.commit()
|
||||
return Order(order_id)
|
||||
45
doners/shop-telegram-bot/src/search.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import pymorphy2
|
||||
from pyparsing import opAssoc
|
||||
import item as itm
|
||||
|
||||
m = pymorphy2.MorphAnalyzer()
|
||||
|
||||
excluded_words = ["без", "в", "для", "до", "за", "из", "к", "под", "а", "о", "над", "на", "о", "об", "от", "перед", "по", "под", "при", "про", "с", "у"]
|
||||
|
||||
def get_normal_forms(word):
|
||||
return set(p.normal_form for p in m.parse(word))
|
||||
|
||||
|
||||
class Query:
|
||||
def __init__(self, results):
|
||||
self.results = results
|
||||
|
||||
def __get_items(self):
|
||||
return list(map(itm.Item, self.results.keys()))[::-1][:90]
|
||||
|
||||
def match(self):
|
||||
return self.__get_items()
|
||||
|
||||
def price(self):
|
||||
return sorted(list(map(itm.Item, self.results.keys()))[::-1], key=lambda item: item.get_price())
|
||||
|
||||
def popular(self):
|
||||
pass
|
||||
|
||||
# a match in a name is 3 points
|
||||
# a match in a desc is 1 point
|
||||
def search_item(query):
|
||||
points = dict()
|
||||
for item in itm.get_item_list():
|
||||
points[item.get_id()] = 0
|
||||
for word in list(filter(lambda word: word not in excluded_words, query.split())):
|
||||
for p in get_normal_forms(word):
|
||||
for word_title in item.get_name().split():
|
||||
if p in get_normal_forms(word_title):
|
||||
points[item.get_id()] += 3
|
||||
for word_title in item.get_desc().split():
|
||||
if p in get_normal_forms(word_title):
|
||||
points[item.get_id()] += 1
|
||||
if points[item.get_id()] == 0:
|
||||
points.pop(item.get_id())
|
||||
return Query(dict(sorted(points.items(), key=lambda item: item[1])))
|
||||
170
doners/shop-telegram-bot/src/settings.py
Normal file
@@ -0,0 +1,170 @@
|
||||
from configparser import ConfigParser
|
||||
from genericpath import exists
|
||||
from os import remove
|
||||
import sqlite3
|
||||
|
||||
class Settings:
|
||||
def __init__(self, config_path="config.ini"):
|
||||
self.__config_path = config_path
|
||||
|
||||
def __get_config(self):
|
||||
conf = ConfigParser()
|
||||
conf.read(self.__config_path)
|
||||
return conf
|
||||
|
||||
def __set_setting(self, category, subcategory, value):
|
||||
conf = self.__get_config()
|
||||
conf.set(category, subcategory, str(value))
|
||||
with open(self.__config_path, 'w') as config:
|
||||
conf.write(config)
|
||||
|
||||
# main_settings
|
||||
def get_token(self):
|
||||
return self.__get_config()["main_settings"]["token"]
|
||||
|
||||
def set_token(self, value):
|
||||
self.__set_setting("main_settings", "token", value)
|
||||
|
||||
def is_debug(self):
|
||||
return self.__get_config()["main_settings"]["debug"] == "1"
|
||||
|
||||
def set_debug(self, value):
|
||||
self.__set_setting("main_settings", "debug", value)
|
||||
|
||||
def get_main_admin_id(self):
|
||||
return self.__get_config()["main_settings"]["mainadminid"]
|
||||
|
||||
def set_main_admin_id(self, value):
|
||||
self.__set_setting("main_settings", "mainadminid", value)
|
||||
|
||||
# shop_settings
|
||||
def get_shop_name(self):
|
||||
return self.__get_config()["shop_settings"]["name"]
|
||||
|
||||
def set_shop_name(self, value):
|
||||
self.__set_setting("shop_settings", "name", value)
|
||||
|
||||
def get_shop_greeting(self):
|
||||
return self.__get_config()["shop_settings"]["greeting"]
|
||||
|
||||
def set_shop_greeting(self, value):
|
||||
self.__set_setting("shop_settings", "greeting", value)
|
||||
|
||||
def is_sticker_enabled(self):
|
||||
return self.__get_config()["shop_settings"]["enablesticker"] == "1"
|
||||
|
||||
def set_enable_sticker(self, value):
|
||||
self.__set_setting("shop_settings", "enablesticker", value)
|
||||
|
||||
def get_refund_policy(self):
|
||||
return self.__get_config()["shop_settings"]["refundpolicy"]
|
||||
|
||||
def set_refund_policy(self, value):
|
||||
self.__set_setting("shop_settings", "refundpolicy", value)
|
||||
|
||||
def get_shop_contacts(self):
|
||||
return self.__get_config()["shop_settings"]["contacts"]
|
||||
|
||||
def set_shop_contacts(self, value):
|
||||
self.__set_setting("shop_settings", "contacts", value)
|
||||
|
||||
def is_item_image_enabled(self):
|
||||
return self.__get_config()["shop_settings"]["enableimage"] == "1"
|
||||
|
||||
def set_item_image(self, value):
|
||||
self.__set_setting("shop_settings", "enableimage", value)
|
||||
|
||||
def is_phone_number_enabled(self):
|
||||
return self.__get_config()["shop_settings"]["enablephonenumber"] == "1"
|
||||
|
||||
def set_enable_phone_number(self, value):
|
||||
self.__set_setting("shop_settings", "enablephonenumber", value)
|
||||
|
||||
def is_delivery_enabled(self):
|
||||
return self.__get_config()["shop_settings"]["enabledelivery"] == "1"
|
||||
|
||||
def set_delivery(self, value):
|
||||
self.__set_setting("shop_settings", "enabledelivery", value)
|
||||
|
||||
def get_delivery_price(self):
|
||||
return float(self.__get_config()["shop_settings"]["delivery_price"])
|
||||
|
||||
def set_delivery_price(self, value):
|
||||
self.__set_setting("shop_settings", "delivery_price", value)
|
||||
|
||||
def is_captcha_enabled(self):
|
||||
return self.__get_config()["shop_settings"]["enablecaptcha"] == "1"
|
||||
|
||||
def set_enable_captcha(self, value):
|
||||
self.__set_setting("shop_settings", "enablecaptcha", value)
|
||||
|
||||
# stats_settings
|
||||
def get_barcolor(self):
|
||||
return self.__get_config()["stats_settings"]["barcolor"]
|
||||
|
||||
def set_barcolor(self, value):
|
||||
self.__set_setting("stats_settings", "barcolor", value)
|
||||
|
||||
def get_borderwidth(self):
|
||||
return self.__get_config()["stats_settings"]["borderwidth"]
|
||||
|
||||
def set_borderwidth(self, value):
|
||||
self.__set_setting("stats_settings", "borderwidth", value)
|
||||
|
||||
def get_titlefontsize(self):
|
||||
return self.__get_config()["stats_settings"]["titlefontsize"]
|
||||
|
||||
def set_titlefontsize(self, value):
|
||||
self.__set_setting("stats_settings", "titlefontsize", value)
|
||||
|
||||
def get_axisfontsize(self):
|
||||
return self.__get_config()["stats_settings"]["axisfontsize"]
|
||||
|
||||
def set_axisfontsize(self, value):
|
||||
self.__set_setting("stats_settings", "axisfontsize", value)
|
||||
|
||||
def get_tickfontsize(self):
|
||||
return self.__get_config()["stats_settings"]["tickfontsize"]
|
||||
|
||||
def set_tickfontsize(self, value):
|
||||
self.__set_setting("stats_settings", "tickfontsize", value)
|
||||
|
||||
def reset(self):
|
||||
DEFAULT_CONFIG_TEXT = f"""[main_settings]
|
||||
token = {self.get_token()}
|
||||
mainadminid = {self.get_main_admin_id()}
|
||||
debug = 0
|
||||
|
||||
[shop_settings]
|
||||
name = Название магазина
|
||||
greeting = Добро пожаловать!
|
||||
refundpolicy = Текст для вкладки "Политика возврата"
|
||||
contacts = Текст для вкладки "Контакты"
|
||||
enableimage = 1
|
||||
enablesticker = 0
|
||||
enablephonenumber = 0
|
||||
enabledelivery = 0
|
||||
delivery_price = 0.0
|
||||
enablecaptcha = 1
|
||||
|
||||
[stats_settings]
|
||||
barcolor = 3299ff
|
||||
borderwidth = 1
|
||||
titlefontsize = 20
|
||||
axisfontsize = 12
|
||||
tickfontsize = 8
|
||||
"""
|
||||
if exists("config.ini"):
|
||||
remove("config.ini")
|
||||
with open("config.ini", "w") as config:
|
||||
config.write(DEFAULT_CONFIG_TEXT)
|
||||
|
||||
def clean_db(self):
|
||||
conn = sqlite3.connect("data.db")
|
||||
c = conn.cursor()
|
||||
c.execute("DELETE FROM cats")
|
||||
c.execute("DELETE FROM items")
|
||||
c.execute("DELETE FROM orders")
|
||||
c.execute("DELETE FROM commands")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
111
doners/shop-telegram-bot/src/state_handler.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from urllib import response
|
||||
from aiogram.dispatcher.filters.state import StatesGroup, State
|
||||
|
||||
# Item management
|
||||
class addCat(StatesGroup):
|
||||
state_message = State()
|
||||
name = State()
|
||||
|
||||
class changeCatName(StatesGroup):
|
||||
state_message = State()
|
||||
cat_id = State()
|
||||
name = State()
|
||||
|
||||
class addItem(StatesGroup):
|
||||
# Required
|
||||
name = State()
|
||||
price = State()
|
||||
cat_id = State()
|
||||
desc = State()
|
||||
confirmation = State()
|
||||
|
||||
# Additional
|
||||
image = State()
|
||||
|
||||
class changeItemPrice(StatesGroup):
|
||||
state_message = State()
|
||||
item_id = State()
|
||||
price = State()
|
||||
|
||||
class changeItemImage(StatesGroup):
|
||||
state_message = State()
|
||||
item_id = State()
|
||||
image = State()
|
||||
|
||||
class changeItemDesc(StatesGroup):
|
||||
state_message = State()
|
||||
item_id = State()
|
||||
desc = State()
|
||||
|
||||
class changeItemName(StatesGroup):
|
||||
state_message = State()
|
||||
item_id = State()
|
||||
name = State()
|
||||
|
||||
class changeItemCat(StatesGroup):
|
||||
item_id = State()
|
||||
cat = State()
|
||||
|
||||
class changeItemStock(StatesGroup):
|
||||
item_id = State()
|
||||
state_message = State()
|
||||
stock = State()
|
||||
|
||||
# User management
|
||||
class notifyEveryone(StatesGroup):
|
||||
state_message = State()
|
||||
message = State()
|
||||
confirmation = State()
|
||||
|
||||
class seeUserProfile(StatesGroup):
|
||||
state_message = State()
|
||||
user_id = State()
|
||||
|
||||
|
||||
# Checkout
|
||||
class checkoutCart(StatesGroup):
|
||||
# Data
|
||||
user_id = State()
|
||||
item_list_comma = State()
|
||||
order_id = State()
|
||||
|
||||
# Required
|
||||
email = State()
|
||||
additional_message = State()
|
||||
confirmation = State()
|
||||
|
||||
# Additional
|
||||
phone_number = State()
|
||||
home_adress = State()
|
||||
captcha = State()
|
||||
|
||||
# Main settings
|
||||
class changeShopName(StatesGroup):
|
||||
state_message = State()
|
||||
name = State()
|
||||
|
||||
class changeShopGreeting(StatesGroup):
|
||||
state_message = State()
|
||||
greeting = State()
|
||||
|
||||
class changeShopRefundPolicy(StatesGroup):
|
||||
state_message = State()
|
||||
refund_policy = State()
|
||||
|
||||
class changeShopContacts(StatesGroup):
|
||||
state_message = State()
|
||||
contacts = State()
|
||||
|
||||
class changeDeliveryPrice(StatesGroup):
|
||||
state_message = State()
|
||||
price = State()
|
||||
|
||||
class addCustomCommand(StatesGroup):
|
||||
command = State()
|
||||
response = State()
|
||||
|
||||
|
||||
# Misc
|
||||
class search(StatesGroup):
|
||||
state_message = State()
|
||||
query = State()
|
||||
63
doners/shop-telegram-bot/src/stats.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import datetime
|
||||
import matplotlib.pyplot as plt
|
||||
import order as ordr
|
||||
import user as usr
|
||||
from random import randint
|
||||
from settings import Settings
|
||||
|
||||
settings = Settings()
|
||||
|
||||
def saveplot(data, title, ylabel):
|
||||
plt.autoscale()
|
||||
plt.figure(figsize=(10, 10))
|
||||
plt.title(title, fontsize=settings.get_titlefontsize())
|
||||
plt.xlabel("Дата", fontsize=settings.get_axisfontsize())
|
||||
plt.ylabel(ylabel, fontsize=settings.get_axisfontsize())
|
||||
plt.tick_params(labelsize=settings.get_tickfontsize())
|
||||
plt.bar(range(len(data)), list(data.values()), color="#" + settings.get_barcolor(), edgecolor="black", linewidth=settings.get_borderwidth())
|
||||
plt.xticks(range(len(data)), list(data.keys()), rotation=90)
|
||||
plt.savefig(f"images/stats.png")
|
||||
plt.close()
|
||||
return open(f"images/stats.png", "rb")
|
||||
|
||||
|
||||
def get_random_data():
|
||||
return {f"{randint(1, 30):02}.{randint(1, 12):02}.{randint(2010, 2030)}": randint(5, 100) for _ in range(randint(2, 30))}
|
||||
|
||||
|
||||
def get_random_graph():
|
||||
return saveplot(get_random_data(), "Название", "Ось Y")
|
||||
|
||||
|
||||
class RegistrationCharts:
|
||||
def __init__(self):
|
||||
self.user_list = usr.get_user_list()
|
||||
|
||||
def saveplot(self, data, title):
|
||||
return saveplot(data, title, "Количество регистраций")
|
||||
|
||||
def all_time(self):
|
||||
return self.saveplot({f"{date.day:02}.{date.month:02}.{date.year}": [user.get_register_date().date() for user in self.user_list].count(date) for date in dict.fromkeys([user.get_register_date().date() for user in self.user_list])}, "Регистрации за все время")
|
||||
|
||||
def last_x_days(self, days):
|
||||
return self.saveplot({f"{(datetime.date.today() - datetime.timedelta(days=i)).day:02}.{(datetime.date.today() - datetime.timedelta(days=i)).month:02}": len(list(filter(lambda user: user.get_register_date().date() == datetime.date.today() - datetime.timedelta(days=i), self.user_list))) for i in range(30, -1, -1)}, f"Регистрации за последние {days} дней")
|
||||
|
||||
def last_x_hours(self, hours):
|
||||
return self.saveplot({f"{(datetime.datetime.today() - datetime.timedelta(hours=i)).hour:02}:00": len(list(filter(lambda user: user.get_register_date().hour == (datetime.datetime.now() - datetime.timedelta(hours=i)).hour and user.get_register_date() > datetime.datetime.now() - datetime.timedelta(hours=hours), self.user_list))) for i in range(hours, -1, -1)}, f"Регистрации за последние {hours} часов.")
|
||||
|
||||
|
||||
class OrderCharts:
|
||||
def __init__(self):
|
||||
self.order_list = ordr.get_order_list()
|
||||
|
||||
def saveplot(self, data, title):
|
||||
return saveplot(data, title, "Количество заказов")
|
||||
|
||||
def all_time(self):
|
||||
return self.saveplot({f"{date.day:02}.{date.month:02}.{date.year}": [order.get_date().date() for order in self.order_list].count(date) for date in dict.fromkeys([order.get_date().date() for order in self.order_list])}, "Заказы за все время")
|
||||
|
||||
def last_x_days(self, days):
|
||||
return self.saveplot({f"{(datetime.date.today() - datetime.timedelta(days=i)).day:02}.{(datetime.date.today() - datetime.timedelta(days=i)).month:02}": len(list(filter(lambda order: order.get_date().date() == datetime.date.today() - datetime.timedelta(days=i), self.order_list))) for i in range(days, -1, -1)}, f"Заказы за последние {days} дней")
|
||||
|
||||
def last_x_hours(self, hours):
|
||||
return self.saveplot({f"{(datetime.datetime.today() - datetime.timedelta(hours=i)).hour:02}:00": len(list(filter(lambda order: order.get_date() > datetime.datetime.now() - datetime.timedelta(hours=hours) and order.get_date().hour == (datetime.datetime.today() - datetime.timedelta(hours=i)).hour, self.order_list))) for i in range(hours, -1, -1)}, "Заказы за сегодня" if hours == (datetime.datetime.now().hour + 1) else f"Заказы за последние {hours} часов.")
|
||||
177
doners/shop-telegram-bot/src/text_templates.py
Normal file
@@ -0,0 +1,177 @@
|
||||
from settings import Settings
|
||||
|
||||
settings = Settings()
|
||||
|
||||
line_separator = "➖➖➖➖➖"
|
||||
|
||||
|
||||
# Multiple lines
|
||||
def get_profile_template(user):
|
||||
return f"{line_separator}\n📝 id: {user.get_id()}\n📈 Кол-во заказов: {len(user.get_orders())}\n📅 Дата регистрации: {user.get_register_date_string()}\n{line_separator}"
|
||||
|
||||
def get_faq_template(shop_name):
|
||||
return f"{line_separator}\nℹ️ FAQ магазина {shop_name}\n{line_separator}"
|
||||
|
||||
def get_categories_template():
|
||||
return f"{line_separator}\n🛍️ Категории\n{line_separator}"
|
||||
|
||||
def get_category_was_created_successfuly(cat_name):
|
||||
return f"Категория {cat_name} была успешно создана."
|
||||
|
||||
def get_category_data(cat):
|
||||
return f"{line_separator}\nID: {cat.get_id()}\nНазвание: {cat.get_name()}\n{line_separator}"
|
||||
|
||||
def get_item_card(item=None, name=None, price=None, desc=None, amount=None):
|
||||
if item:
|
||||
name = item.get_name()
|
||||
price = item.get_price()
|
||||
desc = item.get_desc()
|
||||
amount = item.get_amount()
|
||||
|
||||
return f"{line_separator}\n{name} - {'{:.2f}'.format(price)} руб.\nВ наличии: {amount} шт.\n{line_separator}\n{desc}"
|
||||
|
||||
def get_order_confirmation_template(item_amount_dict, cart_price, email_adress, additional_message, phone_number=None, home_adress=None):
|
||||
item_amount_dict_formatted = '\n'.join([f'\t· {item[0].get_name()} - {item[1]} шт.' for item in item_amount_dict])
|
||||
phone_number = f"Номер телефона: {phone_number}\n" if phone_number else ""
|
||||
home_adress = f"Адрес доставки: {home_adress}\n" if home_adress else ""
|
||||
return f"{line_separator}\nТовары:\n{item_amount_dict_formatted}\nСумма: {cart_price}руб.\nEmail: {email_adress}\n{phone_number}{home_adress}Комментарий к заказу: {additional_message}\n{line_separator}\nВы уверены, что хотите оформить заказ?"
|
||||
|
||||
def get_order_template(order):
|
||||
item_list_amount_formatted = '\n'.join([f'\t· {item[0].get_name()} - {item[1]} шт.' for item in order.get_item_list_amount()])
|
||||
phone_number = f"Номер телефона: {order.get_phone_number()}\n" if settings.is_phone_number_enabled() else ""
|
||||
home_adress = f"Адрес доставки: {order.get_home_adress()}\n" if settings.is_delivery_enabled() else f"Самовывоз\n"
|
||||
return f"{line_separator}\nID заказа: {order.get_order_id()}\nID пользователя: {order.get_user_id()}\nТовары:\n{item_list_amount_formatted}\nСумма: {order.get_item_list_price()}руб.\nEmail: {order.get_email_adress()}\n{phone_number}{home_adress}Комментарий к заказу: {order.get_additional_message()}\nСтатус заказа: {order.get_status_string()}\nДата: {order.get_date_string()}\n{line_separator}"
|
||||
|
||||
# Single phrases
|
||||
# /start
|
||||
admin_panel = "🔴 Админ панель"
|
||||
faq = "ℹ️ FAQ"
|
||||
profile = "📁 Профиль"
|
||||
catalogue = "🗄️ Каталог"
|
||||
cart = "🛒 Корзина"
|
||||
support_menu = "☎ Меню тех. поддержки"
|
||||
|
||||
# Admin panel tabs
|
||||
item_management = "📦 Управление товаром"
|
||||
user_management = "🧍 Управление пользователями"
|
||||
shop_stats = "📈 Статистика магазина (BETA)"
|
||||
bot_settings = "⚙ Настройки бота"
|
||||
|
||||
# FAQ
|
||||
contacts = "📞 Контакты"
|
||||
refund = "🎫 Политика возврата"
|
||||
|
||||
# Profile
|
||||
my_orders = "📂 Мои заказы"
|
||||
cancel_order = "❌ Отменить заказ"
|
||||
restore_order = "✅ Восстановить заказ"
|
||||
my_support_tickets = "🙋 Мои тикеты в тех. поддержку"
|
||||
enable_notif = "🔔Включить оповещения о заказах"
|
||||
disable_notif = "🔕Выключить оповещения о заказах"
|
||||
|
||||
# Catalogue / Item / Cart
|
||||
search = "🔍 Найти"
|
||||
add_to_cart = "🛒 Добавить в корзину"
|
||||
cart_is_empty = "Корзина пуста."
|
||||
pickup = "✅Самовывоз"
|
||||
def delivery_on(price): return f"✅ Доставка - {price}руб."
|
||||
def delivery_off(price): return f"❌ Доставка - {price}руб."
|
||||
cart_checkout = "Оформить заказ"
|
||||
clear_cart = "Очистить корзину"
|
||||
processing = "Обрабатывается"
|
||||
delivery = "Ожидает доставки"
|
||||
done = "Готов"
|
||||
cancelled = "Отменён"
|
||||
|
||||
# Item management
|
||||
add_cat = "🛍️ Добавить категорию"
|
||||
add_item = "🗃️ Добавить товар"
|
||||
edit_cat = "✏️ Редактировать категорию"
|
||||
edit_item = "✏️ Редактировать товар"
|
||||
change_name = "📋 Изменить название"
|
||||
change_image = "🖼️ Изменить изображение"
|
||||
hide_image = "🙈 Скрыть изображение"
|
||||
show_image = "🐵 Показать изображение"
|
||||
change_desc = "📝 Изменить описание"
|
||||
change_price = "🏷️ Изменить цену"
|
||||
change_item_cat = "🛍️ Изменить категорию"
|
||||
change_stock = "📦 Изменить кол-во"
|
||||
|
||||
# User management
|
||||
user_profile = "📁Профиль пользователя"
|
||||
notify_everyone = "🔔Оповещение всем пользователям"
|
||||
orders = "📁 Заказы"
|
||||
remove_manager_role = "👨💼 Убрать роль менеджера"
|
||||
add_manager_role = "👨💼 Сделать менеджером"
|
||||
remove_admin_role = "🔴 Убрать роль администратора"
|
||||
add_admin_role = "🔴 Сделать администратором"
|
||||
def change_order_status(status): return f"Изменить статус на \"{status}\""
|
||||
|
||||
# Shop stats
|
||||
registration_stats = "👥Статистика регистраций"
|
||||
order_stats = "📦Статистика заказов"
|
||||
all_time = "За всё время"
|
||||
monthly = "За последние 30 дней"
|
||||
weekly = "За последние 7 дней"
|
||||
daily = "За последние 24 часа"
|
||||
|
||||
# Shop settings
|
||||
main_settings = "🛠️ Основные настройки"
|
||||
item_settings = "🗃️ Настройки товаров"
|
||||
additional_settings = "📖 Дополнительные настройки"
|
||||
custom_commands = "📖 Команды"
|
||||
add_command = "📝 Добавить команду"
|
||||
clean_logs = "📖 Очистить логи"
|
||||
clean_logs_text = "⚠️ Вы уверены, что хотите очистить логи? Они будут удалены без возможности восстановления!\n(Логи за сегодняшний день не будут удалены)"
|
||||
backups = "💾 Резервное копирование"
|
||||
update_backup = "🔄 Обновить резервную копию"
|
||||
load_backup = "💿 Загрузить резервную копию"
|
||||
clean_backups = "🧹 Очистка резервных копий"
|
||||
system_settings = "💻 Система"
|
||||
clean_images = "🗑️ Удалить неиспользуемые изображения"
|
||||
clean_images_text = "⚠️ Вы уверены, что хотите удалить неспользуемые изображения? Они будут удалены без возможности восстановления!"
|
||||
clean_database = "📚 Очистить базу данных"
|
||||
clean_database_text = "⚠️ Вы уверены, что хотите очистить базу данных? Все данные будут удалены без возможности восстановления!"
|
||||
reset_settings = "⚙️ Сбросить настройки"
|
||||
resert_settings_text = "⚠️ Вы уверены, что хотите сбросить настройки? Все данные будут удалены без возможности восстановления!"
|
||||
disable_item_image = "✅ Картинки товаров"
|
||||
enable_item_image = "❌ Картинки товаров"
|
||||
checkout_settings = "💳 Настройки оформления заказа"
|
||||
stats_settings = "📈 Настройки статистики"
|
||||
graph_color = "🌈 Цвет графика"
|
||||
border_width = "🔲 Ширина обводки"
|
||||
title_font_size = "ℹ️ Размер названия графика"
|
||||
axis_font_size = "↔️Размер текста для осей"
|
||||
tick_font_size = "🔢Размер текста для делений"
|
||||
unavailable = "⛔️"
|
||||
minus = "➖"
|
||||
plus = "➕"
|
||||
enable_sticker = "❌ Стикер в приветствии"
|
||||
disable_sticker = "✅ Стикер в приветствии"
|
||||
enable_phone_number = "❌ Номер телефона при заказе"
|
||||
disable_phone_number = "✅ Номер телефона при заказе"
|
||||
enable_delivery = "❌ Доставка"
|
||||
disable_delivery = "✅ Доставка"
|
||||
def delivery_price(price): return f"🚚 Стоимость доставки: {price}руб."
|
||||
enable_captcha = "❌ CAPTCHA при заказе"
|
||||
disable_captcha = "✅ CAPTCHA при заказе"
|
||||
enable_debug = "❌ Режим отладки"
|
||||
disable_debug = "✅ Режим отладки"
|
||||
|
||||
# Manager tab
|
||||
view_order = "📂 Посмотреть заказ"
|
||||
|
||||
# Misc buttons
|
||||
skip = "⏭ Пропустить"
|
||||
back = "🔙 Назад"
|
||||
confirm = "✅ Да"
|
||||
deny = "❌ Нет"
|
||||
error = "Произошла ошибка!"
|
||||
or_press_back = "или нажмите на кнопку \"Назад\"."
|
||||
hide = "🙈 Скрыть"
|
||||
show = "🐵 Показать"
|
||||
delete = "❌ Удалить"
|
||||
reset = "❌ Сбросить"
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(delivery_on)
|
||||
111
doners/shop-telegram-bot/src/user.py
Normal file
@@ -0,0 +1,111 @@
|
||||
import sqlite3
|
||||
from datetime import datetime
|
||||
import item as itm
|
||||
from order import Order
|
||||
from settings import Settings
|
||||
|
||||
conn = sqlite3.connect('data.db')
|
||||
c = conn.cursor()
|
||||
settings = Settings()
|
||||
|
||||
class User:
|
||||
def __init__(self, user_id):
|
||||
self.__user_id = user_id
|
||||
|
||||
if not does_user_exist(self.get_id()):
|
||||
c.execute(f"INSERT INTO users VALUES(?, ?, ?, ?, ?, ?, ?)", [self.get_id(), 1 if str(self.get_id()) == settings.get_main_admin_id() else 0, 0, 0, datetime.now().strftime("%Y-%m-%d %H:%M:%S"), "None", 1])
|
||||
conn.commit()
|
||||
|
||||
def get_id(self):
|
||||
return self.__user_id
|
||||
|
||||
def __clist(self):
|
||||
c.execute(f"SELECT * FROM users WHERE user_id=?", [self.get_id()])
|
||||
return list(c)[0]
|
||||
|
||||
def is_admin(self):
|
||||
return self.__clist()[1] == 1
|
||||
|
||||
def set_admin(self, value):
|
||||
c.execute(f"UPDATE users SET is_admin=? WHERE user_id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def is_manager(self):
|
||||
return self.__clist()[2] == 1
|
||||
|
||||
def set_manager(self, value):
|
||||
c.execute(f"UPDATE users SET is_manager=? WHERE user_id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_register_date(self):
|
||||
return datetime.strptime(self.__clist()[4], "%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def get_register_date_string(self):
|
||||
return self.__clist()[4]
|
||||
|
||||
def notif_on(self):
|
||||
return self.__clist()[3] == 1
|
||||
|
||||
def set_notif_enable(self, value):
|
||||
c.execute(f"UPDATE users SET notification=? WHERE user_id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def get_orders(self):
|
||||
c.execute(f"SELECT * FROM orders WHERE user_id=?", [self.get_id()])
|
||||
return list(map(Order, [order[0] for order in list(c)]))[::-1]
|
||||
|
||||
def get_cart_comma(self):
|
||||
return self.__clist()[5]
|
||||
|
||||
def get_cart(self):
|
||||
cart = self.get_cart_comma()
|
||||
return [] if cart == "None" else list(map(itm.Item, cart.split(",")))
|
||||
|
||||
def get_cart_amount(self):
|
||||
cart = [item.get_id() for item in self.get_cart()]
|
||||
return [[itm.Item(item_id), cart.count(item_id)] for item_id in set(cart)]
|
||||
|
||||
def get_cart_price(self):
|
||||
return sum([item_and_price[0].get_price() * item_and_price[1] for item_and_price in self.get_cart_amount()]) + (settings.get_delivery_price() if self.is_cart_delivery() else 0)
|
||||
|
||||
def clear_cart(self):
|
||||
c.execute(f"UPDATE users SET cart=\"None\" WHERE user_id=?", [self.get_id()])
|
||||
self.set_cart_delivery(1)
|
||||
conn.commit()
|
||||
|
||||
def add_to_cart(self, item_id):
|
||||
cart = self.get_cart()
|
||||
c.execute(f"UPDATE users SET cart=? WHERE user_id=?", [",".join([str(item.get_id()) for item in cart + [itm.Item(item_id)]]) if cart else item_id, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def remove_from_cart(self, item_id):
|
||||
cart = [item.get_id() for item in self.get_cart()]
|
||||
cart.remove(str(item_id))
|
||||
c.execute(f"UPDATE users SET cart=? WHERE user_id=?", [",".join(cart) if cart else "None", self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
def is_cart_delivery(self):
|
||||
return self.__clist()[6] == 1
|
||||
|
||||
def set_cart_delivery(self, value):
|
||||
c.execute(f"UPDATE users SET cart_delivery=? WHERE user_id=?", [value, self.get_id()])
|
||||
conn.commit()
|
||||
|
||||
|
||||
def does_user_exist(user_id):
|
||||
c.execute(f"SELECT * FROM users WHERE user_id=?", [user_id])
|
||||
return len(list(c)) != 0
|
||||
|
||||
|
||||
def get_notif_list():
|
||||
c.execute(f"SELECT * FROM users WHERE notification=1")
|
||||
return list(map(User, [user[0] for user in list(c)]))
|
||||
|
||||
|
||||
def get_user_login(message):
|
||||
return message.from_user.username
|
||||
|
||||
|
||||
def get_user_list():
|
||||
c.execute("SELECT * FROM users")
|
||||
return list(map(User, [user[0] for user in list(c)]))
|
||||
3
doners/shop-telegram-bot/start.cmd
Normal file
@@ -0,0 +1,3 @@
|
||||
cls
|
||||
echo "Starting the bot..."
|
||||
python3 src/main.py
|
||||
4
doners/shop-telegram-bot/start.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
clear
|
||||
echo "Starting the bot..."
|
||||
python3.10 src/main.py
|
||||
|
||||
0
doners/shop-telegram-bot/tests/__init__.py
Normal file
127
doners/shop-telegram-bot/tests/test_item.py
Normal file
@@ -0,0 +1,127 @@
|
||||
import unittest
|
||||
from random import randint
|
||||
from .utils import *
|
||||
|
||||
import src.item as itm
|
||||
import src.category as cat
|
||||
|
||||
|
||||
|
||||
class TestItem(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.test_data_random = {
|
||||
"name": get_random_string(10, 40),
|
||||
"price": get_random_float(100, 10000),
|
||||
"cat_id": randint(1, 100),
|
||||
"desc": get_random_string(10, 255),
|
||||
}
|
||||
self.item_random = itm.create_item(self.test_data_random["name"], self.test_data_random["price"], self.test_data_random["cat_id"], self.test_data_random["desc"], active=False)
|
||||
|
||||
self.test_data = {
|
||||
"name": "Test Item Name",
|
||||
"price": 999.99,
|
||||
"cat_id": cat.get_cat_list()[0].get_id(),
|
||||
"desc": "Test Item's description here. Blah-blah-blah..."
|
||||
}
|
||||
self.item = itm.create_item(self.test_data["name"], self.test_data["price"], self.test_data["cat_id"], self.test_data["desc"], active=False)
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
self.item.delete()
|
||||
|
||||
def test_name(self):
|
||||
self.assertEqual(self.item_random.get_name(), self.test_data_random["name"])
|
||||
name_random = get_random_string(2, 20)
|
||||
self.item_random.set_name(name_random)
|
||||
self.test_data_random["name"] = name_random
|
||||
self.assertEqual(self.item_random.get_name(), self.test_data_random["name"])
|
||||
|
||||
self.assertEqual(self.item.get_name(), self.test_data["name"])
|
||||
name = "New test Item name"
|
||||
self.item.set_name(name)
|
||||
self.test_data["name"] = name
|
||||
self.assertEqual(self.item.get_name(), self.test_data["name"])
|
||||
|
||||
def test_price(self):
|
||||
self.assertEqual(self.item_random.get_price(), self.test_data_random["price"])
|
||||
price_random = get_random_float(10, 1000)
|
||||
self.item_random.set_price(price_random)
|
||||
self.test_data_random["price"] = price_random
|
||||
self.assertEqual(self.item_random.get_price(), self.test_data_random["price"])
|
||||
|
||||
self.assertEqual(self.item.get_price(), self.test_data["price"])
|
||||
price = 123.12
|
||||
self.item.set_price(price)
|
||||
self.test_data["price"] = price
|
||||
self.assertEqual(self.item.get_price(), self.test_data["price"])
|
||||
|
||||
def test_cat_id(self):
|
||||
self.assertEqual(self.item_random.get_cat_id(), self.test_data_random["cat_id"])
|
||||
cat_id_random = randint(1, 100)
|
||||
self.item_random.set_cat_id(cat_id_random)
|
||||
self.test_data_random["cat_id"] = cat_id_random
|
||||
self.assertEqual(self.item_random.get_cat_id(), self.test_data_random["cat_id"])
|
||||
|
||||
self.assertEqual(self.item.get_cat_id(), self.test_data["cat_id"])
|
||||
cat_id = 2
|
||||
self.item.set_cat_id(cat_id)
|
||||
self.test_data["cat_id"] = cat_id
|
||||
self.assertEqual(self.item.get_cat_id(), self.test_data["cat_id"])
|
||||
|
||||
def test_desc(self):
|
||||
self.assertEqual(self.item_random.get_desc(), self.test_data_random["desc"])
|
||||
desc_random = get_random_string(10, 255)
|
||||
self.item_random.set_desc(desc_random)
|
||||
self.test_data_random["desc"] = desc_random
|
||||
self.assertEqual(self.item_random.get_desc(), self.test_data_random["desc"])
|
||||
|
||||
self.assertEqual(self.item.get_desc(), self.test_data["desc"])
|
||||
desc = "New desc"
|
||||
self.item.set_desc(desc)
|
||||
self.test_data["desc"] = desc
|
||||
self.assertEqual(self.item.get_desc(), self.test_data["desc"])
|
||||
|
||||
def test_active(self):
|
||||
self.assertFalse(self.item.is_active())
|
||||
|
||||
self.item.set_active(1)
|
||||
self.assertTrue(self.item.is_active())
|
||||
self.item.set_active(0)
|
||||
self.assertFalse(self.item.is_active())
|
||||
|
||||
def test_amount(self):
|
||||
self.assertEqual(self.item_random.get_amount(), 0)
|
||||
amount_random = randint(1, 1000)
|
||||
self.item_random.set_amount(amount_random)
|
||||
self.test_data_random["amount"] = amount_random
|
||||
self.assertEqual(self.item_random.get_amount(), self.test_data_random["amount"])
|
||||
|
||||
self.assertEqual(self.item.get_amount(), 0)
|
||||
amount = 90
|
||||
self.item.set_amount(amount)
|
||||
self.test_data["amount"] = amount
|
||||
self.assertEqual(self.item.get_amount(), self.test_data["amount"])
|
||||
|
||||
# TODO: image test cases
|
||||
@unittest.SkipTest
|
||||
def test_get_image_id(self):
|
||||
pass
|
||||
|
||||
@unittest.SkipTest
|
||||
def test_get_image(self):
|
||||
pass
|
||||
|
||||
@unittest.SkipTest
|
||||
def test_set_image_id(self):
|
||||
pass
|
||||
|
||||
def test_hide_image(self):
|
||||
self.assertFalse(self.item.is_hide_image())
|
||||
self.item.set_hide_image(1)
|
||||
self.assertTrue(self.item.is_hide_image())
|
||||
self.item.set_hide_image(0)
|
||||
self.assertFalse(self.item.is_hide_image())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
8
doners/shop-telegram-bot/tests/utils.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from random import randint, choice
|
||||
from string import ascii_lowercase, digits
|
||||
|
||||
def get_random_string(a, b):
|
||||
return ''.join([choice(ascii_lowercase+digits) for _ in range(randint(a, b))])
|
||||
|
||||
def get_random_float(a, b):
|
||||
return float(f"{randint(a, b)}.{randint(0, 99)}")
|
||||
215
doners/shopbot/README.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# Сообщение от разработчика (31.01.2023)
|
||||
Всем привет, код писался мною когда я только учился пайтону, просьба не используйте данный код в коммерческих целях.
|
||||
|
||||
Скажу откровенно, код очень не граммотный и требует полного рефакторинга
|
||||
|
||||
Почему? Потому что данный код написан в одном файле что является очень плохим способом.
|
||||
|
||||
Кому не лень - перепишите, я посмотрю и закомичу Ваш апдейт :D
|
||||
|
||||
Возможно я ещё возьмусь за данного бота и переработаю его чтобы Вы могли использовать в коммерческих целях и в личных :D
|
||||
|
||||
Спасибо!
|
||||
|
||||
Так-же хочу выразить глубокую благодарность за ваши звёзды и форки, приятно это видеть, даже если код откровенно ужасен.
|
||||
|
||||
Хочу посоветовать Вам, братья учите пайтон, не пожалеете. Данный код пока что используйте только в ознакомительных целях, не думайте что он хороший и его нужно использовать.
|
||||
|
||||
# Обновление 4.0 (18.10.2021)
|
||||
✅ Обновления:
|
||||
1. Был полностью оптимизирован код, теперь если пользователь введёт что-то не так, бот не будет крашиться, а напишет
|
||||
|
||||

|
||||
|
||||
2. Инлайновые кнопки были полностью изменены, вместо текста, эмодзи.
|
||||
|
||||

|
||||
|
||||
3. Обновлён профиль пользователя
|
||||
|
||||

|
||||
|
||||
4. Добавлена команда для просмотра всех пользователей `/users` (Синяя ссылка это кликабельно)
|
||||
|
||||

|
||||
|
||||
# Обновление 3.0 (17.10.2021)
|
||||
✅ Обновления:
|
||||
1. Добавлена команда для просмотра купленных товаров `/mybuy`
|
||||
2. При покупке товара, если у пользователя уже куплен товар пишет
|
||||
|
||||

|
||||
|
||||
3. Добавлена команда для связи с тех.поддержкой `/teh`
|
||||
4. Для администратора добавлена команда `/ot` для ответа пользователю (отправки сообщения пользователю)
|
||||
|
||||
# Обновление 2.0 (17.10.2021)
|
||||
|
||||
✅ Обновления:
|
||||
1. Добавлена команда для удаления товаров `/rembuy`
|
||||
2. Добавлена команда для изменения данных о товаре (Название, Цена, Ссылка) `/editbuy`
|
||||
3. Код стал более оптимизированым
|
||||
|
||||
# ShopBot Telegram
|
||||
|
||||
🤖 Бот Магазин для Телеграмма на Python 🤖
|
||||
|
||||
|
||||
✅ Функционал бота:
|
||||
1. Уровни доступа (Пользователь, Администратор, Разработчик)
|
||||
2. База данных SQLite3
|
||||
3. Добавление товаров
|
||||
4. Продажа товаров
|
||||
5. Пополнение счёта при помощи QIWI (Библиотека SimpleQIWI)
|
||||
6. Просмотр профилей других пользователей
|
||||
7. Выдача средств, выдача уровней доступа.
|
||||
8. Изменение данных о товаре
|
||||
9. Удаление товаров
|
||||
10. Список купленных товаров
|
||||
11. Система тех.поддержки
|
||||
|
||||
🎄 В будущем:
|
||||
1. Ссылка для оплаты при попоплнении счёта
|
||||
|
||||
Список будет дополняться и изменятся.
|
||||
|
||||
📄 Список библиотек которые нужно установить:
|
||||
|
||||
`pip install pyTelegramBotAPI`
|
||||
`pip install SimpleQIWI`
|
||||
`pip install requests`
|
||||
|
||||
👾 Список команд:
|
||||
|
||||
`/start` - Зарегистрироваться в боте
|
||||
|
||||
`/profile` - Профиль
|
||||
|
||||
`/donate` - Пополнить счёт
|
||||
|
||||
`/buy` - Купить товар
|
||||
|
||||
`/help` - Помощь по командам
|
||||
|
||||
`/mybuy` - Список купленных товаров
|
||||
|
||||
`/teh` - Связаться с тех.поддержкой
|
||||
|
||||
🚨 Админские команды:
|
||||
|
||||
`/getprofile` - Посмотреть чужой профиль
|
||||
|
||||
`/getid` - Узнать ID пользователя
|
||||
|
||||
`/getcid` - Узнать Conference ID
|
||||
|
||||
`/access` - Выдать уровень доступа
|
||||
|
||||
`/giverub` - Выдать средства пользователю
|
||||
|
||||
`/addbuy` - Добавить товар
|
||||
|
||||
`/getrazrab` - Выдать себе разработчика (читать установку)
|
||||
|
||||
`/editbuy` - Изменить данные о товаре
|
||||
|
||||
`/rembuy` - Удалить товар
|
||||
|
||||
`/ot` - Ответить пользователю (отправить сообщение)
|
||||
|
||||
|
||||
# 🛠 Установка бота:
|
||||
1. Скачать файлы, затем распоковать папку в любое удобное место.
|
||||
2. Открыть папку затем открыть файл `configure.py`
|
||||
3. Найти бота `@BotFather` в телеграмм затем написать `/newbot`
|
||||
4. Ввести имя бота, ник бота затем вылезет токен
|
||||
|
||||

|
||||
|
||||
5. Копируем токен, затем открываем файл `configure.py`
|
||||
|
||||

|
||||
|
||||
У вас будет пустой файл в строку `'name'` нужно вписать имя бота пример:
|
||||
|
||||

|
||||
|
||||
В строку `'token'` нужно вписать токен который мы получили ранее:
|
||||
|
||||

|
||||
|
||||
Далее переходим на сайт `qiwi.com/api`
|
||||
Затем нажимаем на кнопку Выпустить новый токен
|
||||
|
||||

|
||||
|
||||
Выбираем все галочки, нажимаем на кнопку выбрать всё, затем нажимаем продолжить
|
||||
|
||||

|
||||
|
||||
Далее копируем токен и вставляем его в строку `'tokenqiwi'`
|
||||
|
||||

|
||||
|
||||
Далее в строку `'phoneqiwi'` вставляем свой номер телефона от QIWI кошелька
|
||||
|
||||

|
||||
|
||||
Обязательно с + и кодом страны
|
||||
|
||||
Отлично! Всё почти готово.
|
||||
|
||||
Переходим по ссылке которая в сообщении с токеном бота
|
||||
|
||||

|
||||
|
||||
Запускаем бота открывая файл `start.bat`
|
||||
|
||||
Далее нажимаем кнопку `Начать` либо `/start`
|
||||
|
||||

|
||||
|
||||
Изменить этот текст Вы сможете тут
|
||||
|
||||

|
||||
|
||||
Весь исходный код находиться в файле `bot.py`
|
||||
|
||||
Открываем файл `bot.py` затем ищем строку
|
||||
|
||||

|
||||
|
||||
В чате с ботом пишем `/profile`
|
||||
|
||||

|
||||
|
||||
Копируем ваш ID затем вставялем в строку `WHERE id = 596060542` и `if message.from_user.id == 596060542:` ваш ID
|
||||
|
||||

|
||||
|
||||
Затем сохраняем и перезапускаем бота.
|
||||
В чат с ботом пишем `/getrazrab` и всё! Вы получили админку разработчика
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Далее находим строку 223 'def donateyesoplacheno'
|
||||
|
||||

|
||||
|
||||
И вместо `596060542` вписываем свой ID
|
||||
|
||||
Всё, бот полностью настроен!
|
||||
Советую поставить бота на хостинг по типу:
|
||||
|
||||
`eu.pythonanywhere.com`
|
||||
|
||||
Либо на ВДС или на Выделенный сервер
|
||||
|
||||
Бот делал лично Я с нуля, один. Просьба если будете его пересливать, дорабатывать и сливать то указывайте автора исходного кода. ❤
|
||||
|
||||
# 📞 Связь со мной:
|
||||
|
||||
1. ВК: https://vk.com/nosemka
|
||||
2. Дискорд: semmy#0068
|
||||
883
doners/shopbot/bot.py
Normal file
@@ -0,0 +1,883 @@
|
||||
import telebot
|
||||
import configure
|
||||
import sqlite3
|
||||
from telebot import types
|
||||
import threading
|
||||
from requests import get
|
||||
from time import sleep
|
||||
from SimpleQIWI import *
|
||||
|
||||
client = telebot.TeleBot(configure.config['token'])
|
||||
db = sqlite3.connect('baza.db', check_same_thread=False)
|
||||
sql = db.cursor()
|
||||
lock = threading.Lock()
|
||||
api = QApi(token=configure.config['tokenqiwi'], phone=configure.config['phoneqiwi'])
|
||||
markdown = """
|
||||
*bold text*
|
||||
_italic text_
|
||||
[text](URL)
|
||||
"""
|
||||
|
||||
#database
|
||||
|
||||
sql.execute("""CREATE TABLE IF NOT EXISTS users (id BIGINT, nick TEXT, cash INT, access INT, bought INT)""")
|
||||
sql.execute("""CREATE TABLE IF NOT EXISTS shop (id INT, name TEXT, price INT, tovar TEXT, whobuy TEXT)""")
|
||||
db.commit()
|
||||
|
||||
@client.message_handler(commands=['start'])
|
||||
def start(message):
|
||||
try:
|
||||
getname = message.from_user.first_name
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
|
||||
sql.execute(f"SELECT id FROM users WHERE id = {uid}")
|
||||
if sql.fetchone() is None:
|
||||
sql.execute(f"INSERT INTO users VALUES ({uid}, '{getname}', 0, 0, 0)")
|
||||
client.send_message(cid, f"🛒 | Добро пожаловать, {getname}!\nТы попал в бота магазин\nИзмените этот текст!")
|
||||
db.commit()
|
||||
else:
|
||||
client.send_message(cid, f"⛔️ | Ты уже зарегистрирован! Пропиши /help чтобы узнать команды.")
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['profile', 'myinfo', 'myprofile'])
|
||||
def myprofile(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
sql.execute(f"SELECT * FROM users WHERE id = {uid}")
|
||||
getaccess = sql.fetchone()[3]
|
||||
if getaccess == 0:
|
||||
accessname = 'Пользователь'
|
||||
elif getaccess == 1:
|
||||
accessname = 'Администратор'
|
||||
elif getaccess == 777:
|
||||
accessname = 'Разработчик'
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"):
|
||||
client.send_message(cid, f"*📇 | Твой профиль:*\n\n*👤 | Ваш ID:* {info[0]}\n*💸 | Баланс:* {info[2]} ₽\n*👑 | Уровень доступа:* {accessname}\n*🛒 | Куплено товаров:* {info[4]}\n\n*🗂 Чтобы посмотреть список купленных товаров напишите /mybuy*", parse_mode='Markdown')
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['users'])
|
||||
def allusers(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
sql.execute(f"SELECT * FROM users WHERE id = {uid}")
|
||||
getaccess = sql.fetchone()[3]
|
||||
accessquery = 1
|
||||
if getaccess < accessquery:
|
||||
client.send_message(cid, '⚠️ | У вас нет доступа!')
|
||||
else:
|
||||
text = '*🗃 | Список всех пользователей:*\n\n'
|
||||
idusernumber = 0
|
||||
for info in sql.execute(f"SELECT * FROM users"):
|
||||
if info[3] == 0:
|
||||
accessname = 'Пользователь'
|
||||
elif info[3] == 1:
|
||||
accessname = 'Администратор'
|
||||
elif info[3] == 777:
|
||||
accessname = 'Разработчик'
|
||||
idusernumber += 1
|
||||
text += f"*{idusernumber}. {info[0]} ({info[1]})*\n*💸 | Баланс:* {info[2]} ₽\n*👑 | Уровень доступа:* {accessname}\n*✉️ | Профиль:*" + f" [{info[1]}](tg://user?id="+str(info[0])+")\n\n"
|
||||
client.send_message(cid, f"{text}",parse_mode='Markdown')
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['mybuy'])
|
||||
def mybuy(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
text = '*🗂 | Список купленных товаров:*\n\n'
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"):
|
||||
for infoshop in sql.execute(f"SELECT * FROM shop"):
|
||||
if str(info[0]) in infoshop[4]:
|
||||
text += f"*{infoshop[0]}. {infoshop[1]}*\nТовар: {infoshop[3]}\n\n"
|
||||
client.send_message(cid,f"{text}",parse_mode='Markdown',disable_web_page_preview=True)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['getprofile', 'info'])
|
||||
def getprofile(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
sql.execute(f"SELECT * FROM users WHERE id = {uid}")
|
||||
getaccess = sql.fetchone()[3]
|
||||
accessquery = 1
|
||||
if getaccess < accessquery:
|
||||
client.send_message(cid, '⚠️ | У вас нет доступа!')
|
||||
else:
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"):
|
||||
msg = client.send_message(cid, f'Введите ID пользователя:\nПример: {info[0]}')
|
||||
client.register_next_step_handler(msg, getprofile_next)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def getprofile_next(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
getprofileid = message.text
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {getprofileid}"):
|
||||
if info[3] == 0:
|
||||
accessname = 'Пользователь'
|
||||
elif info[3] == 1:
|
||||
accessname = 'Администратор'
|
||||
elif info[3] == 777:
|
||||
accessname = 'Разработчик'
|
||||
client.send_message(cid, f"*📇 | Профиль {info[1]}:*\n\n*ID пользователя:* {info[0]}\n*Баланс:* {info[2]} ₽\n*Уровень доступа:* {accessname}\n*Куплено товаров:* {info[4]}",parse_mode='Markdown')
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['editbuy'])
|
||||
def editbuy(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
accessquery = 1
|
||||
with lock:
|
||||
sql.execute(f"SELECT * FROM users WHERE id = {uid}")
|
||||
getaccess = sql.fetchone()[3]
|
||||
if getaccess < 1:
|
||||
client.send_message(cid, '⚠️ | У вас нет доступа!')
|
||||
else:
|
||||
rmk = types.InlineKeyboardMarkup()
|
||||
item_name = types.InlineKeyboardButton(text='Название',callback_data='editbuyname')
|
||||
item_price = types.InlineKeyboardButton(text='Цена',callback_data='editbuyprice')
|
||||
item_tovar = types.InlineKeyboardButton(text='Товар',callback_data='editbuytovar')
|
||||
rmk.add(item_name, item_price, item_tovar)
|
||||
msg = client.send_message(cid, f"🔰 | Выберите что Вы хотите изменить:",reply_markup=rmk,parse_mode='Markdown')
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def editbuy_name(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global editbuynameidtovar
|
||||
editbuynameidtovar = int(message.text)
|
||||
msg = client.send_message(cid, f"*Введите новое название товара:*",parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, editbuy_name_new_name)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def editbuy_name_new_name(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global editbuynametovar
|
||||
editbuynametovar = message.text
|
||||
for infoshop in sql.execute(f"SELECT * FROM shop WHERE id = {editbuynameidtovar}"):
|
||||
rmk = types.InlineKeyboardMarkup()
|
||||
item_yes = types.InlineKeyboardButton(text='✅', callback_data='editbuynewnametovaryes')
|
||||
item_no = types.InlineKeyboardButton(text='❌', callback_data='editbuynewnametovarno')
|
||||
rmk.add(item_yes, item_no)
|
||||
msg = client.send_message(cid, f"*🔰 | Данные об изменении названия товара:*\n\nID товара: {editbuynameidtovar}\nСтарое имя товара: {infoshop[1]}\nНовое имя товара: {editbuynametovar}\n\nВы подверждаете изменения?",parse_mode='Markdown',reply_markup=rmk)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def editbuy_price(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global editbuypriceidtovar
|
||||
editbuypriceidtovar = int(message.text)
|
||||
msg = client.send_message(cid, f"*Введите новую цену товара:*",parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, editbuy_price_new_price)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def editbuy_price_new_price(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global editbuypricetovar
|
||||
editbuypricetovar = int(message.text)
|
||||
for infoshop in sql.execute(f"SELECT * FROM shop WHERE id = {editbuypriceidtovar}"):
|
||||
rmk = types.InlineKeyboardMarkup()
|
||||
item_yes = types.InlineKeyboardButton(text='✅', callback_data='editbuynewpricetovaryes')
|
||||
item_no = types.InlineKeyboardButton(text='❌', callback_data='editbuynewpricetovarno')
|
||||
rmk.add(item_yes, item_no)
|
||||
msg = client.send_message(cid, f"*🔰 | Данные об изменении цены товара:*\n\nID товара: {editbuypriceidtovar}\nСтарая цена: {infoshop[2]}\nНовая цена: {editbuypricetovar}\n\nВы подверждаете изменения?",parse_mode='Markdown',reply_markup=rmk)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def editbuy_tovar(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global editbuytovaridtovar
|
||||
editbuytovaridtovar = int(message.text)
|
||||
msg = client.send_message(cid, f"*Введите новую ссылку на товар:*",parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, editbuy_tovar_new_tovar)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def editbuy_tovar_new_tovar(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global editbuytovartovar
|
||||
editbuytovartovar = message.text
|
||||
for infoshop in sql.execute(f"SELECT * FROM shop WHERE id = {editbuytovaridtovar}"):
|
||||
rmk = types.InlineKeyboardMarkup()
|
||||
item_yes = types.InlineKeyboardButton(text='✅', callback_data='editbuynewtovartovaryes')
|
||||
item_no = types.InlineKeyboardButton(text='❌', callback_data='editbuynewtovartovarno')
|
||||
rmk.add(item_yes, item_no)
|
||||
msg = client.send_message(cid, f"*🔰 | Данные об изменении сcылки товара:*\n\nID товара: {editbuytovaridtovar}\nСтарая ссылка: {infoshop[3]}\nНовая ссылка: {editbuytovartovar}\n\nВы подверждаете изменения?",parse_mode='Markdown',reply_markup=rmk)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.callback_query_handler(lambda call: call.data == 'editbuynewtovartovaryes' or call.data == 'editbuynewtovartovarno')
|
||||
def editbuy_tovar_new_callback(call):
|
||||
try:
|
||||
if call.data == 'editbuynewtovartovaryes':
|
||||
sql.execute(f"SELECT * FROM shop WHERE id = {editbuytovaridtovar}")
|
||||
sql.execute(f"UPDATE shop SET tovar = '{editbuytovartovar}' WHERE id = {editbuytovaridtovar}")
|
||||
db.commit()
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"✅ | Вы успешно изменили ссылку на товар на {editbuytovartovar}")
|
||||
elif call.data == 'editbuynewtovartovarno':
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"🚫 | Вы отменили изменение сcылки товара")
|
||||
client.answer_callback_query(callback_query_id=call.id)
|
||||
except:
|
||||
client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.callback_query_handler(lambda call: call.data == 'editbuynewpricetovaryes' or call.data == 'editbuynewpricetovarno')
|
||||
def editbuy_price_new_callback(call):
|
||||
try:
|
||||
if call.data == 'editbuynewpricetovaryes':
|
||||
sql.execute(f"SELECT * FROM shop WHERE id = {editbuypriceidtovar}")
|
||||
sql.execute(f"UPDATE shop SET price = {editbuypricetovar} WHERE id = {editbuypriceidtovar}")
|
||||
db.commit()
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"✅ | Вы успешно изменили цену товара на {editbuypricetovar}")
|
||||
elif call.data == 'editbuynewpricetovarno':
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"🚫 | Вы отменили изменение цены товара")
|
||||
client.answer_callback_query(callback_query_id=call.id)
|
||||
except:
|
||||
client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды')
|
||||
@client.callback_query_handler(lambda call: call.data == 'editbuynewnametovaryes' or call.data == 'editbuynewnametovarno')
|
||||
def editbuy_name_new_callback(call):
|
||||
try:
|
||||
if call.data == 'editbuynewnametovaryes':
|
||||
sql.execute(f"SELECT * FROM shop WHERE id = {editbuynameidtovar}")
|
||||
sql.execute(f"UPDATE shop SET name = '{editbuynametovar}' WHERE id = {editbuynameidtovar}")
|
||||
db.commit()
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"✅ | Вы успешно изменили название товара на {editbuynametovar}")
|
||||
elif call.data == 'editbuynewnametovarno':
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"🚫 | Вы отменили изменение названия товара")
|
||||
client.answer_callback_query(callback_query_id=call.id)
|
||||
except:
|
||||
client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
|
||||
@client.callback_query_handler(lambda call: call.data == 'editbuyname' or call.data == 'editbuyprice' or call.data == 'editbuytovar')
|
||||
def editbuy_first_callback(call):
|
||||
try:
|
||||
if call.data == 'editbuyname':
|
||||
msg = client.send_message(call.message.chat.id, f"*Введите ID товара которому хотите изменить название:*",parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, editbuy_name)
|
||||
elif call.data == 'editbuyprice':
|
||||
msg = client.send_message(call.message.chat.id, f"*Введите ID товара которому хотите изменить цену:*",parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, editbuy_price)
|
||||
elif call.data == 'editbuytovar':
|
||||
msg = client.send_message(call.message.chat.id, f"*Введите ID товара которому хотите изменить ссылку:*",parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, editbuy_tovar)
|
||||
client.answer_callback_query(callback_query_id=call.id)
|
||||
except:
|
||||
client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['rembuy'])
|
||||
def removebuy(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
accessquery = 1
|
||||
with lock:
|
||||
sql.execute(f"SELECT * FROM users WHERE id = {uid}")
|
||||
getaccess = sql.fetchone()[3]
|
||||
if getaccess < 1:
|
||||
client.send_message(cid, '⚠️ | У вас нет доступа!')
|
||||
else:
|
||||
msg = client.send_message(cid, f"*Введите ID товара который хотите удалить:*",parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, removebuy_next)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def removebuy_next(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global removeidtovar
|
||||
removeidtovar = int(message.text)
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"):
|
||||
for infoshop in sql.execute(f"SELECT * FROM shop WHERE id = {removeidtovar}"):
|
||||
rmk = types.InlineKeyboardMarkup()
|
||||
item_yes = types.InlineKeyboardButton(text='✅',callback_data='removebuytovaryes')
|
||||
item_no = types.InlineKeyboardButton(text='❌',callback_data='removebuytovarno')
|
||||
rmk.add(item_yes, item_no)
|
||||
msg = client.send_message(cid, f"🔰 | Данные об удалении:\n\nID товара: {infoshop[0]}\nИмя товара: {infoshop[1]}\nЦена товара: {infoshop[2]}\nТовар: {infoshop[3]}\n\nВы действительно хотите удалить товар? Отменить действие будет НЕВОЗМОЖНО.",reply_markup=rmk)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.callback_query_handler(lambda call: call.data == 'removebuytovaryes' or call.data == 'removebuytovarno')
|
||||
def removebuy_callback(call):
|
||||
try:
|
||||
if call.data == 'removebuytovaryes':
|
||||
sql.execute(f"SELECT * FROM shop")
|
||||
sql.execute(f"DELETE FROM shop WHERE id = {removeidtovar}")
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"✅ | Вы успешно удалили товар")
|
||||
db.commit()
|
||||
elif call.data == 'removebuytovarno':
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"🚫 | Вы отменили удаление товара")
|
||||
client.answer_callback_query(callback_query_id=call.id)
|
||||
except:
|
||||
client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['addbuy'])
|
||||
def addbuy(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
with lock:
|
||||
sql.execute(f"SELECT * FROM users WHERE id = {uid}")
|
||||
getaccess = sql.fetchone()[3]
|
||||
if getaccess < 1:
|
||||
client.send_message(cid, '⚠️ | У вас нет доступа!')
|
||||
else:
|
||||
msg = client.send_message(cid, '*Введите ID товара:*',parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, addbuy_id)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def addbuy_id(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global addbuyid
|
||||
addbuyid = message.text
|
||||
msg = client.send_message(cid, '*Введите цену товара:*',parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, addbuy_price)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def addbuy_price(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global addbuyprice
|
||||
addbuyprice = message.text
|
||||
msg = client.send_message(cid, '*Введите название товара:*',parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, addbuy_name)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def addbuy_name(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global addbuyname
|
||||
addbuyname = message.text
|
||||
msg = client.send_message(cid, '*Введите ссылку на товар:*',parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, addbuy_result)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def addbuy_result(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global addbuytovar
|
||||
addbuytovar = message.text
|
||||
sql.execute(f"SELECT name FROM shop WHERE name = '{addbuyname}'")
|
||||
if sql.fetchone() is None:
|
||||
sql.execute(f"INSERT INTO shop VALUES ({addbuyid}, '{addbuyname}', {addbuyprice}, '{addbuytovar}', '')")
|
||||
db.commit()
|
||||
sql.execute(f"SELECT * FROM shop WHERE name = '{addbuyname}'")
|
||||
client.send_message(cid, f'✅ | Вы успешно добавили товар\nID товара: {sql.fetchone()[0]}\nИмя: {addbuyname}\nЦена: {addbuyprice}\nСсылка на товар: {addbuytovar}')
|
||||
else:
|
||||
client.send_message(cid, f"⛔️ | Данный товар уже добавлен!")
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['buy'])
|
||||
def buy(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
|
||||
text = '🛒 | *Список товаров*\n\n'
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"):
|
||||
for infoshop in sql.execute(f"SELECT * FROM shop"):
|
||||
text += f"{infoshop[0]}. {infoshop[1]}\nЦена: {infoshop[2]}\n\n"
|
||||
rmk = types.InlineKeyboardMarkup()
|
||||
item_yes = types.InlineKeyboardButton(text='✅', callback_data='firstbuytovaryes')
|
||||
item_no = types.InlineKeyboardButton(text='❌', callback_data='firstbuytovarno')
|
||||
rmk.add(item_yes, item_no)
|
||||
msg = client.send_message(cid, f'{text}*Вы хотите перейти к покупке товара?*',parse_mode='Markdown',reply_markup=rmk)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def buy_next(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global tovarid
|
||||
tovarid = int(message.text)
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"):
|
||||
for infoshop in sql.execute(f"SELECT * FROM shop WHERE id = {tovarid}"):
|
||||
if info[2] < infoshop[2]:
|
||||
client.send_message(cid, '⚠️ | У вас недостаточно средств для приобретения товара!\n\nЧтобы пополнить счёт напишите /donate')
|
||||
else:
|
||||
rmk = types.InlineKeyboardMarkup()
|
||||
item_yes = types.InlineKeyboardButton(text='✅',callback_data='buytovaryes')
|
||||
item_no = types.InlineKeyboardButton(text='❌',callback_data='buytovarno')
|
||||
rmk.add(item_yes, item_no)
|
||||
msg = client.send_message(cid, f"💸 | Вы подверждаете покупку товара?\n\nВернуть средства за данный товар НЕВОЗМОЖНО.",reply_markup=rmk)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.callback_query_handler(lambda call: call.data == 'firstbuytovaryes' or call.data == 'firstbuytovarno')
|
||||
def firstbuy_callback(call):
|
||||
try:
|
||||
if call.data == 'firstbuytovaryes':
|
||||
msg = client.send_message(call.message.chat.id, f"*Введите ID товара который хотите купить:*",parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, buy_next)
|
||||
elif call.data == 'firstbuytovarno':
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"🚫 | Вы отменили покупку товара")
|
||||
client.answer_callback_query(callback_query_id=call.id)
|
||||
except:
|
||||
client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.callback_query_handler(lambda call: call.data == 'buytovaryes' or call.data == 'buytovarno')
|
||||
def buy_callback(call):
|
||||
try:
|
||||
if call.data == 'buytovaryes':
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {call.from_user.id}"):
|
||||
for infoshop in sql.execute(f"SELECT * FROM shop WHERE id = {tovarid}"):
|
||||
if str(info[0]) not in infoshop[4]:
|
||||
cashtovar = int(info[2] - infoshop[2])
|
||||
boughttovar = int(info[4] + 1)
|
||||
whobuytovarinttostr = str(info[0])
|
||||
whobuytovar = str(infoshop[4] + whobuytovarinttostr + ',')
|
||||
sql.execute(f"SELECT * FROM users WHERE id = {call.from_user.id}")
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"✅ | Вы успешно купили товар\n\nНазвание товара: {infoshop[1]}\nЦена: {infoshop[2]}\n\nТовар: {infoshop[3]}\n\nСпасибо за покупку!")
|
||||
sql.execute(f"UPDATE users SET cash = {cashtovar} WHERE id = {call.from_user.id}")
|
||||
sql.execute(f"UPDATE users SET bought = {boughttovar} WHERE id = {call.from_user.id}")
|
||||
sql.execute(f"SELECT * FROM shop WHERE id = {tovarid}")
|
||||
sql.execute(f"UPDATE shop SET whobuy = '{whobuytovar}' WHERE id = {tovarid}")
|
||||
db.commit()
|
||||
else:
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"*⛔️ | Данный товар уже куплен!*\n\nЧтобы посмотреть список купленных товаров напишите /mybuy",parse_mode='Markdown')
|
||||
elif call.data == 'buytovarno':
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"❌ | Вы отменили покупку товара!")
|
||||
client.answer_callback_query(callback_query_id=call.id)
|
||||
except:
|
||||
client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['donate'])
|
||||
def donate(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
global uid
|
||||
uid = message.from_user.id
|
||||
msg = client.send_message(cid, f"*💰 | Введите сумму для пополнения:*",parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, donate_value)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def donate_value(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global donatevalue
|
||||
global commentdonate
|
||||
global getusername
|
||||
global getuserdonateid
|
||||
getusername = message.from_user.first_name
|
||||
getuserdonateid = message.from_user.id
|
||||
sql.execute(f"SELECT * FROM users WHERE id = {uid}")
|
||||
commentdonate = sql.fetchone()[0]
|
||||
donatevalue = int(message.text)
|
||||
rmk = types.InlineKeyboardMarkup()
|
||||
item_yes = types.InlineKeyboardButton(text='✅',callback_data='donateyes')
|
||||
item_no = types.InlineKeyboardButton(text='❌',callback_data='donateno')
|
||||
rmk.add(item_yes, item_no)
|
||||
global qiwibalancebe
|
||||
qiwibalancebe = api.balance
|
||||
msg = client.send_message(cid, f"🔰 | Заявка на пополнение средств успешно создана\n\nВы действительно хотите пополнить средства?",parse_mode='Markdown',reply_markup=rmk)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def donateyesoplacheno(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
removekeyboard = types.ReplyKeyboardRemove()
|
||||
if message.text == '✅ Оплачено':
|
||||
client.send_message(cid, f"✉️ | Ваш запрос отправлен администраторам, ожидайте одобрения и выдачи средств.",reply_markup=removekeyboard)
|
||||
client.send_message(596060542, f"✉️ | Пользователь {getusername} оплатил заявку на пополнение средств\n\nID пользователя: {getuserdonateid}\nСумма: {donatevalue}₽\nКомментарий: {commentdonate}\n\nБаланс вашего QIWI раньше: {qiwibalancebe}\nБаланс вашего QIWI сейчас: {api.balance}\n\nПерепроверьте верность оплаты затем подтвердите выдачу средств.\nДля выдачи средств напишите: /giverub")
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.callback_query_handler(lambda call: call.data == 'donateyes' or call.data == 'donateno')
|
||||
def donate_result(call):
|
||||
try:
|
||||
removekeyboard = types.ReplyKeyboardRemove()
|
||||
rmk = types.ReplyKeyboardMarkup(resize_keyboard=True)
|
||||
rmk.add(types.KeyboardButton('✅ Оплачено'))
|
||||
if call.data == 'donateyes':
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
msg = client.send_message(call.message.chat.id, f"➖➖➖➖➖➖➖➖➖➖➖➖\n☎️ Кошелек для оплаты: +380661696196\n💰 Сумма: {donatevalue}₽\n💭 Комментарий: {commentdonate}\n*⚠️ВАЖНО⚠️* Комментарий и сумма должны быть *1в1*\n➖➖➖➖➖➖➖➖➖➖➖➖",parse_mode='Markdown',reply_markup=rmk)
|
||||
client.register_next_step_handler(msg, donateyesoplacheno)
|
||||
elif call.data == 'donateno':
|
||||
client.send_message(call.message.chat.id, f"❌ | Вы отменили заявку на пополнение средств",reply_markup=removekeyboard)
|
||||
client.answer_callback_query(callback_query_id=call.id)
|
||||
except:
|
||||
client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['getcid'])
|
||||
def getcid(message):
|
||||
client.send_message(message.chat.id, f"ID чата | {message.chat.id}\nТвой ID | {message.from_user.id}")
|
||||
|
||||
@client.message_handler(commands=['help'])
|
||||
def helpcmd(message):
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
with lock:
|
||||
sql.execute(f"SELECT * FROM users WHERE id = {uid}")
|
||||
getaccess = sql.fetchone()[3]
|
||||
if getaccess >= 1:
|
||||
client.send_message(cid, '*Помощь по командам:*\n\n/profile - Посмотреть свой профиль\n/help - Посмотреть список команд\n/buy - Купить товар\n/donate - Пополнить счёт\n/mybuy - Посмотреть список купленных товаров\n/teh - Связаться с тех.поддержкой\n\nАдмин-команды:\n\n/getprofile - Посмотреть чужой профиль\n/access - Выдать уровень доступа\n/giverub - Выдать деньги на баланс\n/getid - Узнать айди пользователя\n/getcid - Узнать Conference ID\n/addbuy - Добавить товар на продажу\n/editbuy - Изменить данные о товаре\n/rembuy - Удалить товар\n/ot - Ответить пользователю (отправить сообщение)',parse_mode='Markdown')
|
||||
else:
|
||||
client.send_message(cid, '*Помощь по командам:*\n\n/profile - Посмотреть свой профиль\n/help - Посмотреть список команд\n/buy - Купить товар\n/donate - Пополнить счёт\n/mybuy - Посмотреть список купленных товаров\n/teh - Связаться с тех.поддержкой',parse_mode='Markdown')
|
||||
|
||||
@client.message_handler(commands=['access', 'setaccess', 'dostup'])
|
||||
def setaccess(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
sql.execute(f"SELECT * FROM users WHERE id = {uid}")
|
||||
getaccess = sql.fetchone()[3]
|
||||
accessquery = 777
|
||||
if getaccess < accessquery:
|
||||
client.send_message(cid, f"⚠️ | У вас нет доступа!")
|
||||
else:
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"):
|
||||
msg = client.send_message(cid, 'Введите ID пользователя:\nПример: 596060542', parse_mode="Markdown")
|
||||
client.register_next_step_handler(msg, access_user_id_answer)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
def access_user_id_answer(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global usridaccess
|
||||
usridaccess = message.text
|
||||
rmk = types.ReplyKeyboardMarkup(resize_keyboard=True)
|
||||
rmk.add(types.KeyboardButton('Пользователь'), types.KeyboardButton('Администратор'), types.KeyboardButton('Разработчик'))
|
||||
msg = client.send_message(cid, 'Какой уровень доступа Вы хотите выдать?:', reply_markup=rmk, parse_mode="Markdown")
|
||||
client.register_next_step_handler(msg, access_user_access_answer)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def access_user_access_answer(message):
|
||||
try:
|
||||
global accessgaved
|
||||
global accessgavedname
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
rmk = types.InlineKeyboardMarkup()
|
||||
access_yes = types.InlineKeyboardButton(text='✅',callback_data='setaccessyes')
|
||||
access_no = types.InlineKeyboardButton(text='❌',callback_data='setaccessno')
|
||||
rmk.add(access_yes, access_no)
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {usridaccess}"):
|
||||
if message.text == "Пользователь":
|
||||
accessgavedname = "Пользователь"
|
||||
accessgaved = 0
|
||||
elif message.text == "Администратор":
|
||||
accessgavedname = "Администратор"
|
||||
accessgaved = 1
|
||||
elif message.text == "Разработчик":
|
||||
accessgavedname = "Разработчик"
|
||||
accessgaved = 777
|
||||
|
||||
client.send_message(cid, f'Данные для выдачи:\nID пользователя: {usridaccess} ({info[1]})\nУровень доступа: {message.text}\n\nВерно?', reply_markup=rmk)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.callback_query_handler(lambda call: call.data == 'setaccessyes' or call.data == 'setaccessno')
|
||||
def access_user_gave_access(call):
|
||||
try:
|
||||
removekeyboard = types.ReplyKeyboardRemove()
|
||||
if call.data == 'setaccessyes':
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {usridaccess}"):
|
||||
sql.execute(f"UPDATE users SET access = {accessgaved} WHERE id = {usridaccess}")
|
||||
db.commit()
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f'✅ | Пользователю {info[1]} выдан уровень доступа {accessgavedname}', reply_markup=removekeyboard)
|
||||
elif call.data == 'setaccessno':
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {usridaccess}"):
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f'🚫 | Вы отменили выдачу уровня доступа {accessgavedname} пользователю {info[1]}', reply_markup=removekeyboard)
|
||||
client.answer_callback_query(callback_query_id=call.id)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['getrazrab'])
|
||||
def getrazrabotchik(message):
|
||||
if message.from_user.id == 596060542:
|
||||
sql.execute(f"UPDATE users SET access = 777 WHERE id = 596060542")
|
||||
client.send_message(message.chat.id, f"✅ | Вы выдали себе Разработчика")
|
||||
db.commit()
|
||||
else:
|
||||
client.send_message(message.chat.id, f"⛔️ | Отказано в доступе!")
|
||||
|
||||
@client.message_handler(commands=['giverub', 'givedonate', 'givebal'])
|
||||
def giverubles(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
sql.execute(f"SELECT * FROM users WHERE id = {uid}")
|
||||
getaccess = sql.fetchone()[3]
|
||||
accessquery = 777
|
||||
if getaccess < accessquery:
|
||||
client.send_message(cid, f"⚠️ | У вас нет доступа!")
|
||||
else:
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {uid}"):
|
||||
msg = client.send_message(cid, 'Введите ID пользователя:\nПример: 596060542', parse_mode="Markdown")
|
||||
client.register_next_step_handler(msg, rubles_user_id_answer)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def rubles_user_id_answer(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global usridrubles
|
||||
usridrubles = message.text
|
||||
rmk = types.ReplyKeyboardMarkup(resize_keyboard=True)
|
||||
rmk.add(types.KeyboardButton('10'), types.KeyboardButton('100'), types.KeyboardButton('1000'), types.KeyboardButton('Другая сумма'))
|
||||
msg = client.send_message(cid, 'Выберите сумму для выдачи:', reply_markup=rmk, parse_mode="Markdown")
|
||||
client.register_next_step_handler(msg, rubles_user_rubles_answer)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def rubles_user_rubles_answer(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
global rublesgavedvalue
|
||||
removekeyboard = types.ReplyKeyboardRemove()
|
||||
rmk = types.InlineKeyboardMarkup()
|
||||
access_yes = types.InlineKeyboardButton(text='✅',callback_data='giverublesyes')
|
||||
access_no = types.InlineKeyboardButton(text='❌',callback_data='giverublesno')
|
||||
rmk.add(access_yes, access_no)
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {usridrubles}"):
|
||||
if message.text == '10':
|
||||
rublesgavedvalue = 10
|
||||
client.send_message(cid, f'Данные для выдачи:\nID пользователя: {usridrubles} ({info[1]})\nСумма: {rublesgavedvalue}\n\nВерно?',reply_markup=rmk)
|
||||
elif message.text == '100':
|
||||
rublesgavedvalue = 100
|
||||
client.send_message(cid, f'Данные для выдачи:\nID пользователя: {usridrubles} ({info[1]})\nСумма: {rublesgavedvalue}\n\nВерно?',reply_markup=rmk)
|
||||
elif message.text == '1000':
|
||||
rublesgavedvalue = 1000
|
||||
client.send_message(cid, f'Данные для выдачи:\nID пользователя: {usridrubles} ({info[1]})\nСумма: {rublesgavedvalue}\n\nВерно?',reply_markup=rmk)
|
||||
elif message.text == 'Другая сумма':
|
||||
msg = client.send_message(cid, f"*Введите сумму для выдачи:*",parse_mode='Markdown',reply_markup=removekeyboard)
|
||||
client.register_next_step_handler(msg, rubles_user_rubles_answer_other)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def rubles_user_rubles_answer_other(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
global rublesgavedvalue
|
||||
rmk = types.InlineKeyboardMarkup()
|
||||
access_yes = types.InlineKeyboardButton(text='✅',callback_data='giverublesyes')
|
||||
access_no = types.InlineKeyboardButton(text='❌',callback_data='giverublesno')
|
||||
rmk.add(access_yes, access_no)
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {usridrubles}"):
|
||||
if message.text == message.text:
|
||||
rublesgavedvalue = int(message.text)
|
||||
client.send_message(cid, f'Данные для выдачи:\nID пользователя: {usridrubles} ({info[1]})\nСумма: {rublesgavedvalue}\n\nВерно?',reply_markup=rmk)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.callback_query_handler(func=lambda call: call.data == 'giverublesyes' or call.data == 'giverublesno')
|
||||
def rubles_gave_rubles_user(call):
|
||||
try:
|
||||
removekeyboard = types.ReplyKeyboardRemove()
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {usridrubles}"):
|
||||
rubless = int(info[2] + rublesgavedvalue)
|
||||
if call.data == 'giverublesyes':
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {usridrubles}"):
|
||||
sql.execute(f"UPDATE users SET cash = {rubless} WHERE id = {usridrubles}")
|
||||
db.commit()
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f'✅ | Пользователю {info[1]} выдано {rublesgavedvalue} рублей', reply_markup=removekeyboard)
|
||||
elif call.data == 'giverublesno':
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {usridrubles}"):
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f'🚫 | Вы отменили выдачу рублей пользователю {info[1]}', reply_markup=removekeyboard)
|
||||
client.answer_callback_query(callback_query_id=call.id)
|
||||
except:
|
||||
client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['teh'])
|
||||
def teh(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
msg = client.send_message(cid, f"*📨 | Введите текст который хотите отправить тех.поддержке*",parse_mode='Markdown')
|
||||
client.register_next_step_handler(msg, teh_next)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def teh_next(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
global tehtextbyuser
|
||||
global tehnamebyuser
|
||||
global tehidbyuser
|
||||
tehidbyuser = int(message.from_user.id)
|
||||
tehnamebyuser = str(message.from_user.first_name)
|
||||
tehtextbyuser = str(message.text)
|
||||
rmk = types.InlineKeyboardMarkup()
|
||||
item_yes = types.InlineKeyboardButton(text='✉️',callback_data='tehsend')
|
||||
item_no = types.InlineKeyboardButton(text='❌',callback_data='tehno')
|
||||
rmk.add(item_yes, item_no)
|
||||
msg = client.send_message(cid, f"✉️ | Данные об отправке:\n\nТекст для отправки: {tehtextbyuser}\n\nВы действительно хотите отправить это тех.поддержке?",parse_mode='Markdown',reply_markup=rmk)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.callback_query_handler(func=lambda call: call.data == 'tehsend' or call.data == 'tehno')
|
||||
def teh_callback(call):
|
||||
try:
|
||||
if call.data == 'tehsend':
|
||||
for info in sql.execute(f"SELECT * FROM users WHERE id = {call.from_user.id}"):
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"✉️ | Ваше сообщение отправлено тех.поддержке, ожидайте ответа.")
|
||||
client.send_message(596060542, f"✉️ | Пользователь {tehnamebyuser} отправил сообщение в тех.поддержку\n\nID пользователя: {tehidbyuser}\nТекст: {tehtextbyuser}\n\nЧтобы ответить пользователю напишите /ot")
|
||||
elif call.data == 'tehno':
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"🚫 | Вы отменили отправку сообщения тех.поддержке")
|
||||
client.answer_callback_query(callback_query_id=call.id)
|
||||
except:
|
||||
client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['ot'])
|
||||
def sendmsgtouser(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
|
||||
msg = client.send_message(cid, f"👤 | Введите ID пользователя которому хотите отправить сообщение:")
|
||||
client.register_next_step_handler(msg, sendmsgtouser_next)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def sendmsgtouser_next(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
|
||||
if message.text == message.text:
|
||||
global getsendmsgtouserid
|
||||
getsendmsgtouserid = int(message.text)
|
||||
msg = client.send_message(cid, f"📨 | Введите текст который хотите отправить пользователю:")
|
||||
client.register_next_step_handler(msg, sendmsgtouser_next_text)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def sendmsgtouser_next_text(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
|
||||
if message.text == message.text:
|
||||
global getsendmsgtousertext
|
||||
getsendmsgtousertext = str(message.text)
|
||||
rmk = types.InlineKeyboardMarkup()
|
||||
item_yes = types.InlineKeyboardButton(text='✅',callback_data='sendmsgtouseryes')
|
||||
item_no = types.InlineKeyboardButton(text='❌',callback_data='sendmsgtouserno')
|
||||
rmk.add(item_yes, item_no)
|
||||
msg = client.send_message(cid, f"🔰 | Данные об отправке сообщения:\n\nID пользователя: {getsendmsgtouserid}\nТекст для отправки: {getsendmsgtousertext}\n\nОтправить сообщение?",reply_markup=rmk)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.callback_query_handler(func=lambda call: call.data == 'sendmsgtouseryes' or call.data == 'sendmsgtouserno')
|
||||
def sendmsgtouser_callback(call):
|
||||
try:
|
||||
if call.data == 'sendmsgtouseryes':
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"✉️ | Сообщение отправлено!")
|
||||
client.send_message(getsendmsgtouserid, f"✉️ | Администратор прислал вам сообщение:\n\n{getsendmsgtousertext}")
|
||||
elif call.data == 'sendmsgtouserno':
|
||||
client.delete_message(call.message.chat.id, call.message.message_id-0)
|
||||
client.send_message(call.message.chat.id, f"🚫 | Вы отменили отправку сообщения пользователю")
|
||||
client.answer_callback_query(callback_query_id=call.id)
|
||||
except:
|
||||
client.send_message(call.message.chat.id, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
@client.message_handler(commands=['getid'])
|
||||
def getiduser(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
sql.execute(f"SELECT * FROM users WHERE id = {uid}")
|
||||
getaccess = sql.fetchone()[3]
|
||||
accessquery = 1
|
||||
if getaccess < accessquery:
|
||||
client.send_message(cid, f"⚠️ | У вас нет доступа!")
|
||||
else:
|
||||
msg = client.send_message(cid, 'Введите никнейм пользователя:')
|
||||
client.register_next_step_handler(msg, next_getiduser_name)
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
def next_getiduser_name(message):
|
||||
try:
|
||||
cid = message.chat.id
|
||||
uid = message.from_user.id
|
||||
if message.text == message.text:
|
||||
getusername = message.text
|
||||
sql.execute(f"SELECT * FROM users WHERE nick = '{getusername}'")
|
||||
result = sql.fetchone()[0]
|
||||
client.send_message(cid, f'👤 | ID пользователя: {result}')
|
||||
except:
|
||||
client.send_message(cid, f'🚫 | Ошибка при выполнении команды')
|
||||
|
||||
|
||||
|
||||
client.polling(none_stop=True,interval=0)
|
||||
6
doners/shopbot/configure.py
Normal file
@@ -0,0 +1,6 @@
|
||||
config = {
|
||||
'name': '',
|
||||
'token': '',
|
||||
'tokenqiwi': '',
|
||||
'phoneqiwi': ''
|
||||
}
|
||||
2
doners/telegram-shop-bot-template/.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
152
doners/telegram-shop-bot-template/.gitignore
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
21
doners/telegram-shop-bot-template/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 ssayand
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
47
doners/telegram-shop-bot-template/README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
i dont like this project tbh :\
|
||||
|
||||
# Telegram shop bot template
|
||||

|
||||

|
||||
[](https://codecov.io/gh/anekobtw/telegram-shop-bot-template)
|
||||

|
||||
|
||||
Customizable telegram shop bot
|
||||
|
||||
## Installing
|
||||
Clone the project to your local machine.
|
||||
```console
|
||||
$ git clone https://github.com/anekobtw/telegram-shop-bot-template.git
|
||||
```
|
||||
|
||||
## Setting up
|
||||
Do not modify any files except `config.py`. Doing so may result in numerous bugs :)
|
||||
|
||||
- Replace `BOT_TOKEN` with your Telegram bot token (Obtain it from [BotFather](https://web.telegram.org/k/#@BotFather)).
|
||||
```python
|
||||
BOT_TOKEN = '' #replace with your token
|
||||
```
|
||||
|
||||
- Change the currency you're using for selling items. (optional)
|
||||
```python
|
||||
currency = '$'
|
||||
```
|
||||
|
||||
- Furthermore, add the products you're selling into the dictionary using the following format:\
|
||||
`'product name' - price`
|
||||
|
||||
```python
|
||||
items = {
|
||||
'Item1': 100,
|
||||
'Item2': 200,
|
||||
'Item3': 500
|
||||
}
|
||||
```
|
||||
|
||||
Remember to restart the bot to apply the changes.
|
||||
|
||||
## Acknowledgements
|
||||
- [aiogram](https://github.com/aiogram/aiogram) - A modern and fully asynchronous framework for Telegram Bot API written in Python using asyncio
|
||||
|
||||
## Contributing
|
||||
Contributions are always welcome! If you have any suggestions, feature requests, or bug reports, please feel free to open an issue on the [GitHub repository](https://github.com/anekobtw/timewise).
|
||||
17
doners/telegram-shop-bot-template/src/config.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# Insert here your bot token here
|
||||
BOT_TOKEN = ""
|
||||
|
||||
# Enter here the shop owners' telegram ids (@getmyid_bot may help you)
|
||||
# You may add as much as you can, but I recommend not to add too much.
|
||||
admins = {1718021890}
|
||||
|
||||
currency = "$"
|
||||
|
||||
items = {
|
||||
"tissue box": 10,
|
||||
"magnet": 20,
|
||||
"table": 50,
|
||||
"glow stick": 30,
|
||||
"bottle cap": 20,
|
||||
"toothpaste": 15
|
||||
}
|
||||
77
doners/telegram-shop-bot-template/src/database.py
Normal file
@@ -0,0 +1,77 @@
|
||||
import sqlite3
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class Order:
|
||||
def __init__(self, order_id, tgid, tg_nickname, product, is_open, timestamp) -> None:
|
||||
"""Initialize an order object with provided attributes"""
|
||||
self.order_id = order_id
|
||||
self.tgid = tgid
|
||||
self.tg_nickname = tg_nickname
|
||||
self.product = product
|
||||
self.is_open = is_open
|
||||
self.timestamp = timestamp
|
||||
|
||||
@property
|
||||
def formatted_timestamp(self) -> str:
|
||||
"""Return the timestamp in a formatted string (YYYY-MM-DD HH:MM:SS)"""
|
||||
return datetime.fromtimestamp(self.timestamp).strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
def all_data(self) -> str:
|
||||
return f"""Order ID: {self.order_id}
|
||||
Telegram ID: {self.tgid}
|
||||
Telegram Nickname: {self.tg_nickname}
|
||||
Product: {self.product}
|
||||
Date and Time of Purchase: {self.formatted_timestamp}
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class OrderManager:
|
||||
DATABASE_FILE = "database.db"
|
||||
TIMEZONE_OFFSET = 0 # Assuming offset in seconds
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.connection = sqlite3.connect(self.DATABASE_FILE)
|
||||
self.cursor = self.connection.cursor()
|
||||
|
||||
self.cursor.execute(
|
||||
"""CREATE TABLE IF NOT EXISTS orders (
|
||||
order_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
tgid INT,
|
||||
tg_nickname TEXT,
|
||||
product TEXT,
|
||||
is_open INTEGER,
|
||||
timestamp DATETIME
|
||||
)
|
||||
"""
|
||||
)
|
||||
self.connection.commit()
|
||||
|
||||
def get_active_orders(self) -> list:
|
||||
"""Retrieve all orders that are not completed from the database"""
|
||||
self.cursor.execute("SELECT * FROM orders WHERE is_open = 1")
|
||||
return self.cursor.fetchall()
|
||||
|
||||
def get_user_orders(self, tgid: int) -> list:
|
||||
"""Get the information about all user's orders"""
|
||||
self.cursor.execute("SELECT * FROM orders WHERE tgid = ?", (tgid,))
|
||||
return self.cursor.fetchall()
|
||||
|
||||
def get_order(self, order_id: int) -> int:
|
||||
"""Get the order info"""
|
||||
self.cursor.execute("SELECT * FROM orders WHERE order_id = ?", (order_id,))
|
||||
return self.cursor.fetchone()
|
||||
|
||||
def insert_order(self, tgid: int, tg_nickname: str, product: str) -> None:
|
||||
"""Insert a new order into the database with the provided details"""
|
||||
timestamp = round(time.time()) + self.TIMEZONE_OFFSET
|
||||
values = (tgid, tg_nickname, product, 1, timestamp)
|
||||
self.cursor.execute("INSERT INTO orders(tgid, tg_nickname, product, is_open, timestamp) VALUES (?, ?, ?, ?, ?)", values)
|
||||
self.connection.commit()
|
||||
|
||||
def delete_order(self, order_id: int) -> None:
|
||||
"""Delete the order"""
|
||||
self.cursor.execute("DELETE FROM orders WHERE order_id = ?", (order_id,))
|
||||
self.connection.commit()
|
||||
124
doners/telegram-shop-bot-template/src/main.py
Normal file
@@ -0,0 +1,124 @@
|
||||
import logging
|
||||
|
||||
import config
|
||||
import database
|
||||
from aiogram import Bot, Dispatcher, types
|
||||
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
|
||||
from aiogram.utils import executor
|
||||
from aiogram.utils.callback_data import CallbackData
|
||||
|
||||
# <<< Constants >>>
|
||||
admins = config.admins
|
||||
order_manager = database.OrderManager()
|
||||
order_cb = CallbackData("order", "action", "item")
|
||||
|
||||
|
||||
# <<< Initializing >>>
|
||||
bot = Bot(token=config.BOT_TOKEN)
|
||||
dp = Dispatcher(bot)
|
||||
logging.basicConfig(level=logging.INFO, filename="log.txt")
|
||||
|
||||
|
||||
async def notify_admins_order(order_id: int) -> None:
|
||||
for admin_id in admins:
|
||||
await bot.send_message(chat_id=admin_id, text=f"❗ Check out a new order!\nOrder id: {order_id}\n")
|
||||
|
||||
|
||||
# <<< Inline start buttons >>>
|
||||
@dp.message_handler(commands=["start"])
|
||||
async def start(message: types.Message):
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
keyboard.add(
|
||||
InlineKeyboardButton(text="Check the shop", callback_data="items"),
|
||||
InlineKeyboardButton(text="My orders", callback_data="my_orders"),
|
||||
InlineKeyboardButton(text="Developer", callback_data="developer"),
|
||||
)
|
||||
|
||||
await message.answer(f"Hi!, {message.from_user.mention}!", reply_markup=keyboard)
|
||||
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == "items")
|
||||
async def process_callback_items(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
await items(callback_query.message)
|
||||
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == "my_orders")
|
||||
async def process_callback_my_orders(callback_query: types.CallbackQuery):
|
||||
await callback_query.answer()
|
||||
orders_data = order_manager.get_user_orders(callback_query.from_user.id)
|
||||
if orders_data:
|
||||
orders_text = "\n".join(database.Order(*order_data).all_data() for order_data in orders_data)
|
||||
await bot.send_message(chat_id=callback_query.from_user.id, text=orders_text)
|
||||
else:
|
||||
await bot.send_message(chat_id=callback_query.from_user.id, text="You don't have any orders yet.")
|
||||
|
||||
|
||||
@dp.callback_query_handler(lambda c: c.data == "developer")
|
||||
async def process_callback_developer(callback_query: types.CallbackQuery):
|
||||
await bot.send_message(
|
||||
chat_id=callback_query.from_user.id,
|
||||
text="""Bot developer is @anekobtw
|
||||
Telegram channel: @anekobtww
|
||||
Github: https://github.com/anekobtw
|
||||
Source code of this bot: https://github.com/anekobtw/telegram-shop-bot""",
|
||||
)
|
||||
|
||||
|
||||
# <<< Items >>>
|
||||
@dp.message_handler(commands=["items"])
|
||||
async def items(message: types.Message):
|
||||
keyboard = InlineKeyboardMarkup()
|
||||
for item, price in config.items.items():
|
||||
keyboard.add(InlineKeyboardButton(text=f"{item} - {price}{config.currency}", callback_data=order_cb.new(action="create", item=item)))
|
||||
|
||||
await message.answer(text="Choose the product to buy:", reply_markup=keyboard)
|
||||
|
||||
|
||||
@dp.callback_query_handler(order_cb.filter(action="create"))
|
||||
async def order_cb_handler(query: types.CallbackQuery, callback_data: dict):
|
||||
item = callback_data["item"]
|
||||
order_manager.insert_order(query.from_user.id, query.from_user.full_name, item)
|
||||
order_id = database.Order(*order_manager.get_active_orders()[-1]).order_id
|
||||
|
||||
text = f"""You\'ve bought {item} that costs {config.items[item]}{config.currency}
|
||||
Order_id: {order_id}
|
||||
However, if it was an accident, you may delete your purchase by simply typing /delete_order {order_id}"""
|
||||
await bot.send_message(chat_id=query.from_user.id, text=text)
|
||||
await notify_admins_order(order_id=order_id)
|
||||
|
||||
|
||||
# <<< Admin commands >>>
|
||||
@dp.message_handler(commands=["orders"])
|
||||
async def orders(message: types.Message):
|
||||
if message.from_user.id in admins:
|
||||
orders_data = order_manager.get_active_orders()
|
||||
for order_data in orders_data:
|
||||
order_obj = database.Order(*order_data)
|
||||
await message.answer(order_obj.all_data())
|
||||
|
||||
|
||||
@dp.message_handler(commands=["next"])
|
||||
async def next1(message: types.Message):
|
||||
orders_data = order_manager.get_active_orders()
|
||||
order_obj = database.Order(*orders_data[0])
|
||||
await message.answer(order_obj.all_data())
|
||||
|
||||
|
||||
# <<< Others >>>
|
||||
@dp.message_handler(commands=["delete_order"])
|
||||
async def delete_order(message: types.Message):
|
||||
try:
|
||||
args = message.get_args()
|
||||
order_id = int(args.split()[0])
|
||||
except (ValueError, IndexError):
|
||||
await message.answer("The command is used incorrectly. Please, provide me with the order id. Example:\n/delete_order 4 ")
|
||||
else:
|
||||
order_data = order_manager.get_order(order_id)
|
||||
if database.Order(*order_data).tgid == message.from_user.id:
|
||||
order_manager.delete_order(order_id=order_id)
|
||||
await message.answer(f"Order {order_id} was deleted successfully.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
executor.start_polling(dp)
|
||||
1
doners/telegram-shop-bot-template/src/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
aiogram==2.23.1
|
||||
@@ -0,0 +1,7 @@
|
||||
from aiogram import Dispatcher
|
||||
|
||||
def register_handlers(dp: Dispatcher):
|
||||
from . import start, admin, polls
|
||||
start.register_handlers(dp)
|
||||
admin.register_handlers(dp)
|
||||
polls.register_handlers(dp)
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
from aiogram import types, Dispatcher
|
||||
from config import load_config
|
||||
|
||||
config = load_config()
|
||||
|
||||
async def admin_command(message: types.Message):
|
||||
if message.from_user.id not in config['ADMINS']:
|
||||
await message.reply("Вы не являетесь админом!")
|
||||
return
|
||||
await message.reply("Приветствую, повелитель!")
|
||||
|
||||
def register_handlers(dp: Dispatcher):
|
||||
dp.register_message_handler(admin_command, commands=['admin'])
|
||||
|
||||