import asyncio from datetime import datetime, timedelta from aiogram import Bot, Dispatcher, Router, F from aiogram.enums import ParseMode from aiogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup from aiogram.filters import CommandStart, Command from aiogram.fsm.storage.memory import MemoryStorage import aiosqlite TOKEN = 'YOUR_BOT_TOKEN' # Вставь сюда токен CHANNEL_ID = -1001234567890 # Вставь ID канала ADMINS = [123456789] # Вставь ID админов bot = Bot(token=TOKEN, parse_mode=ParseMode.HTML) dp = Dispatcher(storage=MemoryStorage()) router = Router() dp.include_router(router) async def init_db(): async with aiosqlite.connect("bot.db") as db: await db.execute(""" CREATE TABLE IF NOT EXISTS tickets ( user_id INTEGER, username TEXT, post_id INTEGER, is_closed INTEGER DEFAULT 0, created_at TEXT )""") await db.execute(""" CREATE TABLE IF NOT EXISTS moderators ( user_id INTEGER PRIMARY KEY )""") await db.commit() async def add_ticket(user_id, username, post_id): async with aiosqlite.connect("bot.db") as db: await db.execute("INSERT INTO tickets (user_id, username, post_id, created_at) VALUES (?, ?, ?, ?)", (user_id, username, post_id, datetime.utcnow().isoformat())) await db.commit() async def close_ticket(user_id): async with aiosqlite.connect("bot.db") as db: await db.execute("UPDATE tickets SET is_closed = 1 WHERE user_id = ?", (user_id,)) await db.commit() async def get_stats(): async with aiosqlite.connect("bot.db") as db: open_ = await db.execute_fetchone("SELECT COUNT(*) FROM tickets WHERE is_closed = 0") closed = await db.execute_fetchone("SELECT COUNT(*) FROM tickets WHERE is_closed = 1") return open_[0], closed[0] async def get_expired_tickets(): expired_users = [] async with aiosqlite.connect("bot.db") as db: cursor = await db.execute("SELECT user_id, created_at FROM tickets WHERE is_closed = 0") rows = await cursor.fetchall() for user_id, created_at in rows: created = datetime.fromisoformat(created_at) if datetime.utcnow() - created > timedelta(hours=47): expired_users.append(user_id) return expired_users async def is_moderator(user_id): async with aiosqlite.connect("bot.db") as db: result = await db.execute_fetchone("SELECT 1 FROM moderators WHERE user_id = ?", (user_id,)) return result is not None async def add_moderator(user_id): async with aiosqlite.connect("bot.db") as db: await db.execute("INSERT OR IGNORE INTO moderators (user_id) VALUES (?)", (user_id,)) await db.commit() async def get_all_moderators(): async with aiosqlite.connect("bot.db") as db: rows = await db.execute_fetchall("SELECT user_id FROM moderators") return [row[0] for row in rows] async def get_open_tickets(): async with aiosqlite.connect("bot.db") as db: cursor = await db.execute("SELECT user_id, username, created_at FROM tickets WHERE is_closed = 0") return await cursor.fetchall() def user_keyboard(): return InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="Создать обращение", callback_data="create_ticket")] ]) def admin_keyboard(): return InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="Создать обращение", callback_data="create_ticket")], [InlineKeyboardButton(text="Канал", url="https://t.me/yourchannel")], [InlineKeyboardButton(text="Админ-панель", callback_data="admin_menu")] ]) def admin_menu_kb(): return InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="Статистика", callback_data="admin_stats")], [InlineKeyboardButton(text="Открытые обращения", callback_data="admin_open_tickets")], [InlineKeyboardButton(text="Закрыть все", callback_data="admin_close_all")], [InlineKeyboardButton(text="Добавить модератора", callback_data="admin_addmod")] ]) @router.message(CommandStart()) async def start_cmd(message: Message): is_mod = await is_moderator(message.from_user.id) if message.from_user.id in ADMINS or is_mod: kb = admin_keyboard() else: kb = user_keyboard() await message.answer("Добро пожаловать в SupportMasterBot! Что вы хотите сделать?", reply_markup=kb) @router.callback_query(F.data == "create_ticket") async def create_ticket(callback): await callback.message.answer("Пожалуйста, напишите ваше обращение текстом.") @router.message(F.text) async def handle_user_message(message: Message): if message.from_user.id in ADMINS: return post = await bot.send_message( CHANNEL_ID, f"Новое сообщение от <a href='tg://user?id={message.from_user.id}'>{message.from_user.full_name}</a>:\n\n{message.text}" ) await add_ticket(message.from_user.id, message.from_user.username, post.message_id) await message.answer("Ваше сообщение отправлено. Ожидайте ответа.") notifiers = ADMINS + await get_all_moderators() for uid in notifiers: try: await bot.send_message(uid, f"Новое обращение от пользователя:\n\n{message.text}") except: pass @router.message(Command("admin")) async def admin_panel(message: Message): if message.from_user.id in ADMINS: await message.answer("Админ-панель:", reply_markup=admin_menu_kb()) @router.callback_query(F.data == "admin_menu") async def open_admin_menu(callback): if callback.from_user.id in ADMINS: await callback.message.answer("Админ-панель:", reply_markup=admin_menu_kb()) @router.callback_query(F.data == "admin_stats") async def stats_callback(callback): if callback.from_user.id in ADMINS: open_t, closed_t = await get_stats() await callback.message.answer(f"Открытых: {open_t}\nЗакрытых: {closed_t}") @router.callback_query(F.data == "admin_close_all") async def close_all_tickets(callback): if callback.from_user.id in ADMINS: async with aiosqlite.connect("bot.db") as db: await db.execute("UPDATE tickets SET is_closed = 1 WHERE is_closed = 0") await db.commit() await callback.message.answer("Все обращения закрыты.") @router.callback_query(F.data == "admin_addmod") async def prompt_add_mod(callback): if callback.from_user.id in ADMINS: await callback.message.answer("Отправьте команду:\n<b>/addmod <user_id></b>") @router.message(Command("addmod")) async def add_mod_cmd(message: Message): if message.from_user.id in ADMINS: try: _, user_id = message.text.split() user_id = int(user_id) await add_moderator(user_id) await message.answer(f"Пользователь {user_id} добавлен как модератор.") except Exception: await message.answer("Использование: /addmod <user_id>") @router.callback_query(F.data == "admin_open_tickets") async def show_open_tickets(callback): uid = callback.from_user.id if uid not in ADMINS and not await is_moderator(uid): return tickets = await get_open_tickets() if not tickets: await callback.message.answer("Нет открытых обращений.") return for user_id, username, created_at in tickets: name = f"@{username}" if username else f"ID: {user_id}" date = created_at.split("T")[0] msg = f"<b>{name}</b>\nСоздано: {date}" btn = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="Закрыть", callback_data=f"close_{user_id}")] ]) await callback.message.answer(msg, reply_markup=btn) @router.callback_query(F.data.startswith("close_")) async def close_ticket_by_button(callback): user_id = callback.from_user.id if user_id not in ADMINS and not await is_moderator(user_id): return try: target_uid = int(callback.data.split("_")[1]) await close_ticket(target_uid) await callback.message.edit_text("Обращение закрыто.") try: await bot.send_message(target_uid, "Ваше обращение было закрыто модератором.") except: pass except Exception: await callback.message.answer("Ошибка при закрытии обращения.") # === АВТОМАТИЧЕСКОЕ ЗАКРЫТИЕ === async def auto_close_loop(): while True: expired = await get_expired_tickets() for user_id in expired: await close_ticket(user_id) try: await bot.send_message(user_id, "Ваше обращение было автоматически закрыто (через 47 часов).") except: pass for admin_id in ADMINS: try: await bot.send_message(admin_id, f"Обращение от пользователя {user_id} закрыто автоматически.") except: pass await asyncio.sleep(3600) async def main(): await init_db() asyncio.create_task(auto_close_loop()) await dp.start_polling(bot) if __name__ == "__main__": asyncio.run(main()) Python import asyncio from datetime import datetime, timedelta from aiogram import Bot, Dispatcher, Router, F from aiogram.enums import ParseMode from aiogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup from aiogram.filters import CommandStart, Command from aiogram.fsm.storage.memory import MemoryStorage import aiosqlite TOKEN = 'YOUR_BOT_TOKEN' # Вставь сюда токен CHANNEL_ID = -1001234567890 # Вставь ID канала ADMINS = [123456789] # Вставь ID админов bot = Bot(token=TOKEN, parse_mode=ParseMode.HTML) dp = Dispatcher(storage=MemoryStorage()) router = Router() dp.include_router(router) async def init_db(): async with aiosqlite.connect("bot.db") as db: await db.execute(""" CREATE TABLE IF NOT EXISTS tickets ( user_id INTEGER, username TEXT, post_id INTEGER, is_closed INTEGER DEFAULT 0, created_at TEXT )""") await db.execute(""" CREATE TABLE IF NOT EXISTS moderators ( user_id INTEGER PRIMARY KEY )""") await db.commit() async def add_ticket(user_id, username, post_id): async with aiosqlite.connect("bot.db") as db: await db.execute("INSERT INTO tickets (user_id, username, post_id, created_at) VALUES (?, ?, ?, ?)", (user_id, username, post_id, datetime.utcnow().isoformat())) await db.commit() async def close_ticket(user_id): async with aiosqlite.connect("bot.db") as db: await db.execute("UPDATE tickets SET is_closed = 1 WHERE user_id = ?", (user_id,)) await db.commit() async def get_stats(): async with aiosqlite.connect("bot.db") as db: open_ = await db.execute_fetchone("SELECT COUNT(*) FROM tickets WHERE is_closed = 0") closed = await db.execute_fetchone("SELECT COUNT(*) FROM tickets WHERE is_closed = 1") return open_[0], closed[0] async def get_expired_tickets(): expired_users = [] async with aiosqlite.connect("bot.db") as db: cursor = await db.execute("SELECT user_id, created_at FROM tickets WHERE is_closed = 0") rows = await cursor.fetchall() for user_id, created_at in rows: created = datetime.fromisoformat(created_at) if datetime.utcnow() - created > timedelta(hours=47): expired_users.append(user_id) return expired_users async def is_moderator(user_id): async with aiosqlite.connect("bot.db") as db: result = await db.execute_fetchone("SELECT 1 FROM moderators WHERE user_id = ?", (user_id,)) return result is not None async def add_moderator(user_id): async with aiosqlite.connect("bot.db") as db: await db.execute("INSERT OR IGNORE INTO moderators (user_id) VALUES (?)", (user_id,)) await db.commit() async def get_all_moderators(): async with aiosqlite.connect("bot.db") as db: rows = await db.execute_fetchall("SELECT user_id FROM moderators") return [row[0] for row in rows] async def get_open_tickets(): async with aiosqlite.connect("bot.db") as db: cursor = await db.execute("SELECT user_id, username, created_at FROM tickets WHERE is_closed = 0") return await cursor.fetchall() def user_keyboard(): return InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="Создать обращение", callback_data="create_ticket")] ]) def admin_keyboard(): return InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="Создать обращение", callback_data="create_ticket")], [InlineKeyboardButton(text="Канал", url="https://t.me/yourchannel")], [InlineKeyboardButton(text="Админ-панель", callback_data="admin_menu")] ]) def admin_menu_kb(): return InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="Статистика", callback_data="admin_stats")], [InlineKeyboardButton(text="Открытые обращения", callback_data="admin_open_tickets")], [InlineKeyboardButton(text="Закрыть все", callback_data="admin_close_all")], [InlineKeyboardButton(text="Добавить модератора", callback_data="admin_addmod")] ]) @router.message(CommandStart()) async def start_cmd(message: Message): is_mod = await is_moderator(message.from_user.id) if message.from_user.id in ADMINS or is_mod: kb = admin_keyboard() else: kb = user_keyboard() await message.answer("Добро пожаловать в SupportMasterBot! Что вы хотите сделать?", reply_markup=kb) @router.callback_query(F.data == "create_ticket") async def create_ticket(callback): await callback.message.answer("Пожалуйста, напишите ваше обращение текстом.") @router.message(F.text) async def handle_user_message(message: Message): if message.from_user.id in ADMINS: return post = await bot.send_message( CHANNEL_ID, f"Новое сообщение от <a href='tg://user?id={message.from_user.id}'>{message.from_user.full_name}</a>:\n\n{message.text}" ) await add_ticket(message.from_user.id, message.from_user.username, post.message_id) await message.answer("Ваше сообщение отправлено. Ожидайте ответа.") notifiers = ADMINS + await get_all_moderators() for uid in notifiers: try: await bot.send_message(uid, f"Новое обращение от пользователя:\n\n{message.text}") except: pass @router.message(Command("admin")) async def admin_panel(message: Message): if message.from_user.id in ADMINS: await message.answer("Админ-панель:", reply_markup=admin_menu_kb()) @router.callback_query(F.data == "admin_menu") async def open_admin_menu(callback): if callback.from_user.id in ADMINS: await callback.message.answer("Админ-панель:", reply_markup=admin_menu_kb()) @router.callback_query(F.data == "admin_stats") async def stats_callback(callback): if callback.from_user.id in ADMINS: open_t, closed_t = await get_stats() await callback.message.answer(f"Открытых: {open_t}\nЗакрытых: {closed_t}") @router.callback_query(F.data == "admin_close_all") async def close_all_tickets(callback): if callback.from_user.id in ADMINS: async with aiosqlite.connect("bot.db") as db: await db.execute("UPDATE tickets SET is_closed = 1 WHERE is_closed = 0") await db.commit() await callback.message.answer("Все обращения закрыты.") @router.callback_query(F.data == "admin_addmod") async def prompt_add_mod(callback): if callback.from_user.id in ADMINS: await callback.message.answer("Отправьте команду:\n<b>/addmod <user_id></b>") @router.message(Command("addmod")) async def add_mod_cmd(message: Message): if message.from_user.id in ADMINS: try: _, user_id = message.text.split() user_id = int(user_id) await add_moderator(user_id) await message.answer(f"Пользователь {user_id} добавлен как модератор.") except Exception: await message.answer("Использование: /addmod <user_id>") @router.callback_query(F.data == "admin_open_tickets") async def show_open_tickets(callback): uid = callback.from_user.id if uid not in ADMINS and not await is_moderator(uid): return tickets = await get_open_tickets() if not tickets: await callback.message.answer("Нет открытых обращений.") return for user_id, username, created_at in tickets: name = f"@{username}" if username else f"ID: {user_id}" date = created_at.split("T")[0] msg = f"<b>{name}</b>\nСоздано: {date}" btn = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text="Закрыть", callback_data=f"close_{user_id}")] ]) await callback.message.answer(msg, reply_markup=btn) @router.callback_query(F.data.startswith("close_")) async def close_ticket_by_button(callback): user_id = callback.from_user.id if user_id not in ADMINS and not await is_moderator(user_id): return try: target_uid = int(callback.data.split("_")[1]) await close_ticket(target_uid) await callback.message.edit_text("Обращение закрыто.") try: await bot.send_message(target_uid, "Ваше обращение было закрыто модератором.") except: pass except Exception: await callback.message.answer("Ошибка при закрытии обращения.") # === АВТОМАТИЧЕСКОЕ ЗАКРЫТИЕ === async def auto_close_loop(): while True: expired = await get_expired_tickets() for user_id in expired: await close_ticket(user_id) try: await bot.send_message(user_id, "Ваше обращение было автоматически закрыто (через 47 часов).") except: pass for admin_id in ADMINS: try: await bot.send_message(admin_id, f"Обращение от пользователя {user_id} закрыто автоматически.") except: pass await asyncio.sleep(3600) async def main(): await init_db() asyncio.create_task(auto_close_loop()) await dp.start_polling(bot) if __name__ == "__main__": asyncio.run(main()) ФункцииДля пользователей: /start — кнопка Создать обращение Обращение публикуется в канал + комментарии Пользователь получает уведомление об отправке Для модераторов и админов: Кнопка “Админ-панель”: Статистика — кол-во открытых/закрытых обращений Открытые обращения — список тикетов с кнопками [Закрыть] Закрыть все — массовое закрытие тикетов Добавить модератора — инструкция по /addmod <user_id> Дополнительно: Уведомления о новых обращениях отправляются админам и модераторам модераторы отвечают в комментариях канала Авто-закрытие обращений через 47 часов Только админы и модераторы могут писать в канал/обсуждение Писал данного бота для человека, человек не оплатил. Сливаю сюда