diff --git a/script/applestore/README.md b/script/applestore/README.md
index ca72179d6..6d6da9a20 100644
--- a/script/applestore/README.md
+++ b/script/applestore/README.md
@@ -1,10 +1,10 @@
-# Apple Store 库存监控
+# 🧸 Apple Store 库存监控
## 前言
-脚本用来监控线下AppleStore指定商品库存,目前验证过iPhone13、AppleWatch7系列。
+脚本用来监控线下AppleStore指定商品库存,已经支持iPhone14全系列。
长话短说,需要做一些准备:
@@ -16,25 +16,88 @@
### 确认型号
-iPhone13
+##### iPhone 14 系列
-https://www.apple.com.cn/shop/buy-iphone/iphone-13/MLDH3CH/A
+https://www.apple.com.cn/shop/buy-iphone/iphone-14
-iPhone 13 Pro
+##### iPhone 14 Pro 系列
-https://www.apple.com.cn/shop/buy-iphone/iphone-13-pro/MLTE3CH/A
+https://www.apple.com.cn/shop/buy-iphone/iphone-14-pro
-在上面的链接中选择需要的型号、颜色、容量,然后把地址中类似MLDH3CH/A的文本复制下来,就是需要监控的型号
+在上面的链接中选择需要的型号、颜色、容量,然后把浏览器地址中类似 `MLDH3CH/A` 的文本复制下来,就是需要监控的型号。
-AppleWatch
+如下图:
-https://www.apple.com.cn/shop/buy-watch/apple-watch/45mm-cellular-graphite-stainless-steel-pride-edition-braided-solo-loop-size-5
+![](https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/applestore/images/01.png)
-AppleWatch因为有选项,所以会稍微麻烦,需要在浏览器的“开发人员工具”中,找到“网络”,在“筛选器”中输入`https://www.apple.com.cn/shop/fulfillment-messages`,找到网络请求,将`parts.0=xxxxx`和`option.0=xxxxxxxxxxx`中xxxxx部分复制出来保存。
+##### Apple Watch 系列
-将两个拼接在一起,如 `Z0YQ#MKMR3CH/A,MJXA3FE/A`,就是需要监控的型号。
+Apple Watch 因为有选项,所以会稍微麻烦,请以下步骤操作:
-AppleWatch目前没办法自动获取标题,所以可以自定义标题,如`Z0YQ#MKMR3CH/A,MJXA3FE/A#AppleWatch 7 石墨色不锈钢`,把这部分输入到BoxJs的监控型号中。
+1. 在浏览器中选择你所需的型号,得到类似 `https://www.apple.com.cn/shop/buy-watch/apple-watch-ultra/49mm-cellular-titanium-black-gray-trail-loop-m-l` 的地址。
+2. 在浏览器中按下`F12`,打开“开发人员工具”,在顶部的标签中选择“网络”或“network”。
+3. 在一堆网络请求中找到类似 `fulfillment-messages` 的地址,如果没有找到,保持“开发人员工具”打开的情况下,再刷新一次页面。
+4. 把含有 `https://www.apple.com.cn/shop/fulfillment-messages` 的链接复制下来,里面有我们需要的信息。
+
+> 如果你对上述步骤感到比较疑惑,建议在搜索引擎中以“`Chrome 开发人员 工具 教程`”为关键词,搜索相关资料。如何查看浏览器的网络请求,并不在本项目的说明范围内。
+
+###### Apple Watch Ultra
+
+如果你需要监控的是Apple Watch Ultra,事情就很简单,把链接中类似parts.0=MNHT3CH/A的内容记录下来,`MNHT3CH/A`就是你需要监控的型号,保存好它!
+
+###### Apple Watch 8
+
+Apple Watch 8 中地址存在类似的格式 `parts.0=Z0YQ&option.0=MNNQ3CH/A,MPLE3FE/A` 。我们需要的是`parts.0` 和 `option.0` 之后的数据。
+
+以上面的地址为例,我们需要的是 `Z0YQ` 和 `MNNQ3CH/A,MPLE3FE/A`。
+
+至此,完成所需监控的iPhone或Apple Watch型号收集。
+
+### 型号拼接
+
+这步是把获取的型号,拼接成字符串,填写到BoxJS中,让脚本知道你要监控什么商品的库存。
+
+#### iPhone 14、iPhone 14 Pro
+
+采用如下格式
+
+```
+<型号>##<名称>
+```
+
+型号就是获取的设备型号,名称可以自己取,便于在通知中识别。
+
+假设上面获取的型号是MLDH3CH/A,那拼接后的字符串就是
+
+```
+MLDH3CH/A##小黑紫
+```
+
+注意有两个##
+
+#### Apple Watch
+
+##### Apple Watch Ultra
+
+和iPhone相似
+
+```
+MNHT3CH/A##Apple Watch Ultra
+```
+
+##### Apple Watch 8
+
+```
+Z0YQ#MNNQ3CH/A,MPLE3FE/A#Apple Watch 8
+```
+
+#### 多个型号
+
+如下拼接
+
+```
+MLDH3CH/A##小黑紫;MNHT3CH/A##Apple Watch Ultra;Z0YQ#MNNQ3CH/A,MPLE3FE/A#Apple Watch 8
+```
### 确认地区
@@ -42,11 +105,19 @@ AppleWatch目前没办法自动获取标题,所以可以自定义标题,如`
直辖市示例:北京 北京 昌平区
+如果搞不定的话,在Apple官网选择查看取货情况,把下面的字保存下来,每个间隔一个空格。
+
+![](https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/applestore/images/03.png)
+
+## BoxJS填写效果
+
+![](https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/applestore/images/04.png)
+
## 进阶问题
### 脏数据
-每次配置型号和地区,都必须在BoxJS中把`iphone_stock`的值清理掉,避免脏数据导致脚本异常。
+每次配置型号和地区,都必须在BoxJS中把库存的数据清理掉,避免脏数据导致脚本异常。
### 监控间隔
@@ -81,7 +152,7 @@ AppleWatch目前没办法自动获取标题,所以可以自定义标题,如`
使用模块
```ini
-https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/applestore/iphone.sgmodule
+https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/applestore/applestore.sgmodule
```
### Loon
@@ -89,7 +160,7 @@ https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/app
使用插件
```ini
-https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/applestore/iphone.lnplugin
+https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/applestore/applestore.lnplugin
```
### Quantumult X
@@ -98,6 +169,21 @@ https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/app
```ini
[task_local]
-0/5 * 6-23 * * * https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/applestore/iphone.js, tag=AppleStore_商品库存监控, enabled=true
+0/5 * 6-23 * * * https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/applestore/applestore.js, tag=AppleStore_商品库存监控, enabled=true
```
+## 变量说明
+
+NodeJS(含青龙面板),请根据下表配置magic.json的变量。
+
+更新详细的配置说明,请访问:https://github.com/blackmatrix7/ios_rule_script/blob/master/script/README.md。
+
+| 变量 | 类型 | 说明 |
+| ---------------------------------- | ------ | ------------------------------------------------------------ |
+| applestore_goods_model | string | 监控的商品型号,类似`MPU93CH/A##iPhone 14 午夜色 128G` 。 |
+| applestore_region | string | 监控的地区,类似`北京 北京 昌平区`、`吉林 长春 朝阳区` 。 |
+| applestore_settings_notify_soldout | bool | 售罄也进行推送,建议为`false` 。 |
+| applestore_run_forever | bool | NodeJS环境下只运行一次或无限次运行。只运行一次适合在青龙面板设置定时任务;无限运行适合用NodeJS直接启动脚本。 |
+| applestore_watch_interval | number | NodeJS无限运行时,库存监控间隔,单位毫秒,最小2000。 |
+| applestore_goods_stock | json | 监控的库存数据,自动生成,不要修改。 |
+
diff --git a/script/applestore/applestore.js b/script/applestore/applestore.js
index 3ca70b877..6608da756 100644
--- a/script/applestore/applestore.js
+++ b/script/applestore/applestore.js
@@ -1,10 +1,11 @@
const SCRIPT_NAME = "AppleStore";
-const APPLESTORE_MODEL_KEY = "goods_model";
+const APPLESTORE_MODEL_KEY = "applestore_goods_model";
const APPLESTORE_REGION_KEY = "applestore_region";
-const APPLESTORE_STOCK_KEY = "goods_stock";
+const APPLESTORE_STOCK_KEY = "applestore_goods_stock";
+const APPLESTORE_RUN_FOREVER_KEY = "applestore_run_forever";
+const APPLESTORE_WATCH_INTERVAL = "applestore_watch_interval";
-let magicJS = MagicJS(SCRIPT_NAME);
-magicJS.barkUrl = magicJS.read("applestore_bark_url") || magicJS.read("magicjs_bark_url");
+const $ = MagicJS(SCRIPT_NAME);
function getGoodsStock(parts, location, option = "") {
return new Promise((resolve) => {
@@ -14,40 +15,36 @@ function getGoodsStock(parts, location, option = "") {
} else {
url = encodeURI(`https://www.apple.com.cn/shop/fulfillment-messages?pl=true&mt=compact&parts.0=${parts}&location=${location}&_=${new Date().getTime()}`);
}
- magicJS.get(url, (err, resp, data) => {
- try{
- let obj = JSON.parse(data);
- let stores = obj["body"]["content"]["pickupMessage"]["stores"];
- if (stores) {
- resolve(stores);
- } else {
- magicJS.notify("查询库存失败,请检查配置是否正确。");
- resolve([]);
- }
- }
- catch (err){
- magicJS.logError(`解析库存数据失败,异常信息:${err}`);
+ $.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 = magicJS.read(APPLESTORE_STOCK_KEY);
- stock = !!stock ? stock : {};
+ 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 () =>{
+ 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) {
@@ -69,7 +66,7 @@ async function watchStock(goods_models, applestore_region) {
stock[parts]["stores"][storeNumber]["notify"] = false;
}
}
-
+
let now = new Date();
if (!stock[parts]["title"] && !name) {
name = "未命名商品";
@@ -81,7 +78,7 @@ async function watchStock(goods_models, applestore_region) {
let soldOutContent = ""; // 售罄的型号与店铺
let unchangContent = ""; // 没有变化的型号与店铺
let content = "";
-
+
// 整理通知内容
for (let storeStock of Object.values(stock[parts]["stores"])) {
subObj["watch"] += 1;
@@ -99,7 +96,7 @@ async function watchStock(goods_models, applestore_region) {
}
logStr += `${storeStock["name"]} - ${storeStock["msg"]}\n`;
}
-
+
// 售罄
else {
subObj["soldout"] += 1;
@@ -118,14 +115,14 @@ async function watchStock(goods_models, applestore_region) {
content = stockInContent;
}
// 配置为无货通知且存在无货情况时
- if (magicJS.read("applestore_settings_notify_soldout") == true) {
+ 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}`;
- magicJS.notify(title, subTitle, content, "applestore://");
+ $.notification.post(title, subTitle, content, "applestore://");
}
- magicJS.logInfo(logStr);
+ $.logger.info(logStr);
}
}
tasks.push(wrap());
@@ -133,17 +130,17 @@ async function watchStock(goods_models, applestore_region) {
await Promise.all(tasks);
// 存储本次库存检查结果
- magicJS.write(APPLESTORE_STOCK_KEY, stock);
+ $.data.write(APPLESTORE_STOCK_KEY, stock);
}
(async () => {
- let goods_model = magicJS.read(APPLESTORE_MODEL_KEY).trim();
- let applestore_region = magicJS.read(APPLESTORE_REGION_KEY).trim();
+ 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中配置心仪的商品型号及购买地区";
- magicJS.logWarning(msg);
- magicJS.notify(msg);
+ $.logger.warning(msg);
+ $.notification.post(msg);
return;
}
@@ -152,16 +149,37 @@ async function watchStock(goods_models, applestore_region) {
// 监控库存
await watchStock(goods_models, applestore_region);
- while (magicJS.isNode) {
- let hours = new Date().getHours()
- if (hours <= 1 || hours >= 6){
- 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);
}
- await magicJS.sleep(5000);
}
- magicJS.done();
+ $.done();
})();
-// prettier-ignore
-function MagicJS(scriptName="MagicJS",logLevel="INFO"){return new class{constructor(){if(this._startTime=Date.now(),this.version="2.2.3.6",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="INFO",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 config error.")}}set logLevel(level){let magic_loglevel=this.read("magicjs_loglevel");this._logLevel=magic_loglevel||level.toUpperCase()}get logLevel(){return this._logLevel}get isRequest(){return"undefined"!=typeof $request&&"undefined"==typeof $response}get isResponse(){return"undefined"!=typeof $response}get isDebug(){return"DEBUG"===this.logLevel}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?`[${session}]`:""}(${typeof val})\n${JSON.stringify(val)}`)}del(key,session=""){this.logDebug(`DELETE KEY [${key}]${session?`[${session}]`:""}`),this.write(key,null,session)}notify(title=this.scriptName,subTitle="",body="",opts=""){let convertOptions;if(opts=(_opts=>{let newOpts={};if("string"==typeof _opts)this.isLoon?newOpts={openUrl:_opts}:this.isQuanX?newOpts={"open-url":_opts}:this.isSurge&&(newOpts={url:_opts});else if("object"==typeof _opts)if(this.isLoon)newOpts.openUrl=_opts["open-url"]?_opts["open-url"]:"",newOpts.mediaUrl=_opts["media-url"]?_opts["media-url"]:"";else if(this.isQuanX)newOpts=_opts["open-url"]||_opts["media-url"]?_opts:{};else if(this.isSurge){let openUrl=_opts["open-url"]||_opts.openUrl;newOpts=openUrl?{url:openUrl}:{}}return newOpts})(opts),1==arguments.length&&(title=this.scriptName,subTitle="",body=arguments[0]),this.logNotify(`title:${title}\nsubTitle:${subTitle}\nbody:${body}\noptions:${"object"==typeof opts?JSON.stringify(opts):opts}`),this.isSurge)$notification.post(title,subTitle,body,opts);else if(this.isLoon)opts?$notification.post(title,subTitle,body,opts):$notification.post(title,subTitle,body);else if(this.isQuanX)$notify(title,subTitle,body,opts);else if(this.isJSBox){let push={title:title,body:subTitle?`${subTitle}\n${body}`:body};$push.schedule(push)}this._barkUrl&&this._barkKey&&this.notifyBark(title,subTitle,body)}notifyDebug(title=this.scriptName,subTitle="",body="",opts=""){"DEBUG"===this.logLevel&&(1==arguments.length&&(title=this.scriptName,subTitle="",body=arguments[0]),this.notify(title,subTitle,body,opts))}notifyBark(title=this.scriptName,subTitle="",body="",opts=""){let options={url:this._barkUrl,headers:{"Content-Type":"application/json; charset=utf-8"},body:{title:title,body:subTitle?`${subTitle}\n${body}`:body,device_key:this._barkKey}};this.post(options,err=>{})}log(msg,level="INFO"){this.logLevels[this._logLevel]void 0===_options.body?"":`${encodeURIComponent(key)}=${encodeURIComponent(_options.body[key])}`).join("&");_options.url.indexOf("?")<0&&(_options.url+="?"),_options.url.lastIndexOf("&")+1!=_options.url.length&&_options.url.lastIndexOf("?")+1!=_options.url.length&&(_options.url+="&"),_options.url+=qs,delete _options.body}return this.isQuanX?(_options.hasOwnProperty("body")&&"string"!=typeof _options.body&&(_options.body=JSON.stringify(_options.body)),_options.method=method):this.isNode?(delete _options.headers["Accept-Encoding"],"object"==typeof _options.body&&("GET"===method?(_options.qs=_options.body,delete _options.body):"POST"===method&&(_options.json=!0,_options.body=_options.body))):this.isJSBox&&(_options.header=_options.headers,delete _options.headers),_options}adapterHttpResponse(resp){let _resp={body:resp.body,headers:resp.headers,json:()=>JSON.parse(_resp.body)};return resp.hasOwnProperty("statusCode")&&resp.statusCode&&(_resp.status=resp.statusCode),_resp}get(options,callback){let _options=this.adapterHttpOptions(options,"GET");this.logDebug(`HTTP GET: ${JSON.stringify(_options)}`),this.isSurge||this.isLoon?$httpClient.get(_options,callback):this.isQuanX?$task.fetch(_options).then(resp=>{resp.status=resp.statusCode,callback(null,resp,resp.body)},reason=>callback(reason.error,null,null)):this.isNode?this.node.request.get(_options,(err,resp,data)=>{resp=this.adapterHttpResponse(resp),callback(err,resp,data)}):this.isJSBox&&(_options.handler=resp=>{let err=resp.error?JSON.stringify(resp.error):void 0,data="object"==typeof resp.data?JSON.stringify(resp.data):resp.data;callback(err,resp.response,data)},$http.get(_options))}getPromise(options){return new Promise((resolve,reject)=>{magicJS.get(options,(err,resp)=>{err?reject(err):resolve(resp)})})}post(options,callback){let _options=this.adapterHttpOptions(options,"POST");if(this.logDebug(`HTTP POST: ${JSON.stringify(_options)}`),this.isSurge||this.isLoon)$httpClient.post(_options,callback);else if(this.isQuanX)$task.fetch(_options).then(resp=>{resp.status=resp.statusCode,callback(null,resp,resp.body)},reason=>{callback(reason.error,null,null)});else if(this.isNode){let resp=this.node.request.post(_options,callback);resp.status=resp.statusCode,delete resp.statusCode}else this.isJSBox&&(_options.handler=resp=>{let err=resp.error?JSON.stringify(resp.error):void 0,data="object"==typeof resp.data?JSON.stringify(resp.data):resp.data;callback(err,resp.response,data)},$http.post(_options,{}))}done(value={}){this._endTime=Date.now();let span=(this._endTime-this._startTime)/1e3;magicJS.logDebug(`SCRIPT COMPLETED: ${span}S.`),"undefined"!=typeof $done&&$done(value)}isToday(day){if(null==day)return!1;{let today=new Date;return"string"==typeof day&&(day=new Date(day)),today.getFullYear()==day.getFullYear()&&today.getMonth()==day.getMonth()&&today.getDay()==day.getDay()}}isNumber(val){return"NaN"!==parseFloat(val).toString()}attempt(promise,defaultValue=null){return promise.then(args=>[null,args]).catch(ex=>(this.logError(ex),[ex,defaultValue]))}retry(fn,retries=5,interval=0,callback=null){return(...args)=>new Promise((resolve,reject)=>{function _retry(...args){Promise.resolve().then(()=>fn.apply(this,args)).then(result=>{"function"==typeof callback?Promise.resolve().then(()=>callback(result)).then(()=>{resolve(result)}).catch(ex=>{retries>=1?interval>0?setTimeout(()=>_retry.apply(this,args),interval):_retry.apply(this,args):reject(ex),retries--}):resolve(result)}).catch(ex=>{this.logRetry(ex),retries>=1&&interval>0?setTimeout(()=>_retry.apply(this,args),interval):retries>=1?_retry.apply(this,args):reject(ex),retries--})}_retry.apply(this,args)})}formatTime(time,fmt="yyyy-MM-dd hh:mm:ss"){var o={"M+":time.getMonth()+1,"d+":time.getDate(),"h+":time.getHours(),"m+":time.getMinutes(),"s+":time.getSeconds(),"q+":Math.floor((time.getMonth()+3)/3),S:time.getMilliseconds()};/(y+)/.test(fmt)&&(fmt=fmt.replace(RegExp.$1,(time.getFullYear()+"").substr(4-RegExp.$1.length)));for(let k in o)new RegExp("("+k+")").test(fmt)&&(fmt=fmt.replace(RegExp.$1,1==RegExp.$1.length?o[k]:("00"+o[k]).substr((""+o[k]).length)));return fmt}now(){return this.formatTime(new Date,"yyyy-MM-dd hh:mm:ss")}today(){return this.formatTime(new Date,"yyyy-MM-dd")}sleep(time){return new Promise(resolve=>setTimeout(resolve,time))}}(scriptName)}
+
+/**
+ *
+ * $$\ $$\ $$\ $$$$$\ $$$$$$\ $$$$$$\
+ * $$$\ $$$ | \__| \__$$ |$$ __$$\ $$ ___$$\
+ * $$$$\ $$$$ | $$$$$$\ $$$$$$\ $$\ $$$$$$$\ $$ |$$ / \__| \_/ $$ |
+ * $$\$$\$$ $$ | \____$$\ $$ __$$\ $$ |$$ _____| $$ |\$$$$$$\ $$$$$ /
+ * $$ \$$$ $$ | $$$$$$$ |$$ / $$ |$$ |$$ / $$\ $$ | \____$$\ \___$$\
+ * $$ |\$ /$$ |$$ __$$ |$$ | $$ |$$ |$$ | $$ | $$ |$$\ $$ | $$\ $$ |
+ * $$ | \_/ $$ |\$$$$$$$ |\$$$$$$$ |$$ |\$$$$$$$\\$$$$$$ |\$$$$$$ | \$$$$$$ |
+ * \__| \__| \_______| \____$$ |\__| \_______|\______/ \______/ \______/
+ * $$\ $$ |
+ * \$$$$$$ |
+ * \______/
+ *
+*/
+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]{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(new RegExp(`${e}=[^&]*`,"ig"),"");n.url=n.url.replace(new RegExp(`${t}=[^&]*`,"ig"),"");return`${t}=${encodeURIComponent(n.params[e])}`}).join("&");if(n.url.indexOf("?")<0)n.url+="?";if(!/(&|\?)$/g.test(n.url)){n.url+="&"}n.url+=e;delete n.params;f.debug(`Params to QueryString: ${n.url}`)}}return n}const d=(e,t)=>{let n=typeof t==="object"?{headers:{},...t}:{url:t,headers:{}};if(!n.method){n["method"]=e}n=r(n);if(n["rewrite"]===true){if(u.isSurge){n.headers["X-Surge-Skip-Scripting"]=false;delete n["rewrite"]}else if(u.isQuanX){n["hints"]=false;delete n["rewrite"]}}if(u.isSurge){if(n["method"]!=="GET"&&n.headers["Content-Type"].indexOf("application/json")>=0&&n.body instanceof Array){n.body=JSON.stringify(n.body);f.debug(`Convert Array object to String: ${n.body}`)}}else if(u.isQuanX){if(n.hasOwnProperty("body")&&typeof n["body"]!=="string")n["body"]=JSON.stringify(n["body"]);n["method"]=e}else if(u.isNode){if(e==="POST"||e==="PUT"||e==="PATCH"||e==="DELETE"){n.data=n.data||n.body}else if(e==="GET"){n.params=n.params||n.body}delete n.body}return n};const p=(t,n=null)=>{if(t){let e={...t,config:t.config||n,status:t.statusCode||t.status,body:t.body||t.data,headers:t.headers||t.header};if(typeof e.body==="string"){try{e.body=JSON.parse(e.body)}catch{}}delete t.data;return e}else{return t}};const s=r=>{if(!!r){delete r["Content-Length"];let e=new Set(["Accept","Accept-CH","Accept-Charset","Accept-Features","Accept-Encoding","Accept-Language","Accept-Ranges","Access-Control-Allow-Credentials","Access-Control-Allow-Origin","Access-Control-Allow-Methods","Access-Control-Allow-Headers","Access-Control-Max-Age","Access-Control-Expose-Headers","Access-Control-Request-Method","Access-Control-Request-Headers","Age","Allow","Alternates","Authorization","Cache-Control","Connection","Content-Encoding","Content-Language","ontent-Length","Content-Location","Content-Range","Content-Security-Policy","Content-Type","Cookie","DNT","Date","ETag","Expect","Expires","From","Host","If-Match","If-Modified-Since","If-None-Match","If-Range","If-Unmodified-Since","Last-Event-ID","Last-Modified","Link","Location","Max-Forwards","Negotiate","Origin","Pragma","Proxy-Authenticate","Proxy-Authorization","Range","Referer","Retry-After","Sec-Websocket-Extensions","Sec-Websocket-Key","Sec-Websocket-Origin","Sec-Websocket-Protocol","Sec-Websocket-Version","Server","Set-Cookie","Set-Cookie2","Strict-Transport-Security","TCN","TE","Trailer","Transfer-Encoding","Upgrade","User-Agent","Variant-Vary","Vary","Via","Warning","WWW-Authenticate","X-Content-Duration","X-Content-Security-Policy","X-DNSPrefetch-Control","X-Frame-Options","X-Requested-With"]);for(let n of Object.keys(r)){if(!e.has(n)){for(let t of e){let e=n.replace(new RegExp(t,"ig"),t);if(n!==e){r[e]=r[n];delete r[n];break}}}}if(!r["User-Agent"]){if(u.isNode){r["User-Agent"]=n}else{r["User-Agent"]=t}}return r}return r};const g=(t,n=null)=>{if(!!t&&t.status>=400){f.debug(`Raise exception when status code is ${t.status}`);let e={name:"RequestException",message:`Request failed with status code ${t.status}`,config:n||t.config,response:t};return e}};const i={request:new e,response:new e(false)};let y=[];let h=[];let m=true;function $(e){if(typeof e==="object"&&e["modify"]!==false){e["headers"]=s(e["headers"])}e=r(e);return e}function S(e){try{e=!!e?p(e):e;f.sniffer(`HTTP ${e.config["method"].toUpperCase()}:\n${JSON.stringify(e.config)}\nSTATUS CODE:\n${e.status}\nRESPONSE:\n${typeof e.body==="object"?JSON.stringify(e.body):e.body}`);const t=g(e);if(!!t){return Promise.reject(t)}return e}catch(t){f.error(t);return e}}const b=t=>{try{y=[];h=[];i.request.forEach(e=>{if(typeof e.runWhen==="function"&&e.runWhen(t)===false){return}m=m&&e.synchronous;y.unshift(e.fulfilled,e.rejected)});i.response.forEach(e=>{h.push(e.fulfilled,e.rejected)})}catch(e){f.error(`failed to register interceptors: ${e}`)}};const o=(e,r)=>{let s;const t=e.toUpperCase();r=d(t,r);if(u.isNode){s=c}else{if(u.isSurgeLike){s=i=>{return new Promise((r,s)=>{$httpClient[e.toLowerCase()](i,(t,n,e)=>{if(t){let e={name:t.name||t,message:t.message||t,stack:t.stack||t,config:i,response:p(n)};s(e)}else{n.config=i;n.body=e;r(n)}})})}}else{s=s=>{return new Promise((n,r)=>{$task.fetch(s).then(e=>{e=p(e,s);const t=g(e,s);if(t){return Promise.reject(t)}n(e)}).catch(e=>{let t={name:e.message||e.error,message:e.message||e.error,stack:e.error,config:s,response:!!e.response?p(e.response):null};r(t)})})}}}let i;b(r);const o=[$,undefined];const l=[S,undefined];if(!m){f.debug("Interceptors are executed in asynchronous mode");let n=[s,undefined];Array.prototype.unshift.apply(n,o);Array.prototype.unshift.apply(n,y);Array.prototype.unshift.apply(n,o);n=n.concat(l);n=n.concat(h);i=Promise.resolve(r);while(n.length){try{let e=n.shift();let t=n.shift();if(!u.isNode&&r["timeout"]&&e===s){i=a(r)}else{i=i.then(e,t)}}catch(e){f.error(`request exception: ${e}`)}}return i}else{f.debug("Interceptors are executed in synchronous mode");Array.prototype.unshift.apply(y,o);y=y.concat([$,undefined]);while(y.length){let e=y.shift();let t=y.shift();try{r=e(r)}catch(e){t(e);break}}try{if(!u.isNode&&r["timeout"]){i=a(r)}else{i=s(r)}}catch(e){return Promise.reject(e)}Array.prototype.unshift.apply(h,l);while(h.length){i=i.then(h.shift(),h.shift())}return i}function a(n){try{const e=new Promise((e,t)=>{setTimeout(()=>{let e={message:`timeout of ${n["timeout"]}ms exceeded`,config:n};t(e)},n["timeout"])});return Promise.race([s(n),e])}catch(e){f.error(`Request Timeout exception: ${e}`)}}};return{request:o,interceptors:i,modifyHeaders:s,modifyResponse:p,get:e=>{return o("GET",e)},post:e=>{return o("POST",e)},put:e=>{return o("PUT",e)},patch:e=>{return o("PATCH",e)},delete:e=>{return o("DELETE",e)},head:e=>{return o("HEAD",e)},options:e=>{return o("OPTIONS",e)}}}function MagicNotification(i,o,l,a){let u=null;let f=null;const e=t=>{try{let e=t.replace(/\/+$/g,"");u=`${/^https?:\/\/([^/]*)/.exec(e)[0]}/push`;f=/\/([^\/]+)\/?$/.exec(e)[1]}catch(e){l.error(`Bark url error: ${e}.`)}};function t(e=i,t="",n="",r=""){const s=n=>{try{let t={};if(typeof n==="string"){if(o.isLoon)t={openUrl:n};else if(o.isQuanX)t={"open-url":n};else if(o.isSurge)t={url:n}}else if(typeof n==="object"){if(o.isLoon){t["openUrl"]=!!n["open-url"]?n["open-url"]:"";t["mediaUrl"]=!!n["media-url"]?n["media-url"]:""}else if(o.isQuanX){t=!!n["open-url"]||!!n["media-url"]?n:{}}else if(o.isSurge){let e=n["open-url"]||n["openUrl"];t=e?{url:e}:{}}}return t}catch(e){l.error(`Failed to convert notification option, ${e}`)}return n};r=s(r);if(arguments.length==1){e=i;t="",n=arguments[0]}l.notify(`title:${e}\nsubTitle:${t}\nbody:${n}\noptions:${typeof r==="object"?JSON.stringify(r):r}`);if(o.isSurge){$notification.post(e,t,n,r)}else if(o.isLoon){if(!!r)$notification.post(e,t,n,r);else $notification.post(e,t,n)}else if(o.isQuanX){$notify(e,t,n,r)}if(u&&f){c(e,t,n)}}function n(e=i,t="",n="",r=""){if(l.level==="DEBUG"){if(arguments.length==1){e=i;t="",n=arguments[0]}this.notify(e,t,n,r)}}function c(e=i,t="",n="",r=""){if(typeof a==="undefined"||typeof a.post==="undefined"){throw"Bark notification needs to import MagicHttp module."}let s={url:u,headers:{"Content-Type":"application/json; charset=utf-8"},body:{title:e,body:t?`${t}\n${n}`:n,device_key:f}};a.post(s).catch(e=>{l.error(`Bark notify error: ${e}`)})}return{post:t,debug:n,bark:c,setBark:e}}function MagicData(o,l){let a={fs:undefined,data:{}};if(o.isNode){a.fs=require("fs");try{a.fs.accessSync("./magic.json",a.fs.constants.R_OK|a.fs.constants.W_OK)}catch(e){a.fs.writeFileSync("./magic.json","{}",{encoding:"utf8"})}a.data=require("./magic.json")}const u=(e,t)=>{if(typeof t==="object"){return false}else{return e===t}};const f=e=>{if(e==="true"){return true}else if(e==="false"){return false}else if(typeof e==="undefined"){return null}else{return e}};const c=(e,t,n,r)=>{if(n){try{if(typeof e==="string")e=JSON.parse(e);if(e["magic_session"]===true){e=e[n]}else{e=null}}catch{e=null}}if(typeof e==="string"&&e!=="null"){try{e=JSON.parse(e)}catch{}}if(r===false&&!!e&&e["magic_session"]===true){e=null}if((e===null||typeof e==="undefined")&&t!==null&&typeof t!=="undefined"){e=t}e=f(e);return e};const i=t=>{if(typeof t==="string"){let e={};try{e=JSON.parse(t);const n=typeof e;if(n!=="object"||e instanceof Array||n==="bool"||e===null){e={}}}catch{}return e}else if(t instanceof Array||t===null||typeof t==="undefined"||t!==t||typeof t==="boolean"){return{}}else{return t}};const d=(e,t=null,n="",r=false,s=null)=>{let i=s||a.data;if(!!i&&typeof i[e]!=="undefined"&&i[e]!==null){val=i[e]}else{val=!!n?{}:null}val=c(val,t,n,r);return val};const p=(e,t=null,n="",r=false,s=null)=>{let i="";if(s||o.isNode){i=d(e,t,n,r,s)}else{if(o.isSurgeLike){i=$persistentStore.read(e)}else if(o.isQuanX){i=$prefs.valueForKey(e)}i=c(i,t,n,r)}l.debug(`READ DATA [${e}]${!!n?`[${n}]`:""} <${typeof i}>\n${JSON.stringify(i)}`);return i};const g=(t,n,r="",e=null)=>{let s=e||a.data;s=i(s);if(!!r){let e=i(s[t]);e["magic_session"]=true;e[r]=n;s[t]=e}else{s[t]=n}if(e!==null){e=s}return s};const y=(e,t,n="",r=null)=>{if(typeof t==="undefined"||t!==t){return false}if(!o.isNode&&(typeof t==="boolean"||typeof t==="number")){t=String(t)}let s="";if(r||o.isNode){s=g(e,t,n,r)}else{if(!n){s=t}else{if(o.isSurgeLike){s=!!$persistentStore.read(e)?$persistentStore.read(e):s}else if(o.isQuanX){s=!!$prefs.valueForKey(e)?$prefs.valueForKey(e):s}s=i(s);s["magic_session"]=true;s[n]=t}}if(!!s&&typeof s==="object"){s=JSON.stringify(s,"","\t")}l.debug(`WRITE DATA [${e}]${n?`[${n}]`:""} <${typeof t}>\n${JSON.stringify(t)}`);if(!r){if(o.isSurgeLike){return $persistentStore.write(s,e)}else if(o.isQuanX){return $prefs.setValueForKey(s,e)}else if(o.isNode){try{a.fs.writeFileSync("./magic.json",s);return true}catch(e){l.error(e);return false}}}return true};const e=(t,n,r,s=u,i=null)=>{n=f(n);const e=p(t,null,r,false,i);if(s(e,n)===true){return false}else{const o=y(t,n,r,i);let e=p(t,null,r,false,i);if(s===u&&typeof e==="object"){return o}return s(n,e)}};const h=(e,t,n)=>{let r=n||a.data;r=i(r);if(!!t){obj=i(r[e]);delete obj[t];r[e]=obj}else{delete r[e]}if(!!n){n=r}return r};const t=(e,t="",n=null)=>{let r={};if(n||o.isNode){r=h(e,t,n);if(!n){a.fs.writeFileSync("./magic.json",JSON.stringify(r))}else{n=r}}else{if(!t){if(o.isStorm){return $persistentStore.remove(e)}else if(o.isSurgeLike){return $persistentStore.write(null,e)}else if(o.isQuanX){return $prefs.removeValueForKey(e)}}else{if(o.isSurgeLike){r=$persistentStore.read(e)}else if(o.isQuanX){r=$prefs.valueForKey(e)}r=i(r);delete r[t];const s=JSON.stringify(r);y(e,s)}}l.debug(`DELETE KEY [${e}]${!!t?`[${t}]`:""}`)};const n=(e,t=null)=>{let n=[];let r=p(e,null,null,true,t);r=i(r);if(r["magic_session"]!==true){n=[]}else{n=Object.keys(r).filter(e=>e!=="magic_session")}l.debug(`READ ALL SESSIONS [${e}] <${typeof n}>\n${JSON.stringify(n)}`);return n};return{read:p,write:y,del:t,update:e,allSessions:n,defaultValueComparator:u,convertToObject:i}}function MagicUtils(r,u){const e=(i,o=5,l=0,a=null)=>{return(...e)=>{return new Promise((n,r)=>{function s(...t){Promise.resolve().then(()=>i.apply(this,t)).then(e=>{if(typeof a==="function"){Promise.resolve().then(()=>a(e)).then(()=>{n(e)}).catch(e=>{if(o>=1){if(l>0)setTimeout(()=>s.apply(this,t),l);else s.apply(this,t)}else{r(e)}o--})}else{n(e)}}).catch(e=>{u.error(e);if(o>=1&&l>0){setTimeout(()=>s.apply(this,t),l)}else if(o>=1){s.apply(this,t)}else{r(e)}o--})}s.apply(this,e)})}};const t=(e,t="yyyy-MM-dd hh:mm:ss")=>{let n={"M+":e.getMonth()+1,"d+":e.getDate(),"h+":e.getHours(),"m+":e.getMinutes(),"s+":e.getSeconds(),"q+":Math.floor((e.getMonth()+3)/3),S:e.getMilliseconds()};if(/(y+)/.test(t))t=t.replace(RegExp.$1,(e.getFullYear()+"").substr(4-RegExp.$1.length));for(let e in n)if(new RegExp("("+e+")").test(t))t=t.replace(RegExp.$1,RegExp.$1.length==1?n[e]:("00"+n[e]).substr((""+n[e]).length));return t};const n=()=>{return t(new Date,"yyyy-MM-dd hh:mm:ss")};const s=()=>{return t(new Date,"yyyy-MM-dd")};const i=t=>{return new Promise(e=>setTimeout(e,t))};const o=(e,t=null)=>{if(r.isNode){const n=require("assert");if(t)n(e,t);else n(e)}else{if(e!==true){let e=`AssertionError: ${t||"The expression evaluated to a falsy value"}`;u.error(e)}}};return{retry:e,formatTime:t,now:n,today:s,sleep:i,assert:o}}
\ No newline at end of file
diff --git a/script/applestore/applestore.lnplugin b/script/applestore/applestore.lnplugin
new file mode 100644
index 000000000..9be2a1699
--- /dev/null
+++ b/script/applestore/applestore.lnplugin
@@ -0,0 +1,10 @@
+#!name= AppleStore
+#!desc= AppleStore线下库存监控
+#!openUrl= https://github.com/blackmatrix7/ios_rule_script/tree/master/script/applestore
+#!author= blackmatrix7
+#!homepage= https://github.com/blackmatrix7/ios_rule_script
+#!icon= https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/applestore/icon/applestore.png
+
+[Script]
+cron "0/5 * 6-23 * * *" script-path=https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/applestore/applestore.js,timeout=5,tag=AppleStore_查询商品库存
+
diff --git a/script/applestore/applestore.lnscript b/script/applestore/applestore.lnscript
deleted file mode 100644
index 1e8b80280..000000000
--- a/script/applestore/applestore.lnscript
+++ /dev/null
@@ -1 +0,0 @@
-cron "0/5 * 6-23 * * *" script-path=https://raw.githubusercontent.com/blackmatrix7/ios_rule_script/master/script/applestore/applestore.js,timeout=5,tag=AppleStore_查询商品库存
\ No newline at end of file
diff --git a/script/applestore/images/01.png b/script/applestore/images/01.png
new file mode 100644
index 000000000..32654d408
Binary files /dev/null and b/script/applestore/images/01.png differ
diff --git a/script/applestore/images/02.png b/script/applestore/images/02.png
new file mode 100644
index 000000000..e199b08a7
Binary files /dev/null and b/script/applestore/images/02.png differ
diff --git a/script/applestore/images/03.png b/script/applestore/images/03.png
new file mode 100644
index 000000000..8bf4f4f94
Binary files /dev/null and b/script/applestore/images/03.png differ
diff --git a/script/applestore/images/04..jpg b/script/applestore/images/04..jpg
new file mode 100644
index 000000000..db9ad524f
Binary files /dev/null and b/script/applestore/images/04..jpg differ
diff --git a/script/applestore/images/04.png b/script/applestore/images/04.png
new file mode 100644
index 000000000..10d232b72
Binary files /dev/null and b/script/applestore/images/04.png differ