Initial commit
This commit is contained in:
190
routers/admin.py
Normal file
190
routers/admin.py
Normal file
@@ -0,0 +1,190 @@
|
||||
"""Handlers for admin review/contact groups."""
|
||||
import logging
|
||||
|
||||
from aiogram import F, Router, types
|
||||
from aiogram.filters import Command
|
||||
|
||||
import bot_state as state_store
|
||||
from config import (
|
||||
BOT_USERNAME,
|
||||
CHAT_CONTACT_ADMIN_GROUP_ID,
|
||||
CHATROOM_PRIVATE_BACKUP_GROUP_ID,
|
||||
CHATROOM_SEMIPUBLIC_GROUP_ID,
|
||||
MAIN_PUBLIC_CHANNEL_ID,
|
||||
REVIEW_ADMIN_CHATROOM_SEMIPUBLIC_GROUP_ID,
|
||||
)
|
||||
from hashing import register_file
|
||||
from keyboards import publish_kb
|
||||
from utils import build_quote, get_admin_display_name, send_media_items
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = Router(name="admin")
|
||||
|
||||
# ── Submission approve / reject ───────────────────────────────────────────────
|
||||
|
||||
@router.callback_query(F.data.startswith("a|"))
|
||||
async def approve(cb: types.CallbackQuery):
|
||||
sub_id = int(cb.data.split("|")[1])
|
||||
if sub_id not in state_store.submissions:
|
||||
await cb.answer("Submission not found.", show_alert=True)
|
||||
return
|
||||
await cb.message.edit_reply_markup(reply_markup=publish_kb(sub_id))
|
||||
await cb.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("p|"))
|
||||
async def publish(cb: types.CallbackQuery):
|
||||
parts = cb.data.split("|")
|
||||
target = parts[1]
|
||||
sub_id = int(parts[2])
|
||||
submission = state_store.submissions.get(sub_id)
|
||||
if not submission:
|
||||
await cb.answer("Submission not found.", show_alert=True)
|
||||
return
|
||||
|
||||
user_id = submission["user_id"]
|
||||
media = submission["media"]
|
||||
|
||||
duplicate_media = [m for m in media if m.get("file_unique_id", "") in state_store.backup_hashes]
|
||||
unique_media = [m for m in media if m.get("file_unique_id", "") not in state_store.backup_hashes]
|
||||
|
||||
if duplicate_media:
|
||||
try:
|
||||
user_info = await cb.bot.get_chat(user_id)
|
||||
user_ref = (
|
||||
f'@{user_info.username}'
|
||||
if getattr(user_info, "username", None)
|
||||
else str(user_id)
|
||||
)
|
||||
except Exception:
|
||||
user_ref = str(user_id)
|
||||
try:
|
||||
await cb.bot.send_message(
|
||||
REVIEW_ADMIN_CHATROOM_SEMIPUBLIC_GROUP_ID,
|
||||
f"⚠️ Content from {user_ref} contains {len(duplicate_media)} duplicate(s). "
|
||||
"Skipping those.",
|
||||
parse_mode="HTML",
|
||||
)
|
||||
except Exception:
|
||||
logger.warning("Could not send duplicate notice")
|
||||
|
||||
caption = (
|
||||
"This is an anonymous submission reviewed by admins.\n\n"
|
||||
"You can apply having self-harm imagery posted using "
|
||||
f'<a href="https://t.me/{BOT_USERNAME}">this bot</a>.'
|
||||
)
|
||||
|
||||
if unique_media:
|
||||
await send_media_items(cb.bot, CHATROOM_PRIVATE_BACKUP_GROUP_ID, unique_media)
|
||||
for item in unique_media:
|
||||
register_file(item["file_unique_id"])
|
||||
|
||||
if target in {"d", "both"}:
|
||||
await send_media_items(cb.bot, CHATROOM_SEMIPUBLIC_GROUP_ID, unique_media, caption=caption)
|
||||
if target in {"b", "both"}:
|
||||
await send_media_items(cb.bot, MAIN_PUBLIC_CHANNEL_ID, unique_media, caption=caption)
|
||||
|
||||
try:
|
||||
await cb.bot.send_message(user_id, "Your submission was approved ✅")
|
||||
except Exception:
|
||||
logger.warning("Could not notify user %s of approval", user_id)
|
||||
|
||||
state_store.submissions.pop(sub_id, None)
|
||||
try:
|
||||
await cb.message.edit_reply_markup()
|
||||
except Exception:
|
||||
pass
|
||||
await cb.answer()
|
||||
|
||||
|
||||
@router.callback_query(F.data.startswith("r|"))
|
||||
async def reject(cb: types.CallbackQuery):
|
||||
sub_id = int(cb.data.split("|")[1])
|
||||
submission = state_store.submissions.get(sub_id)
|
||||
if not submission:
|
||||
await cb.answer("Submission not found.", show_alert=True)
|
||||
return
|
||||
try:
|
||||
await cb.bot.send_message(submission["user_id"], "Your submission was rejected ❌")
|
||||
except Exception:
|
||||
logger.warning("Could not notify user %s of rejection", submission["user_id"])
|
||||
state_store.submissions.pop(sub_id, None)
|
||||
try:
|
||||
await cb.message.edit_reply_markup()
|
||||
except Exception:
|
||||
pass
|
||||
await cb.answer()
|
||||
|
||||
# ── Admin contact-group reply commands ────────────────────────────────────────
|
||||
|
||||
@router.message(F.chat.id == CHAT_CONTACT_ADMIN_GROUP_ID, F.reply_to_message, Command("stop"))
|
||||
async def admin_stop_chat(message: types.Message):
|
||||
route = state_store.chat_message_map.get(message.reply_to_message.message_id)
|
||||
if not route:
|
||||
return
|
||||
user_id = route["user_id"]
|
||||
if state_store.chat_sessions.pop(user_id, None):
|
||||
try:
|
||||
await message.bot.send_message(user_id, "An admin has closed this chat.")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@router.message(F.chat.id == CHAT_CONTACT_ADMIN_GROUP_ID, F.reply_to_message, Command("ban"))
|
||||
async def admin_ban_user(message: types.Message):
|
||||
route = state_store.chat_message_map.get(message.reply_to_message.message_id)
|
||||
if not route:
|
||||
return
|
||||
user_id = route["user_id"]
|
||||
state_store.banned_chat_users.add(user_id)
|
||||
if state_store.chat_sessions.pop(user_id, None):
|
||||
try:
|
||||
await message.bot.send_message(user_id, "An admin has closed this chat.")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
@router.message(F.chat.id == CHAT_CONTACT_ADMIN_GROUP_ID, F.reply_to_message, Command("private"))
|
||||
async def admin_private_chat(message: types.Message):
|
||||
route = state_store.chat_message_map.get(message.reply_to_message.message_id)
|
||||
if not route:
|
||||
return
|
||||
user_id = route["user_id"]
|
||||
username = (
|
||||
f"@{message.from_user.username}"
|
||||
if message.from_user.username
|
||||
else message.from_user.full_name
|
||||
)
|
||||
try:
|
||||
await message.bot.send_message(
|
||||
user_id,
|
||||
f"An admin wants to continue this chat privately. Feel free to text them at {username}, "
|
||||
"or open a new chat here. This chat is now closed.",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
state_store.chat_sessions.pop(user_id, None)
|
||||
|
||||
|
||||
@router.message(F.chat.id == CHAT_CONTACT_ADMIN_GROUP_ID, F.reply_to_message)
|
||||
async def admin_reply(message: types.Message):
|
||||
if message.text in {"/stop", "/ban", "/private"}:
|
||||
return
|
||||
route = state_store.chat_message_map.get(message.reply_to_message.message_id)
|
||||
if not route:
|
||||
return
|
||||
user_id = route["user_id"]
|
||||
if user_id not in state_store.chat_sessions:
|
||||
return
|
||||
quote = build_quote(route["quote"])
|
||||
admin_name = get_admin_display_name(message.from_user)
|
||||
try:
|
||||
if message.text:
|
||||
await message.bot.send_message(user_id, f"{admin_name}\n{quote}\n\n{message.text}")
|
||||
else:
|
||||
await message.bot.send_message(user_id, f"{admin_name}\n{quote}")
|
||||
await message.bot.copy_message(
|
||||
user_id, CHAT_CONTACT_ADMIN_GROUP_ID, message.message_id
|
||||
)
|
||||
except Exception:
|
||||
logger.exception("Failed to relay admin reply to user %s", user_id)
|
||||
Reference in New Issue
Block a user