Files
hlt1995-qlScripts/mcloud.py
T
2026-05-30 19:11:46 +08:00

2084 lines
92 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
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.
# cron: 5 12 * * *
# const $ = new Env('移动云盘')
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
移动云盘自动签到 v5.0.5
包含以下功能:
1. 每日自动签到 (签到/抽奖/摇一摇/新版云朵领取)
2. 多账号并发支持 (环境变量 & 分隔)
3. 云朵中心新版任务自动处理 (上传/分享/AI相机/月任务补传等)
4. 临时文件智能清理与详细日志推送
更新说明:
### 20260503
v5.0.5:
- 修复分享文件任务流程。
- 新增去体验AI相机任务。
- 新增五一福利任务。
### 20260425
v5.0.4:
- 整合 Authorization 自动刷新、本地 token 缓存与过期前续期。
- 新增账号级 deviceId/token 缓存,支持手动补填 deviceId 并复用。
- 优化 JWT 失败重试、设备信息与启动提示。
### 20260420
v5.0.3:
- 适配新版签到链路,修复旧接口报错。
- 接入云朵中心新版任务。
- 新增 AI 相机、上传清理、分享文件每日自动化与月上传补传能力。
- 优化启动与任务日志输出。
配置说明:
变量名: ydyp
赋值方式:
格式:Authorization值#手机号
⚠️ 依赖安装:
pip3 install requests pycryptodome
定时规则建议 (Cron):
0 0 8,16,20 * * * (每日早中晚执行)
Author: YaoHuo8648
Email: zheyizzf@188.com
Update: 2026.05.03
"""
import base64
import hashlib
import json
import os
import random
import re
import time
import uuid
from datetime import datetime, timezone, timedelta
from os import path
from pathlib import Path
from urllib.parse import unquote
import requests
try:
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
except ImportError:
AES = None
pad = None
SCRIPT_VERSION = '5.0.5'
TOKEN_STORAGE_FILENAME = ''
DEVICE_ID_STORAGE_FILENAME = ''
# ⭐ 统一设备信息:iPhone 16 Pro + iOS 18_7 + 版本 12.5.4(抓包真实值)
ua = ''
market_ua = ''
cloud_file_dummy_content = b'0'
cloud_file_dummy_hash = hashlib.sha256(cloud_file_dummy_content).hexdigest()
TOKEN_VALID_TIME = 21600000
TOKEN_REFRESH_ADVANCE = 24 * 60 * 60 * 1000
TOKEN_REFRESH_COOLDOWN = 864000
REFRESH_TOKEN_AES_KEY = 'c7lXOigXahPnTViq'
AI_TOOL_ACCOUNT_AES_KEY = 'xuL97!x7GGxG%8V4'
AI_TOOL_ACCOUNT_AES_IV = '5OuCxk4XNu0NA*%x'
TOKEN_EXPIRE_SECONDS_FALLBACK = 2592000
err_accounts = '' # 异常账号
all_logs = '' # 所有用户的详细运行日志 (原 err_message)
user_amount = '' # 用户云朵·数量
GLOBAL_DEBUG = False
def current_millis():
return int(time.time() * 1000)
def normalize_authorization(token):
token = (token or '').strip()
if token and not token.startswith('Basic '):
return f'Basic {token}'
return token
def random_string(length=16):
chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
return ''.join(random.choice(chars) for _ in range(length))
def generate_uuid():
return ''.join([
random_string(8), '-',
random_string(4), '-4',
random_string(3), '-',
random.choice('89ab'), random_string(3), '-',
random_string(12)
])
# 真实抓包 deviceId(base64 格式,iOS iPhone 16 Pro + iOS 18_7)
REAL_DEVICE_ID = ''
DEVICE_PROFILE_URL = "https://slw.h5cmpassport.com:9090/deviceprofile/v4"
DEVICE_PROFILE_PAYLOAD = {
"appId": "default",
"organization": "FXlyfmWg2AzwbrxDKSv5",
"ep": "WydTnuOv+Rtg/Qj8Q4vnhSXJN4UHQPF2jjs+LVJkD3u8HXglndPAndOgrlmg2Q8q0FUQRZpN0N7e61ebhjw/Gba22ydgOMbBRfbSmKSnNWaACA+MzAX5Q4dd980zPqelMxGVzB3jkr1wGE6cVQkwWFq/xbdnkK/Sh6xrPDjYvho=",
"data": "5b5f2054405d6155102ed35a134758f768e60b7c1ac7af66acb16871d78a099cbcbabb3fb5ebeefe6cbae063407fca585a343ce5bef4f4e4588df42ca8ae8a6504b3646066fd7dc46465a83d510fbb477ba72f7375db7cbcba9b712ec88d85fc8d410536b96ca644c8ca3afbca00e0084ad9709b93b86923bf1fadef48be3e888b52dd2775c180b5b8e7bae139fadf2944f73010be9704daa6f4cf596b4adccff7b5de84e45698b781d963b69fa8ec28e43083512ab5749ea05c4efce14945d647c9f33d6296750ff2ba59bff5b7fcf698ffa146b7fe7e5c405b13100818b53fd034d05edea63c8365d9113bc7d4c0652892fcae75cdb491ae0215fbd822b1877b209fc8c68710badc6915080b7b994fa4b86a8f7b37e929cecbd1c590ad7382beb3ae8b9cc56ed84e927cbe41d8b4b15bbeecc69f5463d402cc2732fe5b76ec201632afbc16228531a65c1810482e4eb48157bc8b23cd363c6809a3fd629e3520514c06a720616e1788fe10203f9ebfa1de24c66213e334e3a3b3ff8a8866b7aefd9b4f2c88d216f45b551d693433940569092f0c7aca25019dc2003e8eab1967ac1dc32b0912701b0abc17e0509bada0cf0fcbe3c5fb64f0d5c6f02303b1540829a301673da89f7460d00190bda07c9b82c263277066f8e7e91c4916f247f9d9fe295a46d16cd087cee865d9e50edeb8e88842c560b09f853b5f89d2d0c4ed160f5bc293f7c69ece9e2d64d7217857fd2d64d57bea1ccea1b52896bb9aaf2ec3baa2421bce8d011813a1b26f0acb3a3cf594298bd725f8da17717b965f85e46a52c758ed1e95218e06f7e96a9f13e4855a0bb4bcf8b5f571887ec58c7438e99f06562414bcb274038fe6ffc1b8991021e35866cef5010184e3fbbd49c19d6020315731e9e57b7cd6a1e8b33c97746a782f9b4a26696966f40324f1ff76d3d1d24bf544230438dc32ab26d6dc107adf9feac34ffbbaa8814cec674e9469de54a714273a47f4fd06561e611f6741a4f0362a3b8821b0c69a3a04ced876fbf1b5fdc58097b1d7087aa2c0df556f8a06288db8c306cda4525d91c0452a0d2747982bd70b31c6905d4e483e8519d4d605af776be2a81224e3a6cc0b6ec49ad2cdb434bd85b5079ff86f68bf5ebb41336f30ec84fe19fabbd10a4422a274a3749d70c6b39cf7cdc1eb0cb228abee2475d16c57635a332628727b76a1fac0b26bf7bbdf4c5b956261919e7d2bd67733656855503670d48fe3680d04b65aac48d99bd47aedb6091c0a6df53be5bd662c1130feb6b469578cb146e1ae004471641fbc028cc06b80cfcdc50f8231e58b4126ab750b1d02eb8ac417b53a5ae50846db9aeadf4f1c98e33228db5143cb3d928217b769eaf32d181320a0bee4805334c28a03995d925b52fda358d19c52e3838c243b8c7d3256337943705c1311526c290fad975b7d7ade4bbc9292dbd7b9c0314715ef3c785a720e674dc23538af333cc6ff541aea70086287a8b4407c66ce673c9a47268de014c876a3a6a577d501285f6f489e2519f51bf4feafe307333a9e077f613527bbe1ce632127df654588410f713bb4a61e050cae618e98cc9adbb77d9df95733449c06e62094f3cdaf2ba39f94223ed7ca63ea4dec37d7283bdd0d2015511e7e57212073a540b308b10d7f85de73865fc2ffbf05a85ae25a7b52f0292236ee75f738add8144c7b2767a2100451363a47c12dfb674bd3ee000fa41565e9fbc60440a629160a2d2a99ec23dccc6815f644a2dd1eb059ab8593d9b04b1b81f5e427570cfc06eba8456b68159e6886843bcf4374b02de2e5be8d900882f78a71c2f3819d2e9c45e64b5d006c7a5914d1482f01ed5c0cfb44c3543656e96b5d91b39cd667af4dc60f44752da28eda57d2453d26a099529a2a38c9b9b2f0a73a69445030321b0a87287f6469f4d585739cded2e79c66df9c949eb7b2b8a8ff78e80a88ca494f3410195e021ec5009f8cd29781f09d58e6f866102072f1cee202c6ce21d72795b47a0ab8464fa54836c36a28ff73828e7a39dd1203d5a051ac4cd22b4f8c9f1e4e9c42f0c85b101b1eb495c0a767697dccab920489fae867ff38c5f917aec269d0ac9a1d6005407db762349d77e990581e19b1912fc975a9cdd2",
"os": "web",
"encode": 5,
"compress": 2,
}
def fetch_device_id():
"""从设备指纹接口获取 deviceId,加 B 前缀"""
try:
resp = requests.post(
DEVICE_PROFILE_URL,
json=DEVICE_PROFILE_PAYLOAD,
headers={
"Content-Type": "application/json;charset=utf-8",
"Origin": "https://m.mcloud.139.com",
"Referer": "https://m.mcloud.139.com/",
},
timeout=15,
)
data = resp.json()
raw_id = data.get("detail", {}).get("deviceId", "")
if raw_id:
return "B" + raw_id
except Exception as e:
print(f" 获取设备指纹失败: {e}")
return ""
def generate_device_id():
if REAL_DEVICE_ID:
return REAL_DEVICE_ID
device_info = {
'deviceId': uuid.uuid4().hex.upper(),
'brand': 'Apple',
'model': 'iPhone 16 Pro',
'system': 'iOS 18.7',
'timestamp': current_millis()
}
raw = json.dumps(device_info, ensure_ascii=False, separators=(',', ':'))
return base64.b64encode(raw.encode('utf-8')).decode('utf-8')
def get_env_device_id():
return ''
def build_x_device_info(device_id, net_type='wifi', terminal_type='8',
version='12.5.4', brand='Apple', model='iPhone 16 Pro',
system='iOS 18.7'):
"""构造 x-DeviceInfo 头,与 UA 保持一致"""
return f"{net_type}||{terminal_type}|{version}|{brand}|{model}|{device_id}||{system.lower()}|||||"
def aes_encrypt(data, key):
if AES is None or pad is None:
raise ImportError('未安装 pycryptodome,无法执行 Token 自动刷新')
key_bytes = key.encode('utf-8')
if not isinstance(data, str):
data = json.dumps(data, ensure_ascii = False, separators = (',', ':'))
cipher = AES.new(key_bytes, AES.MODE_ECB)
encrypted = cipher.encrypt(pad(data.encode('utf-8'), AES.block_size))
return base64.b64encode(encrypted).decode('utf-8')
def extract_raw_token(token, account):
token = normalize_authorization(token)
if token.startswith('Basic '):
token = token[6:]
try:
decoded = base64.b64decode(token).decode('utf-8')
parts = decoded.split(':')
if len(parts) >= 3:
return parts[2]
except Exception:
pass
return token
def build_authorization(account, raw_token):
return f"Basic {base64.b64encode(f'mobile:{account}:{raw_token}'.encode()).decode()}"
def parse_expire_time_to_millis(expire_time):
try:
expire_seconds = int(float(expire_time))
except (TypeError, ValueError):
expire_seconds = TOKEN_EXPIRE_SECONDS_FALLBACK
if expire_seconds <= 0:
expire_seconds = TOKEN_EXPIRE_SECONDS_FALLBACK
return current_millis() + expire_seconds * 1000
def get_storage_base_dir():
return Path(path.abspath(path.dirname(__file__)))
def get_storage_file_path(filename):
return get_storage_base_dir() / filename
def get_token_storage_path():
return get_storage_file_path(TOKEN_STORAGE_FILENAME)
def load_token_storage():
return {'accounts': {}}
def save_token_storage(data):
pass
# ============ 简易存储兼容层(不再写入 deviceId 缓存文件)============
def get_device_id_storage_path():
return get_storage_file_path(DEVICE_ID_STORAGE_FILENAME)
def load_device_id_storage():
storage_path = get_device_id_storage_path()
if not storage_path.exists():
return {}
try:
with open(storage_path, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception:
return {}
def save_device_id(device_id, account):
pass
def save_token_info(account, token, expires_at, last_refresh_at):
pass
def ensure_account_storage_entry(account, token=''):
pass
def get_token_info(account):
return {}
def get_device_id(account):
return ''
def print_startup_info(account_count):
print(f"移动云盘自动签到 v{SCRIPT_VERSION}")
print(f"移动云盘共获取到{account_count}个账号")
def print_device_id_notice():
print("变量格式: Authorization值#手机号")
def print_storage_path_notice():
pass
# 发送通知
def load_send():
cur_path = path.abspath(path.dirname(__file__))
notify_file = cur_path + "/notify.py"
if path.exists(notify_file):
try:
import sys
if cur_path not in sys.path:
sys.path.insert(0, cur_path)
from notify import send
print("加载通知服务成功!")
return send
except Exception as e:
print(f"加载通知服务失败: {e}")
else:
print("加载通知服务失败: notify.py不存在")
return False
class YP:
def __init__(self, cookie):
try:
self.notebook_id = None
self.note_token = None
self.note_auth = None
self.auth_token = None
self.click_num = 15
self.draw = 1
self.client_version = '12.5.4'
self.market_base_url = 'https://m.mcloud.139.com'
self.market_source_id = '1097'
self.sso_token = None
self.user_domain_id = ''
self.market_device_id = ''
self.market_x_device_info = ''
self.market_device_from_env = False
self.market_device_is_placeholder = False
self.market_headers = {}
self.market_cookies = {}
self.session = requests.Session()
self.user_log_lines = []
self.timestamp = str(int(round(time.time() * 1000)))
self.cookies = {'sensors_stay_time': self.timestamp}
parts = cookie.split("#")
if len(parts) < 2:
raise ValueError(f"⚠️ 变量值格式错误,需要: Authorization值#手机号")
self.Authorization = normalize_authorization(parts[0])
self.account = parts[1].strip()
self.init_device_id = parts[2].strip() if len(parts) >= 3 else ''
self.token_storage = load_token_storage()
self.refresh_capable = AES is not None and pad is not None
self.load_persisted_authorization()
self.load_or_create_market_device_profile()
self.auth_token = extract_raw_token(self.Authorization, self.account)
if len(self.account) >= 11:
self.encrypt_account = self.account[:3] + "****" + self.account[-4:]
else:
self.encrypt_account = self.account
self.fruit_url = 'https://happy.mail.10086.cn/jsp/cn/garden/'
self.jwtHeaders = {
'User-Agent': ua,
'Accept': '*/*',
'Host': 'caiyun.feixin.10086.cn:7071',
}
self.treeHeaders = {
'Host': 'happy.mail.10086.cn',
'Accept': 'application/json, text/plain, */*',
'User-Agent': ua,
'Referer': 'https://happy.mail.10086.cn/jsp/cn/garden/wap/index.html?sourceid=1003',
'Cookie': '',
}
except Exception as e:
print(f"{e}")
self.Authorization = None
def get_storage_record(self):
"""获取存储记录,优先从新版简易存储读取"""
# 优先从新版简易存储读取
new_storage = get_token_info(self.account)
if new_storage.get('token') or new_storage.get('deviceId'):
return new_storage
# 后备:从旧版 token_storage 读取
return (self.token_storage.get('accounts') or {}).get(self.account, {}) or {}
def load_persisted_authorization(self):
"""加载已保存的 Authorization,优先从简易存储读取"""
# 优先从新版简易存储读取
new_storage = get_token_info(self.account)
stored_token = normalize_authorization(new_storage.get('token', ''))
if stored_token:
self.Authorization = stored_token
# 同时也写入旧版存储,保持兼容
token_storage = load_token_storage()
if self.account not in token_storage.get('accounts', {}):
token_storage['accounts'][self.account] = {}
token_storage['accounts'][self.account]['token'] = stored_token
save_token_storage(token_storage)
return
# 后备:从旧版 token_storage 读取
stored = self.get_storage_record()
stored_token = normalize_authorization(stored.get('token', ''))
if stored_token:
self.Authorization = stored_token
def load_or_create_market_device_profile(self):
"""加载 deviceId:强制从设备指纹接口获取,忽略环境变量和缓存"""
ensure_account_storage_entry(self.account, self.Authorization)
fetched_id = fetch_device_id()
if fetched_id:
self.market_device_id = fetched_id
self.market_x_device_info = build_x_device_info(fetched_id)
self.market_device_from_env = False
self.market_device_is_placeholder = False
save_device_id(fetched_id, self.account)
return
self.market_device_id = generate_device_id()
self.market_x_device_info = build_x_device_info(self.market_device_id)
self.market_device_is_placeholder = False
if self.market_device_id:
save_device_id(self.market_device_id, self.account)
def save_authorization_record(self, token=None, user_domain_id=None, refreshed=False, expires_at=None,
market_device_id=None, x_device_info=None, market_device_generated=None):
token = normalize_authorization(token or self.Authorization)
accounts = self.token_storage.setdefault('accounts', {})
old_record = accounts.get(self.account, {}) or {}
if not token:
token = normalize_authorization(old_record.get('token', ''))
last_refresh_at = old_record.get('lastRefreshAt', 0)
if refreshed:
last_refresh_at = current_millis()
record_expires_at = expires_at or old_record.get('expiresAt') or (current_millis() + TOKEN_VALID_TIME)
record = {
**old_record,
'account': self.account,
'token': token,
'authToken': extract_raw_token(token, self.account),
'expiresAt': int(record_expires_at),
'updatedAt': current_millis(),
'lastRefreshAt': last_refresh_at,
}
if user_domain_id or old_record.get('userDomainId'):
record['userDomainId'] = user_domain_id or old_record.get('userDomainId')
final_market_device_id = market_device_id or old_record.get('marketDeviceId')
final_x_device_info = x_device_info or old_record.get('xDeviceInfo')
if final_market_device_id:
record['marketDeviceId'] = final_market_device_id
if final_x_device_info:
record['xDeviceInfo'] = final_x_device_info
if market_device_generated is None:
if 'marketDeviceGenerated' in old_record:
record['marketDeviceGenerated'] = old_record.get('marketDeviceGenerated')
else:
record['marketDeviceGenerated'] = bool(market_device_generated)
accounts[self.account] = record
save_token_storage(self.token_storage)
def should_refresh_authorization(self, force=False):
if force:
return True, '强制刷新'
stored = self.get_storage_record()
now = current_millis()
expires_at = int(stored.get('expiresAt') or 0)
if not stored.get('token'):
return True, '本地无缓存Token'
if now >= expires_at:
return True, '缓存Token已过期'
if expires_at - now <= TOKEN_REFRESH_ADVANCE:
hours = (expires_at - now) / 3600000
return True, f'将在 {hours:.1f} 小时后过期'
days = (expires_at - now) / 86400000
return False, f'Token 状态良好(剩余 {days:.1f} 天)'
def refresh_authorization_token(self, force=False):
need_refresh, reason = self.should_refresh_authorization(force = force)
if not need_refresh:
self.log(f'-Authorization状态: {reason}')
return True
if not self.refresh_capable:
self.log('-未安装 pycryptodome,跳过Authorization自动刷新')
return True
# 使用微信小程序UA刷新Token(源码:miniprogram|||1.0)
refresh_headers = {
'Content-Type': 'application/json',
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.47(0x18002f2c) NetType/WIFI Language/zh_CN miniProgram/wx4e4ed37286c816c2',
'x-yun-tid': generate_uuid(),
'Authorization': self.Authorization,
'x-yun-api-version': 'v1',
'x-yun-module-type': '100',
'x-yun-op-type': '1',
'x-yun-app-channel': '10214200',
'x-yun-client-info': '||8||||||||||||',
'hcy-cool-flag': '1',
}
stored = self.get_storage_record()
if stored.get('userDomainId'):
refresh_headers['x-yun-uni'] = stored['userDomainId']
encrypted_data = aes_encrypt({'phoneNumber': self.account}, REFRESH_TOKEN_AES_KEY)
refresh_data = self.request_json('https://user-njs.yun.139.com/user/auth/refreshToken',
headers = refresh_headers,
data = {'data': encrypted_data},
method = 'POST',
retries = 1)
if not refresh_data:
self.log(f'-Authorization刷新失败: 接口无响应 ({reason})')
return True
code = str(refresh_data.get('code', ''))
data = refresh_data.get('data')
success = refresh_data.get('success', False)
is_success = (code in ('0', '00', '000', '0000')) or bool(success) or (code.startswith('0') and len(code) <= 4)
if not is_success or not isinstance(data, dict):
self.log(f"-Authorization刷新失败: {refresh_data.get('message') or refresh_data.get('msg', '未知错误')} (code={code or 'unknown'})")
return True
raw_token = data.get('token')
if not raw_token:
self.log('-Authorization刷新失败: 响应缺少token')
return True
expires_at = parse_expire_time_to_millis(data.get('expireTime'))
self.Authorization = build_authorization(self.account, raw_token)
self.auth_token = raw_token
self.save_authorization_record(token = self.Authorization, refreshed = True, expires_at = expires_at)
self.log(f'-Authorization自动刷新成功: {reason}')
self.log(f"-Authorization有效期: {datetime.fromtimestamp(expires_at / 1000).strftime('%Y-%m-%d %H:%M:%S')}")
return True
def sync_token_storage(self):
"""同步存储 token 和 deviceId 到简易存储"""
# 保存 token 刷新信息到简易存储
stored = self.get_storage_record()
expires_at = stored.get('expiresAt', current_millis() + TOKEN_VALID_TIME)
last_refresh_at = stored.get('lastRefreshAt', 0)
save_token_info(self.account, self.Authorization, expires_at, last_refresh_at)
# 仅当账号已手动配置真实 deviceId 时才回写缓存;
# 运行时兜底 deviceId 不落盘,避免覆盖成自动值。
if self.market_device_id and not self.market_device_is_placeholder:
save_device_id(self.market_device_id, self.account)
# 同时保持旧版存储兼容
self.save_authorization_record(token = self.Authorization, user_domain_id = self.user_domain_id)
def log(self, content):
print(content)
self.user_log_lines.append(content)
@staticmethod
def catch_errors(func):
def wrapper(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except Exception as e:
err_str = f"错误: {str(e)}"
print(err_str)
self.user_log_lines.append(err_str)
return None
return wrapper
@catch_errors
def run(self):
self.refresh_authorization_token()
if self.jwt():
self.signin_status()
self.click()
self.get_tasklist(url = 'sign_in_3', app_type = 'cloud_app')
self.log(f'\n📰 公众号任务')
self.wxsign()
self.shake()
self.surplus_num()
self.log(f'\n🔥 热门任务')
self.backup_cloud()
# self.log(f'\n📧 139邮箱任务')
self.get_tasklist(url = 'newsign_139mail', app_type = 'email_app')
self.receive()
global all_logs
user_log_str = "\n".join(self.user_log_lines)
all_logs += f"用户【{self.encrypt_account}】日志:\n{user_log_str}\n\n"
else:
global err_accounts
# 失效账号
err_accounts += f'{self.encrypt_account}\n'
@catch_errors
def send_request(self, url, headers=None, cookies=None, data=None, params=None, method='GET', debug=None,
retries=5):
debug = debug if debug is not None else GLOBAL_DEBUG
request_headers = dict(headers or {})
request_cookies = dict(cookies or {})
request_args = {'json': data} if isinstance(data, dict) else {'data': data}
for attempt in range(retries):
try:
response = self.session.request(method, url, params = params, headers = request_headers or None,
cookies = request_cookies or None, **request_args)
response.raise_for_status()
if debug:
print(f'\n【{url}】响应数据:\n{response.text}')
return response
except (requests.RequestException, ConnectionError, TimeoutError) as e:
print(f"请求异常: {e}")
if attempt >= retries - 1:
print("达到最大重试次数。")
return None
time.sleep(1)
def request_json(self, url, headers=None, cookies=None, data=None, params=None, method='GET', debug=None,
retries=5):
response = self.send_request(url, headers = headers, cookies = cookies, data = data, params = params,
method = method, debug = debug, retries = retries)
if response is None:
return None
try:
return response.json()
except ValueError as e:
self.log(f'响应解析失败: {e}')
return None
@staticmethod
def get_today_sign_state(result):
today_sign_in = result.get('todaySignIn')
if isinstance(today_sign_in, bool):
return today_sign_in
for day in result.get('cal') or []:
if day.get('t'):
return bool(day.get('s'))
return None
@staticmethod
def extract_user_domain_id(jwt_token):
try:
payload = jwt_token.split('.')[1]
payload += '=' * (-len(payload) % 4)
data = json.loads(base64.urlsafe_b64decode(payload).decode())
sub = data.get('sub', '')
if isinstance(sub, str):
sub = json.loads(sub)
return sub.get('userDomainId', '')
except (IndexError, KeyError, TypeError, ValueError, json.JSONDecodeError):
return ''
def build_market_context(self, jwt_token):
self.user_domain_id = self.extract_user_domain_id(jwt_token)
self.market_headers = {
'User-Agent': market_ua,
'Accept': '*/*',
'jwtToken': jwt_token,
'X-Requested-With': 'com.chinamobile.mcloud',
'Referer': self.build_market_page_url(),
}
self.market_cookies = {'jwtToken': jwt_token}
if self.user_domain_id:
self.market_cookies['userDomainId'] = self.user_domain_id
self.seed_market_device_cookie()
self.sync_token_storage()
def build_market_page_url(self, source_id=None):
current_source_id = source_id or self.market_source_id
return f'{self.market_base_url}/portal/mobilecloud/index.html?path=newsignin&sourceid={current_source_id}&enableShare=1&token={self.sso_token or ""}&targetSourceId=001005'
def get_market_device_id(self):
if self.market_device_id:
return self.market_device_id
for cookie in self.session.cookies:
if cookie.name.startswith('.thumbcache_') and cookie.value:
return unquote(cookie.value)
return ''
def seed_market_device_cookie(self):
device_id = self.market_device_id
if not device_id:
return
cookie_value = device_id[1:] if self.market_device_from_env and device_id.startswith('B') else device_id
if any(cookie.name.startswith('.thumbcache_') and unquote(cookie.value) == cookie_value
for cookie in self.session.cookies):
return
self.session.cookies.set(f'.thumbcache_{self.account}', cookie_value, domain='m.mcloud.139.com', path='/')
def build_market_headers(self, extra_headers=None, referer=None):
headers = dict(self.market_headers)
headers['Referer'] = referer or headers.get('Referer') or self.build_market_page_url()
device_id = self.get_market_device_id()
if device_id:
headers['deviceId'] = device_id
if self.market_x_device_info:
headers['x-DeviceInfo'] = self.market_x_device_info
if extra_headers:
headers.update(extra_headers)
return headers
def build_receive_headers(self, source_id=None):
return self.build_market_headers({
'showLoading': 'true',
'appVersion': f'{self.client_version}.0',
'activityId': 'sign_in_3',
}, referer = self.build_market_page_url(source_id))
def request_market_json(self, url, params=None, data=None, method='GET', debug=None, retries=5, headers=None,
cookies=None):
request_cookies = dict(self.market_cookies)
if cookies:
request_cookies.update(cookies)
return self.request_json(url, headers = self.build_market_headers(headers), cookies = request_cookies,
data = data, params = params, method = method, debug = debug, retries = retries)
def post_signin_journaling(self, keyword, source_id=None):
current_source_id = source_id or self.market_source_id
payload = f'module=uservisit&optkeyword={keyword}&sourceid={current_source_id}&marketName=sign_in_3'
response = self.send_request(f'{self.market_base_url}/ycloud/visitlog/journaling',
headers = self.build_market_headers(
{'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'},
referer = self.build_market_page_url(current_source_id)
),
cookies = self.market_cookies, data = payload, method = 'POST', retries = 1)
return response is not None
def prepare_signin_center_session(self, for_receive=False, source_id=None):
current_source_id = source_id or self.market_source_id
page_url = self.build_market_page_url(current_source_id)
self.send_request(page_url, headers = self.build_market_headers(referer = page_url),
cookies = self.market_cookies, retries = 1)
for keyword in (
'newsignin_index_pv',
'newsignin_index_client',
'newsignin_index_app_client',
'newsignin_index_cookie_login',
'newsignin_index_cookie',
'newsignin_index_app_cookie_login',
):
self.post_signin_journaling(keyword, current_source_id)
if for_receive:
self.post_signin_journaling('newsignin_index_receive_type', current_source_id)
return True
def click_task(self, task_id, key='task'):
return self.request_market_json(f'{self.market_base_url}/market/signin/task/click?key={key}&id={task_id}')
def get_notice_status(self):
send_data = self.request_json('https://caiyun.feixin.10086.cn/market/msgPushOn/task/status',
headers = self.jwtHeaders) or {}
if send_data.get('code') != 0:
return {}
return send_data.get('result', {}) or {}
def format_notice_task_log(self, task_name, notice_status):
if not notice_status:
return f'-需手动完成: {task_name}'
push_on = int(notice_status.get('pushOn') or 0)
first_status = int(notice_status.get('firstTaskStatus') or 0)
second_status = int(notice_status.get('secondTaskStatus') or 0)
on_duration = int(notice_status.get('onDuaration') or 0)
total = int(notice_status.get('total') or 31)
if push_on != 1:
return f'-需手动完成: {task_name} (通知未开启)'
if second_status == 3:
return f'-已完成: {task_name}'
if first_status != 3:
return f'-待领取: {task_name} (首日奖励可领取)'
if second_status == 2:
return f'-待领取: {task_name} (已开启{on_duration}/{total}天)'
return f'-进行中: {task_name} (已开启{on_duration}/{total}天)'
def build_cloud_file_headers(self):
"""云盘文件操作请求头:沿用 v5.0.3 已验证可用的上传头,避免 00010014"""
return {
'x-yun-op-type': '1',
'x-yun-sub-op-type': '100',
'x-yun-api-version': 'v1',
'x-yun-client-info': '6|127.0.0.1|1|12.1.0|realme|RMX5060|BCFF2BBA6881DD8E4971803C63DDB5E4|02-00-00-00-00-00|android 15|1264X2592|zh||||032|0|',
'x-yun-app-channel': '10000023',
'Authorization': self.Authorization,
'Content-Type': 'application/json; charset=UTF-8',
'User-Agent': 'okhttp/4.12.0',
'Host': 'personal-kd-njs.yun.139.com',
'Connection': 'Keep-Alive'
}
def build_share_headers(self):
"""分享文件请求头:沿用 v5.0.3 已验证可用的分享头"""
return {
'Authorization': self.Authorization,
'x-yun-api-version': 'v1',
'x-yun-app-channel': '10000023',
'x-yun-client-info': f'||9|{self.client_version}|Chrome|143.0.7499.146|codextestshare||Windows 10||zh-CN|||Q2hyb21l||',
'x-yun-module-type': '100',
'x-yun-svc-type': '1',
'x-SvcType': '1',
'x-yun-channel-source': '10000023',
'x-huawei-channelSrc': '10000023',
'Content-Type': 'application/json;charset=UTF-8',
'CMS-DEVICE': 'default',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36',
'Referer': 'https://yun.139.com/shareweb/',
'Origin': 'https://yun.139.com',
}
def create_cloud_file(self, prefix, extension='txt'):
beijing_tz = timezone(timedelta(hours=8))
now = datetime.now(beijing_tz)
file_size = len(cloud_file_dummy_content)
file_name = f"{prefix}{now.strftime('%Y%m%d_%H%M%S')}.{extension.lstrip('.')}"
payload = {
"contentHash": cloud_file_dummy_hash,
"contentHashAlgorithm": "SHA256",
"contentType": "application/oct-stream",
"fileRenameMode": "force_rename",
"localCreatedAt": now.isoformat(timespec='milliseconds'),
"name": file_name,
"parallelUpload": True,
"parentFileId": "/",
"partInfos": [{
"end": file_size,
"partNumber": 1,
"partSize": file_size,
"start": 0
}],
"size": file_size,
"type": "file"
}
response = self.send_request('https://personal-kd-njs.yun.139.com/hcy/file/create',
headers = self.build_cloud_file_headers(), data = payload, method = 'POST')
if not response or response.status_code != 200:
return None
try:
res_json = response.json()
except ValueError:
return None
if not res_json.get("success"):
return None
data = res_json.get("data", {})
return {
"fileId": data.get("fileId"),
"fileName": data.get("fileName", file_name),
}
def list_cloud_root_files(self):
items = []
page_cursor = ''
while True:
response = self.request_json('https://personal-kd-njs.yun.139.com/hcy/file/list',
headers = self.build_cloud_file_headers(),
data = {
'imageThumbnailStyleList': ['Small', 'Large'],
'orderBy': 'updated_at',
'orderDirection': 'DESC',
'pageInfo': {'pageCursor': page_cursor, 'pageSize': 100},
'parentFileId': '/',
},
method = 'POST')
if not response:
return items
if not response.get('success'):
self.log(f"获取云盘文件列表失败: {response.get('message', '未知错误')}")
return items
data = response.get('data', {})
items.extend(data.get('items', []))
page_cursor = data.get('nextPageCursor') or ''
if not page_cursor:
return items
@staticmethod
def is_cleanup_upload_file(item):
if item.get('type') != 'file' or item.get('parentFileId') != '/':
return False
name = item.get('name', '')
if not (name.endswith('.txt') and (name.startswith('auto_upload_') or name.startswith('auto_share_'))):
if not (name.endswith('.jpg') and name.startswith('auto_mayday_')):
return False
size = item.get('size')
content_hash = item.get('contentHash')
return size in (0, 1, None) or content_hash == cloud_file_dummy_hash
def trash_cloud_files(self, file_ids):
if not file_ids:
return True
response = self.request_json('https://personal-kd-njs.yun.139.com/hcy/recyclebin/batchTrash',
headers = self.build_cloud_file_headers(),
data = {'fileIds': file_ids},
method = 'POST')
if not response:
self.log('清理上传文件失败: 接口无响应')
return False
if response.get('success'):
return True
self.log(f"清理上传文件失败: {response.get('message', '未知错误')}")
return False
def cleanup_uploaded_files(self, current_file=None):
file_ids = []
if current_file and current_file.get('fileId'):
file_ids.append(current_file['fileId'])
for item in self.list_cloud_root_files():
if self.is_cleanup_upload_file(item):
file_ids.append(item.get('fileId'))
seen = set()
unique_file_ids = []
for file_id in file_ids:
if not file_id or file_id in seen:
continue
seen.add(file_id)
unique_file_ids.append(file_id)
if not unique_file_ids:
return True
if self.trash_cloud_files(unique_file_ids):
self.log(f'-已清理上传文件: {len(unique_file_ids)}个')
return True
return False
def complete_share_file_task(self, task):
share_file = self.create_cloud_file('auto_share_')
if not share_file:
self.log('分享文件失败: 创建临时文件失败')
return None
try:
response = self.request_json('https://yun.139.com/orchestration/personalCloud-rebuild/outlink/v1.0/getOutLink',
headers = self.build_share_headers(),
data = {
'getOutLinkReq': {
'subLinkType': 0,
'encrypt': 1,
'coIDLst': [share_file.get('fileId')],
'caIDLst': [],
'pubType': 1,
'dedicatedName': share_file.get('fileName', ''),
'period': 1,
'periodUnit': 1,
'viewerLst': [],
'extInfo': {'isWatermark': 0, 'shareChannel': '3001'},
'commonAccountInfo': {'account': self.account, 'accountType': 1},
}
},
method = 'POST', retries = 1)
finally:
self.trash_cloud_files([share_file.get('fileId')])
data = response.get('data', {}) if response else {}
result = data.get('result', {}) or data.get('getOutLinkRes', {})
outlinks = result.get('getOutLinkResSet') or []
success = False
if response:
success = (
response.get('success') and result.get('resultCode') == '0'
) or (
str(response.get('code')) == '0' and bool(outlinks)
)
if not success:
if response:
msg = result.get('resultDesc') or response.get('message') or response.get('msg', '未知错误')
else:
msg = '接口无响应'
self.log(f'分享文件失败: {msg}')
return None
return self.query_cloud_task(task.get('id', 434), 'month') or task
def build_ai_headers(self, use_client_info=False):
"""AI相机请求头,与主UA保持一致"""
device_id = self.market_device_id or generate_device_id()
headers = {
'Connection': 'keep-alive',
'sec-ch-ua-platform': '"iOS"',
'Authorization': self.Authorization,
'x-yun-api-version': 'v1',
'x-yun-tid': str(uuid.uuid4()),
'sec-ch-ua': '"Not A(Brand";v="8", "Chromium";v="130", "Mobile Safari";v="130"',
'sec-ch-ua-mobile': '?1',
'X-Requested-With': 'com.chinamobile.mcloud',
'Origin': 'https://frontend.mcloud.139.com',
'Referer': 'https://frontend.mcloud.139.com/',
'User-Agent': market_ua,
'Content-Type': 'application/json',
'Sec-Fetch-Site': 'same-site',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Dest': 'empty',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'Accept-Language': 'zh,zh-CN;q=0.9,en-US;q=0.8,en;q=0.7',
}
if use_client_info:
headers['Accept'] = 'text/event-stream'
headers['x-yun-client-info'] = f'4||1|{self.client_version}|Apple|iPhone 16 Pro|{device_id.lstrip("B")}|iOS 18.7|||||'
headers['x-yun-app-channel'] = '101'
return headers
headers['Accept'] = '*/*'
headers['x-DeviceInfo'] = f'||36|{self.client_version}|Apple|iPhone 16 Pro|{device_id.lstrip("B")}|iOS 18.7|||||'
return headers
def encrypt_ai_tool_account(self):
if AES is None or pad is None:
raise ImportError('未安装 pycryptodome,无法加密AI工具账号')
cipher = AES.new(AI_TOOL_ACCOUNT_AES_KEY.encode('utf-8'), AES.MODE_CBC, AI_TOOL_ACCOUNT_AES_IV.encode('utf-8'))
encrypted = cipher.encrypt(pad(self.account.encode('utf-8'), AES.block_size))
raw = AI_TOOL_ACCOUNT_AES_IV + base64.b64encode(encrypted).decode('utf-8')
return base64.b64encode(raw.encode('utf-8')).decode('utf-8')
def build_ai_tool_ad_headers(self):
return {
'Authorization': self.Authorization,
'User-Agent': market_ua,
'Accept': 'application/json, text/plain, */*',
'Content-Type': 'application/json',
'Origin': 'https://yun.139.com',
'Referer': 'https://yun.139.com/aiTools/',
'x-yun-tid': str(uuid.uuid4()),
}
def get_ai_camera_sample_base64(self):
sample_path = path.join(path.abspath(path.dirname(__file__)), 'assets', 'ai_camera_sample.jpg')
if not path.exists(sample_path):
return ''
with open(sample_path, 'rb') as file:
return f"data:image/jpg;base64,{base64.b64encode(file.read()).decode()}"
@staticmethod
def is_ai_chat_success(text):
payloads = []
for line in (text or '').splitlines():
if line.startswith('data:'):
payloads.append(line[5:].strip())
if not payloads and text:
payloads.append(text.strip())
for payload in payloads:
if not payload or payload == '[DONE]':
continue
try:
data = json.loads(payload)
except ValueError:
continue
if data.get('success') or data.get('code') == '0000':
return True
return False
def complete_ai_camera_task(self):
if not self.user_domain_id:
self.log('AI相机任务失败: 缺少用户信息')
return False
image_data = self.get_ai_camera_sample_base64()
if not image_data:
self.log('AI相机任务失败: 缺少样图')
return False
recognize_payload = json.dumps({
'channelId': '101',
'userId': self.user_domain_id,
'recognizeType': '1',
'base64': image_data,
'sendType': '2',
'imageExt': 'jpg',
'uploadToCloud': True,
'timeout': 30000,
}, ensure_ascii = False, separators = (',', ':'))
recognize_data = self.request_json('https://ai.yun.139.com/api/image/aiRecognize',
headers = self.build_ai_headers(),
data = recognize_payload,
method = 'POST')
if not recognize_data:
self.log('AI相机识图失败: 接口无响应')
return False
if not recognize_data.get('success'):
self.log(f"AI相机识图失败: {recognize_data.get('message', '未知错误')}")
return False
recognize_result = recognize_data.get('data') or {}
file_id = recognize_result.get('fileId')
if not file_id:
self.log('AI相机识图失败: 缺少文件ID')
return False
task_id = str(recognize_result.get('taskId') or int(time.time() * 1000))
file_name = f'{int(task_id) + 1}.jpeg' if task_id.isdigit() else f'{task_id}.jpeg'
input_time = datetime.now(timezone(timedelta(hours = 8))).isoformat(timespec = 'milliseconds')
chat_payload = json.dumps({
'userId': self.user_domain_id,
'sessionId': '',
'applicationType': 'chat',
'applicationId': '',
'sourceChannel': '101',
'dialogueInput': {
'dialogue': '?',
'prompt': '',
'inputTime': input_time,
'enableForceLlm': False,
'enableForceNetworkSearch': True,
'enableModelThinking': False,
'enableAllNetworkSearch': False,
'enableKnowledgeAndNetworkSearch': False,
'enableRegenerate': False,
'versionInfo': {'h5Version': '2.7.6'},
'extInfo': '{}',
'sortInfo': {},
'toolSetting': {'imageToolSetting': {'enableLlmDescribe': True}},
'attachment': {
'attachmentTypeList': [3],
'fileList': [{'fileId': file_id, 'name': file_name}],
},
},
}, ensure_ascii = False, separators = (',', ':'))
chat_response = self.send_request('https://ai.yun.139.com/api/outer/assistant/chat/v2/add',
headers = self.build_ai_headers(use_client_info = True),
data = chat_payload,
method = 'POST')
if not chat_response:
self.log('AI相机对话失败: 接口无响应')
return False
if self.is_ai_chat_success(chat_response.text):
return True
try:
chat_data = chat_response.json()
except ValueError:
chat_data = None
if chat_data and (chat_data.get('success') or chat_data.get('code') == '0000'):
return True
if chat_data:
self.log(f"AI相机对话失败: {chat_data.get('message') or chat_data.get('msg', '未知错误')}")
return False
self.log('AI相机对话失败: 响应解析失败')
return False
def complete_travel_guide_ai_task(self):
if not self.user_domain_id:
self.log('出行攻略问AI失败: 缺少用户信息')
return False
input_time = datetime.now(timezone(timedelta(hours = 8))).isoformat(timespec = 'milliseconds')
chat_payload = json.dumps({
'userId': self.user_domain_id,
'sessionId': '',
'applicationType': 'chat',
'applicationId': '',
'sourceChannel': '101',
'dialogueInput': {
'dialogue': '给我一份五一出行攻略',
'prompt': '',
'inputTime': input_time,
'enableForceLlm': False,
'enableForceNetworkSearch': True,
'enableModelThinking': False,
'enableAllNetworkSearch': False,
'enableKnowledgeAndNetworkSearch': False,
'enableRegenerate': False,
'versionInfo': {'h5Version': '2.7.6'},
'extInfo': '{}',
'sortInfo': {},
'toolSetting': {'imageToolSetting': {'enableLlmDescribe': True}},
'attachment': {},
},
}, ensure_ascii = False, separators = (',', ':'))
chat_response = self.send_request('https://ai.yun.139.com/api/outer/assistant/chat/v2/add',
headers = self.build_ai_headers(use_client_info = True),
data = chat_payload,
method = 'POST')
if not chat_response:
self.log('出行攻略问AI失败: 接口无响应')
return False
response_text = chat_response.text or ''
if chat_response.status_code == 200 and not response_text.strip():
return True
if self.is_ai_chat_success(response_text):
return True
try:
chat_data = chat_response.json()
except ValueError:
chat_data = None
if chat_data and (chat_data.get('success') or chat_data.get('code') == '0000'):
return True
if chat_data:
self.log(f"出行攻略问AI失败: {chat_data.get('message') or chat_data.get('msg', '未知错误')}")
return False
self.log('出行攻略问AI失败: 响应解析失败')
return False
@staticmethod
def get_task_progress(task):
progress_parts = []
currstep = task.get('currstep', 0)
process = task.get('process', 0)
if currstep:
progress_parts.append(f'阶段{currstep}')
if process:
progress_parts.append(f'进度{process}')
if not progress_parts:
return ''
return f" ({','.join(progress_parts)})"
@staticmethod
def strip_task_name(task):
return re.sub(r'<[^>]+>', '', task.get('name', ''))
@staticmethod
def get_task_step_types(task):
return set(task.get('stepTypeSet') or [])
def get_task_click_keys(self, task):
task_id = task.get('id')
currstep = task.get('currstep', 0)
step_types = self.get_task_step_types(task)
if task_id == 409:
if currstep > 0:
return ['task2']
return ['task', 'task2']
if 'click' in step_types and currstep == 0:
return ['task']
return []
def get_cloud_task_groups(self):
return [
('beiyong1', '\n🎁 福利任务'),
('cloudEmail', '\n📮 联动任务'),
('time', '\n✨ 热门任务'),
('day', '\n📆 云盘每日任务'),
('month', '\n📆 云盘每月任务'),
]
def query_cloud_task(self, task_id, group='time'):
return_data = self.request_market_json(
f'{self.market_base_url}/market/signin/task/taskListV2',
method="POST",
data={
'marketname': 'sign_in_3',
'clientVersion': self.client_version,
'group': group,
})
if not return_data or return_data.get('code') != 0:
return None
for task in return_data.get('result', {}).get(group, []):
if task.get('id') == task_id:
return task
return None
def complete_monthly_upload_task(self, task):
target_count = 100
current_process = int(task.get('process') or 0)
for attempt in range(3):
remaining = max(0, target_count - current_process)
if remaining == 0:
return True
self.log(f'-{"开始" if attempt == 0 else "继续"}补上传进度: 当前{current_process}/{target_count},还需{remaining}次')
success = 0
for _ in range(remaining):
if self.create_cloud_file('auto_upload_'):
success += 1
if success:
self.log(f'-批量上传完成: {success}次')
refreshed_task = self.query_cloud_task(task.get('id', 522), 'time')
if not refreshed_task:
return False
refreshed_process = int(refreshed_task.get('process') or 0)
if refreshed_task.get('state') == 'FINISH' or refreshed_process >= target_count:
return True
if refreshed_process <= current_process:
self.log(f'-月上传任务进度: {refreshed_process}/{target_count}')
return False
current_process = refreshed_process
self.log(f'-月上传任务进度: {current_process}/{target_count}')
return False
def complete_mayday_memory_task(self, task):
target_count = 10
task_id = task.get('id', 587)
current_process = int(task.get('process') or 0)
remaining = max(0, target_count - current_process)
if remaining == 0:
return True
self.log(f'-开始补五一回忆上传: 当前{current_process}/{target_count},还需{remaining}张')
success = 0
for _ in range(remaining):
if self.create_cloud_file('auto_mayday_', extension = 'jpg'):
success += 1
if success:
self.log(f'-五一回忆上传完成: {success}张')
self.click_task(task_id)
refreshed_task = self.query_cloud_task(task_id, 'beiyong1')
if not refreshed_task:
return False
refreshed_process = int(refreshed_task.get('process') or 0)
if refreshed_task.get('state') == 'FINISH' or refreshed_process >= target_count:
return True
self.log(f'-五一回忆任务进度: {refreshed_process}/{target_count}')
return False
def complete_holiday_nine_grid_task(self):
try:
encrypted_account = self.encrypt_ai_tool_account()
except ImportError:
self.log('生成假期九宫格失败: 缺少加密依赖')
return False
template_data = self.request_json(
'https://ad.mcloud.139.com/advertapi/adv-config/adv-config/AdInfoFilter/getAdInfos',
headers = self.build_ai_tool_ad_headers(),
data = {
'account': encrypted_account,
'adpostid': '66340',
'channel': '10000023',
'version': '10.5.0',
'client': 'iphone',
},
method = 'POST',
)
if not template_data:
self.log('生成假期九宫格失败: 模板接口无响应')
return False
if str(template_data.get('returnCode')) != '0':
self.log(f"生成假期九宫格失败: {template_data.get('returnMsg', '未知错误')}")
return False
click_data = self.click_task(589)
if click_data and str(click_data.get('code')) == '0':
return True
msg = click_data.get('msg', '未知错误') if click_data else '接口无响应'
self.log(f'生成假期九宫格失败: {msg}')
return False
def get_cloud_tasklist_v2(self):
for group, title in self.get_cloud_task_groups():
return_data = self.request_market_json(
f'{self.market_base_url}/market/signin/task/taskListV2',
method="POST",
data={
'marketname': 'sign_in_3',
'clientVersion': self.client_version,
'group': group,
})
if not return_data:
self.log(f'获取任务列表失败: {group}')
continue
if return_data.get('code') != 0:
self.log(f"获取任务列表失败: {group} {return_data.get('msg', '未知错误')}")
continue
tasks = return_data.get('result', {}).get(group, [])
if not tasks:
continue
self.log(title)
for task in tasks:
self.handle_cloud_v2_task(group, task)
self.cleanup_uploaded_files()
def handle_cloud_v2_task(self, group, task):
task_id = task.get('id')
task_name = self.strip_task_name(task)
task_status = task.get('state', '')
if task_status == 'FINISH':
print(f'-已完成: {task_name}')
return
if group == 'day' and task_id == 106:
self.log(f'-去完成: {task_name}')
self.do_task(task_id, task_type = 'day', app_type = 'cloud_app')
return
if task_id == 522:
self.log(f'-去完成: {task_name}')
if self.complete_monthly_upload_task(task):
self.log(f'-已完成: {task_name}')
return
refreshed_task = self.query_cloud_task(task_id, group) or task
self.log(f'-需手动完成: {task_name}{self.get_task_progress(refreshed_task)}')
return
if task_id == 434:
self.log(f'-去完成: {task_name}')
refreshed_task = self.complete_share_file_task(task)
if refreshed_task:
refreshed_name = self.strip_task_name(refreshed_task)
if refreshed_task.get('state') == 'FINISH':
self.log(f'-已完成: {refreshed_name}')
return
self.log(f'-分享成功: {refreshed_name}{self.get_task_progress(refreshed_task)}')
return
self.log(f'-需手动完成: {task_name}{self.get_task_progress(task)}')
return
if task_id == 585:
self.log(f'-去完成: {task_name}')
if 'click' in self.get_task_step_types(task) and int(task.get('currstep') or 0) == 0:
click_data = self.click_task(task_id)
if not click_data or click_data.get('code') != 0:
msg = click_data.get('msg', '未知错误') if click_data else '接口无响应'
self.log(f'-任务登记失败: {task_name} {msg}')
return
if self.complete_ai_camera_task():
refreshed_task = self.query_cloud_task(task_id, group) or task
refreshed_name = self.strip_task_name(refreshed_task)
if refreshed_task.get('state') == 'FINISH':
self.log(f'-已完成: {refreshed_name}')
else:
self.log(f'-AI相机已体验: {refreshed_name}{self.get_task_progress(refreshed_task)}')
return
refreshed_task = self.query_cloud_task(task_id, group) or task
self.log(f'-需手动完成: {task_name}{self.get_task_progress(refreshed_task)}')
return
if task_id == 587:
self.log(f'-去完成: {task_name}')
if 'click' in self.get_task_step_types(task) and int(task.get('currstep') or 0) == 0:
click_data = self.click_task(task_id)
if not click_data or click_data.get('code') != 0:
msg = click_data.get('msg', '未知错误') if click_data else '接口无响应'
self.log(f'-任务登记失败: {task_name} {msg}')
return
if self.complete_mayday_memory_task(task):
refreshed_task = self.query_cloud_task(task_id, group) or task
refreshed_name = self.strip_task_name(refreshed_task)
if refreshed_task.get('state') == 'FINISH':
self.log(f'-已完成: {refreshed_name}')
else:
self.log(f'-五一回忆已上传: {refreshed_name}{self.get_task_progress(refreshed_task)}')
return
refreshed_task = self.query_cloud_task(task_id, group) or task
self.log(f'-需手动完成: {task_name}{self.get_task_progress(refreshed_task)}')
return
if task_id == 588:
self.log(f'-去完成: {task_name}')
if 'click' in self.get_task_step_types(task) and int(task.get('currstep') or 0) == 0:
click_data = self.click_task(task_id)
if not click_data or click_data.get('code') != 0:
msg = click_data.get('msg', '未知错误') if click_data else '接口无响应'
self.log(f'-任务登记失败: {task_name} {msg}')
return
if self.complete_travel_guide_ai_task():
refreshed_task = self.query_cloud_task(task_id, group) or task
refreshed_name = self.strip_task_name(refreshed_task)
if refreshed_task.get('state') == 'FINISH':
self.log(f'-已完成: {refreshed_name}')
else:
self.log(f'-出行攻略已生成: {refreshed_name}{self.get_task_progress(refreshed_task)}')
return
refreshed_task = self.query_cloud_task(task_id, group) or task
self.log(f'-需手动完成: {task_name}{self.get_task_progress(refreshed_task)}')
return
if task_id == 589:
self.log(f'-去完成: {task_name}')
if 'click' in self.get_task_step_types(task) and int(task.get('currstep') or 0) == 0:
click_data = self.click_task(task_id)
if not click_data or click_data.get('code') != 0:
msg = click_data.get('msg', '未知错误') if click_data else '接口无响应'
self.log(f'-任务登记失败: {task_name} {msg}')
return
if self.complete_holiday_nine_grid_task():
refreshed_task = self.query_cloud_task(task_id, group) or task
refreshed_name = self.strip_task_name(refreshed_task)
if refreshed_task.get('state') == 'FINISH':
self.log(f'-已完成: {refreshed_name}')
else:
self.log(f'-假期九宫格已生成: {refreshed_name}{self.get_task_progress(refreshed_task)}')
return
refreshed_task = self.query_cloud_task(task_id, group) or task
self.log(f'-需手动完成: {task_name}{self.get_task_progress(refreshed_task)}')
return
if group == 'beiyong1':
self.log(f'-需手动完成: {task_name}{self.get_task_progress(task)}')
return
if task_id == 406:
self.complete_notice_task(task_name)
return
task_keys = self.get_task_click_keys(task)
if task_keys:
self.log(f'-去完成: {task_name}')
for task_key in task_keys:
click_data = self.click_task(task_id, task_key)
if click_data and click_data.get('code') == 0:
continue
msg = click_data.get('msg', '未知错误') if click_data else '接口无响应'
self.log(f'-任务登记失败: {task_name} {msg}')
return
self.log(f'-已登记任务: {task_name}')
return
self.log(f'-需手动完成: {task_name}{self.get_task_progress(task)}')
def sleep(self, min_delay=1, max_delay=1.5):
delay = random.uniform(min_delay, max_delay)
time.sleep(delay)
def sso(self):
sso_url = 'https://orches.yun.139.com/orchestration/auth-rebuild/token/v1.0/querySpecToken'
sso_headers = {
'Authorization': self.Authorization,
'User-Agent': ua,
'Content-Type': 'application/json',
'Accept': '*/*',
'Host': 'orches.yun.139.com'
}
sso_payload = {"account": self.account, "toSourceId": "001005"}
sso_data = self.request_json(sso_url, headers = sso_headers, data = sso_payload, method = 'POST')
if not sso_data:
self.log('刷新Token失败: 接口无响应')
return None
if sso_data['success']:
refresh_token = sso_data['data']['token']
self.sso_token = refresh_token
return refresh_token
else:
self.log(f"刷新Token失败: {sso_data.get('message', '未知错误')}")
return None
def jwt(self):
token = self.sso()
if token is None:
self.log('-尝试强制刷新Authorization后重试')
self.refresh_authorization_token(force = True)
token = self.sso()
if token is not None:
jwt_url = f"https://caiyun.feixin.10086.cn:7071/portal/auth/tyrzLogin.action?ssoToken={token}"
jwt_data = self.request_json(jwt_url, headers = self.jwtHeaders, method = 'POST')
if not jwt_data:
self.log('JWT获取失败: 接口无响应')
return False
if jwt_data['code'] != 0:
self.log('-尝试强制刷新Authorization后重新获取JWT')
self.refresh_authorization_token(force = True)
token = self.sso()
if token is None:
self.log('-ck可能失效了')
return False
jwt_url = f"https://caiyun.feixin.10086.cn:7071/portal/auth/tyrzLogin.action?ssoToken={token}"
jwt_data = self.request_json(jwt_url, headers = self.jwtHeaders, method = 'POST')
if not jwt_data:
self.log('JWT获取失败: 接口无响应')
return False
if jwt_data['code'] != 0:
self.log(f"JWT获取失败: {jwt_data['msg']}")
return False
jwt_token = jwt_data['result']['token']
self.jwtHeaders['jwtToken'] = jwt_token
self.cookies['jwtToken'] = jwt_token
self.build_market_context(jwt_token)
self.save_authorization_record(token = self.Authorization, user_domain_id = self.user_domain_id)
return True
else:
self.log('-ck可能失效了')
return False
@catch_errors
def signin_status(self):
self.sleep()
self.prepare_signin_center_session()
check_url = f'{self.market_base_url}/market/signin/page/infoV3'
check_data = self.request_market_json(check_url, params = {'client': 'app'})
if not check_data:
self.log('查询签到失败: 接口无响应')
return
if check_data.get('code') != 0:
self.log(f"查询签到失败: {check_data.get('msg', '未知错误')}")
return
if self.get_today_sign_state(check_data.get('result', {})):
self.log('✅已签到')
return
signin_data = self.request_market_json(f'{self.market_base_url}/market/signin/page/startSignIn',
params = {'client': 'app'})
if not signin_data:
self.log('签到失败: 接口无响应')
return
if signin_data.get('code') == 0 and self.get_today_sign_state(signin_data.get('result', {})):
self.log('✅签到成功')
return
latest_data = self.request_market_json(check_url, params = {'client': 'app'})
if latest_data and latest_data.get('code') == 0 and self.get_today_sign_state(latest_data.get('result', {})):
self.log('✅签到成功')
return
self.log(f"签到失败: {signin_data.get('msg', '未知错误')}")
if signin_data.get('code') in (614, 615) and not get_env_device_id() and not get_device_id(self.account):
self.log('-请检查变量中 deviceId 是否正确')
def click(self):
successful_click = 0
try:
for _ in range(self.click_num):
return_data = self.click_task(319) or {}
time.sleep(0.2)
if 'result' in return_data:
self.log(f'✅戳一戳: {return_data["result"]}')
successful_click += 1
if successful_click == 0:
print(f'❌戳一戳: 未获得 x {self.click_num}')
except Exception as e:
print(f'错误信息:{e}')
@catch_errors
def refresh_notetoken(self):
note_url = 'http://mnote.caiyun.feixin.10086.cn/noteServer/api/authTokenRefresh.do'
note_payload = {
"authToken": self.auth_token,
"userPhone": self.account
}
note_headers = {
'X-Tingyun-Id': 'p35OnrDoP8k;c=2;r=1122634489;u=43ee994e8c3a6057970124db00b2442c::8B3D3F05462B6E4C',
'Charset': 'UTF-8',
'Connection': 'Keep-Alive',
'User-Agent': 'mobile',
'APP_CP': 'ios',
'CP_VERSION': '3.2.0',
'x-huawei-channelsrc': '10001400',
'Host': 'mnote.caiyun.feixin.10086.cn',
'Content-Type': 'application/json; charset=UTF-8',
'Accept-Encoding': 'gzip'
}
try:
response = self.send_request(note_url, headers = note_headers, data = note_payload, method = "POST")
response.raise_for_status()
except requests.exceptions.RequestException as e:
print('出错了:', e)
return
self.note_token = response.headers.get('NOTE_TOKEN')
self.note_auth = response.headers.get('APP_AUTH')
def get_tasklist(self, url, app_type):
if url == 'sign_in_3' and app_type == 'cloud_app':
self.get_cloud_tasklist_v2()
return
url = f'https://caiyun.feixin.10086.cn/market/signin/task/taskList?marketname={url}'
return_data = self.send_request(url, headers = self.jwtHeaders, cookies = self.cookies).json()
self.sleep()
task_list = return_data.get('result', {})
try:
for task_type, tasks in task_list.items():
if task_type in ["new", "hidden", "hiddenabc"]:
continue
if app_type == 'cloud_app':
if task_type == "month":
self.log('\n📆 云盘每月任务')
for month in tasks:
task_id = month.get('id')
if task_id in [110, 113, 417, 409]:
continue
task_name = re.sub(r'<[^>]+>', '', month.get('name', ''))
task_status = month.get('state', '')
if task_status == 'FINISH':
print(f'-已完成: {task_name}')
continue
self.log(f'-去完成: {task_name}')
self.do_task(task_id, task_type = 'month', app_type = 'cloud_app')
time.sleep(2)
elif task_type == "day":
self.log('\n📆 云盘每日任务')
for day in tasks:
task_id = day.get('id')
if task_id == 404:
continue
task_name = re.sub(r'<[^>]+>', '', day.get('name', ''))
task_status = day.get('state', '')
if task_status == 'FINISH':
print(f'-已完成: {task_name}')
continue
self.log(f'-去完成: {task_name}')
self.do_task(task_id, task_type = 'day', app_type = 'cloud_app')
elif app_type == 'email_app':
if task_type == "month":
self.log('\n📆 139邮箱每月任务')
for month in tasks:
task_id = month.get('id')
task_name = re.sub(r'<[^>]+>', '', month.get('name', ''))
task_status = month.get('state', '')
if task_id in [1004, 1005, 1015, 1020]:
continue
if task_status == 'FINISH':
print(f'-已完成: {task_name}')
continue
self.log(f'-去完成: {task_name}')
self.do_task(task_id, task_type = 'month', app_type = 'email_app')
time.sleep(2)
except Exception as e:
self.log(f'获取任务列表错误:{e}')
@catch_errors
def do_task(self, task_id, task_type, app_type):
self.sleep()
if app_type == 'cloud_app':
self.click_task(task_id)
else:
task_url = f'https://caiyun.feixin.10086.cn/market/signin/task/click?key=task&id={task_id}'
self.send_request(task_url, headers = self.jwtHeaders, cookies = self.cookies)
if app_type == 'cloud_app':
if task_type == 'day':
if task_id == 106:
self.log('-开始上传文件,默认0kb')
self.updata_file()
elif task_id == 107:
self.refresh_notetoken()
print('-获取默认笔记id')
note_url = 'http://mnote.caiyun.feixin.10086.cn/noteServer/api/syncNotebookV3.do'
headers = {
'X-Tingyun-Id': 'p35OnrDoP8k;c=2;r=1122634489;u=43ee994e8c3a6057970124db00b2442c::8B3D3F05462B6E4C',
'Charset': 'UTF-8',
'Connection': 'Keep-Alive',
'User-Agent': 'mobile',
'APP_CP': 'ios',
'CP_VERSION': '3.2.0',
'x-huawei-channelsrc': '10001400',
'APP_NUMBER': self.account,
'APP_AUTH': self.note_auth,
'NOTE_TOKEN': self.note_token,
'Host': 'mnote.caiyun.feixin.10086.cn',
'Content-Type': 'application/json; charset=UTF-8',
'Accept': '*/*'
}
payload = {
"addNotebooks": [],
"delNotebooks": [],
"notebookRefs": [],
"updateNotebooks": []
}
return_data = self.send_request(url = note_url, headers = headers, data = payload,
method = 'POST').json()
if return_data is None:
return print('出错了')
self.notebook_id = return_data['notebooks'][0]['notebookId']
print('开始创建笔记')
self.create_note(headers)
@catch_errors
def updata_file(self):
upload_info = self.create_cloud_file('auto_upload_')
if not upload_info:
self.log('-上传失败: 接口无响应')
return
self.log(f"-上传文件成功,文件名: {upload_info.get('fileName', '')}")
self.cleanup_uploaded_files(upload_info)
def create_note(self, headers):
note_id = self.get_note_id(32)
createtime = str(int(round(time.time() * 1000)))
time.sleep(3)
updatetime = str(int(round(time.time() * 1000)))
note_url = 'http://mnote.caiyun.feixin.10086.cn/noteServer/api/createNote.do'
payload = {
"archived": 0,
"attachmentdir": note_id,
"attachmentdirid": "",
"attachments": [],
"audioInfo": {
"audioDuration": 0,
"audioSize": 0,
"audioStatus": 0
},
"contentid": "",
"contents": [{
"contentid": 0,
"data": "<font size=\"3\">000000</font>",
"noteId": note_id,
"sortOrder": 0,
"type": "RICHTEXT"
}],
"cp": "",
"createtime": createtime,
"description": "ios",
"expands": {
"noteType": 0
},
"latlng": "",
"location": "",
"noteid": note_id,
"notestatus": 0,
"remindtime": "",
"remindtype": 1,
"revision": "1",
"sharecount": "0",
"sharestatus": "0",
"system": "mobile",
"tags": [{
"id": self.notebook_id,
"orderIndex": "0",
"text": "默认笔记本"
}],
"title": "00000",
"topmost": "0",
"updatetime": updatetime,
"userphone": self.account,
"version": "1.00",
"visitTime": ""
}
create_note_data = self.send_request(note_url, headers = headers, data = payload, method = "POST")
if create_note_data.status_code == 200:
self.log('-创建笔记成功')
else:
self.log('-创建失败')
def get_note_id(self, length):
characters = '19f3a063d67e4694ca63a4227ec9a94a19088404f9a28084e3e486b928039a299bf756ebc77aa4f6bfa250308ec6a8be8b63b5271a00350d136d117b8a72f39c5bd15cdfd350cba4271dc797f15412d9f269e666aea5039f5049d00739b320bb9e8585a008b52c1cbd86970cae9476446f3e41871de8d9f6112db94b05e5dc7ea0a942a9daf145ac8e487d3d5cba7cea145680efc64794d43dd15c5062b81e1cda7bf278b9bc4e1b8955846e6bc4b6a61c28f831f81b2270289e5a8a677c3141ddc9868129060c0c3b5ef507fbd46c004f6de346332ef7f05c0094215eae1217ee7c13c8dca6d174cfb49c716dd42903bb4b02d823b5f1ff93c3f88768251b56cc'
note_id = ''.join(random.choice(characters) for _ in range(length))
return note_id
@catch_errors
def wxsign(self):
self.sleep()
url = 'https://caiyun.feixin.10086.cn/market/playoffic/followSignInfo?isWx=true'
return_data = self.send_request(url, headers = self.jwtHeaders, cookies = self.cookies).json()
if return_data['msg'] != 'success':
return self.log(return_data['msg'])
if not return_data['result'].get('todaySignIn'):
return self.log('❌签到失败,可能未绑定公众号')
return self.log('✅公众号签到成功')
def shake(self):
url = "https://caiyun.feixin.10086.cn:7071/market/shake-server/shake/shakeIt?flag=1"
successful_shakes = 0
try:
for _ in range(self.click_num):
return_data = self.send_request(url = url, cookies = self.cookies, headers = self.jwtHeaders,
method = 'POST').json()
time.sleep(1)
shake_prize_config = return_data["result"].get("shakePrizeconfig")
if shake_prize_config:
self.log(f"🎉摇一摇获得: {shake_prize_config['name']}")
successful_shakes += 1
except Exception as e:
print(f'错误信息: {e}')
if successful_shakes == 0:
print(f'❌未摇中 x {self.click_num}')
@catch_errors
def surplus_num(self):
self.sleep()
draw_info_url = 'https://caiyun.feixin.10086.cn/market/playoffic/drawInfo'
draw_url = "https://caiyun.feixin.10086.cn/market/playoffic/draw"
draw_info_data = self.send_request(draw_info_url, headers = self.jwtHeaders).json()
if draw_info_data.get('msg') == 'success':
remain_num = draw_info_data['result'].get('surplusNumber', 0)
self.log(f'剩余抽奖次数{remain_num}')
if remain_num > 50 - self.draw:
for _ in range(self.draw):
self.sleep()
draw_data = self.send_request(url = draw_url, headers = self.jwtHeaders).json()
if draw_data.get("code") == 0:
prize_name = draw_data["result"].get("prizeName", "")
self.log("✅抽奖成功,获得:" + prize_name)
else:
print("❌抽奖失败")
else:
self.log(f"抽奖查询失败: {draw_info_data.get('msg')}")
@catch_errors
def do_fruit_task(self, task_name, task_id, water_num):
self.log(f'-去完成: {task_name}')
do_task_url = f'{self.fruit_url}task/doTask.do?taskId={task_id}'
do_task_data = self.send_request(do_task_url, headers = self.treeHeaders).json()
if do_task_data.get('success'):
get_water_url = f'{self.fruit_url}task/givenWater.do?taskId={task_id}'
get_water_data = self.send_request(get_water_url, headers = self.treeHeaders).json()
if get_water_data.get('success'):
self.log(f'-已完成任务获得水滴: {water_num}')
else:
self.log(f'❌领取失败: {get_water_data.get("msg", "")}')
else:
self.log(f'❌参与任务失败: {do_task_data.get("msg", "")}')
@catch_errors
def cloud_game(self):
game_info_url = 'https://caiyun.feixin.10086.cn/market/signin/hecheng1T/info?op=info'
bigin_url = 'https://caiyun.feixin.10086.cn/market/signin/hecheng1T/beinvite'
end_url = 'https://caiyun.feixin.10086.cn/market/signin/hecheng1T/finish?flag=true'
game_info_data = self.send_request(game_info_url, headers = self.jwtHeaders, cookies = self.cookies).json()
if game_info_data and game_info_data.get('code', -1) == 0:
currnum = game_info_data.get('result', {}).get('info', {}).get('curr', 0)
count = game_info_data.get('result', {}).get('history', {}).get('0', {}).get('count', '')
rank = game_info_data.get('result', {}).get('history', {}).get('0', {}).get('rank', '')
self.log(f'今日剩余游戏次数: {currnum} 合成次数: {count}')
for _ in range(currnum):
self.send_request(bigin_url, headers = self.jwtHeaders, cookies = self.cookies).json()
print('-开始游戏,等待10-15秒完成游戏')
time.sleep(random.randint(10, 15))
end_data = self.send_request(end_url, headers = self.jwtHeaders, cookies = self.cookies).json()
if end_data and end_data.get('code', -1) == 0:
self.log('游戏成功')
else:
print("-获取游戏信息失败")
@catch_errors
def receive(self):
prize_url = f"https://caiyun.feixin.10086.cn/market/prizeApi/checkPrize/getUserPrizeLogPage?currPage=1&pageSize=15&_={self.timestamp}"
self.prepare_signin_center_session(for_receive = True)
info_data = self.request_market_json(f'{self.market_base_url}/market/signin/page/infoV3',
params = {'client': 'app'})
if not info_data:
self.log('查询云朵失败: 接口无响应')
return
if info_data.get('code') != 0:
self.log(f"查询云朵失败: {info_data.get('msg', '未知错误')}")
return
info_result = info_data.get('result', {})
pending_amount = info_result.get('toReceive', 0)
total_amount = info_result.get('total', '')
if pending_amount:
receive_headers = self.build_receive_headers()
receive_cookies = dict(self.market_cookies)
receive_data = self.request_json(f'{self.market_base_url}/market/signin/page/receiveV2',
params = {'client': 'app'},
headers = receive_headers,
cookies = receive_cookies)
if not receive_data:
self.log('领取云朵失败: 接口无响应')
self.log(f'-当前待领取:{pending_amount}云朵')
elif receive_data.get('code') == 0:
receive_result = receive_data.get('result', {})
self.log(f'-领取云朵:{receive_result.get("receive", pending_amount)}云朵')
total_amount = receive_result.get('total', total_amount)
else:
latest_info_data = self.request_market_json(f'{self.market_base_url}/market/signin/page/infoV3',
params = {'client': 'app'})
latest_result = latest_info_data.get('result', {}) if latest_info_data and latest_info_data.get('code') == 0 else {}
latest_pending = latest_result.get('toReceive', pending_amount)
latest_total = latest_result.get('total', total_amount)
pending_delta = pending_amount - latest_pending if isinstance(pending_amount, int) and isinstance(latest_pending, int) else 0
total_delta = latest_total - total_amount if isinstance(total_amount, int) and isinstance(latest_total, int) else 0
claimed_amount = total_delta or pending_delta or (pending_amount if latest_pending == 0 else 0)
if claimed_amount > 0:
self.log(f'-领取云朵:{claimed_amount}云朵')
total_amount = latest_total
else:
self.log(f"领取云朵失败: {receive_data.get('msg', '未知错误')}")
if receive_data.get('code') in (614, 615) and not get_env_device_id() and not get_device_id(self.account):
self.log('-请检查变量中 deviceId 是否正确')
self.log(f'-当前待领取:{pending_amount}云朵')
else:
self.log('-当前待领取:0云朵')
self.sleep()
prize_data = self.request_json(prize_url, headers = self.jwtHeaders, cookies = self.cookies) or {}
result = prize_data.get('result', {}).get('result') or []
rewards = ''
for value in result:
if value.get('flag') == 1:
rewards += f'待领取奖品: {value.get("prizeName")}\n'
self.log(f'-当前云朵数量:{total_amount}云朵')
if rewards:
self.log(rewards)
global user_amount
user_amount += f'用户【{self.encrypt_account}】:{total_amount}云朵\n'
@catch_errors
def backup_cloud(self):
backup_url = 'https://caiyun.feixin.10086.cn/market/backupgift/info'
backup_data = self.send_request(backup_url, headers = self.jwtHeaders).json()
state = backup_data.get('result', {}).get('state', '')
if state == -1:
self.log('本月未备份,暂无连续备份奖励')
elif state == 0:
self.log('-领取本月连续备份奖励')
cur_url = 'https://caiyun.feixin.10086.cn/market/backupgift/receive'
cur_data = self.send_request(cur_url, headers = self.jwtHeaders).json()
self.log(f'-获得云朵数量:{cur_data.get("result").get("result")}')
elif state == 1:
print('-已领取本月连续备份奖励')
self.sleep()
expend_url = 'https://caiyun.feixin.10086.cn/market/signin/page/taskExpansion'
expend_data = self.send_request(expend_url, headers = self.jwtHeaders, cookies = self.cookies).json()
curMonthBackup = expend_data.get('result', {}).get('curMonthBackup', '')
preMonthBackup = expend_data.get('result', {}).get('preMonthBackup', '')
curMonthBackupTaskAccept = expend_data.get('result', {}).get('curMonthBackupTaskAccept', '')
nextMonthTaskRecordCount = expend_data.get('result', {}).get('nextMonthTaskRecordCount', '')
acceptDate = expend_data.get('result', {}).get('acceptDate', '')
if curMonthBackup:
self.log(f'- 本月已备份,下月可领取膨胀云朵: {nextMonthTaskRecordCount}')
else:
self.log('- 本月还未备份,下月暂无膨胀云朵')
if preMonthBackup:
if curMonthBackupTaskAccept:
print('- 上月已备份,膨胀云朵已领取')
else:
receive_url = f'https://caiyun.feixin.10086.cn/market/signin/page/receiveTaskExpansion?acceptDate={acceptDate}'
receive_data = self.send_request(receive_url, headers = self.jwtHeaders,
cookies = self.cookies).json()
if receive_data.get("code") != 0:
self.log(f'-领取失败:{receive_data.get("msg")}')
else:
cloudCount = receive_data.get('result', {}).get('cloudCount', '')
self.log(f'- 膨胀云朵领取成功: {cloudCount}朵')
else:
print('-上月未备份,本月无膨胀云朵领取')
@catch_errors
def complete_notice_task(self, task_name):
notice_status = self.get_notice_status()
if not notice_status:
self.log(f'-需手动完成: {task_name}')
return
push_on = int(notice_status.get('pushOn') or 0)
first_status = int(notice_status.get('firstTaskStatus') or 0)
second_status = int(notice_status.get('secondTaskStatus') or 0)
on_duration = int(notice_status.get('onDuaration') or 0)
total = int(notice_status.get('total') or 31)
if push_on != 1:
self.log(f'-需手动完成: {task_name} (通知未开启)')
return
reward_url = 'https://caiyun.feixin.10086.cn/market/msgPushOn/task/obtain'
if first_status != 3:
reward1_data = self.request_json(reward_url, headers = self.jwtHeaders, data = {"type": 1}, method = 'POST')
if reward1_data and reward1_data.get('code') == 0:
self.log(f'-已领取: {task_name} (首日奖励)')
else:
self.log(f'-待领取: {task_name} (首日奖励)')
if second_status == 2:
reward2_data = self.request_json(reward_url, headers = self.jwtHeaders, data = {"type": 2}, method = 'POST')
if reward2_data and reward2_data.get('code') == 0:
self.log(f'-已领取: {task_name} (连续奖励,已开启{on_duration}/{total}天)')
else:
self.log(f'-待领取: {task_name} (已开启{on_duration}/{total}天)')
elif second_status == 3:
self.log(f'-已完成: {task_name}')
else:
self.log(f'-进行中: {task_name} (已开启{on_duration}/{total}天)')
if __name__ == "__main__":
env_name = 'ydyp'
token = os.getenv(env_name)
if not token:
print(f'⛔️未获取到ck变量:请检查变量 {env_name} 是否填写')
exit(0)
cookies = re.split(r'[&]', token)
print_startup_info(len(cookies))
print_device_id_notice()
print_storage_path_notice()
for i, account_info in enumerate(cookies, start=1):
print(f"\n======== ▷ 第 {i} 个账号 ◁ ========")
yp_instance = YP(account_info)
if not yp_instance.Authorization:
print(f"⛔️ 账号 {i} 无效,跳过执行")
continue
yp_instance.run()
print("\n准备进行下一个账号")
time.sleep(random.randint(1, 3))
msg = ""
if err_accounts:
msg += f"失效账号:\n{err_accounts}\n"
msg += f"任务详情:\n{all_logs}\n"
msg += f"云朵汇总:\n{user_amount}"
print("\n================ 运行总结 ================")
if err_accounts:
print(f"❌ 失效账号:\n{err_accounts}")
if user_amount:
print(f"☁️ 云朵汇总:\n{user_amount}")
msg = msg.replace('-', ' ').replace('.', ' ').replace('!', '!').replace('(', '(').replace(')', ')')
msg = msg.replace('_', ' ').replace('=', ' ').replace('~', ' ').replace('{', ' ').replace('}', ' ').replace('|', ' ')
send = load_send()
if send:
send('中国移动云盘任务信息', msg)