146 lines
17 KiB
JavaScript
146 lines
17 KiB
JavaScript
|
const SCRIPT_NAME = "AppleStore";
|
||
|
const IPHONE_MODEL_KEY = "iphone_model";
|
||
|
const APPLE_STORE_REGION_KEY = "apple_store_region";
|
||
|
const IPHONE_STOCK_KEY = "iphone_stock";
|
||
|
const NOTIFY_SOLD_OUT = false;
|
||
|
|
||
|
let magicJS = MagicJS(SCRIPT_NAME);
|
||
|
magicJS.barkUrl = magicJS.read("applestore_bark_url") || magicJS.read("magicjs_bark_url");
|
||
|
|
||
|
function getiPhoneProStock(parts, location) {
|
||
|
return new Promise((resolve) => {
|
||
|
url = encodeURI(`https://www.apple.com.cn/shop/fulfillment-messages?pl=true&mt=compact&parts.0=${parts}&location=${location}`);
|
||
|
magicJS.get(url, (err, resp, data) => {
|
||
|
let obj = JSON.parse(data);
|
||
|
let stores = obj["body"]["content"]["pickupMessage"]["stores"];
|
||
|
if (stores) {
|
||
|
resolve(stores);
|
||
|
} else {
|
||
|
magicJS.notify("查询库存失败,请检查配置是否正确。");
|
||
|
resolve([]);
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
async function watchStock(iphone_models, apple_store_region) {
|
||
|
let stock = magicJS.read(IPHONE_STOCK_KEY);
|
||
|
stock = !!stock ? stock : {};
|
||
|
let len = iphone_models.length;
|
||
|
|
||
|
for (let i = 0; i < len; i++) {
|
||
|
let parts = iphone_models[i];
|
||
|
let subObj = { watch: 0, pickup: 0, soldout: 0, changed: 0 };
|
||
|
let availability = await getiPhoneProStock(parts, apple_store_region);
|
||
|
|
||
|
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();
|
||
|
let logStr = `${stock[parts]["title"]}\n`;
|
||
|
let title = `${stock[parts]["title"]} - ${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 (NOTIFY_SOLD_OUT) {
|
||
|
content = !!stockInContent ? stockInContent + `\n${soldOutContent}\n${unchangContent}` : `${soldOutContent}\n${unchangContent}`;
|
||
|
}
|
||
|
if (!!content) {
|
||
|
let subTitle = `监控: ${subObj.watch} 售罄: ${subObj.soldout} 有货: ${subObj.pickup} ${watchResult}`;
|
||
|
magicJS.notify(title, subTitle, content, "applestore://");
|
||
|
}
|
||
|
magicJS.logInfo(logStr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 存储本次库存检查结果
|
||
|
magicJS.write(IPHONE_STOCK_KEY, stock);
|
||
|
}
|
||
|
|
||
|
(async () => {
|
||
|
let iphone_model = magicJS.read(IPHONE_MODEL_KEY).trim();
|
||
|
let apple_store_region = magicJS.read(APPLE_STORE_REGION_KEY).trim();
|
||
|
|
||
|
if (!iphone_model || !apple_store_region) {
|
||
|
let msg = "请先在BoxJS中配置心仪的iPhne型号及购买地区";
|
||
|
magicJS.logWarning(msg);
|
||
|
magicJS.notify(msg);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
let iphone_models = iphone_model.split(";");
|
||
|
|
||
|
// 监控库存
|
||
|
await watchStock(iphone_models, apple_store_region);
|
||
|
|
||
|
if (magicJS.isNode) {
|
||
|
while (6 <= new Date().getHours() <= 23) {
|
||
|
await watchStock(iphone_models, apple_store_region);
|
||
|
await magicJS.sleep(3000);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
magicJS.done();
|
||
|
})();
|
||
|
|
||
|
// prettier-ignore
|
||
|
function MagicJS(scriptName="MagicJS",logLevel="INFO"){return new class{constructor(){if(this.version="2.2.3.5",this.scriptName=scriptName,this.logLevels={DEBUG:5,INFO:4,NOTIFY:3,WARNING:2,ERROR:1,CRITICAL:0,NONE:-1},this.isLoon="undefined"!=typeof $loon,this.isQuanX="undefined"!=typeof $task,this.isJSBox="undefined"!=typeof $drive,this.isNode="undefined"!=typeof module&&!this.isJSBox,this.isSurge="undefined"!=typeof $httpClient&&!this.isLoon,this.node={request:void 0,fs:void 0,data:{}},this.iOSUserAgent="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",this.pcUserAgent="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",this.logLevel=logLevel,this._barkUrl="",this._barkKey="",this.isNode){this.node.fs=require("fs"),this.node.request=require("request");try{this.node.fs.accessSync("./magic.json",this.node.fs.constants.R_OK|this.node.fs.constants.W_OK)}catch(err){this.node.fs.writeFileSync("./magic.json","{}",{encoding:"utf8"})}this.node.data=require("./magic.json")}else this.isJSBox&&($file.exists("drive://MagicJS")||$file.mkdir("drive://MagicJS"),$file.exists("drive://MagicJS/magic.json")||$file.write({data:$data({string:"{}"}),path:"drive://MagicJS/magic.json"}))}set barkUrl(url){try{let _url=url.replace(/\/+$/g,"");this._barkUrl=`${/^https?:\/\/([^/]*)/.exec(_url)[0]}/push`,this._barkKey=/\/([^\/]+)\/?$/.exec(_url)[1]}catch(err){this.logDebug("读取Bark推送链接失败。")}}set logLevel(level){this._logLevel="string"==typeof level?level.toUpperCase():"DEBUG"}get logLevel(){return this._logLevel}get isRequest(){return"undefined"!=typeof $request&&"undefined"==typeof $response}get isResponse(){return"undefined"!=typeof $response}get request(){return"undefined"!=typeof $request?$request:void 0}get response(){return"undefined"!=typeof $response?($response.hasOwnProperty("status")&&($response.statusCode=$response.status),$response.hasOwnProperty("statusCode")&&($response.status=$response.statusCode),$response):void 0}get platform(){return this.isSurge?"Surge":this.isQuanX?"Quantumult X":this.isLoon?"Loon":this.isJSBox?"JSBox":this.isNode?"Node.js":"Unknown"}read(key,session=""){let val="";this.isSurge||this.isLoon?val=$persistentStore.read(key):this.isQuanX?val=$prefs.valueForKey(key):this.isNode?val=this.node.data:this.isJSBox&&(val=$file.read("drive://MagicJS/magic.json").string);try{this.isNode&&(val=val[key]),this.isJSBox&&(val=JSON.parse(val)[key]),session&&("string"==typeof val&&(val=JSON.parse(val)),val=val&&"object"==typeof val?val[session]:null)}catch(err){this.logError(err),val=session?{}:null,this.del(key)}void 0===val&&(val=null);try{val&&"string"==typeof val&&(val=JSON.parse(val))}catch(err){}return this.logDebug(`READ DATA [${key}]${session?`[${session}]`:""}(${typeof val})\n${JSON.stringify(val)}`),val}write(key,val,session=""){let data=session?{}:"";if(session&&(this.isSurge||this.isLoon)?data=$persistentStore.read(key):session&&this.isQuanX?data=$prefs.valueForKey(key):this.isNode?data=this.node.data:this.isJSBox&&(data=JSON.parse($file.read("drive://MagicJS/magic.json").string)),session){try{"string"==typeof data&&(data=JSON.parse(data)),data="object"==typeof data&&data?data:{}}catch(err){this.logError(err),this.del(key),data={}}this.isJSBox||this.isNode?(data[key]&&"object"==typeof data[key]||(data[key]={}),data[key].hasOwnProperty(session)||(data[key][session]=null),void 0===val?delete data[key][session]:data[key][session]=val):void 0===val?delete data[session]:data[session]=val}else this.isNode||this.isJSBox?void 0===val?delete data[key]:data[key]=val:data=void 0===val?null:val;"object"==typeof data&&(data=JSON.stringify(data)),this.isSurge||this.isLoon?$persistentStore.write(data,key):this.isQuanX?$prefs.setValueForKey(data,key):this.isNode?this.node.fs.writeFileSync("./magic.json",data):this.isJSBox&&$file.write({data:$data({string:data}),path:"drive://MagicJS/magic.json"}),this.logDebug(`WRITE DATA [${key}]${session?`[${sess
|