Files

377 lines
14 KiB
Python
Raw Permalink 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.
# const $ = new Env("电信任务本");
# cron: 0 8,18 * * *
#获取AndroidID地址:https://commissions-yields-exception-personally.trycloudflare.com
#获取iPhone ID地址:https://lodge-stereo-administration-editing.trycloudflare.com/
#变量ZGDX=手机号#密码#AndroidID
import os, sys, json, time, random, string, base64, asyncio, certifi, requests
from typing import Dict, Any, Union
from datetime import datetime
from pathlib import Path
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5, DES3, AES
from Crypto.Util.Padding import pad, unpad
from requests.adapters import HTTPAdapter
from urllib3.util.ssl_ import create_urllib3_context
# 尝试导入通知模块,若不存在则忽略
try:
import notify
HAS_NOTIFY = True
except ImportError:
HAS_NOTIFY = False
notify = None
# --- 配置与常量 ---
KEYS = {
'login_rsa': """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBkLT15ThVgz6/NOl6s8GNPofdWzWbCkWnkaAm7O2LjkM1H7dMvzkiqdxU02jamGRHLX/ZNMCXHnPcW/sDhiFCBN18qFvy8g6VYb9QtroI09e176s+ZCtiv7hbin2cCTj99iUpnEloZm19lwHyo69u5UMiPMpq0/XKBO8lYhN/gwIDAQAB
-----END PUBLIC KEY-----""",
'data_rsa': """-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC+ugG5A8cZ3FqUKDwM57GM4io6JGcStivT8UdGt67PEOihLZTw3P7371+N47PrmsCpnTRzbTgcupKtUv8ImZalYk65dU8rjC/ridwhw9ffW2LBwvkEnDkkKKRi2liWIItDftJVBiWOh17o6gfbPoNrWORcAdcbpk2L+udld5kZNwIDAQAB
-----END PUBLIC KEY-----""",
'des3': b'1234567`90koiuyhgtfrdews',
'aes_def': b'34d7cb0bcdf07523',
'aes_login': 'telecom_wap_2018'
}
global_logs = []
# --- 工具函数 ---
def log(msg: str):
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
full_msg = f"[{timestamp}] {msg}"
global_logs.append(full_msg)
print(full_msg)
def mask(s: str) -> str:
if not s or len(s) < 7:
return s
return f"{s[:3]}****{s[-4:]}"
def ts() -> str:
return datetime.now().strftime('%Y%m%d%H%M%S')
def rd_str(length: int) -> str:
return ''.join(random.choices(string.ascii_letters + string.digits, k=length))
def encode(s: str) -> str:
return ''.join(chr(ord(c) + 2) for c in s)
# --- SSL与HTTP会话 ---
class CustomSSLAdapter(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
ctx = create_urllib3_context(ciphers='DEFAULT@SECLEVEL=1:!aNULL:!eNULL:!MD5')
ctx.check_hostname = False
kwargs['ssl_context'] = ctx
return super().init_poolmanager(*args, **kwargs)
session = requests.Session()
session.verify = certifi.where()
session.headers.update({
'User-Agent': 'Mozilla/5.0 (Linux; U; Android 12; zh-cn) AppleWebKit/533.1 (KHTML, like Gecko) Version/5.0 Mobile Safari/533.1'
})
session.mount('https://', CustomSSLAdapter())
# --- 加密逻辑 ---
def encrypt_des3(data, mode='enc'):
cipher = DES3.new(KEYS['des3'], DES3.MODE_CBC, 8 * b'\0')
if mode == 'enc':
return cipher.encrypt(pad(data.encode(), 8)).hex()
return unpad(cipher.decrypt(bytes.fromhex(data)), 8).decode()
def encrypt_aes(data, key=KEYS['aes_def'], b64=False):
data = json.dumps(data, separators=(',', ':')) if isinstance(data, (dict, list)) else data
cipher = AES.new(key if isinstance(key, bytes) else key.encode(), AES.MODE_ECB)
enc = cipher.encrypt(pad(data.encode(), 16))
return base64.b64encode(enc).decode() if b64 else enc.hex()
def encrypt_rsa(data, key_type='data', out='hex'):
cipher = PKCS1_v1_5.new(RSA.import_key(KEYS[f'{key_type}_rsa']))
data = json.dumps(data, separators=(',', ':')) if isinstance(data, (dict, list)) else data
if out == 'hex':
return ''.join(cipher.encrypt(data[i:i+32].encode()).hex() for i in range(0, len(data), 32))
return base64.b64encode(cipher.encrypt(data.encode())).decode()
# --- 请求函数:完全屏蔽请求/响应网络日志,仅捕获异常打印 ---
def api_req(url: str, method: str = 'POST', raw: bool = False, **kwargs) -> Union[Dict[str, Any], str]:
try:
r = session.request(method, url, timeout=15, **kwargs)
if raw:
return r.text
return r.json()
except Exception as e:
log(f"[网络异常] {str(e)}")
return '' if raw else {}
# --- 唯一登录方法:login---
def login_v2(phone: str, password: str, android_id: str):
"""登录"""
m_phone = mask(phone)
log(f"[登录] {m_phone} 开始登录")
body = {
"headerInfos": {
"code": "userLoginNormal",
"timestamp": ts(),
"broadAccount": "",
"broadToken": "",
"clientType": "#11.0.0#channel8#Xiaomi 20#",
"shopId": "20002",
"source": "110003",
"sourcePassword": "Sid98s",
"token": "",
"userLoginName": encode(phone)
},
"content": {
"attach": "test",
"fieldData": {
"loginType": "4",
"accountType": "",
"loginAuthCipherAsymmertric": encrypt_rsa(
f"Xiaomi 20 8.0.0.{android_id[:12]}{phone}{ts()}{password}0$$$0.",
'login', 'b64'
),
"deviceUid": "",
"phoneNum": encode(phone),
"isChinatelecom": "",
"systemVersion": "8.0.0",
"androidId": encode(android_id),
"loginAuthCipher": "",
"authentication": encode(password)
}
}
}
res = api_req(
'https://appgologin.189.cn:9031/login/client/userLoginNormal',
json=body
)
if not isinstance(res, dict):
log(f"失败] {m_phone} 响应非JSON")
return None
login_data = res.get('responseData', {}).get('data', {}).get('loginSuccessResult')
if not login_data:
err_msg = res.get('responseData', {}).get('data', {}).get('resultMsg') or '接口返回无登录数据'
log(f"[登录失败] {m_phone}: {err_msg}")
return None
# 获取Ticket
xml = f'''<Request>
<HeaderInfos>
<Code>getSingle</Code>
<Timestamp>{ts()}</Timestamp>
<BroadAccount></BroadAccount>
<BroadToken></BroadToken>
<ClientType>#9.6.1#channel50#iPhone 14 Pro Max#</ClientType>
<ShopId>20002</ShopId>
<Source>110003</Source>
<SourcePassword>Sid98s</SourcePassword>
<Token>{login_data["token"]}</Token>
<UserLoginName>{phone}</UserLoginName>
</HeaderInfos>
<Content>
<Attach>test</Attach>
<FieldData>
<TargetId>{encrypt_des3(login_data["userId"])}</TargetId>
<Url>4a6862274835b451</Url>
</FieldData>
</Content>
</Request>'''
xml_res = api_req(
'https://appgologin.189.cn:9031/map/clientXML',
data=xml,
headers={'Content-Type': 'application/xml'},
raw=True
)
if not isinstance(xml_res, str):
log(f"[获取Ticket失败] {m_phone} 返回非字符串")
return None
if '过期' in xml_res or '校验错误' in xml_res:
log(f"[获取Ticket失败] {m_phone} 票据校验异常")
return None
if '<Ticket>' not in xml_res:
log(f"[Ticket异常] {m_phone} 响应缺失Ticket")
return None
try:
ticket = xml_res.split('<Ticket>')[1].split('</Ticket>')[0]
uid = encrypt_des3(ticket, 'dec')
except Exception as e:
log(f"[解析Ticket失败] {m_phone}: {str(e)}")
return None
# 统一登录获取Bearer
auth_body = encrypt_aes(
{"ticket": uid, "backUrl": "https%3A%2F%2Fwapact.189.cn%3A9001", "platformCode": "P201010301", "loginType": 2},
KEYS['aes_login'],
True
)
auth_res = api_req(
'https://wapact.189.cn:9001/unified/user/login',
data=auth_body,
headers={'Content-Type': 'application/json'}
)
user_info = {
**login_data,
'uid': uid,
'phoneNbr': phone
}
if isinstance(auth_res, dict) and auth_res.get('code') == 0:
user_info['Authorization'] = f"Bearer {auth_res['biz']['token']}"
else:
log(f"[统一登录警告] {m_phone} 未获取Bearer,抽奖功能不可用")
return user_info
# --- 任务执行(签到、抽奖等)---
def sign_tasks(user: dict):
m = mask(user['phoneNbr'])
log(f"[任务开始] {m}")
sso_url = f"https://wappark.189.cn/jt-sign/ssoHomLogin?ticket={user['uid']}"
sso = api_req(sso_url, method='GET')
if not isinstance(sso, dict) or not sso or 'sign' not in sso:
log(f"[获取sign失败] {m} 中断所有签到任务")
return
sign_header = {'sign': sso['sign']}
# 签到
log(f"[签到] {m} 执行每日签到")
api_req(
'https://wappark.189.cn/jt-sign/webSign/sign',
json={"encode": encrypt_aes({"phone": user['phoneNbr'], "date": int(time.time()*1000)})},
headers=sign_header
)
def check_and_award(path, key, days_list, label):
res = api_req(
f'https://wappark.189.cn/jt-sign/{path}',
json={"para": encrypt_rsa({"phone": user['phoneNbr']})},
headers=sign_header
)
if not isinstance(res, dict):
return
days = str(res.get('data', {}).get(key) if 'data' in res else res.get(key, 0))
log(f"[{label}] {m}: {days}天")
if days in days_list:
log(f"[{label}领奖] {m} 达标{days}天,领取奖励")
api_req(
'https://wappark.189.cn/jt-sign/webSign/exchangePrize',
json={"para": encrypt_rsa({"phone": user['phoneNbr'], "type": days})},
headers=sign_header
)
check_and_award('api/home/userStatusInfo', 'signDay', ['7'], '连签')
check_and_award('webSign/continueSignDays', 'continueSignDays', ['15', '28'], '累签')
# 金豆转盘
if 'Authorization' in user:
log(f"[抽奖] {m} 查询转盘活动")
tab = api_req(
f"https://wapact.189.cn:9001/gateway/golden/api/queryTurnTable?userType=1&_={int(time.time()*1000)}",
method='GET',
headers={'Authorization': user['Authorization']}
)
if isinstance(tab, dict) and tab.get('code') == 0:
act_id = tab['biz']['wzTurntable']['code']
chk = api_req(
f"https://wapact.189.cn:9001/gateway/standQuery/detail/check?activityId={act_id}",
method='GET',
headers={'Authorization': user['Authorization']}
)
if isinstance(chk, dict) and chk.get('code') == 0:
info = chk.get('biz', {}).get('resultInfo', {})
remain = info.get('userMaximum', 0) - info.get('userCount', 0)
log(f"[抽奖] {m} 剩余可抽奖次数:{remain}次")
for idx in range(remain):
log(f"[抽奖] {m} 进行第{idx+1}次抽奖")
api_req(
'https://wapact.189.cn:9001/gateway/golden/api/lottery',
json={"activityId": act_id},
headers={'Authorization': user['Authorization']}
)
time.sleep(2)
else:
log(f"[抽奖] {m} 查询剩余次数接口异常")
else:
log(f"[抽奖] {m} 无可用转盘活动")
else:
log(f"[抽奖] {m} 缺少Bearer凭证,跳过抽奖")
# 任务列表
tasks_res = api_req(
'https://wappark.189.cn/jt-sign/webSign/homepage',
json={"para": encrypt_rsa({"phone": user['phoneNbr'], "shopId": "20001", "type": "hg_qd_zrwzjd"})},
headers=sign_header
)
if isinstance(tasks_res, dict):
tasks = tasks_res.get('data', {}).get('biz', {}).get('adItems', [])
log(f"[任务列表] {m} 待完成任务总数:{len(tasks)}个")
for t in tasks:
if t.get('taskState') in ['0', '1'] and t.get('contentOne') == '18':
log(f"[任务执行] {m} 执行任务:{t.get('title', '未知任务')}")
api_req(
'https://wappark.189.cn/jt-sign/webSign/polymerize',
json={"para": encrypt_rsa({"phone": user['phoneNbr'], "jobId": t['taskId']})},
headers=sign_header
)
time.sleep(2)
# 喂食
log(f"[喂食] {m} 开始宠物喂食")
for i in range(10):
res = api_req(
'https://wappark.189.cn/jt-sign/paradise/food',
json={"para": encrypt_rsa({"phone": user['phoneNbr']})},
headers=sign_header
)
msg = res.get('resoultMsg', '') if isinstance(res, dict) else ''
if "最大" in msg or "已达" in msg or not msg:
if msg:
log(f"[喂食结束] {m} {msg}")
break
time.sleep(1)
log(f"[任务全部完成] {m}")
# --- 主程序 ---
if __name__ == '__main__':
raw = os.environ.get('ZGDX', '')
if not raw:
log("未找到环境变量 ZGDX,请按格式设置:手机号#密码#AndroidID(AndroidID从小程序“脚本置顶”中获取),多账号换行分隔")
sys.exit(1)
accs = [line.strip().split('#') for line in raw.strip().split('\n') if line.strip() and '#' in line]
if not accs:
log("未解析到有效账号,请检查格式(手机号#密码#AndroidID")
sys.exit(1)
for idx, parts in enumerate(accs, 1):
phone = parts[0].strip()
pwd = parts[1].strip() if len(parts) > 1 else ''
android_id = parts[2].strip() if len(parts) > 2 else ''
# 检查AndroidID是否提供
if not android_id:
log(f"[账号{idx}] 错误:缺少AndroidID,格式应为 手机号#密码#AndroidIDAndroidID请从小程序“脚本置顶”中获取。跳过该账号。")
continue
log(f"\n{'='*10} 账号[{idx}] {mask(phone)} {'='*10}")
user = login_v2(phone, pwd, android_id)
if user:
sign_tasks(user)
else:
log(f"[账号跳过] {mask(phone)} 登录失败,不执行任务")
time.sleep(2)
# --- 推送所有日志 ---
if HAS_NOTIFY and global_logs:
try:
full_log = "\n".join(global_logs)
notify.send('电信任务推送', full_log)
log("通知推送成功")
except Exception as e:
log(f"通知推送失败: {str(e)}")
else:
log("未启用通知模块或无运行日志")