├── 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}")
--------------------------------------------------------------------------------