├── utils ├── __init__.py ├── utils.py └── db.py ├── handlers ├── __init__.py ├── admin_panel.py └── start.py ├── keyboards ├── __init__.py └── kb.py ├── requirements.txt ├── amvera.yml ├── README.md ├── .gitignore ├── create_bot.py └── aiogram_run.py /utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /handlers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /keyboards/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiogram==3.11.0 2 | python-decouple==3.8 3 | aiosqlite==0.20.0 -------------------------------------------------------------------------------- /amvera.yml: -------------------------------------------------------------------------------- 1 | --- 2 | meta: 3 | environment: python 4 | toolchain: 5 | name: pip 6 | version: 3.12 7 | build: 8 | requirementsPath: requirements.txt 9 | run: 10 | persistenceMount: /data 11 | containerPort: 5000 12 | scriptName: aiogram_run.py -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | По функционалу бота: 2 | 3 | 1. Проверка подписки на каналы 4 | 2. Получение своего ID, ID-группы, ID-канала, ID-любого бота 5 | 3. Админка с рассылкой любого типа сообщений всем пользователям с отчетом по доставке 6 | 7 | Ссылка на работающего бота: https://t.me/get_tg_ids_universeBOT 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Игнорирование виртуальной среды Python 2 | venv/ 3 | .venv/ 4 | 5 | 6 | # Игнорирование файлов с окружением 7 | .env 8 | 9 | # Игнорирование файла базы данных 10 | bot.db 11 | 12 | # Игнорирование скомпилированных файлов Python 13 | __pycache__/ 14 | **/__pycache__/ 15 | 16 | # Игнорирование настроек проекта для PyCharm 17 | .idea/ 18 | **/.idea/ -------------------------------------------------------------------------------- /create_bot.py: -------------------------------------------------------------------------------- 1 | from aiogram import Bot, Dispatcher 2 | from aiogram.client.default import DefaultBotProperties 3 | from aiogram.enums import ParseMode 4 | from decouple import config 5 | from aiogram.fsm.storage.memory import MemoryStorage 6 | 7 | # переменные для работы 8 | ADMIN_ID = int(config('ADMIN_ID')) 9 | BOT_TOKEN = config("BOT_TOKEN") 10 | HOST = config("HOST") 11 | PORT = int(config("PORT")) 12 | WEBHOOK_PATH = f'/{BOT_TOKEN}' 13 | BASE_URL = config("BASE_URL") 14 | 15 | kb_list = [{'label': 'Легкий путь в Python', 'url': 'https://t.me/PythonPathMaster'}] 16 | 17 | # инициализируем бота и диспетчера для работы с ними 18 | bot = Bot(token=BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) 19 | dp = Dispatcher(storage=MemoryStorage()) 20 | -------------------------------------------------------------------------------- /utils/utils.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | from aiogram.enums import ContentType, ChatMemberStatus 3 | from create_bot import bot 4 | from keyboards.kb import main_contact_kb 5 | 6 | 7 | async def is_user_subscribed(channel_url: str, telegram_id: int) -> bool: 8 | try: 9 | # Получаем username канала из URL 10 | channel_username = channel_url.split('/')[-1] 11 | 12 | # Получаем информацию о пользователе в канале 13 | member = await bot.get_chat_member(chat_id=f"@{channel_username}", user_id=telegram_id) 14 | 15 | # Проверяем статус пользователя 16 | if member.status in [ChatMemberStatus.MEMBER, ChatMemberStatus.CREATOR, ChatMemberStatus.ADMINISTRATOR]: 17 | return True 18 | else: 19 | return False 20 | except Exception as e: 21 | # Если возникла ошибка (например, пользователь не найден или бот не имеет доступа к каналу) 22 | print(f"Ошибка при проверке подписки: {e}") 23 | return False 24 | 25 | 26 | async def broadcast_message(users_data: list, text: str = None, photo_id: int = None, document_id: int = None, 27 | video_id: int = None, audio_id: int = None, caption: str = None, content_type: str = None): 28 | good_send = 0 29 | bad_send = 0 30 | for user in users_data: 31 | try: 32 | chat_id = user.get('telegram_id') 33 | if content_type == ContentType.TEXT: 34 | await bot.send_message(chat_id=chat_id, text=text, reply_markup=main_contact_kb(chat_id)) 35 | elif content_type == ContentType.PHOTO: 36 | await bot.send_photo(chat_id=chat_id, photo=photo_id, caption=caption, 37 | reply_markup=main_contact_kb(chat_id)) 38 | elif content_type == ContentType.DOCUMENT: 39 | await bot.send_document(chat_id=chat_id, document=document_id, caption=caption, 40 | reply_markup=main_contact_kb(chat_id)) 41 | elif content_type == ContentType.VIDEO: 42 | await bot.send_video(chat_id=chat_id, video=video_id, caption=caption, 43 | reply_markup=main_contact_kb(chat_id)) 44 | elif content_type == ContentType.AUDIO: 45 | await bot.send_audio(chat_id=chat_id, audio=audio_id, caption=caption, 46 | reply_markup=main_contact_kb(chat_id)) 47 | good_send += 1 48 | except Exception as e: 49 | print(e) 50 | bad_send += 1 51 | finally: 52 | await asyncio.sleep(1) 53 | return good_send, bad_send 54 | -------------------------------------------------------------------------------- /utils/db.py: -------------------------------------------------------------------------------- 1 | import aiosqlite 2 | 3 | 4 | async def initialize_database(): 5 | # Подключаемся к базе данных (если база данных не существует, она будет создана) 6 | async with aiosqlite.connect("bot.db") as db: 7 | # Создаем таблицу users, если она не существует 8 | await db.execute(""" 9 | CREATE TABLE IF NOT EXISTS users ( 10 | telegram_id INTEGER PRIMARY KEY, 11 | username TEXT, 12 | first_name TEXT, 13 | bot_open BOOLEAN DEFAULT FALSE 14 | ) 15 | """) 16 | # Сохраняем изменения 17 | await db.commit() 18 | 19 | 20 | async def add_user(telegram_id: int, username: str, first_name: str): 21 | async with aiosqlite.connect("bot.db") as db: 22 | await db.execute(""" 23 | INSERT INTO users (telegram_id, username, first_name) 24 | VALUES (?, ?, ?) 25 | ON CONFLICT(telegram_id) DO NOTHING 26 | """, (telegram_id, username, first_name)) 27 | await db.commit() 28 | 29 | 30 | # Функция для получения всех пользователей в виде списка словарей 31 | async def get_all_users(): 32 | async with aiosqlite.connect("bot.db") as db: 33 | cursor = await db.execute("SELECT * FROM users") 34 | rows = await cursor.fetchall() 35 | 36 | # Преобразуем результаты в список словарей 37 | users = [ 38 | { 39 | "telegram_id": row[0], 40 | "username": row[1], 41 | "first_name": row[2], 42 | "bot_open": bool(row[3]) 43 | } 44 | for row in rows 45 | ] 46 | return users 47 | 48 | 49 | # Функция для обновления статуса bot_open по telegram_id 50 | async def update_bot_open_status(telegram_id: int, bot_open: bool): 51 | async with aiosqlite.connect("bot.db") as db: 52 | await db.execute(""" 53 | UPDATE users 54 | SET bot_open = ? 55 | WHERE telegram_id = ? 56 | """, (bot_open, telegram_id)) 57 | await db.commit() 58 | 59 | 60 | # Функция для получения данных о пользователе по telegram_id в виде словаря 61 | async def get_user_by_id(telegram_id: int): 62 | async with aiosqlite.connect("bot.db") as db: 63 | cursor = await db.execute("SELECT * FROM users WHERE telegram_id = ?", (telegram_id,)) 64 | row = await cursor.fetchone() 65 | 66 | if row is None: 67 | return None 68 | 69 | # Преобразуем результат в словарь 70 | user = { 71 | "telegram_id": row[0], 72 | "username": row[1], 73 | "first_name": row[2], 74 | "bot_open": bool(row[3]) 75 | } 76 | return user 77 | -------------------------------------------------------------------------------- /handlers/admin_panel.py: -------------------------------------------------------------------------------- 1 | from aiogram import Router, F 2 | from aiogram.enums import ContentType 3 | from aiogram.fsm.context import FSMContext 4 | from aiogram.fsm.state import StatesGroup, State 5 | from aiogram.types import Message, CallbackQuery 6 | from create_bot import ADMIN_ID 7 | from utils.db import get_all_users 8 | from keyboards.kb import admin_kb, cancel_btn 9 | from utils.utils import broadcast_message 10 | 11 | router = Router() 12 | 13 | 14 | class Form(StatesGroup): 15 | start_broadcast = State() 16 | 17 | 18 | @router.message((F.from_user.id == ADMIN_ID) & (F.text == '⚙️ АДМИНКА')) 19 | async def admin_handler(message: Message): 20 | await message.answer('Вам открыт доступ в админку! Выберите действие👇', reply_markup=admin_kb()) 21 | 22 | 23 | @router.callback_query((F.from_user.id == ADMIN_ID) & (F.data == 'admin_users')) 24 | async def admin_users_handler(call: CallbackQuery): 25 | await call.answer('Готовлю список пользователей') 26 | users_data = await get_all_users() 27 | 28 | text = f'В базе данных {len(users_data)}. Вот информация по ним:\n\n' 29 | 30 | for user in users_data: 31 | text += f'{user["telegram_id"]} - {user["first_name"]}\n' 32 | 33 | await call.message.answer(text, reply_markup=admin_kb()) 34 | 35 | 36 | @router.callback_query((F.from_user.id == ADMIN_ID) & (F.data == 'admin_broadcast')) 37 | async def admin_broadcast_handler(call: CallbackQuery, state: FSMContext): 38 | await call.answer() 39 | await call.message.answer( 40 | 'Отправьте любое сообщение, а я его перехвачу и перешлю всем пользователям с базы данных', 41 | reply_markup=cancel_btn() 42 | ) 43 | await state.set_state(Form.start_broadcast) 44 | 45 | 46 | @router.message(F.content_type.in_({'text', 'photo', 'document', 'video', 'audio'}), Form.start_broadcast) 47 | async def universe_broadcast(message: Message, state: FSMContext): 48 | users_data = await get_all_users() 49 | await message.answer(f'Начинаю рассылку на {len(users_data)} пользователей.') 50 | 51 | # Определяем параметры для рассылки в зависимости от типа сообщения 52 | content_type = message.content_type 53 | 54 | if content_type == ContentType.TEXT and message.text == '❌ Отмена': 55 | await state.clear() 56 | await message.answer('Рассылка отменена!', reply_markup=admin_kb()) 57 | return 58 | 59 | good_send, bad_send = await broadcast_message( 60 | users_data=users_data, 61 | text=message.text if content_type == ContentType.TEXT else None, 62 | photo_id=message.photo[-1].file_id if content_type == ContentType.PHOTO else None, 63 | document_id=message.document.file_id if content_type == ContentType.DOCUMENT else None, 64 | video_id=message.video.file_id if content_type == ContentType.VIDEO else None, 65 | audio_id=message.audio.file_id if content_type == ContentType.AUDIO else None, 66 | caption=message.caption, 67 | content_type=content_type 68 | ) 69 | 70 | await state.clear() 71 | await message.answer(f'Рассылка завершена. Сообщение получило {good_send}, ' 72 | f'НЕ получило {bad_send} пользователей.', reply_markup=admin_kb()) 73 | -------------------------------------------------------------------------------- /aiogram_run.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from aiogram.types import BotCommand, BotCommandScopeDefault 3 | from aiohttp import web 4 | from aiogram.webhook.aiohttp_server import SimpleRequestHandler, setup_application 5 | from create_bot import bot, dp, BASE_URL, WEBHOOK_PATH, HOST, PORT, ADMIN_ID 6 | from utils.db import initialize_database 7 | from handlers.start import router as start_router 8 | from handlers.admin_panel import router as admin_router 9 | 10 | 11 | # Функция для установки командного меню для бота 12 | async def set_commands(): 13 | # Создаем список команд, которые будут доступны пользователям 14 | commands = [BotCommand(command='start', description='Старт')] 15 | # Устанавливаем эти команды как дефолтные для всех пользователей 16 | await bot.set_my_commands(commands, BotCommandScopeDefault()) 17 | 18 | 19 | # Функция, которая будет вызвана при запуске бота 20 | async def on_startup() -> None: 21 | # Устанавливаем командное меню 22 | await set_commands() 23 | 24 | # Создаем базу данных и таблицу с пользователями, если таблицы не было 25 | await initialize_database() 26 | 27 | # Устанавливаем вебхук для приема сообщений через заданный URL 28 | await bot.set_webhook(f"{BASE_URL}{WEBHOOK_PATH}") 29 | 30 | # Отправляем сообщение администратору о том, что бот был запущен 31 | await bot.send_message(chat_id=ADMIN_ID, text='Бот запущен!') 32 | 33 | 34 | # Функция, которая будет вызвана при остановке бота 35 | async def on_shutdown() -> None: 36 | # Отправляем сообщение администратору о том, что бот был остановлен 37 | await bot.send_message(chat_id=ADMIN_ID, text='Бот остановлен!') 38 | # Удаляем вебхук и, при необходимости, очищаем ожидающие обновления 39 | # await bot.delete_webhook(drop_pending_updates=True) 40 | # Закрываем сессию бота, освобождая ресурсы 41 | await bot.session.close() 42 | 43 | 44 | # Основная функция, которая запускает приложение 45 | def main() -> None: 46 | # Подключаем маршрутизатор (роутер) для обработки сообщений 47 | dp.include_router(start_router) 48 | dp.include_router(admin_router) 49 | 50 | # Регистрируем функцию, которая будет вызвана при старте бота 51 | dp.startup.register(on_startup) 52 | 53 | # Регистрируем функцию, которая будет вызвана при остановке бота 54 | dp.shutdown.register(on_shutdown) 55 | 56 | # Создаем веб-приложение на базе aiohttp 57 | app = web.Application() 58 | 59 | # Настраиваем обработчик запросов для работы с вебхуком 60 | webhook_requests_handler = SimpleRequestHandler( 61 | dispatcher=dp, # Передаем диспетчер 62 | bot=bot # Передаем объект бота 63 | ) 64 | # Регистрируем обработчик запросов на определенном пути 65 | webhook_requests_handler.register(app, path=WEBHOOK_PATH) 66 | 67 | # Настраиваем приложение и связываем его с диспетчером и ботом 68 | setup_application(app, dp, bot=bot) 69 | 70 | # Запускаем веб-сервер на указанном хосте и порте 71 | web.run_app(app, host=HOST, port=PORT) 72 | 73 | 74 | # Точка входа в программу 75 | if __name__ == "__main__": 76 | # Настраиваем логирование (информация, предупреждения, ошибки) и выводим их в консоль 77 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') 78 | logger = logging.getLogger(__name__) # Создаем логгер для использования в других частях программы 79 | main() # Запускаем основную функцию 80 | -------------------------------------------------------------------------------- /keyboards/kb.py: -------------------------------------------------------------------------------- 1 | from aiogram.types import ReplyKeyboardMarkup, KeyboardButtonRequestUser, KeyboardButton, KeyboardButtonRequestChat, \ 2 | InlineKeyboardMarkup, InlineKeyboardButton 3 | 4 | from create_bot import ADMIN_ID 5 | 6 | 7 | def cancel_btn(): 8 | return ReplyKeyboardMarkup( 9 | keyboard=[[KeyboardButton(text="❌ Отмена")]], 10 | resize_keyboard=True, 11 | one_time_keyboard=False, 12 | input_field_placeholder="Или нажмите на 'ОТМЕНА' для отмены" 13 | ) 14 | 15 | 16 | def main_contact_kb(user_id: int): 17 | buttons = [ 18 | [ 19 | KeyboardButton( 20 | text="👤 USER ID", 21 | request_user=KeyboardButtonRequestUser( 22 | request_id=1, 23 | user_is_bot=False 24 | ) 25 | ), 26 | KeyboardButton( 27 | text="🤖 BOT ID", 28 | request_user=KeyboardButtonRequestUser( 29 | request_id=4, 30 | user_is_bot=True 31 | ) 32 | ) 33 | ], 34 | [ 35 | KeyboardButton( 36 | text="👥 GROUP ID", 37 | request_chat=KeyboardButtonRequestChat( 38 | request_id=2, 39 | chat_is_channel=False, # Включает только обычные группы (не каналы) 40 | chat_has_username=True 41 | ) 42 | ), 43 | KeyboardButton( 44 | text="📢 CHANNEL ID", 45 | request_chat=KeyboardButtonRequestChat( 46 | request_id=3, 47 | chat_is_channel=True # Включает только каналы 48 | ) 49 | ) 50 | ], 51 | [ 52 | KeyboardButton( 53 | text="🆔 MY INFO", 54 | ) 55 | ] 56 | ] 57 | 58 | if user_id == ADMIN_ID: 59 | buttons.append([ 60 | KeyboardButton( 61 | text="⚙️ АДМИНКА", 62 | ) 63 | ]) 64 | 65 | keyboard = ReplyKeyboardMarkup( 66 | keyboard=buttons, 67 | resize_keyboard=True, 68 | one_time_keyboard=False, 69 | input_field_placeholder="По ком получим ID?" 70 | ) 71 | 72 | return keyboard 73 | 74 | 75 | def admin_kb(): 76 | keyboard = InlineKeyboardMarkup( 77 | inline_keyboard=[ 78 | [InlineKeyboardButton(text="👥 Пользователи", callback_data="admin_users")], 79 | [InlineKeyboardButton(text="📧 Рассылка", callback_data="admin_broadcast")] 80 | ] 81 | ) 82 | return keyboard 83 | 84 | 85 | def broadcast_kb(): 86 | keyboard = InlineKeyboardMarkup( 87 | inline_keyboard=[ 88 | [ 89 | InlineKeyboardButton(text="⬅️ Назад", callback_data="back_to_admin")], 90 | [ 91 | InlineKeyboardButton(text="🚀 Начать рассылку", callback_data="start_broadcast") 92 | ] 93 | ] 94 | ) 95 | return keyboard 96 | 97 | 98 | def channels_kb(kb_list: list): 99 | inline_keyboard = [] 100 | 101 | for channel_data in kb_list: 102 | label = channel_data.get('label') 103 | url = channel_data.get('url') 104 | 105 | # Проверка на наличие необходимых ключей 106 | if label and url: 107 | kb = [InlineKeyboardButton(text=label, url=url)] 108 | inline_keyboard.append(kb) 109 | 110 | # Добавление кнопки "Проверить подписку" 111 | inline_keyboard.append([InlineKeyboardButton(text="Проверить подписку", callback_data="check_subscription")]) 112 | 113 | return InlineKeyboardMarkup(inline_keyboard=inline_keyboard) 114 | -------------------------------------------------------------------------------- /handlers/start.py: -------------------------------------------------------------------------------- 1 | from aiogram import Router, F 2 | from aiogram.filters import CommandStart 3 | from aiogram.types import Message, CallbackQuery 4 | from create_bot import kb_list 5 | from utils.db import get_user_by_id, add_user, update_bot_open_status 6 | from keyboards.kb import main_contact_kb, channels_kb 7 | from utils.utils import is_user_subscribed 8 | 9 | router = Router() 10 | 11 | 12 | # Функция для реагирования на команду /start 13 | @router.message(CommandStart()) 14 | async def start(message: Message): 15 | telegram_id = message.from_user.id 16 | user_data = await get_user_by_id(telegram_id) 17 | 18 | if user_data is None: 19 | # Если пользователь не найден, добавляем его в базу данных 20 | await add_user( 21 | telegram_id=telegram_id, 22 | username=message.from_user.username, 23 | first_name=message.from_user.first_name 24 | ) 25 | bot_open = False 26 | else: 27 | # Получаем статус bot_open для пользователя 28 | bot_open = user_data.get('bot_open', False) # Второй параметр по умолчанию False 29 | 30 | if bot_open: 31 | # Если пользователь подписался на все каналы 32 | await message.answer("Выберите опцию:", reply_markup=main_contact_kb(telegram_id)) 33 | else: 34 | # Иначе показываем клавиатуру с каналами для подписки 35 | await message.answer( 36 | "Для пользования ботом необходимо подписаться на следующие каналы:", 37 | reply_markup=channels_kb(kb_list) 38 | ) 39 | 40 | 41 | @router.callback_query(F.data == 'check_subscription') 42 | async def check_subs_func(call: CallbackQuery): 43 | await call.answer('Запускаю проверку подписок на каналы') 44 | 45 | for channel in kb_list: 46 | label = channel.get('label') 47 | channel_url = channel.get('url') 48 | telegram_id = call.from_user.id 49 | check = await is_user_subscribed(channel_url, telegram_id) 50 | if check is False: 51 | await call.message.answer(f"❌ вы не подписались на канал 👉 {label}", 52 | reply_markup=channels_kb(kb_list)) 53 | return False 54 | 55 | await update_bot_open_status(telegram_id=call.from_user.id, bot_open=True) 56 | await call.message.answer("Спасибо за подписки на все каналы! Теперь можете воспользоваться функционалом бота", 57 | reply_markup=main_contact_kb(call.from_user.id)) 58 | 59 | 60 | # Обработка команды "MY ID" 61 | @router.message(F.text == '🆔 MY INFO') 62 | async def handle_my_id(message: Message): 63 | user_id = message.from_user.id 64 | first_name = message.from_user.first_name 65 | last_name = message.from_user.last_name or "Не указано" 66 | username = message.from_user.username or "Не указано" 67 | 68 | await message.answer( 69 | f"🔍 Ваши данные:\n\n" 70 | f"🆔 ID: {user_id}\n" 71 | f"👤 Имя: {first_name} {last_name}\n" 72 | f"🔗 Username: @{username}" 73 | ) 74 | 75 | 76 | # Обработка выбора пользователя 77 | @router.message(F.user_shared) 78 | async def handle_user(message: Message): 79 | user_id = message.user_shared.user_id 80 | request_id = message.user_shared.request_id 81 | 82 | if request_id == 1: 83 | await message.answer(f"👤 Вы выбрали пользователя с ID: {user_id}") 84 | elif request_id == 4: 85 | await message.answer(f"🤖 Вы выбрали бота с ID: {user_id}") 86 | 87 | 88 | # Обработка выбора чата или канала 89 | @router.message(F.chat_shared) 90 | async def handle_chat_or_channel(message: Message): 91 | chat_id = message.chat_shared.chat_id 92 | request_id = message.chat_shared.request_id 93 | 94 | if request_id == 2: 95 | await message.answer(f"👥 Вы выбрали группу с ID: {chat_id}") 96 | elif request_id == 3: 97 | await message.answer(f"📢 Вы выбрали канал с ID: {chat_id}") --------------------------------------------------------------------------------