修改ikuuu签到脚本

This commit is contained in:
UPToZ 2026-06-04 17:02:52 +08:00
parent 53332285f3
commit cc6429b64b
2 changed files with 173 additions and 97 deletions

View File

@ -15,33 +15,44 @@
## 安装步骤 ## 安装步骤
1. 将脚本保存为`ik_signin.py`到青龙面板的`scripts`目录 1. 将脚本保存为`ik_signin.py`到青龙面板的`scripts`目录
2. 添加环境变量`IKUUU_ACCOUNTS`,格式为: 2. 添加环境变量`IKUUU_COOKIES`,格式为:
``` ```
邮箱1:密码1 备注1|cookie1
邮箱2:密码2 备注2|cookie2
``` ```
3. 在青龙面板添加定时任务: 3. 在青龙面板添加定时任务:
``` ```
task ik_signin 0 0 1 * * ? task ik_signin 0 0 1 * * ?
``` ```
## 环境变量配置 ## 环境变量配置
| 变量名 | 说明 | 示例 | | 变量名 | 说明 | 示例 |
| ---- | ---- | ---- | | ---- | ---- | ---- |
| IKUUU_ACCOUNTS | iKuuu账号密码每行一个账号格式为`邮箱:密码` | `test@example.com:password123` | | IKUUU_COOKIES | iKuuu登录Cookie每行一个账号格式为`备注|cookie`,备注可省略 | `主账号|PHPSESSID=xxx; uid=xxx` |
## 使用说明 ## 使用说明
1. 脚本会自动检测当前域名是否可用 1. 脚本会自动检测当前域名是否可用
2. 若检测到域名变更,会自动更新脚本中的域名信息 2. 若检测到域名变更,会自动更新脚本中的域名信息
3. 依次对配置的所有账号进行签到操作 3. 依次对配置的所有账号进行签到操作
4. 签到结果会通过青龙面板的通知系统发送 4. 签到结果会通过青龙面板的通知系统发送
## 更新日志 ### Cookie获取说明
### 2025-07-29 1. 浏览器登录iKuuu后打开开发者工具
- 修复「剩余流量」查询 2. 在Network中访问`/user`或`/user/checkin`
3. 复制请求头里的`Cookie`完整内容
4. 填入青龙环境变量`IKUUU_COOKIES`
## 更新日志
### 2026-06-04
- 移除账号密码登录,统一使用`IKUUU_COOKIES`执行签到
- 优化剩余流量查询,支持从页面卡片、文本和脚本片段中提取流量信息
### 2025-07-29
- 修复「剩余流量」查询
### 2025-07-25 ### 2025-07-25
- 智能域名发现机制。使用多种正则表达式模式从网页内容中提取域名当域名不可用时会从旧域名页面自动抓取新的可用域名优先使用ikuuu相关域名其次使用其他发现的域名 - 智能域名发现机制。使用多种正则表达式模式从网页内容中提取域名当域名不可用时会从旧域名页面自动抓取新的可用域名优先使用ikuuu相关域名其次使用其他发现的域名
@ -73,4 +84,4 @@ task ik_signin 0 0 1 * * ?
- 请确保青龙面板已配置通知功能,否则无法接收签到结果通知 - 请确保青龙面板已配置通知功能,否则无法接收签到结果通知
- 脚本运行需要网络连接畅通,否则可能导致签到失败 - 脚本运行需要网络连接畅通,否则可能导致签到失败
- 若官方域名发生变更,脚本会自动更新,但可能需要手动触发一次以完成更新 - 若官方域名发生变更,脚本会自动更新,但可能需要手动触发一次以完成更新

View File

@ -7,15 +7,18 @@ cron: 0 0 8 * * ?
import requests import requests
import re import re
import json
import os import os
import datetime import datetime
import urllib.parse
import sys import sys
import time import time
import base64 import base64
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
# 添加青龙脚本根目录到Python路径 # 添加青龙脚本根目录到Python路径
QL_SCRIPTS_DIR = '/ql/scripts' # 青龙脚本默认目录 QL_SCRIPTS_DIR = '/ql/scripts' # 青龙脚本默认目录
sys.path.append(QL_SCRIPTS_DIR) sys.path.append(QL_SCRIPTS_DIR)
@ -40,12 +43,84 @@ except ImportError:
send = lambda title, content: None # 创建空函数防止报错 send = lambda title, content: None # 创建空函数防止报错
# 初始域名 # 初始域名
ikun_host = "ikuuu.ch" # 自动更新于2025-04-29 13:08:20 ikun_host = "ikuuu.win" # 自动更新于2026-01-17 08:00:07
backup_hosts = ["ikuuu.one", "ikuuu.pw", "ikuuu.me"] # 备用域名列表 backup_hosts = ["ikuuu.win","ikuuu.fyi","ikuuu.one", "ikuuu.pw", "ikuuu.me","ikuuu.cc"] # 备用域名列表
# 统一的User-Agent # 统一的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" 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 build_headers(referer=None, ajax=False):
headers = {"User-Agent": USER_AGENT}
if referer:
headers["Referer"] = referer
headers["Origin"] = f"https://{ikun_host}"
if ajax:
headers["X-Requested-With"] = "XMLHttpRequest"
return headers
def decode_origin_body(html):
match = re.search(r'var originBody = "([^"]+)"', html)
if not match:
return html
return base64.b64decode(match.group(1)).decode('utf-8')
def parse_json_response(response):
try:
return response.json()
except Exception:
return None
def load_cookie_string(session, cookie_string):
for item in cookie_string.split(';'):
if '=' not in item:
continue
name, value = item.split('=', 1)
name = name.strip()
value = value.strip()
if name:
session.cookies.set(name, value)
def compact_text(text):
return re.sub(r'\s+', ' ', text).strip()
def split_flow_text(text):
match = re.search(r'(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB|PB|KiB|MiB|GiB|TiB)', text, re.IGNORECASE)
if not match:
return None
return match.group(1), match.group(2).upper()
def extract_remaining_flow(decoded_content):
soup = BeautifulSoup(decoded_content, 'html.parser')
# Prefer the card/box containing the label, but do not depend on one fixed class name.
label_nodes = [node for node in soup.find_all(string=re.compile(r'剩余流量|剩余.*流量'))]
for label in label_nodes:
current = label.parent
for _ in range(5):
if not current:
break
candidate = split_flow_text(compact_text(current.get_text(' ', strip=True)))
if candidate:
return candidate
current = current.parent
text = compact_text(soup.get_text(' ', strip=True))
patterns = [
r'(?:剩余流量|剩余.*?流量|可用流量|流量余额)[^0-9]{0,80}(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB|PB|KiB|MiB|GiB|TiB)',
r'(\d+(?:\.\d+)?)\s*(B|KB|MB|GB|TB|PB|KiB|MiB|GiB|TiB)[^\n]{0,80}(?:剩余流量|剩余.*?流量|可用流量|流量余额)',
]
for pattern in patterns:
match = re.search(pattern, text, re.IGNORECASE)
if match:
return match.group(1), match.group(2).upper()
# Some pages render counters from inline scripts or JSON-like snippets.
script_candidate = split_flow_text(decoded_content)
if script_candidate and re.search(r'剩余流量|unused|remain|traffic|transfer', decoded_content, re.IGNORECASE):
return script_candidate
return None
def extract_domains_from_content(content): def extract_domains_from_content(content):
""" """
从网页内容中提取可用域名 从网页内容中提取可用域名
@ -263,85 +338,80 @@ def find_working_domain():
print("❌ 所有域名均不可用") print("❌ 所有域名均不可用")
return None return None
def get_remaining_flow(cookies): def get_remaining_flow(session):
"""获取用户剩余流量信息""" """获取用户剩余流量信息"""
user_url = f'https://{ikun_host}/user' user_url = f'https://{ikun_host}/user'
try: try:
# 获取用户页面 user_page = session.get(user_url, headers=build_headers(), timeout=20)
user_page = requests.get(user_url, cookies=cookies, headers={"User-Agent": USER_AGENT}, timeout=20)
if user_page.status_code != 200: if user_page.status_code != 200:
return "获取流量失败", "状态码: " + str(user_page.status_code) return "获取流量失败", "状态码: " + str(user_page.status_code)
# 提取并解码Base64内容 decoded_content = decode_origin_body(user_page.text)
match = re.search(r'var originBody = "([^"]+)"', user_page.text) if re.search(r'/auth/login|id="email"|id="password"|登录\s*—\s*iKuuu', decoded_content, re.IGNORECASE):
if not match: return "Cookie可能已过期", "无法获取"
return "未找到Base64内容", ""
flow = extract_remaining_flow(decoded_content)
base64_content = match.group(1) if flow:
decoded_content = base64.b64decode(base64_content).decode('utf-8') return flow
# 使用BeautifulSoup解析解码后的HTML
soup = BeautifulSoup(decoded_content, '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
return "未找到", "流量信息" return "未找到", "流量信息"
except Exception as e: except Exception as e:
return "流量获取异常", str(e) return "流量获取异常", str(e)
def ikuuu_signin(email, password): def ikuuu_signin_with_cookie(account_name, cookie_string):
params = {'email': email, 'passwd': password, 'code': ''} session = requests.Session()
login_url = f'https://{ikun_host}/auth/login' load_cookie_string(session, cookie_string)
try: try:
# 登录请求添加User-Agent flow_value, flow_unit = get_remaining_flow(session)
login_res = requests.post(login_url, data=params, headers={"User-Agent": USER_AGENT}, timeout=20)
if login_res.status_code != 200: checkin_res = session.post(
flow_value, flow_unit = "登录失败", "无法获取" f'https://{ikun_host}/user/checkin',
return False, f"登录失败(状态码{login_res.status_code}", flow_value, flow_unit headers=build_headers(f'https://{ikun_host}/user'),
timeout=20
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: if checkin_res.status_code != 200:
return False, f"签到失败(状态码{checkin_res.status_code}", flow_value, flow_unit return False, f"签到失败(状态码{checkin_res.status_code}", flow_value, flow_unit
checkin_data = checkin_res.json() checkin_data = parse_json_response(checkin_res)
if checkin_data is None:
return False, "签到响应解析失败,请检查 Cookie 是否已过期", flow_value, flow_unit
flow_value, flow_unit = get_remaining_flow(session)
if checkin_data.get('ret') == 1: if checkin_data.get('ret') == 1:
return True, f"成功 | {checkin_data.get('msg', '')}", flow_value, flow_unit return True, f"成功 | {checkin_data.get('msg', '')}", flow_value, flow_unit
else: return False, f"签到失败:{checkin_data.get('msg', '未知错误')}", flow_value, flow_unit
return False, f"签到失败:{checkin_data.get('msg', '未知错误')}", flow_value, flow_unit
except json.JSONDecodeError:
return False, "响应解析失败", "未知", "未知"
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
return False, "请求超时", "未知", "未知" return False, "请求超时", "未知", "未知"
except Exception as e: except Exception as e:
return False, f"请求异常:{str(e)}", "未知", "未知" return False, f"请求异常:{str(e)}", "未知", "未知"
def parse_cookie_accounts(cookie_str):
accounts = []
for index, line in enumerate(cookie_str.strip().splitlines(), 1):
line = line.strip()
if not line:
continue
name = f"Cookie账号{index}"
cookie = line
if '|' in line:
name, cookie = line.split('|', 1)
name = name.strip() or name
cookie = cookie.strip()
if '=' not in cookie:
print(f"⚠️ 忽略无效 Cookie 行: {line}")
continue
accounts.append({
'type': 'cookie',
'name': name,
'cookie': cookie,
})
return accounts
def send_qinglong_notification(results, current_domain): def send_qinglong_notification(results, current_domain):
""" """
使用青龙面板内置通知系统发送通知 使用青龙面板内置通知系统发送通知
@ -399,19 +469,13 @@ if __name__ == "__main__":
print("=" * 50) print("=" * 50)
# ==================== 账户处理 ==================== # ==================== 账户处理 ====================
accounts = [] cookie_str = os.getenv('IKUUU_COOKIES')
account_str = os.getenv('IKUUU_ACCOUNTS') if not cookie_str:
if not account_str: print("❌ 未找到环境变量 IKUUU_COOKIES")
print("❌ 未找到环境变量 IKUUU_ACCOUNTS")
exit(1) exit(1)
for line in account_str.strip().splitlines(): accounts = parse_cookie_accounts(cookie_str)
if ':' in line:
email, pwd = line.split(':', 1)
accounts.append((email.strip(), pwd.strip()))
else:
print(f"⚠️ 忽略无效账户行: {line}")
if not accounts: if not accounts:
print("❌ 未找到有效账户") print("❌ 未找到有效账户")
exit(1) exit(1)
@ -420,11 +484,12 @@ if __name__ == "__main__":
# ==================== 执行签到 ==================== # ==================== 执行签到 ====================
results = [] results = []
for index, (email, pwd) in enumerate(accounts, 1): for index, account in enumerate(accounts, 1):
print(f"\n👤 [{index}/{len(accounts)}] 处理账户: {email}") print(f"\n👤 [{index}/{len(accounts)}] 处理账户: {account['name']}")
success, msg, flow_value, flow_unit = ikuuu_signin(email, pwd) success, msg, flow_value, flow_unit = ikuuu_signin_with_cookie(account['name'], account['cookie'])
results.append({ results.append({
'email': email, 'email': account['name'],
'success': success, 'success': success,
'message': msg, 'message': msg,
'flow_value': flow_value, 'flow_value': flow_value,
@ -459,4 +524,4 @@ if __name__ == "__main__":
print(f" 流量: {res['flow_value']} {res['flow_unit']}") print(f" 流量: {res['flow_value']} {res['flow_unit']}")
print("=" * 50) print("=" * 50)
print("🏁 脚本执行完成") print("🏁 脚本执行完成")