136 lines
5.4 KiB
Python
136 lines
5.4 KiB
Python
"""账号绑定 handler"""
|
|
import asyncio
|
|
import logging
|
|
import re
|
|
|
|
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update
|
|
from telegram.ext import ContextTypes, CommandHandler, MessageHandler, filters
|
|
|
|
from db.models import Action
|
|
from services.bind_service import bind_with_email_token, unbind, get_binding
|
|
from services.message_utils import safe_reply
|
|
|
|
logger = logging.getLogger("spam_guard")
|
|
|
|
EMAIL_RE = re.compile(r"^[^@\s]+@[^@\s]+\.[^@\s]+$")
|
|
|
|
|
|
async def cmd_bind(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
"""绑定授权账号。群内引导到私聊;私聊进入等待邮箱+Token 状态。"""
|
|
user = update.effective_user
|
|
logger.info(f"🔗 收到绑定指令: user={user.id}, chat_type={update.effective_chat.type}")
|
|
existing = await get_binding(user.id)
|
|
if existing:
|
|
text = (
|
|
"🔗 你已绑定授权账号\n"
|
|
f"📧 邮箱: {existing.get('email') or existing.get('harvest_uid') or '-'}\n"
|
|
f"⏳ 到期: {existing.get('time_expire') or '-'}\n"
|
|
f"📅 绑定时间: {existing.get('bound_at')}\n\n"
|
|
"如需重新绑定,先 /unbind"
|
|
)
|
|
reply = await safe_reply(update, context, text)
|
|
asyncio.create_task(auto_delete(update.message, seconds=60))
|
|
asyncio.create_task(auto_delete(reply, seconds=60))
|
|
return
|
|
|
|
if update.effective_chat.type != "private":
|
|
me = await context.bot.get_me()
|
|
bot_username = me.username
|
|
url = f"https://t.me/{bot_username}?start=bind"
|
|
keyboard = InlineKeyboardMarkup([[InlineKeyboardButton("去私聊绑定", url=url)]])
|
|
text = (
|
|
"🔐 为了保护 Token,请到私聊完成绑定。\n\n"
|
|
f"👉 点击进入:{url}"
|
|
)
|
|
try:
|
|
reply = await safe_reply(update, context, text, reply_markup=keyboard, disable_web_page_preview=True)
|
|
except Exception as e:
|
|
logger.warning(f"🔗 绑定跳转按钮发送失败,改发普通文本: {type(e).__name__}: {e}")
|
|
reply = await update.effective_chat.send_message(text, disable_web_page_preview=True)
|
|
asyncio.create_task(auto_delete(update.message, seconds=60))
|
|
asyncio.create_task(auto_delete(reply, seconds=60))
|
|
return
|
|
|
|
context.user_data["awaiting_bind"] = True
|
|
await safe_reply(update, context,
|
|
"🔐 授权绑定\n"
|
|
"请发送邮箱和 Token,支持空格或换行分隔:\n\n"
|
|
"邮箱 Token\n\n"
|
|
"例如:user@example.com xxxxx"
|
|
)
|
|
|
|
|
|
async def cmd_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
if update.effective_chat.type == "private" and context.args and context.args[0] == "bind":
|
|
await cmd_bind(update, context)
|
|
|
|
|
|
async def handle_private_bind_text(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
if update.effective_chat.type != "private" or not context.user_data.get("awaiting_bind"):
|
|
return
|
|
|
|
text = (update.message.text or "").strip()
|
|
parts = text.replace("\n", " ").split()
|
|
if len(parts) < 2:
|
|
await safe_reply(update, context, "❌ 格式不对,请发送:邮箱 Token")
|
|
return
|
|
|
|
email, token = parts[0].strip(), parts[1].strip()
|
|
if not EMAIL_RE.match(email):
|
|
await safe_reply(update, context, "❌ 邮箱格式不正确,请重新发送:邮箱 Token")
|
|
return
|
|
|
|
processing = await safe_reply(update, context, "⏳ 正在验证授权信息...")
|
|
ok, result = await bind_with_email_token(
|
|
tg_user_id=update.effective_user.id,
|
|
tg_username=update.effective_user.username or update.effective_user.first_name,
|
|
email=email,
|
|
token=token,
|
|
)
|
|
context.user_data["awaiting_bind"] = False
|
|
|
|
try:
|
|
await processing.delete()
|
|
except Exception:
|
|
pass
|
|
|
|
if ok:
|
|
data = result.get("data") or {}
|
|
await Action.create(action="BIND", user_id=update.effective_user.id, username=update.effective_user.username, details={"email": data.get("email")})
|
|
await safe_reply(update, context,
|
|
"✅ 绑定成功\n"
|
|
f"📧 邮箱: {data.get('email') or email}\n"
|
|
f"⏳ 到期: {data.get('time_expire') or '-'}"
|
|
)
|
|
else:
|
|
context.user_data["awaiting_bind"] = True
|
|
await safe_reply(update, context, f"❌ 验证失败:{result.get('msg') or result.get('reason') or '授权信息无效'}\n请重新发送:邮箱 Token")
|
|
|
|
|
|
async def cmd_unbind(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|
"""解绑 harvest 账号"""
|
|
user = update.effective_user
|
|
deleted = await unbind(user.id)
|
|
if deleted:
|
|
reply = await safe_reply(update, context, "🔓 已解除账号绑定")
|
|
await Action.create(action="UNBIND", user_id=user.id, username=user.username)
|
|
else:
|
|
reply = await safe_reply(update, context, "❌ 你没有绑定的账号")
|
|
asyncio.create_task(auto_delete(update.message, seconds=60))
|
|
asyncio.create_task(auto_delete(reply, seconds=60))
|
|
|
|
|
|
async def auto_delete(message, seconds=60):
|
|
try:
|
|
await asyncio.sleep(seconds)
|
|
await message.delete()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def register_bind(app):
|
|
app.add_handler(CommandHandler("start", cmd_start, filters.ChatType.PRIVATE))
|
|
app.add_handler(CommandHandler("bind", cmd_bind))
|
|
app.add_handler(CommandHandler("unbind", cmd_unbind))
|
|
app.add_handler(MessageHandler(filters.TEXT & filters.ChatType.PRIVATE & ~filters.COMMAND, handle_private_bind_text), group=0)
|