Collect seeded Qinglong script repositories
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
# Source: https://github.com/AkenClub/ken-iMoutai-Script/blob/main/3_retrieve_shop_and_product_info.py
|
||||
# Raw: https://raw.githubusercontent.com/AkenClub/ken-iMoutai-Script/main/3_retrieve_shop_and_product_info.py
|
||||
# Repo: AkenClub/ken-iMoutai-Script
|
||||
# Path: 3_retrieve_shop_and_product_info.py
|
||||
# UploadedAt: 2025-11-20T09:54:34+08:00
|
||||
# SHA256: 029d5d6421f5a3432bb04e3040139b27ff23131bba02594dfb2d8391b12992fd
|
||||
# Category: APP版/抓包
|
||||
# Evidence: cookie/token/authorization/header
|
||||
|
||||
import logging
|
||||
import requests
|
||||
import time
|
||||
import datetime
|
||||
"""
|
||||
3、查询可预约的商品 和 售卖商店,获取 SHOP_ID、LAT、LNG 等数据
|
||||
|
||||
如果省份城市填写有问题,可以直接查看这个网址的 json 内容,找到自己的想要预约的商店信息。也可找到正确的省份城市信息后填入下面变量值运行脚本获取完整数据。
|
||||
https://resource.moutai519.com.cn/mt-resource/resource/myserviceshops/1726025401183/myserviceshops.json
|
||||
(若过期无法访问,可参考脚本里 get_shop_info 函数逻辑,查看 ["data"]["myserviceshops"]["url"] 获取最新网址)
|
||||
"""
|
||||
|
||||
# ------ 填写以下 2 个变量值 --------
|
||||
|
||||
# 示例调用,省份和市区需要填写完整,
|
||||
# 省份
|
||||
PROVINCE_NAME = "广西壮族自治区"
|
||||
# 城市
|
||||
CITY_NAME = "南宁市"
|
||||
|
||||
# --------------------
|
||||
|
||||
# ***** 以下内容不用动 *****
|
||||
'''
|
||||
cron: 1 1 1 1 *
|
||||
new Env("3_查询商品商店信息")
|
||||
'''
|
||||
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
|
||||
# 获取可预约的商品信息
|
||||
def get_item_info():
|
||||
# 生成时间戳
|
||||
timestamp = str(int(time.mktime(datetime.date.today().timetuple())) * 1000)
|
||||
|
||||
# 发送请求
|
||||
api_url = f"https://static.moutai519.com.cn/mt-backend/xhr/front/mall/index/session/get/{timestamp}"
|
||||
response = requests.get(api_url)
|
||||
data = response.json()
|
||||
|
||||
if data["code"] != 2000:
|
||||
raise Exception("获取商品信息失败")
|
||||
|
||||
# 解析响应
|
||||
sessionId = data["data"]["sessionId"]
|
||||
# 提取itemCode和title
|
||||
item_list = data["data"]["itemList"]
|
||||
result = [{
|
||||
"itemCode": item["itemCode"],
|
||||
"title": item.get("title", f"未知商品,可结合该商品图片链接来判断:{item.get('pictureV2', '无图片信息')},同时到 APP 核实。")
|
||||
} for item in item_list]
|
||||
|
||||
return {"sessionId": sessionId, "itemList": result}
|
||||
|
||||
|
||||
# 获取售卖商店信息
|
||||
def get_shop_info(province_name, city_name):
|
||||
# 第一步:获取myserviceshops的URL
|
||||
api_url = "https://static.moutai519.com.cn/mt-backend/xhr/front/mall/resource/get"
|
||||
response = requests.get(api_url)
|
||||
data = response.json()
|
||||
|
||||
if data["code"] != 2000:
|
||||
raise Exception("获取资源信息失败")
|
||||
|
||||
myserviceshops_url = data["data"]["myserviceshops"]["url"]
|
||||
|
||||
# 第二步:下载并解析myserviceshops.json
|
||||
response = requests.get(myserviceshops_url)
|
||||
shops_data = response.json()
|
||||
|
||||
# 第三步:根据provinceName和cityName过滤数据
|
||||
result = []
|
||||
for _, shop_info in shops_data.items():
|
||||
if shop_info["provinceName"] == province_name and shop_info[
|
||||
"cityName"] == city_name:
|
||||
result.append({
|
||||
"lat": shop_info["lat"],
|
||||
"lng": shop_info["lng"],
|
||||
"name": shop_info["name"],
|
||||
"shopId": shop_info["shopId"],
|
||||
"fullAddress": shop_info["fullAddress"],
|
||||
"cityName": shop_info["cityName"],
|
||||
"provinceName": shop_info["provinceName"]
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 获取商店信息
|
||||
if not PROVINCE_NAME or not CITY_NAME:
|
||||
logging.error("「获取商店信息-失败」:缺少必要参数")
|
||||
raise Exception("缺少必要参数")
|
||||
|
||||
logging.info(f"「获取商店信息-开始」:{PROVINCE_NAME} - {CITY_NAME}")
|
||||
shop_info = get_shop_info(PROVINCE_NAME, CITY_NAME)
|
||||
|
||||
logging.info(f"「查找到的商店信息」:")
|
||||
logging.info("-------------------------")
|
||||
for shop in shop_info:
|
||||
logging.info(f"店铺名称: {shop['name']}")
|
||||
logging.info(f"店铺ID: {shop['shopId']}")
|
||||
logging.info(f"地址: {shop['fullAddress']}")
|
||||
logging.info(f"纬度: {shop['lat']}")
|
||||
logging.info(f"经度: {shop['lng']}")
|
||||
logging.info("-------------------------")
|
||||
logging.info(f"**** 请自行选择上面一个商店 ****")
|
||||
|
||||
logging.info("-------------------------")
|
||||
|
||||
# 获取可以预约的商品信息
|
||||
try:
|
||||
result = get_item_info()
|
||||
except Exception as e:
|
||||
logging.error(f"获取商品信息失败:{str(e)}")
|
||||
raise
|
||||
logging.info(
|
||||
f"获取到 SessionId(可以理解为申购活动批次,每天都会变化,一般+1): {result['sessionId']}")
|
||||
for item in result['itemList']:
|
||||
logging.info(
|
||||
f"获取到可预约商品:itemCode: {item['itemCode']}, title: {item['title']}")
|
||||
|
||||
logging.info("****************************")
|
||||
logging.info("记录以下信息用于后续预约:")
|
||||
|
||||
logging.info(f"省份 - PROVINCE:{PROVINCE_NAME}")
|
||||
logging.info(f"城市 - CITY:{CITY_NAME}")
|
||||
logging.info(f"店铺ID - SHOP_ID")
|
||||
logging.info(f"纬度 - LAT,可直接用店铺对应的纬度值 或者 自己实际的纬度值(自行想办法获取)")
|
||||
logging.info(f"经度 - LNG,可直接用店铺对应的经度值 或者 自己实际的经度值(自行想办法获取)")
|
||||
logging.info(
|
||||
f"需要预约的商品 ID 列表 - PRODUCT_ID_LIST(对应 itemCode),符号都是用英文符号,例如:['10941', '10923', '2478', '10942']"
|
||||
)
|
||||
197
脚本库/APP版/抓包/env/2026-04-07_env_89a06fae.js
vendored
Normal file
197
脚本库/APP版/抓包/env/2026-04-07_env_89a06fae.js
vendored
Normal file
@@ -0,0 +1,197 @@
|
||||
// # Source: https://github.com/smallfawn/QLScriptPublic/blob/main/tools/env.js
|
||||
// # Raw: https://raw.githubusercontent.com/smallfawn/QLScriptPublic/main/tools/env.js
|
||||
// # Repo: smallfawn/QLScriptPublic
|
||||
// # Path: tools/env.js
|
||||
// # UploadedAt: 2026-04-07T12:33:25Z
|
||||
// # SHA256: 89a06fae1893022eebe3efe869870ddd87549ddaf1cd158c369060a81934cef9
|
||||
// # Category: APP版/抓包
|
||||
// # Evidence: cookie/token/authorization/header
|
||||
//
|
||||
|
||||
// prettier-ignore
|
||||
function Env(t, s) {
|
||||
return new (class {
|
||||
constructor(t, s) {
|
||||
this.userIdx = 1;
|
||||
this.userList = [];
|
||||
this.userCount = 0;
|
||||
this.name = t;
|
||||
this.notifyStr = [];
|
||||
this.logSeparator = "\n";
|
||||
this.startTime = new Date().getTime();
|
||||
Object.assign(this, s);
|
||||
this.log(`\ud83d\udd14${this.name},\u5f00\u59cb!`);
|
||||
this.bucket = this.bucket || ''
|
||||
this.fs = require("fs");
|
||||
if (this.isNode() && this.bucket) {
|
||||
try {
|
||||
if (!this.fs.existsSync(this.bucket)) {
|
||||
this.fs.writeFileSync(this.bucket, JSON.stringify({}, null, 2));
|
||||
this.log(`📁 已创建 bucket 文件: ${this.bucket}`);
|
||||
}
|
||||
} catch (e) {
|
||||
this.log("❌ 初始化 bucket 失败: " + e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
async get(key, def = null) {
|
||||
if (!this.isNode()) return def;
|
||||
try {
|
||||
const data = await this.fs.promises.readFile(this.bucket, "utf-8");
|
||||
const json = JSON.parse(data);
|
||||
return json.hasOwnProperty(key) ? json[key] : def;
|
||||
} catch (e) {
|
||||
this.log("❌ 读取bucket失败: " + e.message);
|
||||
return def;
|
||||
}
|
||||
}
|
||||
async set(key, value) {
|
||||
if (!this.isNode()) return;
|
||||
try {
|
||||
const data = await this.fs.promises.readFile(this.bucket, "utf-8");
|
||||
const json = JSON.parse(data);
|
||||
json[key] = value;
|
||||
await this.fs.promises.writeFile(this.bucket, JSON.stringify(json, null, 2));
|
||||
} catch (e) {
|
||||
this.log("❌ 写入bucket失败: " + e.message);
|
||||
}
|
||||
}
|
||||
checkEnv(ckName) {
|
||||
const envSplitor = ["&", "\n"];
|
||||
let userCookie = (this.isNode() ? process.env[ckName] : "") || "";
|
||||
this.userList = userCookie.split(envSplitor.find((o) => userCookie.includes(o)) || "&").filter((n) => n);
|
||||
this.userCount = this.userList.length;
|
||||
this.log(`共找到${this.userCount}个账号`);
|
||||
}
|
||||
toStr(v) {
|
||||
if (v instanceof Error) return v.stack || v.message;
|
||||
if (v && typeof v == "object") try { return JSON.stringify(v) } catch { return "[Complex Object]" }
|
||||
return String(v);
|
||||
}
|
||||
async sendMsg() {
|
||||
this.log("==============📣Center 通知📣==============")
|
||||
let message = this.notifyStr.join(this.logSeparator);
|
||||
if (this.isNode()) {
|
||||
try {
|
||||
const { sendNotify } = require("./sendNotify.js")
|
||||
await sendNotify(this.name, message);
|
||||
} catch (e) {
|
||||
console.error(e.code === "MODULE_NOT_FOUND" ? "发送通知失败: 未找到 sendNotify.js 模块" : `发送通知失败: sendNotify.js 内部错误 (${e.message})`);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
isNode() {
|
||||
return "undefined" != typeof module && !!module.exports;
|
||||
}
|
||||
jsonToStr(obj, c = '&', encodeUrl = false) {
|
||||
let ret = []
|
||||
for (let keys of Object.keys(obj).sort()) {
|
||||
let v = obj[keys]
|
||||
if (v && encodeUrl) v = encodeURIComponent(v)
|
||||
ret.push(keys + '=' + v)
|
||||
}
|
||||
return ret.join(c);
|
||||
}
|
||||
getURLParams(url) {
|
||||
try { return Object.fromEntries(new URL(url, "http://localhost").searchParams) } catch { return {} }
|
||||
}
|
||||
isJSONString(str) {
|
||||
try {
|
||||
return JSON.parse(str) && typeof JSON.parse(str) === "object";
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
isJson(obj) {
|
||||
var isjson =
|
||||
typeof obj == "object" &&
|
||||
Object.prototype.toString.call(obj).toLowerCase() ==
|
||||
"[object object]" &&
|
||||
!obj.length;
|
||||
return isjson;
|
||||
}
|
||||
|
||||
randomNumber(length) {
|
||||
const characters = "0123456789";
|
||||
return Array.from(
|
||||
{ length },
|
||||
() => characters[Math.floor(Math.random() * characters.length)]
|
||||
).join("");
|
||||
}
|
||||
randomString(length) {
|
||||
const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
return Array.from(
|
||||
{ length },
|
||||
() => characters[Math.floor(Math.random() * characters.length)]
|
||||
).join("");
|
||||
}
|
||||
uuid() {
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
|
||||
/[xy]/g,
|
||||
function (c) {
|
||||
var r = (Math.random() * 16) | 0,
|
||||
v = c == "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
}
|
||||
);
|
||||
}
|
||||
time(t) {
|
||||
let s = {
|
||||
"M+": new Date().getMonth() + 1,
|
||||
"d+": new Date().getDate(),
|
||||
"H+": new Date().getHours(),
|
||||
"m+": new Date().getMinutes(),
|
||||
"s+": new Date().getSeconds(),
|
||||
"q+": Math.floor((new Date().getMonth() + 3) / 3),
|
||||
S: new Date().getMilliseconds(),
|
||||
};
|
||||
/(y+)/.test(t) &&
|
||||
(t = t.replace(
|
||||
RegExp.$1,
|
||||
(new Date().getFullYear() + "").substr(4 - RegExp.$1.length)
|
||||
));
|
||||
for (let e in s) {
|
||||
new RegExp("(" + e + ")").test(t) &&
|
||||
(t = t.replace(
|
||||
RegExp.$1,
|
||||
1 == RegExp.$1.length
|
||||
? s[e]
|
||||
: ("00" + s[e]).substr(("" + s[e]).length)
|
||||
));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
log(content) {
|
||||
this.notifyStr.push(`[${this.time("HH:mm:ss")}]` + " " + this.toStr(content))
|
||||
console.log(content)
|
||||
}
|
||||
|
||||
wait(min, max = null) {
|
||||
const ms = max == null ? min : Math.random() * (max - min + 1) + min | 0;
|
||||
ms >= 1000 && this.log(`等待 ${(ms / 1000).toFixed(2)} 秒...`, { notify: false });
|
||||
return new Promise(r => setTimeout(r, ms));
|
||||
}
|
||||
async done() {
|
||||
await this.sendMsg();
|
||||
const s = new Date().getTime(),
|
||||
e = (s - this.startTime) / 1e3;
|
||||
this.log(
|
||||
`\ud83d\udd14${this.name},\u7ed3\u675f!\ud83d\udd5b ${e}\u79d2`
|
||||
);
|
||||
if (this.isNode()) {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
parseCookie(ck) {
|
||||
return typeof ck != "string" || !ck ? {} : Object.fromEntries(
|
||||
ck.split(/;\s*/).filter(v => v.includes("=")).map(v => [v.slice(0, v.indexOf("=")), v.slice(v.indexOf("=") + 1)])
|
||||
);
|
||||
}
|
||||
|
||||
})(t, s);
|
||||
}
|
||||
module.exports = {
|
||||
Env
|
||||
}
|
||||
149
脚本库/APP版/抓包/万家乐/2025-06-28_万家乐_1adde720.py
Normal file
149
脚本库/APP版/抓包/万家乐/2025-06-28_万家乐_1adde720.py
Normal file
@@ -0,0 +1,149 @@
|
||||
# Source: https://gitee.com/jdqlscript/toulu/blob/main/%E4%B8%87%E5%AE%B6%E4%B9%90.py
|
||||
# Raw: https://gitee.com/jdqlscript/toulu/raw/main/%E4%B8%87%E5%AE%B6%E4%B9%90.py
|
||||
# Repo: jdqlscript/toulu
|
||||
# Path: 万家乐.py
|
||||
# UploadedAt: 2025-06-28T11:18:42+08:00
|
||||
# SHA256: 1adde72027d2e2407e706ee56bc7821d9a59d7ee9b9729563ba68d0b55e6d593
|
||||
# Category: APP版/抓包
|
||||
# Evidence: cookie/token/authorization/header
|
||||
|
||||
#by:哆啦A梦
|
||||
#TL库
|
||||
#https://github.com/3288588344/toulu.git
|
||||
#抓包wakecloud.chinamacro.com域名中的cookie中钱sessionid
|
||||
#多账号换行分割,一行一号
|
||||
#账号环境变量名WJL
|
||||
|
||||
|
||||
import requests
|
||||
import time
|
||||
import os
|
||||
|
||||
# 从环境变量中读取cookie
|
||||
cookies = os.environ.get("WJL", "").split('\n')
|
||||
|
||||
# 设置请求头
|
||||
headers = {
|
||||
"Host": "wakecloud.chinamacro.com",
|
||||
"Accept-Language": "zh",
|
||||
"Content-Type": "application/json",
|
||||
"Charset": "utf-8",
|
||||
"Referer": "https://servicewechat.com/wx07b7a339bb2cf065/117/page-frame.html",
|
||||
"Accept-Encoding": "gzip, deflate, br"
|
||||
}
|
||||
|
||||
# 签到URL
|
||||
sign_url = "https://wakecloud.chinamacro.com/wd-member/app/member/sign"
|
||||
|
||||
# 抽奖URL
|
||||
draw_url = "https://wakecloud.chinamacro.com/mtool/app/luckywheel/draw"
|
||||
|
||||
# 抽奖请求体
|
||||
draw_data = {"activityNo": 2025041400000001}
|
||||
|
||||
# 获取公告信息
|
||||
def get_proclamation():
|
||||
primary_url = "https://github.com/3288588344/toulu/raw/refs/heads/main/tl.txt"
|
||||
backup_url = "https://tfapi.cn/TL/tl.json"
|
||||
try:
|
||||
response = requests.get(primary_url, timeout=10)
|
||||
if response.status_code == 200:
|
||||
print("\n" + "=" * 50)
|
||||
print("📢 公告信息")
|
||||
print("=" * 35)
|
||||
print(response.text)
|
||||
print("=" * 35 + "\n")
|
||||
print("公告获取成功,开始执行任务...\n")
|
||||
return
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"获取公告时发生错误: {e}, 尝试备用链接...")
|
||||
|
||||
try:
|
||||
response = requests.get(backup_url, timeout=10)
|
||||
if response.status_code == 200:
|
||||
print("\n" + "=" * 50)
|
||||
print("📢 公告信息")
|
||||
print("=" * 35)
|
||||
print(response.text)
|
||||
print("=" * 35 + "\n")
|
||||
print("公告获取成功,开始执行任务...\n")
|
||||
else:
|
||||
print(f"⚠️ 获取公告失败,状态码: {response.status_code}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"⚠️ 获取公告时发生错误: {e}, 可能是网络问题或链接无效。")
|
||||
|
||||
|
||||
def sign_in(session_id):
|
||||
"""签到功能"""
|
||||
try:
|
||||
# 更新请求头中的Cookie
|
||||
headers["Cookie"] = f"sessionId={session_id}"
|
||||
|
||||
response = requests.get(sign_url, headers=headers)
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
|
||||
if result.get("success", False):
|
||||
print("签到成功!")
|
||||
else:
|
||||
print("签到失败,请检查原因。")
|
||||
else:
|
||||
print(f"签到请求失败,状态码: {response.status_code}")
|
||||
except Exception as e:
|
||||
print(f"签到发生错误: {e}")
|
||||
|
||||
def draw_prize(session_id):
|
||||
"""抽奖功能,抽奖3次"""
|
||||
try:
|
||||
# 更新请求头中的Cookie
|
||||
headers["Cookie"] = f"sessionId={session_id}"
|
||||
|
||||
for i in range(3):
|
||||
print(f"\n第 {i+1} 次抽奖中...")
|
||||
response = requests.post(draw_url, headers=headers, json=draw_data)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
|
||||
|
||||
if result.get("success", False):
|
||||
gift_name = result["data"]["giftName"]
|
||||
print(f"恭喜你抽到了: {gift_name}")
|
||||
else:
|
||||
print("抽奖失败")
|
||||
else:
|
||||
print(f"抽奖请求失败,状态码: {response.status_code}")
|
||||
|
||||
# 等待1秒再进行下一次抽奖
|
||||
time.sleep(1)
|
||||
except Exception as e:
|
||||
print(f"抽奖发生错误: {e}")
|
||||
|
||||
def main():
|
||||
if not cookies:
|
||||
print("环境变量WJL中没有找到sessionid,请检查配置。")
|
||||
return
|
||||
|
||||
for idx, cookie in enumerate(cookies, 1):
|
||||
if not cookie.strip():
|
||||
continue
|
||||
|
||||
print(f"\n===== 开始处理第 {idx} 个账户 =====")
|
||||
session_id = cookie.strip()
|
||||
|
||||
# 签到
|
||||
print("开始签到...")
|
||||
sign_in(session_id)
|
||||
|
||||
# 抽奖
|
||||
print("\n开始抽奖...")
|
||||
draw_prize(session_id)
|
||||
|
||||
print(f"===== 第 {idx} 个账户处理完成 =====\n")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 获取公告
|
||||
get_proclamation()
|
||||
|
||||
# 执行主程序
|
||||
main()
|
||||
752
脚本库/APP版/抓包/北京现代/2026-04-02_bjxd_a680cf46.py
Normal file
752
脚本库/APP版/抓包/北京现代/2026-04-02_bjxd_a680cf46.py
Normal file
@@ -0,0 +1,752 @@
|
||||
# Source: https://github.com/smallfawn/QLScriptPublic/blob/main/daily/bjxd.py
|
||||
# Raw: https://raw.githubusercontent.com/smallfawn/QLScriptPublic/main/daily/bjxd.py
|
||||
# Repo: smallfawn/QLScriptPublic
|
||||
# Path: daily/bjxd.py
|
||||
# UploadedAt: 2026-04-02T02:22:19Z
|
||||
# SHA256: a680cf465f0ac16d7a83b7b70762ad13ce94b713829255df0a6390cfde87402e
|
||||
# Category: APP版/抓包
|
||||
# Evidence: cookie/token/authorization/header
|
||||
|
||||
"""
|
||||
北京现代 APP 自动任务脚本
|
||||
功能:自动完成签到、浏览文章、每日答题等任务
|
||||
new Env("北京现代");
|
||||
环境变量:
|
||||
BJXD: str - 北京现代 APP api token (多个账号用英文逗号分隔,建议每个账号一个变量)
|
||||
BJXD1/BJXD2/BJXD3: str - 北京现代 APP api token (每个账号一个变量)
|
||||
BJXD_ANSWER: str - 预设答案 (可选, ABCD 中的一个)
|
||||
AI_API_KEY: str - 通用 AI APIKey (可选)
|
||||
AI_REQUEST_URL: str - 通用 AI 请求 URL (可选)
|
||||
AI_MODEL: str - 通用 AI 模型名称 (可选)
|
||||
AI_REQUEST_PARAMS: str - 通用 AI 请求参数 (可选, JSON 格式字符串)
|
||||
HUNYUAN_API_KEY: str - 腾讯混元AI APIKey (已废弃,不建议使用)
|
||||
GLM_API_KEY: str - 智谱 GLM AI APIKey (已废弃,不建议使用)
|
||||
|
||||
cron: 25 6 * * *
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
import json
|
||||
from datetime import datetime
|
||||
from typing import List, Dict, Any
|
||||
import requests
|
||||
from urllib3.exceptions import InsecureRequestWarning, InsecurePlatformWarning
|
||||
|
||||
# 禁用 SSL 警告
|
||||
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
||||
requests.packages.urllib3.disable_warnings(InsecurePlatformWarning)
|
||||
|
||||
|
||||
class BeiJingHyundai:
|
||||
"""北京现代APP自动任务类"""
|
||||
|
||||
# 基础配置
|
||||
NAME = "北京现代 APP 自动任务"
|
||||
BASE_URL = "https://bm2-api.bluemembers.com.cn"
|
||||
|
||||
# API endpoints
|
||||
API_USER_INFO = "/v1/app/account/users/info"
|
||||
API_MY_SCORE = "/v1/app/user/my_score"
|
||||
API_TASK_LIST = "/v1/app/user/task/list"
|
||||
API_SIGN_LIST = "/v1/app/user/reward_list"
|
||||
API_SIGN_SUBMIT = "/v1/app/user/reward_report"
|
||||
API_ARTICLE_LIST = "/v1/app/white/article/list2"
|
||||
API_ARTICLE_DETAIL = "/v1/app/white/article/detail_app/{}"
|
||||
API_ARTICLE_SCORE_SUBMIT = "/v1/app/score"
|
||||
API_QUESTION_INFO = "/v1/app/special/daily/ask_info"
|
||||
API_QUESTION_SUBMIT = "/v1/app/special/daily/ask_answer"
|
||||
|
||||
# 预设的备用 share_user_hid 列表
|
||||
BACKUP_HIDS = [
|
||||
"a6688ec1a9ee429fa7b68d50e0c92b1f",
|
||||
"bb8cd2e44c7b45eeb8cc5f7fa71c3322",
|
||||
"5f640c50061b400c91be326c8fe0accd",
|
||||
"55a5d82dacd9417483ae369de9d9b82d",
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
"""初始化实例变量"""
|
||||
self.token: str = "" # 当前用户token
|
||||
self.user: Dict[str, Any] = {} # 当前用户信息
|
||||
self.users: List[Dict[str, Any]] = [] # 所有用户信息列表
|
||||
self.correct_answer: str = "" # 正确答案
|
||||
self.preset_answer: str = "" # 预设答案
|
||||
self.ai_hunyuan_api_key: str = "" # 腾讯混元AI APIKey(兼容旧环境变量)
|
||||
self.ai_glm_api_key: str = "" # 智谱 GLM AI APIKey(兼容旧环境变量)
|
||||
self.ai_api_key: str = "" # 通用 AI APIKey
|
||||
self.ai_request_url: str = "" # AI 请求地址
|
||||
self.ai_model: str = "" # AI 模型
|
||||
self.ai_request_params: str = "" # AI 请求参数(JSON字符串格式)
|
||||
self.wrong_answers: set = set() # 错误答案集合
|
||||
self.log_content: str = "" # 日志内容
|
||||
|
||||
def log(self, content: str, print_to_console: bool = True) -> None:
|
||||
"""添加日志"""
|
||||
if print_to_console:
|
||||
print(content)
|
||||
self.log_content += content + "\n"
|
||||
|
||||
def push_notification(self) -> None:
|
||||
"""推送通知"""
|
||||
try:
|
||||
QLAPI.notify(self.NAME, self.log_content)
|
||||
except NameError:
|
||||
print(f"\n\n🚀 推送通知\n\n{self.NAME}\n\n{self.log_content}")
|
||||
|
||||
def make_request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
|
||||
"""
|
||||
发送API请求
|
||||
Args:
|
||||
method: 请求方法 (GET/POST)
|
||||
endpoint: API端点
|
||||
**kwargs: 请求参数
|
||||
Returns:
|
||||
Dict[str, Any]: API响应数据
|
||||
"""
|
||||
url = f"{self.BASE_URL}{endpoint}"
|
||||
headers = {"token": self.token, "device": "iOS", "app-version": "8.31.2"}
|
||||
if "headers" not in kwargs:
|
||||
kwargs["headers"] = headers
|
||||
else:
|
||||
kwargs["headers"].update(headers)
|
||||
try:
|
||||
response = requests.request(method, url, timeout=30, **kwargs)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except requests.exceptions.RequestException as e:
|
||||
|
||||
self.log(f"❌ API request failed: {str(e)}")
|
||||
return {"code": -1, "msg": str(e)}
|
||||
|
||||
def get_user_info(self) -> Dict[str, Any]:
|
||||
"""
|
||||
获取用户信息
|
||||
Returns:
|
||||
Dict[str, Any]: 用户信息字典,获取失败返回空字典
|
||||
"""
|
||||
response = self.make_request("GET", self.API_USER_INFO)
|
||||
print(f"get_user_info API response ——> {response}")
|
||||
|
||||
if response.get("code") == 0:
|
||||
data = response.get("data", {})
|
||||
# 直接生成掩码后的手机号
|
||||
masked_phone = f"{data.get('phone', '')[:3]}******{data.get('phone', '')[-2:]}"
|
||||
return {
|
||||
"token": self.token,
|
||||
"hid": data.get("hid", ""),
|
||||
"nickname": data.get("nickname", ""),
|
||||
"phone": masked_phone, # 直接存储掩码后的手机号
|
||||
"score_value": data.get("score_value", 0),
|
||||
"share_user_hid": "",
|
||||
"task": {"sign": False, "view": False, "question": False},
|
||||
}
|
||||
|
||||
self.log(f"❌ 账号已失效, 请重新获取 token: {self.token}")
|
||||
return {}
|
||||
|
||||
def get_score_details(self) -> None:
|
||||
"""显示积分详情,包括总积分、今日变动和最近记录"""
|
||||
params = {"page_no": "1", "page_size": "10"} # 获取最近10条记录
|
||||
response = self.make_request("GET", self.API_MY_SCORE, params=params)
|
||||
print(f"get_score_details API response ——> {response}")
|
||||
|
||||
if response.get("code") == 0:
|
||||
data = response.get("data", {})
|
||||
# 先获取今日记录
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
points_record = data.get("points_record", {})
|
||||
today_records = [
|
||||
record
|
||||
for record in points_record.get("list", [])
|
||||
if record.get("created_at", "").startswith(today)
|
||||
]
|
||||
|
||||
# 计算今日积分变化
|
||||
today_score = sum(
|
||||
int(record.get("score_str", "0").strip("+")) for record in today_records
|
||||
)
|
||||
today_score_str = f"+{today_score}" if today_score > 0 else str(today_score)
|
||||
self.log(f"🎉 总积分: {data.get('score', 0)} | 今日积分变动: {today_score_str}")
|
||||
|
||||
# 输出今日积分记录
|
||||
if today_records:
|
||||
self.log("今日积分记录:")
|
||||
for record in today_records:
|
||||
self.log(
|
||||
f"{record.get('created_at', '')} {record.get('desc', '')} {record.get('score_str', '')}"
|
||||
)
|
||||
else:
|
||||
self.log("今日暂无积分变动")
|
||||
|
||||
# 任务相关
|
||||
def check_task_status(self, user: Dict[str, Any]) -> None:
|
||||
"""检查任务状态"""
|
||||
response = self.make_request("GET", self.API_TASK_LIST)
|
||||
print(f"get_task_status API response ——> {response}")
|
||||
|
||||
if response.get("code") != 0:
|
||||
self.log(f'❌ 获取任务列表失败: {response.get("msg", "未知错误")}')
|
||||
return
|
||||
|
||||
actions = response.get("data", {})
|
||||
|
||||
# 检查签到任务
|
||||
if "action4" in actions:
|
||||
user["task"]["sign"] = actions["action4"].get("status") == 1
|
||||
else:
|
||||
self.log("❌ task list action4 签到任务 不存在")
|
||||
|
||||
# 检查浏览文章任务
|
||||
if "action12" in actions:
|
||||
user["task"]["view"] = actions["action12"].get("status") == 1
|
||||
else:
|
||||
self.log("❌ task list action12 浏览文章任务 不存在")
|
||||
|
||||
# 检查答题任务
|
||||
if "action39" in actions:
|
||||
user["task"]["question"] = actions["action39"].get("status") == 1
|
||||
else:
|
||||
self.log("❌ task list action39 答题任务 不存在")
|
||||
|
||||
# 签到相关
|
||||
def get_sign_info(self) -> None:
|
||||
"""执行签到任务"""
|
||||
max_attempts = 5 # 最大尝试次数
|
||||
best_score = 0
|
||||
best_params = None
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
response = self.make_request("GET", self.API_SIGN_LIST)
|
||||
print(f"get_sign_info (attempt {attempt + 1}) API response ——> {response}")
|
||||
|
||||
if response.get("code") != 0:
|
||||
self.log(f'❌ 获取签到列表失败: {response.get("msg", "未知错误")}')
|
||||
break
|
||||
|
||||
data = response.get("data", {})
|
||||
hid = data.get("hid", "")
|
||||
reward_hash = data.get("rewardHash", "")
|
||||
|
||||
for item in data.get("list", []):
|
||||
if item.get("hid") == hid:
|
||||
current_score = item.get("score", 0)
|
||||
print(
|
||||
f"第{attempt + 1}次获取签到列表: score={current_score} hid={hid} rewardHash={reward_hash}"
|
||||
)
|
||||
|
||||
if current_score > best_score:
|
||||
best_score = current_score
|
||||
best_params = (hid, reward_hash, current_score)
|
||||
print(f"当前可获得签到积分: {best_score}")
|
||||
break
|
||||
|
||||
if attempt < max_attempts - 1: # 不是最后一次循环
|
||||
print(f"继续尝试获取更高积分, 延时5-10s")
|
||||
time.sleep(random.randint(5, 10))
|
||||
else: # 最后一次循环 即将提交签到
|
||||
print(f"即将提交签到, 延时3-4s")
|
||||
time.sleep(random.randint(3, 4))
|
||||
|
||||
if best_params:
|
||||
self.submit_sign(*best_params)
|
||||
else:
|
||||
self.log("❌ 未能获取到有效的签到参数")
|
||||
|
||||
def submit_sign(self, hid: str, reward_hash: str, score: int) -> None:
|
||||
"""提交签到"""
|
||||
json_data = {
|
||||
"hid": hid,
|
||||
"hash": reward_hash,
|
||||
"sm_deviceId": "",
|
||||
"ctu_token": None,
|
||||
}
|
||||
response = self.make_request("POST", self.API_SIGN_SUBMIT, json=json_data)
|
||||
print(f"submit_sign API response ——> {response}")
|
||||
|
||||
if response.get("code") == 0:
|
||||
self.log(f"✅ 签到成功 | 积分 +{score}")
|
||||
else:
|
||||
self.log(f'❌ 签到失败: {response.get("msg", "未知错误")}')
|
||||
|
||||
# 文章浏览相关
|
||||
def get_article_list(self) -> List[str]:
|
||||
"""获取文章列表"""
|
||||
params = {
|
||||
"page_no": "1",
|
||||
"page_size": "20",
|
||||
"type_hid": "",
|
||||
}
|
||||
response = self.make_request("GET", self.API_ARTICLE_LIST, params=params)
|
||||
print(f"get_article_list API response ——> {response}")
|
||||
|
||||
if response.get("code") == 0:
|
||||
# 从文章列表中随机选择3个ID
|
||||
data = response.get("data", {})
|
||||
article_list = [item.get("data_id", "") for item in data.get("list", []) if item.get("data_id")]
|
||||
return random.sample(article_list, min(3, len(article_list)))
|
||||
|
||||
self.log(f'❌ 获取文章列表失败: {response.get("msg", "未知错误")}')
|
||||
return []
|
||||
|
||||
def get_article_detail(self, article_id: str) -> None:
|
||||
"""浏览文章"""
|
||||
self.log(f"浏览文章 article_id: {article_id}")
|
||||
endpoint = self.API_ARTICLE_DETAIL.format(article_id)
|
||||
try:
|
||||
# 调用make_request访问文章详情
|
||||
response = self.make_request("GET", endpoint)
|
||||
# 记录响应状态,便于调试
|
||||
if response.get("code") == -1:
|
||||
self.log(f"⚠️ 文章浏览异常: {response.get('msg', '未知错误')}")
|
||||
else:
|
||||
self.log(f"✅ 文章浏览成功")
|
||||
except Exception as e:
|
||||
# 捕获所有可能的异常,确保脚本不会在此处中断
|
||||
self.log(f"❌ 文章浏览过程中发生异常: {str(e)}")
|
||||
|
||||
def submit_article_score(self) -> None:
|
||||
"""提交文章积分"""
|
||||
json_data = {
|
||||
"ctu_token": "",
|
||||
"action": 12,
|
||||
}
|
||||
response = self.make_request(
|
||||
"POST", self.API_ARTICLE_SCORE_SUBMIT, json=json_data
|
||||
)
|
||||
print(f"submit_article_score API response ——> {response}")
|
||||
|
||||
if response.get("code") == 0:
|
||||
data = response.get("data", {})
|
||||
score = data.get("score", 0)
|
||||
self.log(f"✅ 浏览文章成功 | 积分 +{score}")
|
||||
else:
|
||||
self.log(f'❌ 浏览文章失败: {response.get("msg", "未知错误")}')
|
||||
|
||||
# 答题相关
|
||||
def get_question_info(self, share_user_hid: str) -> None:
|
||||
"""执行答题任务"""
|
||||
params = {"date": datetime.now().strftime("%Y%m%d")}
|
||||
response = self.make_request("GET", self.API_QUESTION_INFO, params=params)
|
||||
print(f"get_question_info API response ——> {response}")
|
||||
if response.get("code") != 0:
|
||||
self.log(f'❌ 获取问题失败: {response.get("msg", "未知错误")}')
|
||||
return
|
||||
|
||||
data = response.get("data", {})
|
||||
# data['state'] 1=表示未答题 2=已答题且正确 3=答错且未有人帮忙答题 4=答错但有人帮忙答题
|
||||
if data.get("state") == 3:
|
||||
self.log("今日已答题但回答错误,当前无人帮助答题,跳过")
|
||||
return
|
||||
if data.get("state") != 1:
|
||||
if data.get("answer"):
|
||||
answer = data.get("answer", [""])[0]
|
||||
if answer in ["A", "B", "C", "D"]:
|
||||
self.correct_answer = answer
|
||||
self.log(f"今日已答题,跳过,答案:{answer}")
|
||||
return
|
||||
self.log("今日已答题,但未获取到答案,跳过")
|
||||
return
|
||||
|
||||
question_info = data.get("question_info", {})
|
||||
questions_hid = question_info.get("questions_hid", "")
|
||||
|
||||
# 构建问题字符串,只包含未被标记为错误的选项
|
||||
question_str = f"{question_info.get('content', '')}\n"
|
||||
valid_options = []
|
||||
for option in question_info.get("option", []):
|
||||
if option.get("option") not in self.wrong_answers:
|
||||
valid_options.append(option)
|
||||
question_str += f'{option.get("option", "")}. {option.get("option_content", "")}\n'
|
||||
else:
|
||||
print(f"跳过错误选项 {option.get('option', '')}. {option.get('option_content', '')}")
|
||||
|
||||
print(f"\n问题详情:\n{question_str}")
|
||||
|
||||
# 如果只剩一个选项,直接使用
|
||||
if len(valid_options) == 1:
|
||||
answer = valid_options[0]["option"]
|
||||
self.log(f"仅剩一个选项,使用答案: {answer}")
|
||||
time.sleep(random.randint(3, 5))
|
||||
self.submit_question_answer(questions_hid, answer, share_user_hid)
|
||||
return
|
||||
|
||||
# 获取答案并提交
|
||||
answer = self.get_question_answer(question_str)
|
||||
|
||||
time.sleep(random.randint(3, 5))
|
||||
self.submit_question_answer(questions_hid, answer, share_user_hid)
|
||||
|
||||
def get_ai_answer(self, question: str) -> str:
|
||||
"""获取通用AI答案"""
|
||||
if not self.ai_api_key or not self.ai_request_url or not self.ai_model:
|
||||
return ""
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self.ai_api_key}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
# 构建默认的消息内容
|
||||
system_prompt = "你是一位北京现代汽车品牌的专家,对车型配置非常熟悉。\n以下是一道单选题,请只从题目实际列出的选项里选择正确答案。\n注意:题目可能只给出 2 个或 3 个选项,并非永远 4 个。\n请仅输出对应选项的那个英文字母,不要输出任何其他字符。"
|
||||
|
||||
# 构建默认的 json_data
|
||||
json_data = {
|
||||
"model": self.ai_model,
|
||||
"messages": [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": question}
|
||||
]
|
||||
}
|
||||
|
||||
# 如果提供了额外的请求参数,合并到 json_data 中
|
||||
if self.ai_request_params:
|
||||
try:
|
||||
extra_params = json.loads(self.ai_request_params)
|
||||
json_data.update(extra_params)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"❌ AI 请求参数解析失败: {str(e)}")
|
||||
|
||||
try:
|
||||
print(f"通用 AI API request ——> {json_data}")
|
||||
response = requests.post(
|
||||
self.ai_request_url,
|
||||
headers=headers,
|
||||
json=json_data,
|
||||
)
|
||||
print(f"通用 AI API response status ——> {response.status_code}")
|
||||
print(f"通用 AI API response text ——> {response.text}")
|
||||
response.raise_for_status()
|
||||
response_json = response.json()
|
||||
|
||||
# 获取AI回答内容并转大写
|
||||
choices = response_json.get("choices", [])
|
||||
if choices and len(choices) > 0:
|
||||
message = choices[0].get("message", {})
|
||||
ai_response = message.get("content", "").upper()
|
||||
else:
|
||||
ai_response = ""
|
||||
|
||||
# 使用集合操作找出有效答案
|
||||
valid_answers = set("ABCD") - self.wrong_answers
|
||||
found_answers = set(ai_response) & valid_answers
|
||||
|
||||
# 如果找到答案则返回其中一个
|
||||
if found_answers:
|
||||
return found_answers.pop()
|
||||
else:
|
||||
self.log(f"❌ 没有找到符合的 AI 答案")
|
||||
return ""
|
||||
|
||||
except Exception as e:
|
||||
self.log(f"通用 AI API 请求失败: {str(e)}")
|
||||
|
||||
return ""
|
||||
|
||||
def get_question_answer(self, question: str) -> str:
|
||||
"""获取答题答案"""
|
||||
# 1. 存在正确答案时,使用正确答案
|
||||
if self.correct_answer:
|
||||
self.log(f"使用历史正确答案: {self.correct_answer}")
|
||||
return self.correct_answer
|
||||
|
||||
# 2. 存在预设答案时,使用预设答案
|
||||
if self.preset_answer:
|
||||
self.log(f"使用预设答案: {self.preset_answer}")
|
||||
return self.preset_answer
|
||||
|
||||
# 3. 存在AI配置时,使用通用AI方法获取答案
|
||||
if self.ai_api_key and self.ai_request_url and self.ai_model:
|
||||
ai_answer = self.get_ai_answer(question)
|
||||
if ai_answer:
|
||||
self.log(f"使用 AI 答案: {ai_answer}")
|
||||
return ai_answer
|
||||
|
||||
# 4. 随机选择答案(排除错误答案)
|
||||
answer = self.get_random_answer()
|
||||
self.log(f"随机答题,答案: {answer}")
|
||||
return answer
|
||||
|
||||
def get_random_answer(self) -> str:
|
||||
"""获取随机答案,排除已知错误答案"""
|
||||
available_answers = set(["A", "B", "C", "D"]) - self.wrong_answers
|
||||
if not available_answers:
|
||||
self.wrong_answers.clear()
|
||||
available_answers = set(["A", "B", "C", "D"])
|
||||
return random.choice(list(available_answers))
|
||||
|
||||
def get_answered_question(self) -> None:
|
||||
"""从已答题账号获取答案"""
|
||||
params = {"date": datetime.now().strftime("%Y%m%d")}
|
||||
response = self.make_request("GET", self.API_QUESTION_INFO, params=params)
|
||||
print(f"get_answered_question API response ——> {response}")
|
||||
if response.get("code") != 0:
|
||||
self.log(f'❌ 从已答题账号获取问题失败: {response.get("msg", "未知错误")}')
|
||||
return
|
||||
|
||||
data = response.get("data", {})
|
||||
# data['state'] 1=表示未答题 2=已答题且正确 4=已答题但错误
|
||||
if response.get("code") == 0 and data.get("answer"):
|
||||
answer = data.get("answer", [""])[0]
|
||||
if answer in ["A", "B", "C", "D"]:
|
||||
self.correct_answer = answer
|
||||
self.log(f"从已答题账号获取到答案:{answer}")
|
||||
return
|
||||
self.log("从已答题账号获取答案失败")
|
||||
|
||||
def submit_question_answer(
|
||||
self, question_id: str, answer: str, share_user_hid: str
|
||||
) -> None:
|
||||
"""提交答题答案"""
|
||||
json_data = {
|
||||
"answer": answer,
|
||||
"questions_hid": question_id,
|
||||
"ctu_token": "",
|
||||
}
|
||||
if share_user_hid:
|
||||
json_data["date"] = datetime.now().strftime("%Y%m%d")
|
||||
json_data["share_user_hid"] = share_user_hid
|
||||
|
||||
response = self.make_request("POST", self.API_QUESTION_SUBMIT, json=json_data)
|
||||
print(f"submit_question_answer API response ——> {response}")
|
||||
|
||||
if response.get("code") == 0:
|
||||
data = response.get("data", {})
|
||||
if data.get("state") == 3: # 答错
|
||||
# 记录错误答案
|
||||
self.wrong_answers.add(answer)
|
||||
# 如果是正确答案,清除它
|
||||
if self.correct_answer == answer:
|
||||
self.correct_answer = ""
|
||||
# 如果是预设答案,清除它
|
||||
if self.preset_answer == answer:
|
||||
self.preset_answer = ""
|
||||
self.log("❌ 答题错误")
|
||||
elif data.get("state") == 2: # 答对了
|
||||
if self.correct_answer != answer:
|
||||
self.correct_answer = answer
|
||||
score = data.get("answer_score", 0)
|
||||
self.log(f"✅ 答题正确 | 积分 +{score}")
|
||||
else:
|
||||
self.log(f'❌ 答题失败: {response.get("msg", "未知错误")}')
|
||||
|
||||
def get_backup_share_hid(self, user_hid: str) -> str:
|
||||
"""从备用 hid 列表中获取一个不同于用户自身的 hid"""
|
||||
available_hids = [hid for hid in self.BACKUP_HIDS if hid != user_hid]
|
||||
return random.choice(available_hids) if available_hids else ""
|
||||
|
||||
def run(self) -> None:
|
||||
"""运行主程序"""
|
||||
|
||||
try:
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
print("✅ dotenv 成功加载 .env 文件")
|
||||
except ImportError:
|
||||
print("⚠️ 缺少 dotenv 库, 青龙环境请忽略, 本地运行请安装此库")
|
||||
|
||||
# 使用列表保持顺序,使用集合实现去重
|
||||
tokens = []
|
||||
tokens_set = set()
|
||||
|
||||
# 方式1: 从BJXD环境变量获取(逗号分隔的多个token)
|
||||
token_str = os.getenv("BJXD")
|
||||
if token_str:
|
||||
# 过滤空值并保持顺序添加
|
||||
for token in token_str.split(","):
|
||||
token = token.strip()
|
||||
if token and token not in tokens_set:
|
||||
tokens.append(token)
|
||||
tokens_set.add(token)
|
||||
|
||||
# 方式2: 从BJXD1/BJXD2/BJXD3等环境变量获取
|
||||
i = 1
|
||||
empty_count = 0 # 记录连续空值的数量
|
||||
while empty_count < 5: # 连续5个空值才退出
|
||||
token = os.getenv(f"BJXD{i}")
|
||||
if not token:
|
||||
empty_count += 1
|
||||
else:
|
||||
token = token.strip()
|
||||
if token and token not in tokens_set: # 确保token不是空字符串且未重复
|
||||
empty_count = 0 # 重置连续空值计数
|
||||
tokens.append(token)
|
||||
tokens_set.add(token)
|
||||
i += 1
|
||||
|
||||
if not tokens:
|
||||
self.log(
|
||||
"⛔️ 未获取到 tokens, 请检查环境变量 BJXD 或 BJXD1/BJXD2/... 是否填写"
|
||||
)
|
||||
self.push_notification()
|
||||
return
|
||||
|
||||
self.log(f"👻 共获取到用户 token {len(tokens)} 个")
|
||||
|
||||
# 获取新的 AI 配置参数
|
||||
self.ai_api_key = os.getenv("AI_API_KEY", "")
|
||||
self.ai_request_url = os.getenv("AI_REQUEST_URL", "")
|
||||
self.ai_model = os.getenv("AI_MODEL", "")
|
||||
self.ai_request_params = os.getenv("AI_REQUEST_PARAMS", "")
|
||||
|
||||
# 兼容旧的环境变量
|
||||
if not self.ai_api_key and not self.ai_request_url and not self.ai_model:
|
||||
# 检查旧的腾讯混元 AI 配置
|
||||
self.ai_hunyuan_api_key = os.getenv("HUNYUAN_API_KEY", "")
|
||||
if self.ai_hunyuan_api_key:
|
||||
self.ai_api_key = self.ai_hunyuan_api_key
|
||||
self.ai_request_url = "https://api.hunyuan.cloud.tencent.com/v1/chat/completions"
|
||||
self.ai_model = "hunyuan-turbo"
|
||||
self.ai_request_params = json.dumps({"enable_enhancement": True, "force_search_enhancement": True, "enable_instruction_search": True})
|
||||
self.log("💯 已获取到腾讯混元 AI 配置, 使用腾讯混元 AI 答题")
|
||||
else:
|
||||
self.log("😭 未设置腾讯混元 AI HUNYUAN_API_KEY 环境变量")
|
||||
|
||||
# 检查旧的智谱 GLM AI 配置
|
||||
self.ai_glm_api_key = os.getenv("GLM_API_KEY", "")
|
||||
if self.ai_glm_api_key:
|
||||
self.ai_api_key = self.ai_glm_api_key
|
||||
self.ai_request_url = "https://open.bigmodel.cn/api/paas/v4/chat/completions"
|
||||
self.ai_model = "glm-4.5-flash"
|
||||
self.ai_request_params = json.dumps({"do_sample": False})
|
||||
self.log("💯 已获取到智谱 GLM AI 配置, 使用智谱 GLM AI 答题")
|
||||
else:
|
||||
self.log("😭 未设置智谱 GLM AI GLM_API_KEY 环境变量")
|
||||
else:
|
||||
# 使用新的 AI 配置
|
||||
if self.ai_api_key and self.ai_request_url and self.ai_model:
|
||||
self.log("💯 已获取到通用 AI 配置, 使用通用 AI 答题")
|
||||
else:
|
||||
self.log("⚠️ 通用 AI 配置不完整, 请检查 AI_API_KEY、AI_REQUEST_URL 和 AI_MODEL 环境变量")
|
||||
|
||||
# 获取预设答案
|
||||
self.preset_answer = os.getenv("BJXD_ANSWER", "").upper()
|
||||
if self.preset_answer:
|
||||
if self.preset_answer in ["A", "B", "C", "D"]:
|
||||
self.log(f"📝 已获取预设答案: {self.preset_answer}")
|
||||
else:
|
||||
self.preset_answer = ""
|
||||
self.log("❌ 预设答案格式错误,仅支持 A/B/C/D")
|
||||
|
||||
self.log("获取用户信息")
|
||||
# 获取所有用户信息
|
||||
for token in tokens:
|
||||
self.token = token
|
||||
user = self.get_user_info()
|
||||
if user:
|
||||
self.users.append(user)
|
||||
time.sleep(random.randint(3, 5))
|
||||
|
||||
if not self.users:
|
||||
self.log("❌ 未获取到有效用户")
|
||||
# 最后推送通知
|
||||
self.push_notification()
|
||||
return
|
||||
|
||||
# 设置分享用户ID
|
||||
for i, user in enumerate(self.users):
|
||||
prev_index = (i - 1) if i > 0 else len(self.users) - 1
|
||||
# 如果有多个用户且上一个用户不是自己,使用上一个用户的 hid
|
||||
if len(self.users) > 1 and self.users[prev_index].get("hid") != user.get("hid"):
|
||||
user["share_user_hid"] = self.users[prev_index].get("hid", "")
|
||||
else:
|
||||
# 否则从备用 hid 列表中选择一个
|
||||
user["share_user_hid"] = self.get_backup_share_hid(user.get("hid", ""))
|
||||
|
||||
# 执行任务
|
||||
self.log("\n============ 执行任务 ============")
|
||||
for i, user in enumerate(self.users, 1):
|
||||
# 更新当前用户信息
|
||||
self.token = user["token"]
|
||||
self.user = user
|
||||
|
||||
# 随机延迟
|
||||
if i > 1:
|
||||
print("\n进行下一个账号, 等待 5-10 秒...")
|
||||
time.sleep(random.randint(5, 10))
|
||||
|
||||
self.log(f"\n======== ▷ 第 {i} 个账号 ◁ ========")
|
||||
|
||||
# 打印用户信息
|
||||
self.log(
|
||||
f"👻 用户名: {self.user.get('nickname', '未知')} | "
|
||||
f"手机号: {self.user.get('phone', '未知')} | "
|
||||
f"积分: {self.user.get('score_value', 0)}\n"
|
||||
f"🆔 用户hid: {self.user.get('hid', '')}\n"
|
||||
f"🆔 分享hid: {self.user.get('share_user_hid', '')}"
|
||||
)
|
||||
|
||||
# 检查任务状态
|
||||
self.check_task_status(self.user)
|
||||
self.log(f"任务状态: {self.user['task']}")
|
||||
|
||||
# 调试使用 设置任务状态
|
||||
self.user["task"]["question"] = True
|
||||
# self.user["task"]["sign"] = False
|
||||
# self.user["task"]["view"] = False
|
||||
|
||||
# 获取任务状态
|
||||
user_task = self.user.get("task", {})
|
||||
|
||||
# 任务:答题
|
||||
if not user_task.get("question"):
|
||||
self.get_question_info(self.user.get("share_user_hid", ""))
|
||||
else:
|
||||
self.log("✅ 答题任务 已完成,跳过")
|
||||
if not self.correct_answer:
|
||||
self.get_answered_question()
|
||||
|
||||
# 任务:签到
|
||||
if not user_task.get("sign"):
|
||||
self.get_sign_info()
|
||||
time.sleep(random.randint(5, 10))
|
||||
else:
|
||||
self.log("✅ 签到任务 已完成,跳过")
|
||||
|
||||
# 任务:阅读文章
|
||||
if not user_task.get("view"):
|
||||
article_ids = self.get_article_list()
|
||||
if article_ids:
|
||||
for index, article_id in enumerate(article_ids): # 已经只有3篇了
|
||||
self.log(f"🔄 开始处理第 {index + 1}/{len(article_ids)} 篇文章")
|
||||
try:
|
||||
self.get_article_detail(article_id)
|
||||
except Exception as e:
|
||||
self.log(f"❌ 第 {index + 1} 篇文章处理失败: {str(e)}")
|
||||
# 每篇文章之间的延迟
|
||||
time.sleep(random.randint(10, 15))
|
||||
# 所有文章处理完成后提交积分
|
||||
try:
|
||||
self.submit_article_score()
|
||||
except Exception as e:
|
||||
self.log(f"❌ 提交文章积分失败: {str(e)}")
|
||||
else:
|
||||
self.log("✅ 浏览文章任务 已完成,跳过")
|
||||
|
||||
self.log("\n============ 积分详情 ============")
|
||||
for i, user in enumerate(self.users, 1):
|
||||
if i > 1:
|
||||
print("\n进行下一个账号, 等待 5-10 秒...")
|
||||
time.sleep(random.randint(5, 10))
|
||||
|
||||
# 更新当前用户信息
|
||||
self.token = user["token"]
|
||||
self.user = user
|
||||
|
||||
self.log(f"\n======== ▷ 第 {i} 个账号 ◁ ========")
|
||||
|
||||
# 打印用户信息
|
||||
self.log(
|
||||
f"👻 用户名: {self.user.get('nickname', '未知')} | 手机号: {self.user.get('phone', '未知')}"
|
||||
)
|
||||
|
||||
# 显示积分详情
|
||||
self.get_score_details()
|
||||
|
||||
# 最后推送通知
|
||||
self.push_notification()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
BeiJingHyundai().run()
|
||||
258
脚本库/APP版/抓包/夸克自动签到/2026-05-14_quark_6ffca99d.py
Normal file
258
脚本库/APP版/抓包/夸克自动签到/2026-05-14_quark_6ffca99d.py
Normal file
@@ -0,0 +1,258 @@
|
||||
# Source: https://github.com/smallfawn/QLScriptPublic/blob/main/daily/quark.py
|
||||
# Raw: https://raw.githubusercontent.com/smallfawn/QLScriptPublic/main/daily/quark.py
|
||||
# Repo: smallfawn/QLScriptPublic
|
||||
# Path: daily/quark.py
|
||||
# UploadedAt: 2026-05-14T17:47:57+08:00
|
||||
# SHA256: 6ffca99dbbda1318e011f003244908a1c83db172b3549699b254b309b0fe5a00
|
||||
# Category: APP版/抓包
|
||||
# Evidence: cookie/token/authorization/header
|
||||
|
||||
'''
|
||||
new Env('夸克自动签到')
|
||||
cron: 0 9 * * *
|
||||
|
||||
V2版-目前有效
|
||||
使用移动端接口修复每日自动签到,移除原有的“登录验证”,参数有效期未知
|
||||
|
||||
V1版-已失效
|
||||
受大佬 @Cp0204 的仓库项目启发改编
|
||||
源码来自 GitHub 仓库:https://github.com/Cp0204/quark-auto-save
|
||||
提取“登录验证”“签到”“领取”方法封装到下文中的“Quark”类中
|
||||
|
||||
Author: BNDou
|
||||
Date: 2024-03-15 21:43:06
|
||||
LastEditTime: 2025-11-18 03:49:26
|
||||
FilePath: \Auto_Check_In\checkIn_Quark.py
|
||||
Description:
|
||||
抓包流程:
|
||||
【手机端】
|
||||
①打开抓包,手机端访问抽奖页
|
||||
②找到url为 https://drive-m.quark.cn/1/clouddrive/act/growth/reward 的请求信息
|
||||
③复制整段url,该链接后面必须要有参数: kps sign vcode,粘贴到环境变量
|
||||
环境变量名为 COOKIE_QUARK 多账户用 回车 或 && 分开
|
||||
user字段是用户名 (可是随意填写,多账户方便区分)
|
||||
例如: user=张三; url=https://drive-m.quark.cn/1/clouddrive/act/growth/reward?xxxxxx=xxxxxx&kps=abcdefg&sign=hijklmn&vcode=111111111;
|
||||
旧版环境变量格式也兼容,例如: user=张三; kps=abcdefg; sign=hijklmn; vcode=111111111;
|
||||
'''
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
# 测试用环境变量
|
||||
# os.environ['COOKIE_QUARK'] = ''
|
||||
|
||||
try: # 异常捕捉
|
||||
from notify import send # 导入消息通知模块
|
||||
except Exception as err: # 异常捕捉
|
||||
print('%s\n❌加载通知服务失败~' % err)
|
||||
|
||||
|
||||
# 获取环境变量
|
||||
def get_env():
|
||||
# 判断 COOKIE_QUARK是否存在于环境变量
|
||||
if "COOKIE_QUARK" in os.environ:
|
||||
# 读取系统变量以 \n 或 && 分割变量
|
||||
cookie_list = re.split('\n|&&', os.environ.get('COOKIE_QUARK'))
|
||||
else:
|
||||
# 标准日志输出
|
||||
print('❌未添加COOKIE_QUARK变量')
|
||||
send('夸克自动签到', '❌未添加COOKIE_QUARK变量')
|
||||
# 脚本退出
|
||||
sys.exit(0)
|
||||
|
||||
return cookie_list
|
||||
|
||||
|
||||
class Quark:
|
||||
'''
|
||||
Quark类封装了签到、领取签到奖励的方法
|
||||
'''
|
||||
def __init__(self, user_data):
|
||||
'''
|
||||
初始化方法
|
||||
:param user_data: 用户信息,用于后续的请求
|
||||
'''
|
||||
self.param = user_data
|
||||
|
||||
def convert_bytes(self, b):
|
||||
'''
|
||||
将字节转换为 MB GB TB
|
||||
:param b: 字节数
|
||||
:return: 返回 MB GB TB
|
||||
'''
|
||||
units = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
||||
i = 0
|
||||
while b >= 1024 and i < len(units) - 1:
|
||||
b /= 1024
|
||||
i += 1
|
||||
return f"{b:.2f} {units[i]}"
|
||||
|
||||
def get_growth_info(self):
|
||||
'''
|
||||
获取用户当前的签到信息
|
||||
:return: 返回一个字典,包含用户当前的签到信息
|
||||
'''
|
||||
url = "https://drive-m.quark.cn/1/clouddrive/capacity/growth/info"
|
||||
querystring = {
|
||||
"pr": "ucpro",
|
||||
"fr": "android",
|
||||
"kps": self.param.get('kps'),
|
||||
"sign": self.param.get('sign'),
|
||||
"vcode": self.param.get('vcode')
|
||||
}
|
||||
response = requests.get(url=url, params=querystring).json()
|
||||
#print(response)
|
||||
if response.get("data"):
|
||||
return response["data"]
|
||||
else:
|
||||
return False
|
||||
|
||||
def get_growth_sign(self):
|
||||
'''
|
||||
获取用户当前的签到信息
|
||||
:return: 返回一个字典,包含用户当前的签到信息
|
||||
'''
|
||||
url = "https://drive-m.quark.cn/1/clouddrive/capacity/growth/sign"
|
||||
querystring = {
|
||||
"pr": "ucpro",
|
||||
"fr": "android",
|
||||
"kps": self.param.get('kps'),
|
||||
"sign": self.param.get('sign'),
|
||||
"vcode": self.param.get('vcode')
|
||||
}
|
||||
data = {"sign_cyclic": True}
|
||||
response = requests.post(url=url, json=data, params=querystring).json()
|
||||
#print(response)
|
||||
if response.get("data"):
|
||||
return True, response["data"]["sign_daily_reward"]
|
||||
else:
|
||||
return False, response["message"]
|
||||
|
||||
def queryBalance(self):
|
||||
'''
|
||||
查询抽奖余额
|
||||
'''
|
||||
url = "https://coral2.quark.cn/currency/v1/queryBalance"
|
||||
querystring = {
|
||||
"moduleCode": "1f3563d38896438db994f118d4ff53cb",
|
||||
"kps": self.param.get('kps'),
|
||||
}
|
||||
response = requests.get(url=url, params=querystring).json()
|
||||
# print(response)
|
||||
if response.get("data"):
|
||||
return response["data"]["balance"]
|
||||
else:
|
||||
return response["msg"]
|
||||
|
||||
def do_sign(self):
|
||||
'''
|
||||
执行签到任务
|
||||
:return: 返回一个字符串,包含签到结果
|
||||
'''
|
||||
log = ""
|
||||
# 每日领空间
|
||||
growth_info = self.get_growth_info()
|
||||
if growth_info:
|
||||
log += (
|
||||
f" {'88VIP' if growth_info['88VIP'] else '普通用户'} {self.param.get('user')}\n"
|
||||
f"💾 网盘总容量:{self.convert_bytes(growth_info['total_capacity'])},"
|
||||
f"签到累计容量:")
|
||||
if "sign_reward" in growth_info['cap_composition']:
|
||||
log += f"{self.convert_bytes(growth_info['cap_composition']['sign_reward'])}\n"
|
||||
else:
|
||||
log += "0 MB\n"
|
||||
if growth_info["cap_sign"]["sign_daily"]:
|
||||
log += (
|
||||
f"✅ 签到日志: 今日已签到+{self.convert_bytes(growth_info['cap_sign']['sign_daily_reward'])},"
|
||||
f"连签进度({growth_info['cap_sign']['sign_progress']}/{growth_info['cap_sign']['sign_target']})\n"
|
||||
)
|
||||
else:
|
||||
sign, sign_return = self.get_growth_sign()
|
||||
if sign:
|
||||
log += (
|
||||
f"✅ 执行签到: 今日签到+{self.convert_bytes(sign_return)},"
|
||||
f"连签进度({growth_info['cap_sign']['sign_progress'] + 1}/{growth_info['cap_sign']['sign_target']})\n"
|
||||
)
|
||||
else:
|
||||
log += f"❌ 签到异常: {sign_return}\n"
|
||||
else:
|
||||
log += f"❌ 签到异常: 获取成长信息失败\n"
|
||||
|
||||
return log
|
||||
|
||||
|
||||
def extract_params(url):
|
||||
'''
|
||||
从URL中提取所需的参数
|
||||
:param url: 包含参数的URL
|
||||
:return: 返回一个字典,包含所需的参数
|
||||
'''
|
||||
# 提取URL中的查询参数部分(?后面的内容)
|
||||
query_start = url.find('?')
|
||||
query_string = url[query_start + 1:] if query_start != -1 else ''
|
||||
|
||||
# 解析查询参数
|
||||
params = {}
|
||||
for param in query_string.split('&'):
|
||||
if '=' in param:
|
||||
key, value = param.split('=', 1)
|
||||
params[key] = value
|
||||
|
||||
# 返回所需的参数
|
||||
return {
|
||||
'kps': params.get('kps', ''),
|
||||
'sign': params.get('sign', ''),
|
||||
'vcode': params.get('vcode', '')
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
'''
|
||||
主函数
|
||||
:return: 返回一个字符串,包含签到结果
|
||||
'''
|
||||
msg = ""
|
||||
global cookie_quark
|
||||
cookie_quark = get_env()
|
||||
|
||||
print("✅ 检测到共", len(cookie_quark), "个夸克账号\n")
|
||||
|
||||
i = 0
|
||||
while i < len(cookie_quark):
|
||||
# 获取user_data参数
|
||||
user_data = {} # 用户信息
|
||||
for a in cookie_quark[i].replace(" ", "").split(';'):
|
||||
if not a == '':
|
||||
user_data.update({a[0:a.index('=')]: a[a.index('=') + 1:]})
|
||||
|
||||
# 从url参数中提取额外信息
|
||||
if 'url' in user_data:
|
||||
url_params = extract_params(user_data['url'])
|
||||
user_data.update(url_params)
|
||||
# print(user_data)
|
||||
|
||||
# 开始任务
|
||||
log = f"🙍🏻♂️ 第{i + 1}个账号"
|
||||
msg += log
|
||||
# 登录
|
||||
log = Quark(user_data).do_sign()
|
||||
msg += log + "\n"
|
||||
|
||||
i += 1
|
||||
|
||||
print(msg)
|
||||
|
||||
try:
|
||||
send('夸克自动签到', msg)
|
||||
except Exception as err:
|
||||
print('%s\n❌ 错误,请查看运行日志!' % err)
|
||||
|
||||
return msg[:-1]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("----------夸克网盘开始签到----------")
|
||||
main()
|
||||
print("----------夸克网盘签到完毕----------")
|
||||
123
脚本库/APP版/账密/1_获取登录验证码/2025-11-20_1_generate_code_8c9e80a6.py
Normal file
123
脚本库/APP版/账密/1_获取登录验证码/2025-11-20_1_generate_code_8c9e80a6.py
Normal file
@@ -0,0 +1,123 @@
|
||||
# Source: https://github.com/AkenClub/ken-iMoutai-Script/blob/main/1_generate_code.py
|
||||
# Raw: https://raw.githubusercontent.com/AkenClub/ken-iMoutai-Script/main/1_generate_code.py
|
||||
# Repo: AkenClub/ken-iMoutai-Script
|
||||
# Path: 1_generate_code.py
|
||||
# UploadedAt: 2025-11-20T09:54:34+08:00
|
||||
# SHA256: 8c9e80a6466a2291a043e5852ed029aa0d610a9f58974757a3b82c4ecd675d34
|
||||
# Category: APP版/账密
|
||||
# Evidence: username/password/mobile/login
|
||||
|
||||
import time
|
||||
import hashlib
|
||||
import uuid
|
||||
import requests
|
||||
import logging
|
||||
import json
|
||||
import re
|
||||
"""
|
||||
1、获取登录验证码,获取 DEVICE_ID、MT_VERSION 等数据
|
||||
"""
|
||||
|
||||
# ------ 填写以下 1 个变量值 --------
|
||||
|
||||
# 填写手机号码,收到验证码后在 login.py 中填写验证码
|
||||
PHONE_NUMBER = ""
|
||||
# --------------------
|
||||
|
||||
# -------- 非必填 ------------
|
||||
# 设备 ID,留空则自动生成;
|
||||
# 若是想要保留之前的设备 ID,可以在这里填写之前的值,则不会生成新的设备 ID。
|
||||
DEVICE_ID_DEFAULT = ""
|
||||
# --------------------
|
||||
'''
|
||||
cron: 1 1 1 1 *
|
||||
new Env("1_获取登录验证码")
|
||||
'''
|
||||
|
||||
# ***** 以下内容不用动 *****
|
||||
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
SALT = "2af72f100c356273d46284f6fd1dfc08"
|
||||
|
||||
|
||||
def get_device_id():
|
||||
if DEVICE_ID_DEFAULT:
|
||||
return DEVICE_ID_DEFAULT
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
def signature(content, time):
|
||||
text = SALT + content + str(time)
|
||||
md5 = hashlib.md5(text.encode()).hexdigest()
|
||||
return md5
|
||||
|
||||
|
||||
def send_code(mobile, device_id, mt_version):
|
||||
cur_time = int(time.time() * 1000)
|
||||
data = {
|
||||
"mobile": mobile,
|
||||
"md5": signature(mobile, cur_time),
|
||||
"timestamp": str(cur_time)
|
||||
}
|
||||
headers = {
|
||||
"MT-Device-ID": device_id,
|
||||
"MT-APP-Version": mt_version,
|
||||
"User-Agent": "iOS;16.3;Apple;?unrecognized?",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
url = "https://app.moutai519.com.cn/xhr/front/user/register/vcode"
|
||||
response = requests.post(url, headers=headers, data=json.dumps(data))
|
||||
json_response = response.json()
|
||||
|
||||
if json_response.get("code") == 2000:
|
||||
logging.info(f"「发送验证码返回」:{json.dumps(json_response)}")
|
||||
return True
|
||||
else:
|
||||
logging.error(f"「发送验证码-失败」:{json.dumps(json_response)}")
|
||||
raise Exception("发送验证码错误")
|
||||
|
||||
|
||||
def get_mt_version():
|
||||
url = "https://apps.apple.com/cn/app/i%E8%8C%85%E5%8F%B0/id1600482450"
|
||||
with requests.get(url) as response:
|
||||
response.encoding = 'utf-8' # 确保使用正确的编码
|
||||
html_content = response.text
|
||||
|
||||
# 使用正则表达式匹配版本号(已过时,这里只是作为兼容,避免又使用回这种规则)
|
||||
pattern = re.compile(r'new__latest__version">(.*?)</p>', re.DOTALL)
|
||||
match = pattern.search(html_content)
|
||||
|
||||
if not match:
|
||||
# 新的匹配方式
|
||||
pattern = re.compile(r'class="metadata[^"]*">\s*<h4[^>]*>(.*?)</h4>', re.DOTALL)
|
||||
match = pattern.search(html_content)
|
||||
|
||||
if match:
|
||||
mt_version = match.group(1).strip().replace('版本 ', '') # 去掉前后的空白字符
|
||||
return mt_version
|
||||
|
||||
raise Exception("获取版本号失败")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 判断 PHONE_NUMBER 是否为空
|
||||
if not PHONE_NUMBER:
|
||||
logging.error("「发送验证码-失败」:请填写手机号码")
|
||||
raise Exception("请填写手机号码")
|
||||
|
||||
try:
|
||||
mt_version = get_mt_version()
|
||||
device_id = get_device_id()
|
||||
|
||||
# 发送验证码
|
||||
if send_code(PHONE_NUMBER, device_id, mt_version):
|
||||
logging.info(f"{PHONE_NUMBER}:验证码发送成功,请注意查收!")
|
||||
|
||||
logging.info("****************************")
|
||||
logging.info("记录以下信息用于后续登录:")
|
||||
logging.info(f"设备ID - DEVICE_ID:{device_id}")
|
||||
logging.info(f"版本号 - MT_VERSION:{mt_version}")
|
||||
except Exception as e:
|
||||
logging.error(e)
|
||||
104
脚本库/APP版/账密/2_登录/2025-11-20_2_login_15377770.py
Normal file
104
脚本库/APP版/账密/2_登录/2025-11-20_2_login_15377770.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# Source: https://github.com/AkenClub/ken-iMoutai-Script/blob/main/2_login.py
|
||||
# Raw: https://raw.githubusercontent.com/AkenClub/ken-iMoutai-Script/main/2_login.py
|
||||
# Repo: AkenClub/ken-iMoutai-Script
|
||||
# Path: 2_login.py
|
||||
# UploadedAt: 2025-11-20T09:54:34+08:00
|
||||
# SHA256: 153777702f935023b805885108b95cc7ecbd1b8f473d6024e20a4f594909fcba
|
||||
# Category: APP版/账密
|
||||
# Evidence: username/password/mobile/login
|
||||
|
||||
import time
|
||||
import hashlib
|
||||
import requests
|
||||
import json
|
||||
import logging
|
||||
"""
|
||||
2、登录,获取 USER_ID、TOKEN、COOKIE 等数据
|
||||
|
||||
上一步获取到验证码后,根据上一步输出的版本号和设备ID,填入下面信息进行登录请求,
|
||||
运行后,输出用户信息,并记录用于后续预约。
|
||||
|
||||
"""
|
||||
# ------ 填写以下 4 个变量值 --------
|
||||
|
||||
# 手机号码,和上一步的手机号码一致
|
||||
PHONE_NUMBER = ''
|
||||
# 验证码,填写收到的验证码
|
||||
CODE = ""
|
||||
# 设备 ID,和上一步的设备 ID 一致
|
||||
DEVICE_ID = ""
|
||||
# 版本号,和上一步的版本号一致
|
||||
MT_VERSION = ""
|
||||
|
||||
# --------------------
|
||||
'''
|
||||
cron: 1 1 1 1 *
|
||||
new Env("2_登录")
|
||||
'''
|
||||
|
||||
# ***** 以下内容不用动 *****
|
||||
|
||||
SALT = "2af72f100c356273d46284f6fd1dfc08"
|
||||
logging.basicConfig(level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s')
|
||||
|
||||
|
||||
def signature(content, time):
|
||||
text = SALT + content + str(time)
|
||||
md5 = hashlib.md5(text.encode()).hexdigest()
|
||||
return md5
|
||||
|
||||
|
||||
def login(mobile, code, device_id, mt_version):
|
||||
cur_time = int(time.time() * 1000)
|
||||
data = {
|
||||
"mobile": mobile,
|
||||
"vCode": code,
|
||||
"md5": signature(mobile + code, cur_time),
|
||||
"timestamp": str(cur_time),
|
||||
"MT-APP-Version": mt_version
|
||||
}
|
||||
headers = {
|
||||
"MT-Device-ID": device_id,
|
||||
"MT-APP-Version": mt_version,
|
||||
"User-Agent": "iOS;16.3;Apple;?unrecognized?",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
url = "https://app.moutai519.com.cn/xhr/front/user/register/login"
|
||||
response = requests.post(url, headers=headers, data=json.dumps(data))
|
||||
json_response = response.json()
|
||||
|
||||
# 输出登录请求的返回结果
|
||||
logging.info(f"「登录请求返回」:{json.dumps(json_response)}")
|
||||
|
||||
if json_response.get("code") == 2000:
|
||||
logging.info("「登录请求-成功」")
|
||||
user_data = json_response.get("data", {})
|
||||
filtered_data = {
|
||||
k: v
|
||||
for k, v in user_data.items() if k not in
|
||||
["idType", "verifyStatus", "idCode", "birthday", "userTag"]
|
||||
}
|
||||
return filtered_data
|
||||
else:
|
||||
logging.error("「登录请求-失败」")
|
||||
raise Exception("登录失败,本地错误日志已记录")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 判断入参
|
||||
if not PHONE_NUMBER or not CODE or not DEVICE_ID or not MT_VERSION:
|
||||
logging.error("「登录请求-失败」:缺少必要参数")
|
||||
raise Exception("缺少必要参数")
|
||||
|
||||
result = login(PHONE_NUMBER, CODE, DEVICE_ID, MT_VERSION)
|
||||
logging.info(f"「结果:用户信息」:{json.dumps(result)}")
|
||||
|
||||
logging.info("****************************")
|
||||
logging.info("记录以下信息用于后续预约:")
|
||||
logging.info(f"手机号码 - PHONE_NUMBER:{PHONE_NUMBER}")
|
||||
logging.info(f"设备ID - DEVICE_ID:{DEVICE_ID}")
|
||||
logging.info(f"版本号 - MT_VERSION:{MT_VERSION}")
|
||||
logging.info(f"用户ID - USER_ID:{result['userId']}")
|
||||
logging.info(f"Token - TOKEN:{result['token']}")
|
||||
logging.info(f"Cookie - COOKIE:{result['cookie']}")
|
||||
@@ -0,0 +1,276 @@
|
||||
# Source: https://github.com/AkenClub/ken-iMoutai-Script/blob/main/98_check_reservation_result.py
|
||||
# Raw: https://raw.githubusercontent.com/AkenClub/ken-iMoutai-Script/main/98_check_reservation_result.py
|
||||
# Repo: AkenClub/ken-iMoutai-Script
|
||||
# Path: 98_check_reservation_result.py
|
||||
# UploadedAt: 2025-11-20T09:54:34+08:00
|
||||
# SHA256: 43d3c06749fa2ee28e34dee08daf91b99b3b8eb224b7095e6bad3d444d3e10e2
|
||||
# Category: APP版/账密
|
||||
# Evidence: username/password/mobile/login
|
||||
|
||||
"""
|
||||
98、查询申购结果
|
||||
|
||||
*** 需要安装依赖 PyJWT ***
|
||||
|
||||
通知:运行结果会调用青龙面板的通知渠道。
|
||||
|
||||
配置环境变量:KEN_IMAOTAI_ENV
|
||||
-- 在旧版本青龙(例如 v2.13.8)中,使用 $ 作为分隔符时会出现解析环境变量失败,此时可以把 `$` 分隔符换为 `#` 作为分隔符。
|
||||
-- 📣 怕出错?**建议直接使用 `#` 作为分隔符即可** (2024-10-15 更新支持)。
|
||||
内容格式为:PHONE_NUMBER$USER_ID$DEVICE_ID$MT_VERSION$PRODUCT_ID_LIST$SHOP_ID^SHOP_MODE^PROVINCE^CITY$LAT$LNG$TOKEN$COOKIE
|
||||
解释:手机号码$用户ID$设备ID$版本号$商品ID列表$店铺ID店铺缺货时自动采用的模式^省份^城市$纬度$经度$TOKEN$COOKIE
|
||||
多个用户时使用 & 连接
|
||||
|
||||
说明:^SHOP_MODE^PROVINCE^CITY 为可选
|
||||
|
||||
常量。
|
||||
- PHONE_NUMBER: 用户的手机号码。 --- 自己手机号码
|
||||
- CODE: 短信验证码。 --- 运行 1_generate_code.py 获取
|
||||
- DEVICE_ID: 设备的唯一标识符。 --- 运行 1_generate_code.py 获取
|
||||
- MT_VERSION: 应用程序的版本号。 --- 运行 1_generate_code.py 获取
|
||||
- USER_ID: 用户的唯一标识符。 --- 运行 2_login.py 获取
|
||||
- TOKEN: 用于身份验证的令牌。 --- 运行 2_login.py 获取
|
||||
- COOKIE: 用于会话管理的Cookie。 --- 运行 2_login.py 获取
|
||||
- PRODUCT_ID_LIST: 商品ID列表,表示用户想要预约的商品。--- 运行 3_retrieve_shop_and_product_info.py 获取
|
||||
- SHOP_ID: 店铺的唯一标识符。 --- 运行 3_retrieve_shop_and_product_info.py 获取
|
||||
可设置为 AUTO,则根据 SHOP_MODE 的值来选择店铺 ID。
|
||||
- SHOP_MODE:店铺缺货模式,可选值为NEAREST(距离最近)或INVENTORY(库存最多)。设置该值时,需要同时设置 PROVINCE 和 CITY。
|
||||
非必填,但 SHOP_ID 设置 AUTO 时为必填,需要同时设置 SHOP_MODE、PROVINCE 和 CITY。
|
||||
- PROVINCE: 用户所在的省份。 --- 与 3_retrieve_shop_and_product_info.py 填写的省份一致
|
||||
非必填,但 SHOP_MODE 设置为 NEAREST 或 INVENTORY 时为必填。
|
||||
- CITY: 用户所在的城市。 --- 与 3_retrieve_shop_and_product_info.py 填写的城市一致
|
||||
非必填,但 SHOP_MODE 设置为 NEAREST 或 INVENTORY 时为必填。
|
||||
- LAT: 用户所在位置的纬度。 --- 运行 3_retrieve_shop_and_product_info.py 获取
|
||||
- LNG: 用户所在位置的经度。 --- 运行 3_retrieve_shop_and_product_info.py 获取
|
||||
|
||||
"""
|
||||
|
||||
import datetime
|
||||
import time
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
import ast
|
||||
import io
|
||||
import logging
|
||||
import re
|
||||
|
||||
from notify import send
|
||||
|
||||
# 每日 18:03 定时查询并通知
|
||||
'''
|
||||
cron: 03 18 * * *
|
||||
new Env("98_查询申购结果")
|
||||
'''
|
||||
|
||||
# 创建 StringIO 对象
|
||||
log_stream = io.StringIO()
|
||||
|
||||
# 配置 logging
|
||||
logger = logging.getLogger()
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
# 创建控制台 Handler
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setFormatter(
|
||||
logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
||||
|
||||
# 创建 StringIO Handler
|
||||
stream_handler = logging.StreamHandler(log_stream)
|
||||
# stream_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
|
||||
|
||||
# 将两个 Handler 添加到 logger
|
||||
logger.addHandler(console_handler)
|
||||
logger.addHandler(stream_handler)
|
||||
|
||||
# 调试模式
|
||||
DEBUG = False
|
||||
|
||||
# 读取 KEN_IMAOTAI_ENV 环境变量
|
||||
KEN_IMAOTAI_ENV = os.getenv('KEN_IMAOTAI_ENV', '')
|
||||
|
||||
# 解析 KEN_IMAOTAI_ENV 环境变量并保存到 user 列表
|
||||
users = []
|
||||
if KEN_IMAOTAI_ENV:
|
||||
env_list = KEN_IMAOTAI_ENV.split('&')
|
||||
for env in env_list:
|
||||
try:
|
||||
# 使用 re.split() 分割字符串,支持 '#' 和 '$'
|
||||
split_values = re.split(r'[#$]', env)
|
||||
|
||||
PHONE_NUMBER, USER_ID, DEVICE_ID, MT_VERSION, PRODUCT_ID_LIST, SHOP_INFO, LAT, LNG, TOKEN, COOKIE = split_values
|
||||
|
||||
SHOP_MODE = ''
|
||||
PROVINCE = ''
|
||||
CITY = ''
|
||||
|
||||
if '^' in SHOP_INFO:
|
||||
parts = SHOP_INFO.split('^')
|
||||
if len(parts) > 1:
|
||||
# 检测 parts 长度是否为 4,否则抛出异常
|
||||
if len(parts) != 4:
|
||||
raise Exception(
|
||||
"🚫 店铺缺货模式值错误,请检查是否为 SHOP_ID^SHOP_MODE^PROVINCE^CITY"
|
||||
)
|
||||
SHOP_ID, SHOP_MODE, PROVINCE, CITY = parts
|
||||
# 检测 SHOP_MODE 是否为 NEAREST 或 INVENTORY
|
||||
if SHOP_MODE not in ['NEAREST', 'INVENTORY', '']:
|
||||
raise Exception(
|
||||
"🚫 店铺缺货模式值错误,请检查 SHOP_MODE 值是否为 NEAREST(<默认> 距离最近) 或 INVENTORY(库存最多) 或 空字符串(不选择其他店铺)"
|
||||
)
|
||||
# 如果 SHOP_MODE 值合法,则需要配合检测 PROVINCE 和 CITY 是否为空(接口需要用到这些值)
|
||||
if not PROVINCE or not CITY:
|
||||
raise Exception(
|
||||
"🚫 店铺缺货模式值为 NEAREST 或 INVENTORY 时,需要同时设置 PROVINCE 和 CITY"
|
||||
)
|
||||
else:
|
||||
logging.warning(
|
||||
"🚨🚨 建议根据环境变量格式,设置 SHOP_ID^SHOP_MODE^PROVINCE^CITY 值,否则无法在指定店铺缺货时自动预约其他店铺!🚨🚨"
|
||||
)
|
||||
# 如果 SHOP_INFO 没有 ^ 符号,则 SHOP_ID 为 SHOP_INFO
|
||||
SHOP_ID = SHOP_INFO
|
||||
|
||||
# 如果 SHOP_ID 为 AUTO,检查 SHOP_MODE 是否为空
|
||||
if SHOP_ID == 'AUTO' and not SHOP_MODE:
|
||||
raise Exception(
|
||||
"🚫 店铺缺货模式值错误,SHOP_ID 值为 AUTO 时,需设置 SHOP_MODE、PROVINCE 和 CITY 值 "
|
||||
)
|
||||
|
||||
user = {
|
||||
'PHONE_NUMBER': PHONE_NUMBER.strip(),
|
||||
'USER_ID': USER_ID.strip(),
|
||||
'DEVICE_ID': DEVICE_ID.strip(),
|
||||
'MT_VERSION': MT_VERSION.strip(),
|
||||
'PRODUCT_ID_LIST': ast.literal_eval(PRODUCT_ID_LIST.strip()),
|
||||
'SHOP_ID': SHOP_ID.strip(),
|
||||
'SHOP_MODE': SHOP_MODE.strip(),
|
||||
'PROVINCE': PROVINCE.strip(),
|
||||
'CITY': CITY.strip(),
|
||||
'LAT': LAT.strip(),
|
||||
'LNG': LNG.strip(),
|
||||
'TOKEN': TOKEN.strip(),
|
||||
'COOKIE': COOKIE.strip()
|
||||
}
|
||||
# 检查字段是否完整且有值,不检查 SHOP_MODE、PROVINCE、CITY 字段(PROVINCE 和 CITY 用于 SHOP_MODE 里,而 SHOP_MODE 可选)
|
||||
required_fields = [
|
||||
'PHONE_NUMBER', 'USER_ID', 'DEVICE_ID', 'MT_VERSION',
|
||||
'PRODUCT_ID_LIST', 'SHOP_ID', 'LAT', 'LNG', 'TOKEN', 'COOKIE'
|
||||
]
|
||||
if all(user.get(field) for field in required_fields):
|
||||
# 判断 PRODUCT_ID_LIST 长度是否大于 0
|
||||
if len(user['PRODUCT_ID_LIST']) > 0:
|
||||
users.append(user)
|
||||
else:
|
||||
raise Exception("🚫 预约商品列表 - PRODUCT_ID_LIST 值为空,请添加后重试")
|
||||
else:
|
||||
logging.info(f"🚫 用户信息不完整: {user}")
|
||||
except Exception as e:
|
||||
errText = f"🚫 KEN_IMAOTAI_ENV 环境变量格式错误: {e}"
|
||||
send("i茅台预约日志:", errText)
|
||||
raise Exception(errText)
|
||||
|
||||
logging.info("找到以下用户配置:")
|
||||
# 输出用户信息
|
||||
for index, user in enumerate(users):
|
||||
if DEBUG:
|
||||
logging.info(f"用户 {index + 1}: {user}")
|
||||
continue
|
||||
logging.info(f"用户 {index + 1}: 📞 {user['PHONE_NUMBER']}")
|
||||
|
||||
else:
|
||||
errText = "🚫 KEN_IMAOTAI_ENV 环境变量未定义"
|
||||
send("i茅台预约日志:", errText)
|
||||
raise Exception(errText)
|
||||
|
||||
|
||||
# 生成请求头
|
||||
def generate_headers(device_id, mt_version, token):
|
||||
mt_k = f'{int(time.time() * 1000)}'
|
||||
headers = {
|
||||
"User-Agent": "iOS;16.3;Apple;?unrecognized?",
|
||||
"MT-Device-ID": device_id,
|
||||
"MT-APP-Version": mt_version,
|
||||
'MT-Token': token,
|
||||
'MT-Network-Type': 'WIFI',
|
||||
'MT-User-Tag': '0',
|
||||
'MT-K': mt_k,
|
||||
'MT-Bundle-ID': 'com.moutai.mall',
|
||||
'MT-R': 'clips_OlU6TmFRag5rCXwbNAQ/Tz1SKlN8THcecBp/HGhHdw==',
|
||||
'MT-SN': 'clips_ehwpSC0fLBggRnJAdxYgFiAYLxl9Si5PfEl/TC0afkw='
|
||||
}
|
||||
return headers
|
||||
|
||||
|
||||
# 查询申购结果
|
||||
def check_reservation_result(token, device_id, mt_version):
|
||||
global DEBUG
|
||||
try:
|
||||
url = f"https://app.moutai519.com.cn/xhr/front/mall/reservation/list/pageOne/queryV2"
|
||||
headers = generate_headers(device_id, mt_version, token)
|
||||
|
||||
response = requests.get(url, headers=headers)
|
||||
resultData = json.loads(response.text)
|
||||
resultCode = resultData.get("code")
|
||||
if resultCode == 4820:
|
||||
message = resultData.get("data", {}).get("updateDesc", "API 可能限制了 APP 版本,可以尝试重新生成环境变量")
|
||||
raise Exception(f"({resultCode}){message}")
|
||||
elif resultCode != 2000:
|
||||
message = resultData.get("message")
|
||||
raise Exception(f"({resultCode}){message}")
|
||||
|
||||
# 处理预约结果
|
||||
reservations = resultData.get("data", {}).get("reservationItemVOS", [])
|
||||
if not reservations:
|
||||
logging.info("🚫 暂无申购记录")
|
||||
return
|
||||
|
||||
# 获取当天日期
|
||||
today = datetime.datetime.now().date()
|
||||
today_str = today.strftime("%Y-%m-%d")
|
||||
logging.info(f"📅 今天的日期是: {today_str}")
|
||||
|
||||
for item in reservations:
|
||||
# 获取预约时间
|
||||
reservation_time = datetime.datetime.fromtimestamp(
|
||||
item.get("reservationTime") / 1000).date()
|
||||
# 筛选今天预约的商品
|
||||
if reservation_time == today:
|
||||
status_text = {
|
||||
0: "⌛️ 静候申购结果",
|
||||
1: "❌ 申购失败",
|
||||
2: "🎉 申购成功"
|
||||
}.get(item.get("status"), "未知状态")
|
||||
|
||||
session_name = f"[{item.get('sessionName', '')}]" if item.get(
|
||||
'sessionName') else ""
|
||||
item_name = item.get("itemName", "")
|
||||
item_id = item.get("itemId", "")
|
||||
|
||||
# 输出结果
|
||||
logging.info(
|
||||
f"🍺 {session_name}[{item_id}] {item_name}:{status_text}。")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"🚫 查询申购结果失败: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
logging.info('--------------------------')
|
||||
logging.info("💬 申购成功后将以短信形式通知您,请您在申购成功次日18:00前选择支付方式,并根据提示完成支付。")
|
||||
for user in users:
|
||||
try:
|
||||
logging.info('--------------------------')
|
||||
logging.info(f"📞 用户 {user['PHONE_NUMBER']} 开始查询申购结果")
|
||||
|
||||
check_reservation_result(user['TOKEN'],
|
||||
user['DEVICE_ID'], user['MT_VERSION'])
|
||||
except Exception as e:
|
||||
logging.error(
|
||||
f"🚫 用户 {user['PHONE_NUMBER']} 查询异常: {e},请到 App 上查看申购结果。")
|
||||
|
||||
logging.info('--------------------------')
|
||||
logging.info("✅ 所有用户查询完成")
|
||||
|
||||
log_contents = log_stream.getvalue()
|
||||
send("i茅台 查询申购结果日志:", log_contents)
|
||||
Reference in New Issue
Block a user