Clash/script/applestore/applestore.js

185 lines
24 KiB
JavaScript
Raw Normal View History

const SCRIPT_NAME = "AppleStore";
const APPLESTORE_MODEL_KEY = "applestore_goods_model";
const APPLESTORE_REGION_KEY = "applestore_region";
const APPLESTORE_STOCK_KEY = "applestore_goods_stock";
const APPLESTORE_RUN_FOREVER_KEY = "applestore_run_forever";
const APPLESTORE_WATCH_INTERVAL = "applestore_watch_interval";
const $ = MagicJS(SCRIPT_NAME);
function getGoodsStock(parts, location, option = "") {
return new Promise((resolve) => {
let url = "";
if (option != "") {
url = encodeURI(`https://www.apple.com.cn/shop/fulfillment-messages?pl=true&mt=compact&parts.0=${parts}&location=${location}&option.0=${option}&_=${new Date().getTime()}`);
} else {
url = encodeURI(`https://www.apple.com.cn/shop/fulfillment-messages?pl=true&mt=compact&parts.0=${parts}&location=${location}&_=${new Date().getTime()}`);
}
$.http.get(url).then(resp => {
const obj = resp.body;
let stores = obj["body"]["content"]["pickupMessage"]["stores"];
if (stores) {
resolve(stores);
} else {
$.logger.error("查询库存失败,请检查配置是否正确。");
resolve([]);
}
}).catch(err => {
$.logger.error(`查询库存出现异常,${err}`);
})
});
}
async function watchStock(goods_models, applestore_region) {
let stock = $.data.read(APPLESTORE_STOCK_KEY);
stock = stock || {};
let len = goods_models.length;
let tasks = [];
for (let i = 0; i < len; i++) {
const wrap = async () => {
let partsConfig = goods_models[i].split("#");
let parts = partsConfig[0];
let option = partsConfig.length >= 2 ? partsConfig[1] : "";
let name = partsConfig.length == 3 ? partsConfig[2] : "";
let subObj = { watch: 0, pickup: 0, soldout: 0, changed: 0 };
let availability = await getGoodsStock(parts, applestore_region, option);
if (availability && availability.length > 0) {
// 获取AppleStore取货信息
for (let store of availability) {
let storeNumber = store["storeNumber"];
if (!stock.hasOwnProperty(parts)) {
stock[parts] = { title: store["partsAvailability"][parts]["storePickupProductTitle"], stores: {} };
}
if (!stock[parts]["stores"][storeNumber]) {
stock[parts]["stores"][storeNumber] = { notify: false, pickup: false, msg: "等待查询", city: store["city"], name: store["storeName"] };
}
if (stock[parts]["stores"][storeNumber]["msg"] != store["partsAvailability"][parts]["pickupSearchQuote"]) {
// 更新库存情况
stock[parts]["stores"][storeNumber]["msg"] = store["partsAvailability"][parts]["pickupSearchQuote"];
stock[parts]["stores"][storeNumber]["pickup"] = store["partsAvailability"][parts]["pickupDisplay"] != "unavailable";
// 库存变化推送通知
stock[parts]["stores"][storeNumber]["notify"] = true;
} else {
// 库存未变化不推送
stock[parts]["stores"][storeNumber]["notify"] = false;
}
}
let now = new Date();
if (!stock[parts]["title"] && !name) {
name = "未命名商品";
}
let logStr = `${name}\n`;
let title = `${name} - ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`;
let watchResult = "全部售罄";
let stockInContent = ""; // 有库存的型号与店铺
let soldOutContent = ""; // 售罄的型号与店铺
let unchangContent = ""; // 没有变化的型号与店铺
let content = "";
// 整理通知内容
for (let storeStock of Object.values(stock[parts]["stores"])) {
subObj["watch"] += 1;
// 有货
if (storeStock["pickup"]) {
subObj["pickup"] += 1;
if (watchResult == "全部售罄") watchResult = `${storeStock["city"]} ${storeStock["name"]}`;
if (storeStock["notify"] === true) {
subObj["changed"] += 1;
if (!!stockInContent) stockInContent += "\n";
stockInContent += `🔆 ${storeStock["name"]} - ${storeStock["msg"]}`;
} else {
if (!!unchangContent) unchangContent += "\n";
unchangContent += `🔆 ${storeStock["name"]} - ${storeStock["msg"]}`;
}
logStr += `${storeStock["name"]} - ${storeStock["msg"]}\n`;
}
// 售罄
else {
subObj["soldout"] += 1;
if (storeStock["notify"] === true) {
subObj["changed"] += 1;
if (!!soldOutContent) soldOutContent += "\n";
soldOutContent += `🚫 ${storeStock["name"]} - ${storeStock["msg"]}`;
} else {
if (!!unchangContent) unchangContent += "\n";
unchangContent += `🚫 ${storeStock["name"]} - ${storeStock["msg"]}`;
}
logStr += `${storeStock["name"]} - ${storeStock["msg"]}\n`;
}
}
if (!!stockInContent) {
content = stockInContent;
}
// 配置为无货通知且存在无货情况时
if ($.data.read("applestore_settings_notify_soldout") == true) {
content = !!stockInContent ? stockInContent + `\n${soldOutContent}\n${unchangContent}` : !!soldOutContent ? `${soldOutContent}\n${unchangContent}` : unchangContent;
}
if (!!content) {
let subTitle = `监控: ${subObj.watch} 售罄: ${subObj.soldout} 有货: ${subObj.pickup} ${watchResult}`;
$.notification.post(title, subTitle, content, "applestore://");
}
$.logger.info(logStr);
}
}
tasks.push(wrap());
}
await Promise.all(tasks);
// 存储本次库存检查结果
$.data.write(APPLESTORE_STOCK_KEY, stock);
}
(async () => {
let goods_model = $.data.read(APPLESTORE_MODEL_KEY).trim();
let applestore_region = $.data.read(APPLESTORE_REGION_KEY).trim();
if (!goods_model || !applestore_region) {
let msg = "请先在BoxJS中配置心仪的商品型号及购买地区";
$.logger.warning(msg);
$.notification.post(msg);
return;
}
let goods_models = goods_model.split(";");
// 监控库存
await watchStock(goods_models, applestore_region);
// NodeJS环境只运行一次或无限监控库存
if ($.env.isNode) {
let interval = Number($.data.read(APPLESTORE_WATCH_INTERVAL, 5000));
interval = interval <= 2000? 5000: interval;
const runForever = $.data.read(APPLESTORE_RUN_FOREVER_KEY, false);
while (runForever === true) {
let hours = new Date().getHours()
if (hours <= 1 || hours >= 6) {
await watchStock(goods_models, applestore_region);
}
await $.utils.sleep(interval);
}
}
$.done();
})();
/**
*
* $$\ $$\ $$\ $$$$$\ $$$$$$\ $$$$$$\
* $$$\ $$$ | \__| \__$$ |$$ __$$\ $$ ___$$\
* $$$$\ $$$$ | $$$$$$\ $$$$$$\ $$\ $$$$$$$\ $$ |$$ / \__| \_/ $$ |
* $$\$$\$$ $$ | \____$$\ $$ __$$\ $$ |$$ _____| $$ |\$$$$$$\ $$$$$ /
* $$ \$$$ $$ | $$$$$$$ |$$ / $$ |$$ |$$ / $$\ $$ | \____$$\ \___$$\
* $$ |\$ /$$ |$$ __$$ |$$ | $$ |$$ |$$ | $$ | $$ |$$\ $$ | $$\ $$ |
* $$ | \_/ $$ |\$$$$$$$ |\$$$$$$$ |$$ |\$$$$$$$\\$$$$$$ |\$$$$$$ | \$$$$$$ |
* \__| \__| \_______| \____$$ |\__| \_______|\______/ \______/ \______/
* $$\ $$ |
* \$$$$$$ |
* \______/
*
*/
function MagicJS(e="MagicJS",t="INFO"){const r=()=>{const e=typeof $loon!=="undefined";const t=typeof $task!=="undefined";const n=typeof module!=="undefined";const r=typeof $httpClient!=="undefined"&&!e;const s=typeof $storm!=="undefined";const i=typeof $environment!=="undefined"&&typeof $environment["stash-build"]!=="undefined";const o=r||e||s||i;const l=typeof importModule!=="undefined";return{isLoon:e,isQuanX:t,isNode:n,isSurge:r,isStorm:s,isStash:i,isSurgeLike:o,isScriptable:l,get name(){if(e){return"Loon"}else if(t){return"QuantumultX"}else if(n){return"NodeJS"}else if(r){return"Surge"}else if(l){return"Scriptable"}else{return"unknown"}},get build(){if(r){return $environment["surge-build"]}else if(i){return $environment["stash-build"]}else if(s){return $storm.buildVersion}},get language(){if(r||i){return $environment["language"]}},get version(){if(r){return $environment["surge-version"]}else if(i){return $environment["stash-version"]}else if(s){return $storm.appVersion}else if(n){return process.version}},get system(){if(r){return $environment["system"]}else if(n){return process.platform}},get systemVersion(){if(s){return $storm.systemVersion}},get deviceName(){if(s){return $storm.deviceName}}}};const s=(n,e="INFO")=>{let r=e;const s={SNIFFER:6,DEBUG:5,INFO:4,NOTIFY:3,WARNING:2,ERROR:1,CRITICAL:0,NONE:-1};const i={SNIFFER:"",DEBUG:"",INFO:"",NOTIFY:"",WARNING:"❗ ",ERROR:"❌ ",CRITICAL:"❌ ",NONE:""};const t=(e,t="INFO")=>{if(!(s[r]<s[t.toUpperCase()]))console.log(`[${t}] [${n}]\n${i[t.toUpperCase()]}${e}\n`)};const o=e=>{r=e};return{setLevel:o,sniffer:e=>{t(e,"SNIFFER")},debug:e=>{t(e,"DEBUG")},info:e=>{t(e,"INFO")},notify:e=>{t(e,"NOTIFY")},warning:e=>{t(e,"WARNING")},error:e=>{t(e,"ERROR")},retry:e=>{t(e,"RETRY")}}};return new class{constructor(e,t){this._startTime=Date.now();this.version="3.0.0";this.scriptName=e;this.env=r();this.logger=s(e,t);this.http=typeof MagicHttp==="function"?MagicHttp(this.env,this.logger):undefined;this.data=typeof MagicData==="function"?MagicData(this.env,this.logger):undefined;this.notification=typeof MagicNotification==="function"?MagicNotification(this.scriptName,this.env,this.logger,this.http):undefined;this.utils=typeof MagicUtils==="function"?MagicUtils(this.env,this.logger):undefined;this.qinglong=typeof MagicQingLong==="function"?MagicQingLong(this.env,this.data,this.logger):undefined;if(typeof this.data!=="undefined"){let e=this.data.read("magic_loglevel");const n=this.data.read("magic_bark_url");if(e){this.logger.setLevel(e.toUpperCase())}if(n){this.notification.setBark(n)}}}get isRequest(){return typeof $request!=="undefined"&&typeof $response==="undefined"}get isResponse(){return typeof $response!=="undefined"}get isDebug(){return this.logger.level==="DEBUG"}get request(){return typeof $request!=="undefined"?$request:undefined}get response(){if(typeof $response!=="undefined"){if($response.hasOwnProperty("status"))$response["statusCode"]=$response["status"];if($response.hasOwnProperty("statusCode"))$response["status"]=$response["statusCode"];return $response}else{return undefined}}done=(e={})=>{this._endTime=Date.now();let t=(this._endTime-this._startTime)/1e3;this.logger.info(`SCRIPT COMPLETED: ${t} S.`);if(typeof $done!=="undefined"){$done(e)}}}(e,t)}function MagicHttp(u,f){const t="Mozilla/5.0 (iPhone; CPU iPhone OS 13_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.5 Mobile/15E148 Safari/604.1";const n="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36 Edg/84.0.522.59";let c;if(u.isNode){const l=require("axios");c=l.create()}class e{constructor(e=true){this.handlers=[];this.isRequest=e}use(e,t,n){this.handlers.push({fulfilled:e,rejected:t,synchronous:n?n.synchronous:false,runWhen:n?n.runWhen:null});return this.handlers.length-1}eject(e){if(this.handlers[e]){this.handlers[e]=null}}forEach(t){this.handlers.forEach(e=>{if(e!==null){t(e)}})}}function r(e){let n={...e};if(!!n.params){if(!u.isNode){let e=Object.keys(n.params).map(e=>{const t=encodeURIComponent(e);n.url=n.url.replace(