commit e32e9a1bf45ab49e1cd1bf84154a9ed2410ba615 Author: Ivan Zinchenko Date: Tue Apr 19 01:50:49 2022 +0500 Инициализация репозитория diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..802a56c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +venv/ +__pycache__/ +img/ diff --git a/EXAMPLE_DB.db b/EXAMPLE_DB.db new file mode 100644 index 0000000..dc0b801 Binary files /dev/null and b/EXAMPLE_DB.db differ diff --git a/db.py b/db.py new file mode 100644 index 0000000..d76573f --- /dev/null +++ b/db.py @@ -0,0 +1,85 @@ +import sqlite3 + +class BotDB: + def __init__(self, db_file: str): + self.conn = sqlite3.connect(db_file) + self.cursor = self.conn.cursor() + + self.cursor.execute('''CREATE TABLE IF NOT EXISTS "categories" ( + "id" INTEGER NOT NULL UNIQUE, + "name" TEXT NOT NULL, + PRIMARY KEY("id" AUTOINCREMENT) +);''') + self.cursor.execute('''CREATE TABLE IF NOT EXISTS "products" ( + "id" INTEGER NOT NULL UNIQUE, + "name" TEXT NOT NULL, + "category_id" TEXT NOT NULL, + "description" TEXT, + "price" REAL NOT NULL, + "image_path" TEXT, + PRIMARY KEY("id" AUTOINCREMENT), + FOREIGN KEY("category_id") REFERENCES "categories"("id") +);''') + self.cursor.execute('''CREATE TABLE IF NOT EXISTS "orders" ( + "id" INTEGER NOT NULL UNIQUE, + "user_id" INTEGER NOT NULL, + "product_id" INTEGER NOT NULL, + "order_date" TEXT NOT NULL, + "delivery_date" TEXT NOT NULL, + PRIMARY KEY("id" AUTOINCREMENT), + FOREIGN KEY("user_id") REFERENCES "users"("id"), + FOREIGN KEY("product_id") REFERENCES "products"("id") +);''') + self.cursor.execute('''CREATE TABLE IF NOT EXISTS "users" ( + "id" INTEGER NOT NULL UNIQUE, + "user_telegram_id" INTEGER NOT NULL, + "nickname" TEXT NOT NULL, + "phone_number" INTEGER NOT NULL, + PRIMARY KEY("id" AUTOINCREMENT) +);''') + + def user_exists(self, user_id: int): + result = self.cursor.execute("SELECT id FROM users WHERE user_telegram_id = ?", (user_id,)) + return bool(len(result.fetchall())) + + def add_user(self, user_id: int, nickname: str, phone_number: int): + self.cursor.execute("INSERT INTO users (user_telegram_id, nickname, phone_number) VALUES(?, ?, ?)", (user_id, nickname, phone_number)) + return self.conn.commit() + + def get_categories(self): + result = self.cursor.execute("SELECT name FROM categories").fetchall() + lst = [] + for i in result: + lst.append(i[0]) + return lst + + def get_category_id(self, category: str): + result = self.cursor.execute("SELECT id FROM categories WHERE name = ?", (category,)).fetchall() + return result[0] + + def get_products_in_category(self, category_id: int): + result = self.cursor.execute("SELECT name FROM products WHERE category_id = ?", (category_id)).fetchall() + lst = [] + for i in result: + lst.append(i[0]) + return lst + + def get_product_id(self, product_name: str): + return self.cursor.execute("SELECT id FROM products WHERE name = ?", (product_name,)).fetchall()[0] + + def get_product(self, product_id: int): + return { + 'id': product_id[0], + 'name': self.cursor.execute("SELECT name FROM products WHERE id = ?", (product_id)).fetchall()[0][0], + 'category_id': self.cursor.execute("SELECT category_id FROM products WHERE id = ?", (product_id)).fetchall()[0][0], + 'description': self.cursor.execute("SELECT description FROM products WHERE id = ?", (product_id)).fetchall()[0][0], + 'price': self.cursor.execute("SELECT price FROM products WHERE id = ?", (product_id)).fetchall()[0][0], + 'image_path': self.cursor.execute("SELECT image_path FROM products WHERE id = ?", (product_id)).fetchall()[0][0] + } + + def add_order(self, user_id: int, product_id: int, order_date: str, delivery_date: str): + self.cursor.execute("INSERT INTO orders (user_id, product_id, order_date, delivery_date) VALUES(?, ?, ?, ?)", (user_id, product_id, order_date, delivery_date)) + return self.conn.commit() + + def close(self): + self.conn.close() \ No newline at end of file diff --git a/exemple.env b/exemple.env new file mode 100644 index 0000000..2438868 --- /dev/null +++ b/exemple.env @@ -0,0 +1,3 @@ +API_TOKEN=YOUR_SUPER_SECRET_TOKEN +DATABASE_FILE_PATH=INPUT_YOUR_DATABASE_FILE_PATH +IMGS=FOLDER_WITH_IMAGES \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..e7e5e60 --- /dev/null +++ b/main.py @@ -0,0 +1,125 @@ +import os +import asyncio +from datetime import date +from dotenv import load_dotenv +from aiogram import Bot, Dispatcher, executor, types +from aiogram.dispatcher import FSMContext +from aiogram.contrib.fsm_storage.memory import MemoryStorage +from aiogram.dispatcher.filters.state import State, StatesGroup +import logging + +from db import BotDB + +load_dotenv() +TOKEN = os.getenv('API_TOKEN') +bot = Bot(token=TOKEN) +storage = MemoryStorage() +dp = Dispatcher(bot, storage=storage) +logging.basicConfig(level=logging.INFO) + +class StateMachine(StatesGroup): + phone_number = State() + menu = State() + products = State() # Список из категории + product = State() # Описание товара + orderAcception = State() + order = State() + +# Старт +@dp.message_handler(commands="start", state='*') +async def cmd_start(message: types.Message): + user = message.from_user + if db.user_exists(user.id): + await message.answer(f"Здравствуйте, {user.first_name}!\nДавно не видились!") + await StateMachine.menu.set() + await cmd_menu(message) + else: + keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=True) + keyboard.add(types.KeyboardButton("Отправить контакт", request_contact=True)) + await message.answer(f"Здравствуйте, {user.first_name}!") + await message.answer("Пожалуйста зарегистрируйтесь, чтобы пользоваться ботом. Для регистрации требуется номер телефона.", reply_markup=keyboard) + await StateMachine.phone_number.set() + +# Регистрация пользователя через номер телефона +@dp.message_handler(content_types=types.ContentType.CONTACT, state=StateMachine.phone_number) +async def get_contact(message: types.Message): + user = message.from_user + keyboard = types.ReplyKeyboardRemove() + db.add_user(user.id, user.username, message.contact.phone_number) + await message.answer("Спасибо за регистрацию, давайте приступим к покупкам.", reply_markup=keyboard) + await StateMachine.menu.set() + await cmd_menu(message) + +@dp.message_handler(content_types=types.ContentType.TEXT, state=StateMachine.phone_number) +async def get_contact_from_text(message: types.Message): + user = message.from_user + keyboard = types.ReplyKeyboardRemove() + db.add_user(user.id, user.username, int(message.text)) + await message.answer("Спасибо за регистрацию, давайте приступим к покупкам.", reply_markup=keyboard) + await StateMachine.menu.set() + await cmd_menu(message) + +# Каталог товаров +@dp.message_handler(commands="menu") +async def cmd_menu(message: types.Message): + keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=True) + categories = db.get_categories() + for name in categories: + keyboard.add(name) + await message.answer("Выберете категорию товаров:", reply_markup=keyboard) + await StateMachine.products.set() + +# Товары одной категории +@dp.message_handler(state=StateMachine.products, content_types=types.ContentType.TEXT) +async def send_products(message: types.Message): + keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=True) + category_id = db.get_category_id(message.text) + products = db.get_products_in_category(category_id) + for name in products: + keyboard.add(name) + await message.answer("Выберете товар:", reply_markup=keyboard) + await StateMachine.product.set() + +# Информация о товаре +@dp.message_handler(state=StateMachine.product, content_types=types.ContentType.TEXT) +async def send_product(message: types.Message, state: FSMContext): + keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True, one_time_keyboard=True) + keyboard.add("Заказываем") + keyboard.add("Отмена") + name = message.text + product_id = db.get_product_id(name) + product = db.get_product(product_id) + await message.answer_photo(types.InputFile(f"{os.getenv('IMGS')}/{product.get('image_path')}")) + await message.answer(f"Название: {name}\nОписание: {product.get('description')}\nЦена: {product.get('price')} рубелй", parse_mode=types.ParseMode.HTML, reply_markup=keyboard) + await StateMachine.orderAcception.set() + async with state.proxy() as data: + data["product"] = product + +# Подтверждение заказа +@dp.message_handler(state=StateMachine.orderAcception, content_types=types.ContentType.TEXT) +async def send_orderAcception(message: types.Message): + if message.text == "Отмена": + await message.answer("Чтобы снова использовать магазин, напишите /start.") + else: + await message.answer("Укажите время доставки(ДД-ММ):") + await StateMachine.order.set() + +# Выбор даты +@dp.message_handler(state=StateMachine.order, content_types=types.ContentType.TEXT) +async def send_order(message: types.Message, state: FSMContext): + product = 0 + async with state.proxy() as data: + product = data["product"] + order_date = f"{date.today().year}-{date.today().month}-{date.today().day}" + delivery_date = message.text.split('-') + delivery_date = f"{date.today().year}-{delivery_date[1]}-{delivery_date[0]}" + db.add_order(message.from_user.id, product.get("id"), order_date, delivery_date) + await message.answer(f"Спасибо, что воспользовались нашим сервисом.\nЧтобы снова использовать магазин, напишите /start.") + +if __name__ == "__main__": + print(f"[INFO] - Запуск бота") + db = BotDB(os.getenv("DATABASE_FILE_PATH")) + executor.start_polling(dp, skip_updates=True) + db.close() + print(f"[INFO] - База данных закрыта") + print(f"[INFO] - Завершение работы бота") \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..c38e1cf --- /dev/null +++ b/readme.md @@ -0,0 +1,25 @@ +# Телеграм бот для интернет покупок +Демонстрация бота, который позволяет организовать покупку и доставку товаров. +Данные хранятся в sqlite таблице и в папке с изображениями. + +## Настройка бота +Настройки включаются в себя переменные окружения описанные в файле `example.env`, которые могут быть размещены в файле `.env`; + +## Запуск бота +Выполнить команды: +```shell +pip install -r +python main.py +``` + +## Как протестировать бота +На данный момент моя копия не поднята, прошу подождать. Вы можете скачать репозиторий и сами попробовать его. + +## ЧАВО +*Будет ли какой-нибуть интерфейс для отслеживания товаров?* + +Я планирую сделать эту возможность через веб-сайт. + +*А насчёт редактирования товаров* + +А эта функция будет реализована в самом Telegram. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..dec5b6c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +aiogram==2.20 +python-dotenv==0.20.0