security features
This commit is contained in:
@@ -9,6 +9,8 @@ from app.bot.messages import (
|
|||||||
NEED_ADD_FIRST,
|
NEED_ADD_FIRST,
|
||||||
NO_RIGHTS_CHANNEL,
|
NO_RIGHTS_CHANNEL,
|
||||||
NO_RIGHTS_GROUP,
|
NO_RIGHTS_GROUP,
|
||||||
|
ONLY_ADMINS_CAN_BIND,
|
||||||
|
ALREADY_BOUND,
|
||||||
)
|
)
|
||||||
from app.db.session import get_session
|
from app.db.session import get_session
|
||||||
from app.db.models import User, Chat
|
from app.db.models import User, Chat
|
||||||
@@ -16,42 +18,26 @@ from .utils import parse_chat_id, verify_and_fetch_chat
|
|||||||
|
|
||||||
STATE_KEY = "await_chat_id"
|
STATE_KEY = "await_chat_id"
|
||||||
|
|
||||||
|
|
||||||
def _get_forwarded_chat_id(message) -> int | None:
|
def _get_forwarded_chat_id(message) -> int | None:
|
||||||
"""
|
|
||||||
Универсально достаём chat_id источника пересылки.
|
|
||||||
Поддерживает старые поля (forward_from_chat) и новые (forward_origin.chat).
|
|
||||||
"""
|
|
||||||
if not message:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Старое поле (иногда ещё присутствует)
|
|
||||||
fwd_chat = getattr(message, "forward_from_chat", None)
|
fwd_chat = getattr(message, "forward_from_chat", None)
|
||||||
if fwd_chat:
|
if fwd_chat:
|
||||||
return fwd_chat.id
|
return fwd_chat.id
|
||||||
|
|
||||||
# Новая схема Bot API: forward_origin с типами MessageOrigin*
|
|
||||||
origin = getattr(message, "forward_origin", None)
|
origin = getattr(message, "forward_origin", None)
|
||||||
if origin:
|
if origin:
|
||||||
chat = getattr(origin, "chat", None) # у MessageOriginChat/Channel есть .chat
|
chat = getattr(origin, "chat", None)
|
||||||
if chat:
|
if chat:
|
||||||
return chat.id
|
return chat.id
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def add_group_cmd(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
async def add_group_cmd(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||||
# ensure user exists
|
|
||||||
with get_session() as s:
|
with get_session() as s:
|
||||||
u = s.query(User).filter_by(tg_id=update.effective_user.id).first()
|
u = s.query(User).filter_by(tg_id=update.effective_user.id).first()
|
||||||
if not u:
|
if not u:
|
||||||
u = User(tg_id=update.effective_user.id, name=update.effective_user.full_name)
|
u = User(tg_id=update.effective_user.id, name=update.effective_user.full_name)
|
||||||
s.add(u)
|
s.add(u); s.commit()
|
||||||
s.commit()
|
|
||||||
ctx.user_data[STATE_KEY] = True
|
ctx.user_data[STATE_KEY] = True
|
||||||
await update.effective_message.reply_text(ASK_ADD_GROUP, parse_mode=ParseMode.MARKDOWN)
|
await update.effective_message.reply_text(ASK_ADD_GROUP, parse_mode=ParseMode.MARKDOWN)
|
||||||
|
|
||||||
|
|
||||||
async def add_group_capture(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
async def add_group_capture(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||||
if update.effective_chat.type != ChatType.PRIVATE:
|
if update.effective_chat.type != ChatType.PRIVATE:
|
||||||
return
|
return
|
||||||
@@ -59,19 +45,12 @@ async def add_group_capture(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|||||||
return
|
return
|
||||||
|
|
||||||
msg = update.effective_message
|
msg = update.effective_message
|
||||||
raw = None
|
raw = _get_forwarded_chat_id(msg)
|
||||||
|
if not raw:
|
||||||
# 1) Пересланное сообщение из чата/канала
|
|
||||||
fwd_cid = _get_forwarded_chat_id(msg)
|
|
||||||
if fwd_cid:
|
|
||||||
raw = fwd_cid
|
|
||||||
else:
|
|
||||||
# 2) Ввод в виде текста или подписи (caption)
|
|
||||||
txt = (getattr(msg, "text", None) or getattr(msg, "caption", None) or "").strip()
|
txt = (getattr(msg, "text", None) or getattr(msg, "caption", None) or "").strip()
|
||||||
if not txt:
|
if not txt:
|
||||||
await msg.reply_text("Вставьте chat_id или перешлите сообщение из группы/канала.")
|
await msg.reply_text("Вставьте chat_id или перешлите сообщение из группы/канала.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if txt.startswith("@") or "t.me/" in txt:
|
if txt.startswith("@") or "t.me/" in txt:
|
||||||
raw = txt
|
raw = txt
|
||||||
else:
|
else:
|
||||||
@@ -81,21 +60,39 @@ async def add_group_capture(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|||||||
return
|
return
|
||||||
raw = cid
|
raw = cid
|
||||||
|
|
||||||
# Проверяем, что бот в чате и что у него есть право постить (если нужно)
|
# Проверяем наличие бота в чате и его право постить
|
||||||
try:
|
try:
|
||||||
chat, member, can_post = await verify_and_fetch_chat(ctx, raw)
|
chat, member_bot, can_post = await verify_and_fetch_chat(ctx, raw)
|
||||||
except BadRequest:
|
except BadRequest:
|
||||||
await msg.reply_text("Такого чата не существует или у меня нет доступа. Проверьте chat_id/username.")
|
await msg.reply_text("Такого чата не существует или у меня нет доступа. Проверьте chat_id/username.")
|
||||||
return
|
return
|
||||||
|
|
||||||
if member is None:
|
if member_bot is None:
|
||||||
await msg.reply_text(NEED_ADD_FIRST.format(title_or_id=(chat.title or chat.id)))
|
await msg.reply_text(NEED_ADD_FIRST.format(title_or_id=(chat.title or chat.id)))
|
||||||
return
|
return
|
||||||
|
|
||||||
# Сохраняем привязку
|
# --- Новое: проверяем, что привязку делает АДМИН этого чата/канала ---
|
||||||
|
try:
|
||||||
|
user_member = await ctx.bot.get_chat_member(chat.id, update.effective_user.id)
|
||||||
|
user_status = getattr(user_member, "status", "")
|
||||||
|
is_admin = user_status in ("administrator", "creator")
|
||||||
|
except Exception:
|
||||||
|
is_admin = False
|
||||||
|
|
||||||
|
if not is_admin:
|
||||||
|
await msg.reply_text(ONLY_ADMINS_CAN_BIND)
|
||||||
|
return
|
||||||
|
|
||||||
with get_session() as s:
|
with get_session() as s:
|
||||||
me = s.query(User).filter_by(tg_id=update.effective_user.id).first()
|
me = s.query(User).filter_by(tg_id=update.effective_user.id).first()
|
||||||
row = s.query(Chat).filter_by(chat_id=chat.id).first()
|
row = s.query(Chat).filter_by(chat_id=chat.id).first()
|
||||||
|
|
||||||
|
# Если уже привязан к другому владельцу — запрещаем
|
||||||
|
if row and row.owner_user_id and row.owner_user_id != me.id:
|
||||||
|
await msg.reply_text(ALREADY_BOUND)
|
||||||
|
ctx.user_data.pop(STATE_KEY, None)
|
||||||
|
return
|
||||||
|
|
||||||
if not row:
|
if not row:
|
||||||
row = Chat(
|
row = Chat(
|
||||||
chat_id=chat.id,
|
chat_id=chat.id,
|
||||||
@@ -108,8 +105,7 @@ async def add_group_capture(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|||||||
else:
|
else:
|
||||||
row.title = chat.title
|
row.title = chat.title
|
||||||
row.type = chat.type
|
row.type = chat.type
|
||||||
if row.owner_user_id is None:
|
row.owner_user_id = row.owner_user_id or me.id
|
||||||
row.owner_user_id = me.id
|
|
||||||
row.can_post = can_post
|
row.can_post = can_post
|
||||||
s.commit()
|
s.commit()
|
||||||
|
|
||||||
|
|||||||
34
app/bot/handlers/chat_id_cmd.py
Normal file
34
app/bot/handlers/chat_id_cmd.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import asyncio
|
||||||
|
from telegram import Update
|
||||||
|
from telegram.constants import ChatType, ParseMode
|
||||||
|
from telegram.ext import ContextTypes
|
||||||
|
|
||||||
|
TTL_SEC = 20
|
||||||
|
|
||||||
|
async def _auto_delete(ctx: ContextTypes.DEFAULT_TYPE, chat_id: int, message_id: int, delay: int = TTL_SEC):
|
||||||
|
try:
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
await ctx.bot.delete_message(chat_id=chat_id, message_id=message_id)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def chat_id_cmd(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||||
|
chat = update.effective_chat
|
||||||
|
if chat.type not in (ChatType.GROUP, ChatType.SUPERGROUP, ChatType.CHANNEL):
|
||||||
|
await update.effective_message.reply_text("Эта команда работает в группе/канале.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Только админы могут увидеть ID (снижаем риск утечки)
|
||||||
|
try:
|
||||||
|
member = await ctx.bot.get_chat_member(chat.id, update.effective_user.id)
|
||||||
|
if member.status not in ("administrator", "creator"):
|
||||||
|
return # молча игнорируем для не-админов
|
||||||
|
except Exception:
|
||||||
|
return
|
||||||
|
|
||||||
|
text = f"ID этого чата: `{chat.id}`\nСообщение удалится через {TTL_SEC} сек."
|
||||||
|
try:
|
||||||
|
msg = await update.effective_message.reply_text(text, parse_mode=ParseMode.MARKDOWN)
|
||||||
|
ctx.application.create_task(_auto_delete(ctx, chat.id, msg.message_id, delay=TTL_SEC))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
@@ -1,12 +1,27 @@
|
|||||||
|
import asyncio
|
||||||
from telegram import Update
|
from telegram import Update
|
||||||
from telegram.constants import ChatMemberStatus, ChatType, ParseMode
|
from telegram.constants import ChatMemberStatus, ChatType, ParseMode
|
||||||
from telegram.ext import ContextTypes
|
from telegram.ext import ContextTypes
|
||||||
from app.bot.messages import JOIN_INFO_GROUP, JOIN_INFO_CHANNEL
|
from telegram.error import Forbidden
|
||||||
|
from app.bot.messages import (
|
||||||
|
JOIN_DM_GROUP, JOIN_DM_CHANNEL, JOIN_PUBLIC_WITH_ID, NEED_START_DM
|
||||||
|
)
|
||||||
|
|
||||||
|
TTL_SEC = 30 # через столько секунд удаляем публичную подсказку
|
||||||
|
|
||||||
|
async def _auto_delete(ctx: ContextTypes.DEFAULT_TYPE, chat_id: int, message_id: int, delay: int = TTL_SEC):
|
||||||
|
try:
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
await ctx.bot.delete_message(chat_id=chat_id, message_id=message_id)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
async def on_my_chat_member(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
async def on_my_chat_member(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
||||||
"""
|
"""
|
||||||
Сообщаем инструкцию и chat_id, когда бота добавили в группу/канал
|
При добавлении/повышении прав.
|
||||||
или повысили до администратора.
|
1) Пробуем DM актёру (my_chat_member.from_user) с chat_id и инструкцией.
|
||||||
|
2) Если DM не вышел (нет from_user или нет Start/Forbidden) — пишем в чат
|
||||||
|
подсказку с chat_id и удаляем её через TTL_SEC.
|
||||||
"""
|
"""
|
||||||
mcm = update.my_chat_member
|
mcm = update.my_chat_member
|
||||||
if not mcm:
|
if not mcm:
|
||||||
@@ -20,32 +35,43 @@ async def on_my_chat_member(update: Update, ctx: ContextTypes.DEFAULT_TYPE):
|
|||||||
title = chat.title or str(chat.id)
|
title = chat.title or str(chat.id)
|
||||||
chat_id = chat.id
|
chat_id = chat.id
|
||||||
|
|
||||||
# Текст подсказки
|
# 1) Пытаемся отправить DM тому, кто совершил действие
|
||||||
|
actor = getattr(mcm, "from_user", None)
|
||||||
|
dm_sent = False
|
||||||
|
if actor:
|
||||||
|
try:
|
||||||
if chat.type in (ChatType.GROUP, ChatType.SUPERGROUP):
|
if chat.type in (ChatType.GROUP, ChatType.SUPERGROUP):
|
||||||
text = JOIN_INFO_GROUP.format(title=title, chat_id=chat_id)
|
await ctx.bot.send_message(
|
||||||
# Пытаемся написать прямо в группу
|
actor.id, JOIN_DM_GROUP.format(title=title, chat_id=chat_id),
|
||||||
|
parse_mode=ParseMode.MARKDOWN
|
||||||
|
)
|
||||||
|
elif chat.type == ChatType.CHANNEL:
|
||||||
|
await ctx.bot.send_message(
|
||||||
|
actor.id, JOIN_DM_CHANNEL.format(title=title, chat_id=chat_id),
|
||||||
|
parse_mode=ParseMode.MARKDOWN
|
||||||
|
)
|
||||||
|
dm_sent = True
|
||||||
|
except Forbidden:
|
||||||
|
# пользователь не нажал Start — подсказка про Start
|
||||||
try:
|
try:
|
||||||
await ctx.bot.send_message(chat_id=chat_id, text=text, parse_mode=ParseMode.MARKDOWN)
|
await ctx.bot.send_message(actor.id, NEED_START_DM)
|
||||||
except Exception:
|
|
||||||
# как запасной вариант — в ЛС пользователю, который добавил
|
|
||||||
if update.effective_user:
|
|
||||||
try:
|
|
||||||
await ctx.bot.send_message(update.effective_user.id, text=text, parse_mode=ParseMode.MARKDOWN)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
elif chat.type == ChatType.CHANNEL:
|
|
||||||
text = JOIN_INFO_CHANNEL.format(title=title, chat_id=chat_id)
|
|
||||||
# В канале можем не иметь права постинга — пробуем ЛС добавившему
|
|
||||||
sent = False
|
|
||||||
try:
|
|
||||||
# если права даны, сообщим прямо в канал
|
|
||||||
await ctx.bot.send_message(chat_id=chat_id, text=text, parse_mode=ParseMode.MARKDOWN)
|
|
||||||
sent = True
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not sent and update.effective_user:
|
if dm_sent:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 2) DM не удался — публикуем в чат краткий хинт с chat_id, удаляем через TTL
|
||||||
|
# (для каналов сработает только если бот уже админ и может постить)
|
||||||
try:
|
try:
|
||||||
await ctx.bot.send_message(update.effective_user.id, text=text, parse_mode=ParseMode.MARKDOWN)
|
msg = await ctx.bot.send_message(
|
||||||
|
chat_id=chat_id,
|
||||||
|
text=JOIN_PUBLIC_WITH_ID.format(chat_id=chat_id, ttl=TTL_SEC),
|
||||||
|
parse_mode=ParseMode.MARKDOWN
|
||||||
|
)
|
||||||
|
ctx.application.create_task(_auto_delete(ctx, chat_id, msg.message_id, delay=TTL_SEC))
|
||||||
except Exception:
|
except Exception:
|
||||||
|
# Если и сюда не можем — увы, остаётся ручной путь: /id, /add_group и ЛС
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -52,21 +52,59 @@ NEED_ADD_FIRST = "Я не добавлен в «{title_or_id}». Сначала
|
|||||||
NO_RIGHTS_CHANNEL = "⚠️ Я в канале не администратор. Дайте боту право «Публиковать сообщения» и повторите /add_group."
|
NO_RIGHTS_CHANNEL = "⚠️ Я в канале не администратор. Дайте боту право «Публиковать сообщения» и повторите /add_group."
|
||||||
NO_RIGHTS_GROUP = "⚠️ Похоже, я не могу публиковать. Проверьте права чата."
|
NO_RIGHTS_GROUP = "⚠️ Похоже, я не могу публиковать. Проверьте права чата."
|
||||||
|
|
||||||
# --- Новое: инструкции при добавлении бота в чат/канал ---
|
# --- Новое: защита при добавлении и привязке ---
|
||||||
JOIN_INFO_GROUP = (
|
ONLY_ADMINS_CAN_BIND = "Привязку может выполнять только администратор этого чата."
|
||||||
"Спасибо, что добавили меня в группу «{title}»!\n"
|
ALREADY_BOUND = "Этот чат уже привязан другим пользователем. Попросите владельца выдать доступ или отвязать."
|
||||||
|
|
||||||
|
# Инструкции при добавлении: DM (с chat_id) и публичная подсказка без chat_id
|
||||||
|
JOIN_DM_GROUP = (
|
||||||
|
"Вы добавили меня в группу «{title}».\n"
|
||||||
"ID группы: `{chat_id}`\n\n"
|
"ID группы: `{chat_id}`\n\n"
|
||||||
"Чтобы привязать эту группу к своему аккаунту:\n"
|
"Чтобы привязать:\n"
|
||||||
"1) Откройте ЛС со мной\n"
|
"1) Откройте мой ЛС\n"
|
||||||
"2) Выполните команду /add_group\n"
|
"2) Выполните /add_group\n"
|
||||||
"3) Вставьте ID выше *или* просто перешлите сюда любое сообщение из этой группы."
|
"3) Вставьте ID выше *или* перешлите сюда сообщение из этой группы."
|
||||||
|
)
|
||||||
|
JOIN_DM_CHANNEL = (
|
||||||
|
"Вы добавили меня в канал «{title}».\n"
|
||||||
|
"ID канала: `{chat_id}`\n\n"
|
||||||
|
"Чтобы привязать:\n"
|
||||||
|
"1) Откройте мой ЛС и выполните /add_group\n"
|
||||||
|
"2) Вставьте ID выше *или* перешлите сюда сообщение из канала\n\n"
|
||||||
|
"⚠️ Для публикации дайте боту право «Публиковать сообщения» (сделайте администратором)."
|
||||||
|
)
|
||||||
|
JOIN_PUBLIC_HINT = (
|
||||||
|
"Спасибо за добавление! Чтобы активировать отправку постов, напишите мне в ЛС и выполните /add_group.\n"
|
||||||
|
"Это служебное сообщение будет удалено через 30 секунд."
|
||||||
)
|
)
|
||||||
|
|
||||||
JOIN_INFO_CHANNEL = (
|
# Инструкции при добавлении: DM (с chat_id)
|
||||||
"Спасибо, что добавили меня в канал «{title}»!\n"
|
JOIN_DM_GROUP = (
|
||||||
"ID канала: `{chat_id}`\n\n"
|
"Вы добавили меня в группу «{title}».\n"
|
||||||
"Чтобы привязать канал к своему аккаунту:\n"
|
"ID группы: `{chat_id}`\n\n"
|
||||||
"1) Откройте ЛС со мной и выполните /add_group\n"
|
"Чтобы привязать:\n"
|
||||||
"2) Вставьте ID выше *или* перешлите сюда сообщение из канала\n\n"
|
"1) Откройте мой ЛС\n"
|
||||||
"⚠️ Для публикации сообщений мне нужно право «Публиковать сообщения» (сделайте бота администратором с этим правом)."
|
"2) Выполните /add_group\n"
|
||||||
|
"3) Вставьте ID выше *или* перешлите сюда сообщение из этой группы."
|
||||||
|
)
|
||||||
|
JOIN_DM_CHANNEL = (
|
||||||
|
"Вы добавили меня в канал «{title}».\n"
|
||||||
|
"ID канала: `{chat_id}`\n\n"
|
||||||
|
"Чтобы привязать:\n"
|
||||||
|
"1) Откройте мой ЛС и выполните /add_group\n"
|
||||||
|
"2) Вставьте ID выше *или* перешлите сюда сообщение из канала\n\n"
|
||||||
|
"⚠️ Для публикации дайте боту право «Публиковать сообщения» (сделайте администратором)."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Публичный хинт (если DM не удался) — с chat_id и автосносом
|
||||||
|
JOIN_PUBLIC_WITH_ID = (
|
||||||
|
"Спасибо за добавление! ID этого чата: `{chat_id}`.\n"
|
||||||
|
"Напишите мне в ЛС и выполните /add_group, вставив ID.\n"
|
||||||
|
"Сообщение удалится через {ttl} сек."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Подсказка, если пользователь не нажимал Start
|
||||||
|
NEED_START_DM = (
|
||||||
|
"Не удалось отправить ЛС: Telegram запрещает писать до нажатия «Start».\n"
|
||||||
|
"Откройте мой профиль и нажмите Start, затем /add_group."
|
||||||
)
|
)
|
||||||
@@ -6,7 +6,8 @@ from app.bot.handlers.add_group import add_group_cmd, add_group_capture
|
|||||||
from app.bot.handlers.drafts import new_cmd, on_text
|
from app.bot.handlers.drafts import new_cmd, on_text
|
||||||
from app.bot.handlers.media import on_media
|
from app.bot.handlers.media import on_media
|
||||||
from app.bot.handlers.callbacks import on_callback
|
from app.bot.handlers.callbacks import on_callback
|
||||||
from app.bot.handlers.join_info import on_my_chat_member # ← ново
|
from app.bot.handlers.join_info import on_my_chat_member
|
||||||
|
from app.bot.handlers.chat_id_cmd import chat_id_cmd
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
cfg = load_config()
|
cfg = load_config()
|
||||||
@@ -19,6 +20,7 @@ def main():
|
|||||||
app.add_handler(CommandHandler("groups", groups_cmd))
|
app.add_handler(CommandHandler("groups", groups_cmd))
|
||||||
app.add_handler(CommandHandler("add_group", add_group_cmd))
|
app.add_handler(CommandHandler("add_group", add_group_cmd))
|
||||||
app.add_handler(CommandHandler("new", new_cmd))
|
app.add_handler(CommandHandler("new", new_cmd))
|
||||||
|
app.add_handler(CommandHandler("id", chat_id_cmd))
|
||||||
|
|
||||||
# Callback queries
|
# Callback queries
|
||||||
app.add_handler(CallbackQueryHandler(on_callback))
|
app.add_handler(CallbackQueryHandler(on_callback))
|
||||||
@@ -28,7 +30,7 @@ def main():
|
|||||||
app.add_handler(MessageHandler(filters.ChatType.PRIVATE & filters.FORWARDED, add_group_capture))
|
app.add_handler(MessageHandler(filters.ChatType.PRIVATE & filters.FORWARDED, add_group_capture))
|
||||||
app.add_handler(MessageHandler(filters.ChatType.PRIVATE & (filters.PHOTO | filters.VIDEO | filters.ANIMATION), on_media))
|
app.add_handler(MessageHandler(filters.ChatType.PRIVATE & (filters.PHOTO | filters.VIDEO | filters.ANIMATION), on_media))
|
||||||
|
|
||||||
# NEW: реагируем, когда бота добавили/изменили права в чате
|
# Join/rights updates
|
||||||
app.add_handler(ChatMemberHandler(on_my_chat_member, chat_member_types=ChatMemberHandler.MY_CHAT_MEMBER))
|
app.add_handler(ChatMemberHandler(on_my_chat_member, chat_member_types=ChatMemberHandler.MY_CHAT_MEMBER))
|
||||||
|
|
||||||
app.run_polling(allowed_updates=None)
|
app.run_polling(allowed_updates=None)
|
||||||
|
|||||||
Reference in New Issue
Block a user