| import asyncio |
| import os |
| import sys |
| import json |
| import re |
| import requests |
| from telethon import TelegramClient, events |
| from telethon.sessions import StringSession |
| from telethon.tl.types import MessageService |
| from telethon.errors import FloodWaitError |
|
|
| sys.stdout.reconfigure(line_buffering=True) |
|
|
| API_ID = int(os.environ.get("API_ID", "0")) |
| API_HASH = os.environ.get("API_HASH") |
| SESSION_STRING = os.environ.get("TG_SESSION_STRING") |
| SOURCES = [s.strip() for s in os.environ.get("SOURCES", "").split(",") if s.strip()] |
| DESTINATION = os.environ.get("DESTINATION") |
| UNLOCK_API_URL = os.environ.get("UNLOCK_API_URL", "http://localhost:7860") |
| UNLOCK_PASSWORD = os.environ.get("UNLOCK_PASSWORD", "") |
|
|
| if not all([API_ID, API_HASH, SESSION_STRING, SOURCES, DESTINATION]): |
| print("缺少必要的环境变量") |
| sys.exit(1) |
|
|
| slug_cache = {} |
|
|
| def extract_slugs_from_text(text): |
| if not text: |
| return [] |
| hdhive_pattern = r"https?://hdhive\.com/resource/(?:[^/]+/)?([a-f0-9]{32})" |
| matches = re.findall(hdhive_pattern, text, re.IGNORECASE) |
| if matches: |
| return matches |
| return [] |
|
|
| def get_first_slug_from_message(message): |
| if not message.text: |
| return None |
| for entity in message.entities or []: |
| url = None |
| if hasattr(entity, 'url'): |
| url = entity.url |
| if url: |
| slugs = extract_slugs_from_text(url) |
| if slugs: |
| return slugs[0] |
| slugs = extract_slugs_from_text(message.text) |
| return slugs[0] if slugs else None |
|
|
| def get_direct_link(slug): |
| if slug in slug_cache: |
| return slug_cache[slug] |
| url = f"{UNLOCK_API_URL}/api/cache/unlock" |
| payload = {"slug": slug, "allow_points": True} |
| if UNLOCK_PASSWORD: |
| payload["password"] = UNLOCK_PASSWORD |
| try: |
| resp = requests.post(url, json=payload, timeout=10) |
| if resp.status_code == 200: |
| data = resp.json() |
| if data.get("success") and data.get("link"): |
| direct_link = data["link"] |
| slug_cache[slug] = direct_link |
| return direct_link |
| except Exception as e: |
| print(f"解锁错误: {e}") |
| return None |
|
|
| def replace_links_in_text(text, slug, direct_link): |
| if not text: |
| return text |
| pattern = rf"(https?://hdhive\.com/resource/(?:[^/]+/)?{slug})" |
| return re.sub(pattern, direct_link, text, flags=re.IGNORECASE) |
|
|
| async def send_modified_message(client, dest_entity, original_msg): |
| if isinstance(original_msg, MessageService): |
| return |
|
|
| try: |
| slug = get_first_slug_from_message(original_msg) |
| if not slug: |
| await client.forward_messages(dest_entity, original_msg) |
| print(f"直接转发 {original_msg.id}(无HDHive链接)") |
| return |
|
|
| direct_link = get_direct_link(slug) |
| if not direct_link: |
| await client.forward_messages(dest_entity, original_msg) |
| print(f"直接转发 {original_msg.id}(解锁失败)") |
| return |
|
|
| if original_msg.text: |
| new_text = replace_links_in_text(original_msg.text, slug, direct_link) |
| try: |
| await client.send_message(dest_entity, new_text) |
| print(f"✅ 已发送替换后消息 {original_msg.id}") |
| except FloodWaitError as e: |
| print(f"⏳ 速率限制,等待 {e.seconds} 秒后重试...") |
| await asyncio.sleep(e.seconds) |
| await client.send_message(dest_entity, new_text) |
| print(f"✅ 重试成功 {original_msg.id}") |
| else: |
| await client.forward_messages(dest_entity, original_msg) |
| except Exception as e: |
| print(f"❌ 处理失败 {original_msg.id}: {e}") |
|
|
| async def main(): |
| print("等待 60 秒,确保旧容器已退出...") |
| await asyncio.sleep(60) |
|
|
| while True: |
| client = None |
| try: |
| client = TelegramClient(StringSession(SESSION_STRING), API_ID, API_HASH) |
| await client.start() |
| print("✅ 连接成功") |
|
|
| dest = await client.get_entity(DESTINATION) |
| print(f"📢 目标频道: {DESTINATION}") |
|
|
| |
| @client.on(events.NewMessage(from_users=777000)) |
| async def verification_handler(event): |
| msg = event.message |
| text = msg.text or "" |
| print(f"\n📩 收到新官方通知(实时)") |
| print(f" 消息ID: {msg.id}") |
| print(f" 时间: {msg.date}") |
| print(f" 内容: {text[:500]}") |
| match = re.search(r"\b(\d{5,6})\b", text) |
| if match: |
| print(f" 🔑 验证码: {match.group(1)}") |
| else: |
| print(" ℹ️ 未找到验证码") |
| print() |
|
|
| |
| try: |
| print("📜 获取最近 5 条官方通知...") |
| async for msg in client.iter_messages(777000, limit=5, reverse=False): |
| text = msg.text or "" |
| print(f"--- 消息ID: {msg.id} | 时间: {msg.date} ---") |
| print(f"内容: {text[:300]}") |
| match = re.search(r"\b(\d{5,6})\b", text) |
| if match: |
| print(f"验证码: {match.group(1)}") |
| print() |
| except Exception as e: |
| print(f"获取官方通知失败: {e}") |
|
|
| |
| for src in SOURCES: |
| try: |
| entity = await client.get_entity(src) |
| @client.on(events.NewMessage(chats=entity)) |
| async def handler(event, dest=dest, src_name=src): |
| await send_modified_message(client, dest, event.message) |
| print(f"✅ 已监听: {src}") |
| except Exception as e: |
| print(f"❌ 监听失败 {src}: {e}") |
|
|
| print(f"🔄 实时监听已启动,监控 {len(SOURCES)} 个源") |
| print("📢 官方通知监听已启动,等待新消息...") |
|
|
| |
| heartbeat_count = 0 |
| while True: |
| try: |
| await asyncio.wait_for(client.get_me(), timeout=30) |
| heartbeat_count += 1 |
| print(f"💓 心跳正常 [{heartbeat_count}]") |
| await asyncio.sleep(60) |
| except asyncio.TimeoutError: |
| print("⚠️ 心跳超时,可能连接断开") |
| break |
| except Exception as e: |
| print(f"⚠️ 心跳失败: {e}") |
| break |
|
|
| await client.disconnect() |
|
|
| except FloodWaitError as e: |
| print(f"⏳ 主循环触发速率限制,等待 {e.seconds} 秒") |
| await asyncio.sleep(e.seconds) |
| continue |
| except asyncio.TimeoutError: |
| print("⏰ 连接超时,10秒后重连...") |
| await asyncio.sleep(10) |
| continue |
| except (ConnectionError, OSError) as e: |
| print(f"🔌 连接错误: {e},10秒后重连...") |
| await asyncio.sleep(10) |
| continue |
| except Exception as e: |
| print(f"❌ 未知错误: {e},10秒后重连...") |
| await asyncio.sleep(10) |
| continue |
|
|
| if __name__ == "__main__": |
| try: |
| asyncio.run(main()) |
| except KeyboardInterrupt: |
| print("👋 用户停止") |
| except Exception as e: |
| print(f"💥 主程序异常: {e}") |
| import traceback |
| traceback.print_exc() |