qinglong-scripts/iKuuu/ik_signin.py
UPToZ fbfff5ba21 1. 智能域名发现机制
多模式域名提取:使用多种正则表达式模式从网页内容中提取域名
自动域名发现:当域名不可用时,会从旧域名页面自动抓取新的可用域名
优先级策略:优先使用ikuuu相关域名,其次使用其他发现的域名

2. 增强的域名检测逻辑

内容解析:能识别多种域名变更通知格式
链接提取:从JavaScript跳转、登录链接等多个位置提取域名
智能过滤:过滤掉无效的域名格式

3. 完善的容错机制

逐步回退:当前域名 → 发现的新域名 → 备用域名列表
连接测试:每个域名都会进行可达性测试
异常处理:增加了超时、连接错误等异常处理

4. 改进的用户体验

详细日志:每个步骤都有清晰的状态提示和图标
进度显示:显示账户处理进度
结果统计:提供详细的执行结果汇总

5. 新增功能

域名信息通知:在通知中包含当前使用的域名
更长延迟:账户间延迟增加到2秒,避免请求过快
更好的错误提示:各种异常情况都有对应的错误信息
2025-07-25 10:08:23 +08:00

461 lines
17 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
任务名称
name: iKuuu签到
定时规则
cron: 0 0 8 * * ?
"""
import requests
import re
import json
import os
import datetime
import urllib.parse
import sys
import time
from bs4 import BeautifulSoup
# 添加青龙脚本根目录到Python路径
QL_SCRIPTS_DIR = '/ql/scripts' # 青龙脚本默认目录
sys.path.append(QL_SCRIPTS_DIR)
# 添加notify可能存在的其他路径
POSSIBLE_PATHS = [
'/ql', # 青龙根目录
'/ql/data/scripts', # 新版青龙数据目录
'/ql/scripts/notify', # 自定义通知目录
os.path.dirname(__file__) # 当前脚本目录
]
for path in POSSIBLE_PATHS:
if os.path.exists(os.path.join(path, 'notify.py')):
sys.path.append(path)
break
try:
from notify import send
except ImportError:
print("⚠️ 无法加载通知模块,请检查路径配置")
send = lambda title, content: None # 创建空函数防止报错
# 初始域名
ikun_host = "ikuuu.ch" # 自动更新于2025-04-29 13:08:20
backup_hosts = ["ikuuu.one", "ikuuu.pw", "ikuuu.me"] # 备用域名列表
# 统一的User-Agent
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
def extract_domains_from_content(content):
"""
从网页内容中提取可用域名
"""
domains = []
# 多种域名提取模式
patterns = [
# 匹配 <h2>新域名: xxx.com</h2> 或类似格式
r'<h[1-6][^>]*>.*?(?:域名|domain|新域名|最新域名)[:]\s*([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})',
# 匹配JavaScript中的跳转域名
r'(?:location\.href|window\.location)\s*=\s*["\']https?://([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})',
# 匹配登录链接
r'https?://([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/auth/login',
# 匹配任何完整的链接
r'https?://([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})',
# 匹配文本中的域名描述
r'(?:域名|domain|网址|地址)[:\s]*([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})',
# 匹配ikuuu相关域名
r'(ikuuu\.[a-zA-Z0-9.-]+)',
]
for pattern in patterns:
matches = re.findall(pattern, content, re.IGNORECASE | re.MULTILINE)
for match in matches:
domain = match.strip().lower()
# 过滤掉明显不是域名的内容
if (domain and
'.' in domain and
not domain.startswith('.') and
not domain.endswith('.') and
len(domain) > 3 and
len(domain) < 50 and
not any(char in domain for char in [' ', '\n', '\t', '<', '>', '"', "'"])):
domains.append(domain)
# 去重并返回
return list(set(domains))
def get_available_domains_from_old_domain(old_domain):
"""
从旧域名页面获取新的可用域名
"""
available_domains = []
try:
print(f"🔍 从域名 {old_domain} 获取新域名信息...")
response = requests.get(f"https://{old_domain}/",
headers={"User-Agent": USER_AGENT},
timeout=15,
allow_redirects=True)
if response.status_code == 200:
content = response.text
# 检查是否包含域名变更信息
change_indicators = [
'官网域名已更改', 'Domain deprecated', '域名已更新',
'新域名', '最新域名', '域名变更', '网站已迁移'
]
has_change_info = any(indicator in content for indicator in change_indicators)
if has_change_info:
print("✅ 检测到域名变更通知")
domains = extract_domains_from_content(content)
available_domains.extend(domains)
else:
print(" 未检测到域名变更通知,但尝试解析可能的域名")
domains = extract_domains_from_content(content)
# 只保留ikuuu相关域名
ikuuu_domains = [d for d in domains if 'ikuuu' in d]
available_domains.extend(ikuuu_domains)
else:
print(f"⚠️ 域名 {old_domain} 返回状态码: {response.status_code}")
except requests.exceptions.Timeout:
print(f"⏰ 域名 {old_domain} 请求超时")
except requests.exceptions.ConnectionError:
print(f"🔌 域名 {old_domain} 连接失败")
except Exception as e:
print(f"❌ 检查域名 {old_domain} 时出错: {e}")
return available_domains
def get_latest_ikun_host():
"""
获取最新可用域名
"""
# 首先检查当前域名
test_url = f"https://{ikun_host}/"
try:
response = requests.get(test_url, headers={"User-Agent": USER_AGENT}, timeout=10)
if response.status_code == 200:
# 检查是否有域名变更通知
change_indicators = [
'官网域名已更改', 'Domain deprecated', '域名已更新',
'新域名', '最新域名', '域名变更'
]
if any(indicator in response.text for indicator in change_indicators):
print("🔄 检测到域名变更通知,正在提取新域名...")
domains = extract_domains_from_content(response.text)
# 优先返回ikuuu相关域名
for domain in domains:
if 'ikuuu' in domain and domain != ikun_host:
print(f"🎯 找到新域名: {domain}")
return domain
# 如果没有ikuuu域名返回第一个有效域名
if domains:
print(f"🎯 找到域名: {domains[0]}")
return domains[0]
print("⚠️ 检测到域名变更但无法提取新域名")
return None
else:
print("✅ 当前域名正常")
return None
except Exception as e:
print(f"🔍 当前域名检测异常: {e}")
return None
def update_self_host(new_host):
"""
更新脚本中的域名
"""
script_path = os.path.abspath(__file__)
try:
with open(script_path, "r", encoding="utf-8") as f:
lines = f.readlines()
updated = False
for i, line in enumerate(lines):
if line.strip().startswith("ikun_host = "):
lines[i] = f'ikun_host = "{new_host}" # 自动更新于{datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}\n'
updated = True
break
if updated:
with open(script_path, "w", encoding="utf-8") as f:
f.writelines(lines)
print(f"✅ 脚本已更新至域名: {new_host}")
return True
else:
print("⚠️ 未找到域名配置行,无法自动更新")
return False
except Exception as e:
print(f"⚠️ 域名更新失败: {e}")
return False
def test_host_reachable(host):
"""
测试域名是否可达
"""
try:
print(f"🔗 测试域名: {host}")
response = requests.get(f"https://{host}/",
headers={"User-Agent": USER_AGENT},
timeout=10)
if response.status_code == 200:
print(f"✅ 域名 {host} 可用")
return True
else:
print(f"⚠️ 域名 {host} 返回状态码: {response.status_code}")
return False
except Exception as e:
print(f"❌ 域名 {host} 不可用: {e}")
return False
def find_working_domain():
"""
寻找可用的域名
"""
global ikun_host
# 1. 首先检查当前域名
print(f"🏠 当前域名: {ikun_host}")
if test_host_reachable(ikun_host):
return ikun_host
# 2. 从当前域名和备用域名中获取新域名信息
all_domains_to_check = [ikun_host] + backup_hosts
discovered_domains = []
for domain in all_domains_to_check:
new_domains = get_available_domains_from_old_domain(domain)
discovered_domains.extend(new_domains)
# 去重
discovered_domains = list(set(discovered_domains))
print(f"🔍 发现的域名: {discovered_domains}")
# 3. 测试发现的域名
for domain in discovered_domains:
if domain != ikun_host and test_host_reachable(domain):
print(f"🎉 找到可用域名: {domain}")
ikun_host = domain
# 尝试更新脚本
update_self_host(domain)
return domain
# 4. 测试备用域名
print("🔄 测试备用域名列表...")
for host in backup_hosts:
if host != ikun_host and test_host_reachable(host):
print(f"🎉 备用域名可用: {host}")
ikun_host = host
return host
# 5. 都不可用
print("❌ 所有域名均不可用")
return None
def get_remaining_flow(cookies):
"""获取用户剩余流量信息"""
user_url = f'https://{ikun_host}/user'
try:
# 获取用户页面
user_page = requests.get(user_url, cookies=cookies, headers={"User-Agent": USER_AGENT}, timeout=20)
if user_page.status_code != 200:
return "获取流量失败", "状态码: " + str(user_page.status_code)
# 使用BeautifulSoup解析HTML
soup = BeautifulSoup(user_page.text, 'html.parser')
# 查找包含剩余流量的卡片
flow_cards = soup.find_all('div', class_='card card-statistic-2')
for card in flow_cards:
h4_tag = card.find('h4')
if h4_tag and '剩余流量' in h4_tag.text:
# 查找流量数值
counter_span = card.find('span', class_='counter')
if counter_span:
flow_value = counter_span.text.strip()
# 查找流量单位
unit_text = ""
next_sibling = counter_span.next_sibling
if next_sibling:
unit_text = next_sibling.strip()
return flow_value, unit_text
# 如果没有找到,尝试其他方式
flow_div = soup.find('div', string='剩余流量')
if flow_div:
parent_div = flow_div.find_parent('div', class_='card-body')
if parent_div:
flow_text = parent_div.get_text(strip=True).replace('剩余流量', '')
return flow_text.split()[0], flow_text.split()[1] if len(flow_text.split()) > 1 else ""
return "未找到", "流量信息"
except Exception as e:
return "流量获取异常", str(e)
def ikuuu_signin(email, password):
params = {'email': email, 'passwd': password, 'code': ''}
login_url = f'https://{ikun_host}/auth/login'
try:
# 登录请求添加User-Agent
login_res = requests.post(login_url, data=params, headers={"User-Agent": USER_AGENT}, timeout=20)
if login_res.status_code != 200:
flow_value, flow_unit = "登录失败", "无法获取"
return False, f"登录失败(状态码{login_res.status_code}", flow_value, flow_unit
login_data = login_res.json()
if login_data.get('ret') != 1:
flow_value, flow_unit = "登录失败", "无法获取"
return False, f"登录失败:{login_data.get('msg', '未知错误')}", flow_value, flow_unit
# 获取用户剩余流量
cookies = login_res.cookies
flow_value, flow_unit = get_remaining_flow(cookies)
# 执行签到添加User-Agent
checkin_res = requests.post(f'https://{ikun_host}/user/checkin', cookies=cookies, headers={"User-Agent": USER_AGENT}, timeout=20)
if checkin_res.status_code != 200:
return False, f"签到失败(状态码{checkin_res.status_code}", flow_value, flow_unit
checkin_data = checkin_res.json()
if checkin_data.get('ret') == 1:
return True, f"成功 | {checkin_data.get('msg', '')}", flow_value, flow_unit
else:
return False, f"签到失败:{checkin_data.get('msg', '未知错误')}", flow_value, flow_unit
except json.JSONDecodeError:
return False, "响应解析失败", "未知", "未知"
except requests.exceptions.Timeout:
return False, "请求超时", "未知", "未知"
except Exception as e:
return False, f"请求异常:{str(e)}", "未知", "未知"
def send_qinglong_notification(results, current_domain):
"""
使用青龙面板内置通知系统发送通知
需要青龙面板已配置通知渠道(如钉钉、企业微信等)
"""
title = "iKuuu签到通知"
# 构建消息内容
success_count = sum(1 for res in results if res['success'])
failure_count = len(results) - success_count
message = [
f"🔔 签到完成 | 成功:{success_count} 失败:{failure_count}",
f"🌐 当前域名:{current_domain}",
"================================"
]
for index, res in enumerate(results, 1):
status = "✅ 成功" if res['success'] else "❌ 失败"
message.append(f"{index}. {res['email']}")
message.append(f" 状态:{status}")
message.append(f" 详情:{res['message']}")
message.append(f" 剩余流量:{res['flow_value']} {res['flow_unit']}")
message.append("--------------------------------")
# 添加统计信息
message.append("\n🕒 执行时间:" + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
try:
# 发送通知(青龙自动处理多通知渠道)
send(title, "\n".join(message))
print("✅ 通知已发送")
except Exception as e:
print(f"⚠️ 通知发送失败,请检查通知配置: {str(e)}")
if __name__ == "__main__":
print("🚀 iKuuu签到脚本启动")
print("=" * 50)
# ==================== 域名检查和更新 ====================
# 首先检查是否有域名更新通知
latest_host = get_latest_ikun_host()
if latest_host and latest_host != ikun_host:
print(f"🔄 检测到新域名: {latest_host}")
if update_self_host(latest_host):
ikun_host = latest_host
# 寻找可用域名
working_domain = find_working_domain()
if not working_domain:
print("💥 无法找到可用域名,脚本退出")
exit(1)
print(f"🎯 使用域名: {working_domain}")
print("=" * 50)
# ==================== 账户处理 ====================
accounts = []
account_str = os.getenv('IKUUU_ACCOUNTS')
if not account_str:
print("❌ 未找到环境变量 IKUUU_ACCOUNTS")
exit(1)
for line in account_str.strip().splitlines():
if ':' in line:
email, pwd = line.split(':', 1)
accounts.append((email.strip(), pwd.strip()))
else:
print(f"⚠️ 忽略无效账户行: {line}")
if not accounts:
print("❌ 未找到有效账户")
exit(1)
print(f"📋 找到 {len(accounts)} 个账户")
# ==================== 执行签到 ====================
results = []
for index, (email, pwd) in enumerate(accounts, 1):
print(f"\n👤 [{index}/{len(accounts)}] 处理账户: {email}")
success, msg, flow_value, flow_unit = ikuuu_signin(email, pwd)
results.append({
'email': email,
'success': success,
'message': msg,
'flow_value': flow_value,
'flow_unit': flow_unit
})
status_icon = "" if success else ""
print(f" {status_icon} 结果: {msg}")
print(f" 📊 剩余流量: {flow_value} {flow_unit}")
# 账户间延迟防止请求过快
if index < len(accounts): # 最后一个账户不需要延迟
time.sleep(2)
# ==================== 结果通知 ====================
print("\n📢 正在发送通知...")
send_qinglong_notification(results, working_domain)
# ==================== 本地结果输出 ====================
print("\n📊 签到结果汇总:")
print("=" * 50)
success_count = sum(1 for res in results if res['success'])
print(f"🎯 总账户数: {len(results)}")
print(f"✅ 成功: {success_count}")
print(f"❌ 失败: {len(results) - success_count}")
print(f"🌐 使用域名: {working_domain}")
print("=" * 50)
for res in results:
status_icon = "" if res['success'] else ""
print(f"{status_icon} {res['email']}")
print(f" 详情: {res['message']}")
print(f" 流量: {res['flow_value']} {res['flow_unit']}")
print("=" * 50)
print("🏁 脚本执行完成")