修改ikuuu签到脚本
This commit is contained in:
parent
53332285f3
commit
cc6429b64b
@ -15,10 +15,10 @@
|
|||||||
## 安装步骤
|
## 安装步骤
|
||||||
|
|
||||||
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. 在青龙面板添加定时任务:
|
||||||
```
|
```
|
||||||
@ -29,7 +29,7 @@ task ik_signin 0 0 1 * * ?
|
|||||||
|
|
||||||
| 变量名 | 说明 | 示例 |
|
| 变量名 | 说明 | 示例 |
|
||||||
| ---- | ---- | ---- |
|
| ---- | ---- | ---- |
|
||||||
| IKUUU_ACCOUNTS | iKuuu账号密码,每行一个账号,格式为`邮箱:密码` | `test@example.com:password123` |
|
| IKUUU_COOKIES | iKuuu登录Cookie,每行一个账号,格式为`备注|cookie`,备注可省略 | `主账号|PHPSESSID=xxx; uid=xxx` |
|
||||||
|
|
||||||
## 使用说明
|
## 使用说明
|
||||||
|
|
||||||
@ -38,8 +38,19 @@ task ik_signin 0 0 1 * * ?
|
|||||||
3. 依次对配置的所有账号进行签到操作
|
3. 依次对配置的所有账号进行签到操作
|
||||||
4. 签到结果会通过青龙面板的通知系统发送
|
4. 签到结果会通过青龙面板的通知系统发送
|
||||||
|
|
||||||
|
### Cookie获取说明
|
||||||
|
|
||||||
|
1. 浏览器登录iKuuu后打开开发者工具
|
||||||
|
2. 在Network中访问`/user`或`/user/checkin`
|
||||||
|
3. 复制请求头里的`Cookie`完整内容
|
||||||
|
4. 填入青龙环境变量`IKUUU_COOKIES`
|
||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
|
||||||
|
### 2026-06-04
|
||||||
|
- 移除账号密码登录,统一使用`IKUUU_COOKIES`执行签到
|
||||||
|
- 优化剩余流量查询,支持从页面卡片、文本和脚本片段中提取流量信息
|
||||||
|
|
||||||
### 2025-07-29
|
### 2025-07-29
|
||||||
- 修复「剩余流量」查询
|
- 修复「剩余流量」查询
|
||||||
|
|
||||||
|
|||||||
@ -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内容", ""
|
|
||||||
|
|
||||||
base64_content = match.group(1)
|
flow = extract_remaining_flow(decoded_content)
|
||||||
decoded_content = base64.b64decode(base64_content).decode('utf-8')
|
if flow:
|
||||||
|
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:
|
|
||||||
flow_value, flow_unit = "登录失败", "无法获取"
|
|
||||||
return False, f"登录失败(状态码{login_res.status_code})", flow_value, flow_unit
|
|
||||||
|
|
||||||
login_data = login_res.json()
|
checkin_res = session.post(
|
||||||
if login_data.get('ret') != 1:
|
f'https://{ikun_host}/user/checkin',
|
||||||
flow_value, flow_unit = "登录失败", "无法获取"
|
headers=build_headers(f'https://{ikun_host}/user'),
|
||||||
return False, f"登录失败:{login_data.get('msg', '未知错误')}", flow_value, flow_unit
|
timeout=20
|
||||||
|
)
|
||||||
# 获取用户剩余流量
|
|
||||||
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,18 +469,12 @@ 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("❌ 未找到有效账户")
|
||||||
@ -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,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user