From cc6429b64b4f6ffd02db60c4c0b2764310541313 Mon Sep 17 00:00:00 2001 From: UPToZ Date: Thu, 4 Jun 2026 17:02:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9ikuuu=E7=AD=BE=E5=88=B0?= =?UTF-8?q?=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- iKuuu/README.md | 49 ++++++---- iKuuu/ik_signin.py | 221 +++++++++++++++++++++++++++++---------------- 2 files changed, 173 insertions(+), 97 deletions(-) diff --git a/iKuuu/README.md b/iKuuu/README.md index 0b5e275..cfa18c2 100644 --- a/iKuuu/README.md +++ b/iKuuu/README.md @@ -15,33 +15,44 @@ ## 安装步骤 1. 将脚本保存为`ik_signin.py`到青龙面板的`scripts`目录 -2. 添加环境变量`IKUUU_ACCOUNTS`,格式为: -``` -邮箱1:密码1 -邮箱2:密码2 -``` -3. 在青龙面板添加定时任务: -``` -task ik_signin 0 0 1 * * ? +2. 添加环境变量`IKUUU_COOKIES`,格式为: +``` +备注1|cookie1 +备注2|cookie2 +``` +3. 在青龙面板添加定时任务: +``` +task ik_signin 0 0 1 * * ? ``` ## 环境变量配置 -| 变量名 | 说明 | 示例 | -| ---- | ---- | ---- | -| IKUUU_ACCOUNTS | iKuuu账号密码,每行一个账号,格式为`邮箱:密码` | `test@example.com:password123` | +| 变量名 | 说明 | 示例 | +| ---- | ---- | ---- | +| IKUUU_COOKIES | iKuuu登录Cookie,每行一个账号,格式为`备注|cookie`,备注可省略 | `主账号|PHPSESSID=xxx; uid=xxx` | ## 使用说明 1. 脚本会自动检测当前域名是否可用 2. 若检测到域名变更,会自动更新脚本中的域名信息 -3. 依次对配置的所有账号进行签到操作 -4. 签到结果会通过青龙面板的通知系统发送 - -## 更新日志 - -### 2025-07-29 -- 修复「剩余流量」查询 +3. 依次对配置的所有账号进行签到操作 +4. 签到结果会通过青龙面板的通知系统发送 + +### Cookie获取说明 + +1. 浏览器登录iKuuu后打开开发者工具 +2. 在Network中访问`/user`或`/user/checkin` +3. 复制请求头里的`Cookie`完整内容 +4. 填入青龙环境变量`IKUUU_COOKIES` + +## 更新日志 + +### 2026-06-04 +- 移除账号密码登录,统一使用`IKUUU_COOKIES`执行签到 +- 优化剩余流量查询,支持从页面卡片、文本和脚本片段中提取流量信息 + +### 2025-07-29 +- 修复「剩余流量」查询 ### 2025-07-25 - 智能域名发现机制。使用多种正则表达式模式从网页内容中提取域名,当域名不可用时,会从旧域名页面自动抓取新的可用域名,优先使用ikuuu相关域名,其次使用其他发现的域名 @@ -73,4 +84,4 @@ task ik_signin 0 0 1 * * ? - 请确保青龙面板已配置通知功能,否则无法接收签到结果通知 - 脚本运行需要网络连接畅通,否则可能导致签到失败 -- 若官方域名发生变更,脚本会自动更新,但可能需要手动触发一次以完成更新 \ No newline at end of file +- 若官方域名发生变更,脚本会自动更新,但可能需要手动触发一次以完成更新 diff --git a/iKuuu/ik_signin.py b/iKuuu/ik_signin.py index e3addd6..aa64f91 100644 --- a/iKuuu/ik_signin.py +++ b/iKuuu/ik_signin.py @@ -7,15 +7,18 @@ cron: 0 0 8 * * ? import requests import re -import json import os import datetime -import urllib.parse import sys import time import base64 from bs4 import BeautifulSoup +try: + sys.stdout.reconfigure(encoding="utf-8") +except Exception: + pass + # 添加青龙脚本根目录到Python路径 QL_SCRIPTS_DIR = '/ql/scripts' # 青龙脚本默认目录 sys.path.append(QL_SCRIPTS_DIR) @@ -40,12 +43,84 @@ except ImportError: send = lambda title, content: None # 创建空函数防止报错 # 初始域名 -ikun_host = "ikuuu.ch" # 自动更新于2025-04-29 13:08:20 -backup_hosts = ["ikuuu.one", "ikuuu.pw", "ikuuu.me"] # 备用域名列表 +ikun_host = "ikuuu.win" # 自动更新于2026-01-17 08:00:07 +backup_hosts = ["ikuuu.win","ikuuu.fyi","ikuuu.one", "ikuuu.pw", "ikuuu.me","ikuuu.cc"] # 备用域名列表 # 统一的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 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): """ 从网页内容中提取可用域名 @@ -263,85 +338,80 @@ def find_working_domain(): print("❌ 所有域名均不可用") return None -def get_remaining_flow(cookies): +def get_remaining_flow(session): """获取用户剩余流量信息""" user_url = f'https://{ikun_host}/user' try: - # 获取用户页面 - user_page = requests.get(user_url, cookies=cookies, headers={"User-Agent": USER_AGENT}, timeout=20) + user_page = session.get(user_url, headers=build_headers(), timeout=20) if user_page.status_code != 200: return "获取流量失败", "状态码: " + str(user_page.status_code) - # 提取并解码Base64内容 - match = re.search(r'var originBody = "([^"]+)"', user_page.text) - if not match: - return "未找到Base64内容", "" - - base64_content = match.group(1) - decoded_content = base64.b64decode(base64_content).decode('utf-8') - - # 使用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 + decoded_content = decode_origin_body(user_page.text) + if re.search(r'/auth/login|id="email"|id="password"|登录\s*—\s*iKuuu', decoded_content, re.IGNORECASE): + return "Cookie可能已过期", "无法获取" + + flow = extract_remaining_flow(decoded_content) + if flow: + return flow 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' +def ikuuu_signin_with_cookie(account_name, cookie_string): + session = requests.Session() + load_cookie_string(session, cookie_string) 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) + flow_value, flow_unit = get_remaining_flow(session) + + checkin_res = session.post( + f'https://{ikun_host}/user/checkin', + headers=build_headers(f'https://{ikun_host}/user'), + timeout=20 + ) if checkin_res.status_code != 200: 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: 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, "响应解析失败", "未知", "未知" + return False, f"签到失败:{checkin_data.get('msg', '未知错误')}", flow_value, flow_unit except requests.exceptions.Timeout: return False, "请求超时", "未知", "未知" except Exception as 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): """ 使用青龙面板内置通知系统发送通知 @@ -399,19 +469,13 @@ if __name__ == "__main__": print("=" * 50) # ==================== 账户处理 ==================== - accounts = [] - account_str = os.getenv('IKUUU_ACCOUNTS') - if not account_str: - print("❌ 未找到环境变量 IKUUU_ACCOUNTS") + cookie_str = os.getenv('IKUUU_COOKIES') + if not cookie_str: + print("❌ 未找到环境变量 IKUUU_COOKIES") 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}") - + + accounts = parse_cookie_accounts(cookie_str) + if not accounts: print("❌ 未找到有效账户") exit(1) @@ -420,11 +484,12 @@ if __name__ == "__main__": # ==================== 执行签到 ==================== 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) + for index, account in enumerate(accounts, 1): + print(f"\n👤 [{index}/{len(accounts)}] 处理账户: {account['name']}") + success, msg, flow_value, flow_unit = ikuuu_signin_with_cookie(account['name'], account['cookie']) + results.append({ - 'email': email, + 'email': account['name'], 'success': success, 'message': msg, 'flow_value': flow_value, @@ -459,4 +524,4 @@ if __name__ == "__main__": print(f" 流量: {res['flow_value']} {res['flow_unit']}") print("=" * 50) - print("🏁 脚本执行完成") \ No newline at end of file + print("🏁 脚本执行完成")