fix: improve auto-delete reliability and recent cleanup
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
- Команда разбана: `/РАЗБАН` (тоже можно с `!`).
|
- Команда разбана: `/РАЗБАН` (тоже можно с `!`).
|
||||||
- Обе команды работают только как `reply` на сообщение нужного пользователя.
|
- Обе команды работают только как `reply` на сообщение нужного пользователя.
|
||||||
- Список ID хранится в `data/blocked_users.json` и сохраняется между перезапусками.
|
- Список ID хранится в `data/blocked_users.json` и сохраняется между перезапусками.
|
||||||
|
- При бане дополнительно чистятся последние сообщения этого пользователя в текущем чате.
|
||||||
|
|
||||||
## Установка
|
## Установка
|
||||||
1. Установите Python 3.10+.
|
1. Установите Python 3.10+.
|
||||||
@@ -35,8 +36,9 @@
|
|||||||
## Как пользоваться
|
## Как пользоваться
|
||||||
1. В группе ответьте на сообщение пользователя командой `/ВБАННАХУЙ!`.
|
1. В группе ответьте на сообщение пользователя командой `/ВБАННАХУЙ!`.
|
||||||
2. Юзербот покажет короткую анимацию и добавит ID пользователя в автоудаление.
|
2. Юзербот покажет короткую анимацию и добавит ID пользователя в автоудаление.
|
||||||
3. Новые сообщения этого пользователя в группах будут удаляться только у вас.
|
3. После бана бот также попробует удалить недавние сообщения этого пользователя в текущем чате.
|
||||||
4. Чтобы отключить автоудаление, ответьте на его сообщение командой `/РАЗБАН`.
|
4. Новые сообщения этого пользователя в группах будут удаляться только у вас.
|
||||||
|
5. Чтобы отключить автоудаление, ответьте на его сообщение командой `/РАЗБАН`.
|
||||||
|
|
||||||
## Ограничения
|
## Ограничения
|
||||||
- Удаление "только у вас" работает в формате `best effort` и зависит от ограничений Telegram API.
|
- Удаление "только у вас" работает в формате `best effort` и зависит от ограничений Telegram API.
|
||||||
|
|||||||
76
bot.py
76
bot.py
@@ -7,11 +7,13 @@ from typing import Optional
|
|||||||
|
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from telethon import TelegramClient, events
|
from telethon import TelegramClient, events
|
||||||
|
from telethon.errors import RPCError
|
||||||
from telethon.tl.custom.message import Message
|
from telethon.tl.custom.message import Message
|
||||||
|
|
||||||
BAN_COMMAND = "/вбаннахуй"
|
BAN_COMMAND = "/вбаннахуй"
|
||||||
UNBAN_COMMAND = "/разбан"
|
UNBAN_COMMAND = "/разбан"
|
||||||
DATA_PATH = Path("data/blocked_users.json")
|
DATA_PATH = Path("data/blocked_users.json")
|
||||||
|
RECENT_CLEANUP_LIMIT = 50
|
||||||
|
|
||||||
ANIMATION_FRAMES = [
|
ANIMATION_FRAMES = [
|
||||||
"Запускаю анти-режим.",
|
"Запускаю анти-режим.",
|
||||||
@@ -127,6 +129,60 @@ async def animate_ban(message: Message, target_user_id: int, is_new: bool) -> No
|
|||||||
logging.exception("Не удалось отправить финальный статус анимации.")
|
logging.exception("Не удалось отправить финальный статус анимации.")
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_message_best_effort(
|
||||||
|
client: TelegramClient, message: Message
|
||||||
|
) -> bool:
|
||||||
|
try:
|
||||||
|
await message.delete(revoke=False)
|
||||||
|
return True
|
||||||
|
except Exception as exc:
|
||||||
|
logging.debug(
|
||||||
|
"Удаление через message.delete(revoke=False) не сработало: %s", exc
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
target = message.input_chat if message.input_chat else message.chat_id
|
||||||
|
await client.delete_messages(target, [message.id], revoke=False)
|
||||||
|
return True
|
||||||
|
except Exception as exc:
|
||||||
|
logging.debug(
|
||||||
|
"Удаление через client.delete_messages(..., revoke=False) не сработало: %s",
|
||||||
|
exc,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await message.delete()
|
||||||
|
return True
|
||||||
|
except RPCError as exc:
|
||||||
|
logging.warning(
|
||||||
|
"Не удалось удалить сообщение %s в чате %s: %s",
|
||||||
|
message.id,
|
||||||
|
message.chat_id,
|
||||||
|
exc,
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
logging.exception(
|
||||||
|
"Неожиданная ошибка удаления сообщения %s в чате %s.",
|
||||||
|
message.id,
|
||||||
|
message.chat_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
async def cleanup_recent_messages_from_user(
|
||||||
|
client: TelegramClient, chat_id: int, user_id: int, limit: int = RECENT_CLEANUP_LIMIT
|
||||||
|
) -> int:
|
||||||
|
deleted_count = 0
|
||||||
|
async for message in client.iter_messages(chat_id, from_user=user_id, limit=limit):
|
||||||
|
if message.id is None:
|
||||||
|
continue
|
||||||
|
if await delete_message_best_effort(client, message):
|
||||||
|
deleted_count += 1
|
||||||
|
|
||||||
|
return deleted_count
|
||||||
|
|
||||||
|
|
||||||
def load_settings() -> tuple[int, str, str]:
|
def load_settings() -> tuple[int, str, str]:
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
api_id_raw = os.getenv("API_ID", "").strip()
|
api_id_raw = os.getenv("API_ID", "").strip()
|
||||||
@@ -181,6 +237,15 @@ async def main() -> None:
|
|||||||
status_message = await event.reply("Подготовка...")
|
status_message = await event.reply("Подготовка...")
|
||||||
added = await blocklist.add(target_user_id)
|
added = await blocklist.add(target_user_id)
|
||||||
await animate_ban(status_message, target_user_id, added)
|
await animate_ban(status_message, target_user_id, added)
|
||||||
|
cleaned = await cleanup_recent_messages_from_user(
|
||||||
|
client,
|
||||||
|
event.chat_id,
|
||||||
|
target_user_id,
|
||||||
|
)
|
||||||
|
if cleaned > 0:
|
||||||
|
await event.reply(
|
||||||
|
f"Дополнительно очищено последних сообщений в этом чате: {cleaned}."
|
||||||
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
removed = await blocklist.remove(target_user_id)
|
removed = await blocklist.remove(target_user_id)
|
||||||
@@ -196,7 +261,7 @@ async def main() -> None:
|
|||||||
|
|
||||||
@client.on(events.NewMessage(incoming=True))
|
@client.on(events.NewMessage(incoming=True))
|
||||||
async def auto_delete_target_messages(event: events.NewMessage.Event) -> None:
|
async def auto_delete_target_messages(event: events.NewMessage.Event) -> None:
|
||||||
if not event.is_group:
|
if event.is_private:
|
||||||
return
|
return
|
||||||
|
|
||||||
sender_id = event.sender_id
|
sender_id = event.sender_id
|
||||||
@@ -206,11 +271,10 @@ async def main() -> None:
|
|||||||
if not await blocklist.contains(int(sender_id)):
|
if not await blocklist.contains(int(sender_id)):
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
deleted = await delete_message_best_effort(client, event.message)
|
||||||
await client.delete_messages(event.chat_id, [event.id], revoke=False)
|
if not deleted:
|
||||||
except Exception:
|
logging.warning(
|
||||||
logging.exception(
|
"Пропуск удаления: сообщение %s от пользователя %s в чате %s.",
|
||||||
"Не удалось удалить сообщение %s от пользователя %s в чате %s.",
|
|
||||||
event.id,
|
event.id,
|
||||||
sender_id,
|
sender_id,
|
||||||
event.chat_id,
|
event.chat_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user