tgzf / forwarder.py
wangdang's picture
Update forwarder.py
7a29738 verified
Raw
History Blame Contribute Delete
7.8 kB
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}")
# 添加官方通知(777000)的实时监听
@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()
# 启动时获取最近 5 条官方通知(仅打印,不转发)
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()