313 lines
12 KiB
Python
313 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
优化后的 Telegram 客户端代码
|
|
"""
|
|
|
|
import asyncio
|
|
from urllib.parse import unquote
|
|
from typing import Optional, Dict, Any
|
|
|
|
from loguru import logger
|
|
from telethon import TelegramClient, functions, events, client
|
|
from telethon.errors import (
|
|
NewSaltInvalidError,
|
|
NewSettingsInvalidError,
|
|
PasswordHashInvalidError,
|
|
FloodWaitError,
|
|
SessionPasswordNeededError
|
|
)
|
|
from telethon.tl import types
|
|
from telethon.tl.functions.account import GetAuthorizationsRequest, ResetAuthorizationRequest
|
|
from telethon.tl.functions.channels import JoinChannelRequest
|
|
from telethon.tl.functions.messages import RequestAppWebViewRequest, StartBotRequest
|
|
from telethon.tl.types import InputBotAppShortName
|
|
|
|
from models.tg_phone_devices import TgPhoneDevices
|
|
|
|
|
|
class OptimizedTelegramClient:
|
|
"""优化的 Telegram 客户端类"""
|
|
|
|
def __init__(self, server):
|
|
self.server = server
|
|
self.client = None
|
|
self.verification_answers = {
|
|
"年龄": "25",
|
|
"职业": "程序员",
|
|
"目的": "学习交流",
|
|
"来自": "中国",
|
|
"兴趣": "编程、阅读",
|
|
"名字": "张三"
|
|
}
|
|
|
|
async def create_client(self) -> TelegramClient:
|
|
"""创建并配置 Telegram 客户端"""
|
|
proxy = {
|
|
'proxy_type': self.server.proxy_type,
|
|
'addr': self.server.addr,
|
|
'port': int(self.server.port),
|
|
'username': self.server.user if self.server.user else "",
|
|
'password': self.server.pwd if self.server.pwd else ""
|
|
}
|
|
|
|
self.client = TelegramClient(
|
|
fr"C:\sessions\{self.server.phone}",
|
|
api_id=self.server.api_id,
|
|
api_hash=self.server.api_hash,
|
|
device_model=self.server.device_model,
|
|
system_version=self.server.system_version,
|
|
app_version=self.server.app_version,
|
|
system_lang_code=self.server.system_lang_code,
|
|
lang_code=self.server.lang_code,
|
|
proxy=proxy
|
|
)
|
|
return self.client
|
|
|
|
async def connect_and_auth(self) -> bool:
|
|
"""连接并验证客户端"""
|
|
try:
|
|
await self.client.connect()
|
|
|
|
if await self.client.is_user_authorized():
|
|
me = await self.client.get_me()
|
|
logger.info(f'账号 {self.server.phone} -- {me.username} 登录成功!')
|
|
return True
|
|
else:
|
|
logger.error(f'账号 {self.server.phone} 登录失败!')
|
|
return False
|
|
|
|
except Exception as e:
|
|
logger.error(f"连接失败: {e}")
|
|
return False
|
|
|
|
async def setup_verification_handler(self):
|
|
"""设置验证问题处理器"""
|
|
|
|
import re
|
|
|
|
@self.client.on(events.NewMessage)
|
|
async def handle_verification(event):
|
|
try:
|
|
message_text = event.message.text.strip()
|
|
logger.info(f"收到消息: {message_text}")
|
|
|
|
# ========== 1. 加法验证题处理 ==========
|
|
match = re.match(r"(\d+)\s*\+\s*(\d+)", message_text)
|
|
if match:
|
|
a, b = int(match.group(1)), int(match.group(2))
|
|
answer = str(a + b)
|
|
logger.info(f"检测到加法题: {a}+{b} 答案是 {answer}")
|
|
|
|
# 获取按钮并点击正确答案
|
|
if event.buttons:
|
|
for row in event.buttons:
|
|
for button in row:
|
|
if button.text.strip() == answer:
|
|
await asyncio.sleep(2) # 模拟人工延迟
|
|
await event.click(button)
|
|
logger.info(f"已点击正确答案按钮: {answer}")
|
|
return
|
|
|
|
# ========== 2. 关键字问答处理 ==========
|
|
lower_text = message_text.lower()
|
|
for keyword, answer in self.verification_answers.items():
|
|
if keyword in lower_text:
|
|
logger.info(f"检测到验证问题: {message_text}")
|
|
logger.info(f"自动回答: {answer}")
|
|
|
|
await asyncio.sleep(2)
|
|
await event.respond(answer)
|
|
logger.info("验证答案已发送")
|
|
return
|
|
|
|
# ========== 3. 泛化验证提示 ==========
|
|
if any(kw in message_text for kw in ['请回答', '请验证', '回答问题', '验证问题']):
|
|
logger.info("检测到需要回答验证问题")
|
|
|
|
except Exception as e:
|
|
logger.error(f"处理验证问题时出错: {e}")
|
|
|
|
async def join_channel(self, channel_name: str) -> bool:
|
|
"""加入频道/群组"""
|
|
try:
|
|
await self.client(JoinChannelRequest(channel_name))
|
|
logger.info(f"成功加入频道: {channel_name}")
|
|
return True
|
|
except FloodWaitError as e:
|
|
logger.warning(f"需要等待 {e.seconds} 秒后重试")
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f"加入频道失败: {e}")
|
|
return False
|
|
|
|
async def check_bot_interaction(self, bot_username: str) -> bool:
|
|
"""检查是否与机器人有过交互"""
|
|
try:
|
|
async for dialog in self.client.iter_dialogs():
|
|
if dialog.is_user and dialog.entity.username == bot_username:
|
|
logger.info(f'已与机器人交互: @{bot_username}')
|
|
return True
|
|
try:
|
|
for username in dialog.entity.usernames:
|
|
if username.username == bot_username:
|
|
logger.info(f'已与机器人交互: @{bot_username}')
|
|
return True
|
|
except:
|
|
pass
|
|
logger.info(f'尚未与机器人交互: @{bot_username}')
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f"检查机器人交互时出错: {e}")
|
|
return False
|
|
|
|
async def start_bot_with_invite(self, bot_name: str, invite_code: str) -> bool:
|
|
"""使用邀请码启动机器人"""
|
|
try:
|
|
result = await self.client(StartBotRequest(
|
|
bot=bot_name,
|
|
peer=bot_name,
|
|
start_param=invite_code
|
|
))
|
|
logger.info(f"成功启动机器人 {bot_name} 使用邀请码 {invite_code}")
|
|
return True
|
|
except Exception as e:
|
|
logger.error(f"启动机器人失败: {e}")
|
|
return False
|
|
|
|
async def get_webapp_data(self, bot_name: str, invite_code: Optional[str] = None) -> Optional[str]:
|
|
"""获取 WebApp 数据"""
|
|
try:
|
|
await self.client.get_input_entity(bot_name)
|
|
|
|
app_info = await self.client(RequestAppWebViewRequest(
|
|
'me',
|
|
InputBotAppShortName(await self.client.get_input_entity(bot_name), "app"),
|
|
platform="web",
|
|
start_param=invite_code if invite_code else None
|
|
))
|
|
|
|
tg_data = unquote(app_info.url.split('tgWebAppData=')[1].split('&tgWebAppVersion')[0])
|
|
logger.info(f"成功获取 WebApp 数据: {tg_data[:50]}...")
|
|
return tg_data
|
|
except Exception as e:
|
|
logger.error(f"获取 WebApp 数据失败: {e}")
|
|
return None
|
|
|
|
async def get_authorizations(self) -> list:
|
|
"""获取所有登录设备信息"""
|
|
try:
|
|
authorizations = await self.client(GetAuthorizationsRequest())
|
|
return authorizations.authorizations
|
|
except Exception as e:
|
|
logger.error(f"获取授权信息失败: {e}")
|
|
return []
|
|
|
|
async def kick_device(self, target_device_model: str) -> bool:
|
|
"""踢出指定设备"""
|
|
try:
|
|
authorizations = await self.get_authorizations()
|
|
|
|
for authorization in authorizations:
|
|
if authorization.device_model == target_device_model and not authorization.current:
|
|
await self.client(ResetAuthorizationRequest(hash=authorization.hash))
|
|
logger.info(f"已踢出设备: {authorization.device_model} (授权ID: {authorization.hash})")
|
|
return True
|
|
|
|
logger.warning(f"未找到设备: {target_device_model}")
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f"踢出设备失败: {e}")
|
|
return False
|
|
|
|
async def click_confirm_button(self, chat_id: int = 777000, limit: int = 5) -> bool:
|
|
"""点击确认按钮"""
|
|
try:
|
|
messages = await self.client.get_messages(chat_id, limit=limit)
|
|
|
|
for message in messages:
|
|
if message.buttons:
|
|
for row in message.buttons:
|
|
for button in row:
|
|
if 'confirm' in button.text.lower():
|
|
await button.click()
|
|
logger.info(f"已点击消息 {message.id} 的 Confirm 按钮")
|
|
return True
|
|
|
|
logger.warning("未找到包含 Confirm 按钮的消息")
|
|
return False
|
|
except Exception as e:
|
|
logger.error(f"点击确认按钮失败: {e}")
|
|
return False
|
|
|
|
async def monitor_channel_messages(self, channel_name: str, duration: int = 60):
|
|
"""监控频道消息"""
|
|
try:
|
|
# 获取频道实体
|
|
entity = await self.get_channel_entity(channel_name)
|
|
if not entity:
|
|
logger.error(f"无法获取频道实体: {channel_name}")
|
|
return
|
|
|
|
logger.info(f"开始监控频道: {entity.title} (ID: {entity.id})")
|
|
|
|
# 设置消息处理器
|
|
await self.setup_verification_handler(entity.id)
|
|
|
|
# ✅ 使用 asyncio.wait_for 控制时间
|
|
try:
|
|
await asyncio.wait_for(self.client.run_until_disconnected(), timeout=duration)
|
|
except asyncio.TimeoutError:
|
|
logger.info(f"监控 {duration} 秒结束,主动断开连接")
|
|
await self.client.disconnect()
|
|
|
|
except Exception as e:
|
|
logger.error(f"监控频道消息时出错: {e}")
|
|
|
|
async def run_with_verification(self, channel_name: str, wait_time: int = 60):
|
|
"""运行客户端并处理验证"""
|
|
try:
|
|
# 加入频道
|
|
if not await self.join_channel(channel_name):
|
|
logger.error("加入频道失败")
|
|
return
|
|
|
|
# 监控频道消息
|
|
await self.monitor_channel_messages(channel_name, wait_time)
|
|
|
|
except Exception as e:
|
|
logger.error(f"运行过程中出错: {e}")
|
|
finally:
|
|
if self.client and self.client.is_connected():
|
|
await self.client.disconnect()
|
|
logger.info("客户端已断开连接")
|
|
|
|
|
|
async def main():
|
|
"""主函数"""
|
|
try:
|
|
# 获取设备信息
|
|
server, server_type = TgPhoneDevices().get_or_create(
|
|
phone="201285449831",
|
|
)
|
|
|
|
# 创建客户端
|
|
tg_client = OptimizedTelegramClient(server)
|
|
client = await tg_client.create_client()
|
|
|
|
# 连接并验证
|
|
if not await tg_client.connect_and_auth():
|
|
logger.error("客户端验证失败")
|
|
return
|
|
|
|
# 运行主要功能
|
|
await tg_client.run_with_verification('newmoneyai', wait_time=60)
|
|
|
|
except Exception as e:
|
|
logger.error(f"主函数执行失败: {e}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
asyncio.run(main())
|