MagicJS2.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787
  1. function MagicJS(scriptName = "MagicJS", logLevel = "INFO") {
  2. // HTTP/1.1协议头部header字段名大小写不敏感, 但是HTTP/2头部header字段名必须使用小写
  3. const headerFieldMap = {
  4. accept: "Accept",
  5. "accept-ch": "Accept-CH",
  6. "accept-charset": "Accept-Charset",
  7. "accept-features": "Accept-Features",
  8. "accept-encoding": "Accept-Encoding",
  9. "accept-language": "Accept-Language",
  10. "accept-ranges": "Accept-Ranges",
  11. "access-control-allow-credentials": "Access-Control-Allow-Credentials",
  12. "access-control-allow-origin": "Access-Control-Allow-Origin",
  13. "access-control-allow-methods": "Access-Control-Allow-Methods",
  14. "access-control-allow-headers": "Access-Control-Allow-Headers",
  15. "access-control-max-age": "Access-Control-Max-Age",
  16. "access-control-expose-headers": "Access-Control-Expose-Headers",
  17. "access-control-request-method": "Access-Control-Request-Method",
  18. "access-control-request-headers": "Access-Control-Request-Headers",
  19. age: "Age",
  20. allow: "Allow",
  21. alternates: "Alternates",
  22. authorization: "Authorization",
  23. "cache-control": "Cache-Control",
  24. connection: "Connection",
  25. "content-encoding": "Content-Encoding",
  26. "content-language": "Content-Language",
  27. "content-length": "Content-Length",
  28. "content-location": "Content-Location",
  29. "content-md5": "Content-MD5",
  30. "content-range": "Content-Range",
  31. "content-security-policy": "Content-Security-Policy",
  32. "content-type": "Content-Type",
  33. cookie: "Cookie",
  34. dnt: "DNT",
  35. date: "Date",
  36. etag: "ETag",
  37. expect: "Expect",
  38. expires: "Expires",
  39. from: "From",
  40. host: "Host",
  41. "if-match": "If-Match",
  42. "if-modified-since": "If-Modified-Since",
  43. "if-none-match": "If-None-Match",
  44. "if-range": "If-Range",
  45. "if-unmodified-since": "If-Unmodified-Since",
  46. "last-event-id": "Last-Event-ID",
  47. "last-modified": "Last-Modified",
  48. link: "Link",
  49. location: "Location",
  50. "max-forwards": "Max-Forwards",
  51. negotiate: "Negotiate",
  52. origin: "Origin",
  53. pragma: "Pragma",
  54. "proxy-authenticate": "Proxy-Authenticate",
  55. "proxy-authorization": "Proxy-Authorization",
  56. range: "Range",
  57. referer: "Referer",
  58. "retry-after": "Retry-After",
  59. "sec-websocket-extensions": "Sec-Websocket-Extensions",
  60. "sec-websocket-key": "Sec-Websocket-Key",
  61. "sec-websocket-origin": "Sec-Websocket-Origin",
  62. "sec-websocket-protocol": "Sec-Websocket-Protocol",
  63. "sec-websocket-version": "Sec-Websocket-Version",
  64. server: "Server",
  65. "set-cookie": "Set-Cookie",
  66. "set-cookie2": "Set-Cookie2",
  67. "strict-transport-security": "Strict-Transport-Security",
  68. tcn: "TCN",
  69. te: "TE",
  70. trailer: "Trailer",
  71. "transfer-encoding": "Transfer-Encoding",
  72. upgrade: "Upgrade",
  73. "user-agent": "User-Agent",
  74. "variant-vary": "Variant-Vary",
  75. vary: "Vary",
  76. via: "Via",
  77. warning: "Warning",
  78. "www-authenticate": "WWW-Authenticate",
  79. "x-content-duration": "X-Content-Duration",
  80. "x-content-security-policy": "X-Content-Security-Policy",
  81. "x-dnsprefetch-control": "X-DNSPrefetch-Control",
  82. "x-frame-options": "X-Frame-Options",
  83. "x-requested-with": "X-Requested-With",
  84. "x-surge-skip-scripting": "X-Surge-Skip-Scripting"
  85. };
  86. return new class {
  87. constructor() {
  88. this._startTime = Date.now();
  89. this.version = "2.2.3.7";
  90. this.scriptName = scriptName;
  91. this.logLevels = {
  92. DEBUG: 5,
  93. INFO: 4,
  94. NOTIFY: 3,
  95. WARNING: 2,
  96. ERROR: 1,
  97. CRITICAL: 0,
  98. NONE: -1
  99. };
  100. this.isLoon = typeof $loon !== "undefined";
  101. this.isQuanX = typeof $task !== "undefined";
  102. this.isJSBox = typeof $drive !== "undefined";
  103. this.isNode = typeof module !== "undefined" && !this.isJSBox;
  104. this.isSurge = typeof $httpClient !== "undefined" && !this.isLoon;
  105. this.node = {
  106. request: undefined,
  107. fs: undefined,
  108. data: {}
  109. };
  110. 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";
  111. 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";
  112. this._logLevel = "INFO";
  113. this.logLevel = logLevel;
  114. this._barkUrl = "";
  115. this._barkKey = "";
  116. if (this.isNode) {
  117. this.node.fs = require("fs");
  118. this.node.request = require("request");
  119. try {
  120. this.node.fs.accessSync("./magic.json", this.node.fs.constants.R_OK | this.node.fs.constants.W_OK);
  121. } catch (err) {
  122. this.node.fs.writeFileSync("./magic.json", "{}", {
  123. encoding: "utf8"
  124. });
  125. }
  126. this.node.data = require("./magic.json");
  127. } else if (this.isJSBox) {
  128. if (!$file.exists("drive://MagicJS")) {
  129. $file.mkdir("drive://MagicJS");
  130. }
  131. if (!$file.exists("drive://MagicJS/magic.json")) {
  132. $file.write({
  133. data: $data({
  134. string: "{}"
  135. }),
  136. path: "drive://MagicJS/magic.json"
  137. });
  138. }
  139. }
  140. this.log(`${scriptName}, 开始执行!`);
  141. }
  142. /**
  143. * @param {string} url
  144. */
  145. set barkUrl(url) {
  146. try {
  147. let _url = url.replace(/\/+$/g, "");
  148. this._barkUrl = `${/^https?:\/\/([^/]*)/.exec(_url)[0]}/push`;
  149. this._barkKey = /\/([^\/]+)\/?$/.exec(_url)[1];
  150. } catch (err) {
  151. this.logDebug("Bark config error.");
  152. }
  153. }
  154. set logLevel(level) {
  155. let magic_loglevel = this.read("magicjs_loglevel");
  156. this._logLevel = magic_loglevel ? magic_loglevel : level.toUpperCase();
  157. }
  158. get logLevel() {
  159. return this._logLevel;
  160. }
  161. get isRequest() {
  162. return typeof $request !== "undefined" && typeof $response === "undefined";
  163. }
  164. get isResponse() {
  165. return typeof $response !== "undefined";
  166. }
  167. get isDebug() {
  168. return this.logLevel === "DEBUG";
  169. }
  170. get request() {
  171. return typeof $request !== "undefined" ? $request : undefined;
  172. }
  173. get response() {
  174. if (typeof $response !== "undefined") {
  175. if ($response.hasOwnProperty("status")) $response["statusCode"] = $response["status"];
  176. if ($response.hasOwnProperty("statusCode")) $response["status"] = $response["statusCode"];
  177. return $response;
  178. } else {
  179. return undefined;
  180. }
  181. }
  182. get platform() {
  183. if (this.isSurge) return "Surge"; else if (this.isQuanX) return "Quantumult X"; else if (this.isLoon) return "Loon"; else if (this.isJSBox) return "JSBox"; else if (this.isNode) return "Node.js"; else return "Unknown";
  184. }
  185. read(key, session = "") {
  186. let val = "";
  187. // 读取原始数据
  188. if (this.isSurge || this.isLoon) {
  189. val = $persistentStore.read(key);
  190. } else if (this.isQuanX) {
  191. val = $prefs.valueForKey(key);
  192. } else if (this.isNode) {
  193. val = this.node.data;
  194. } else if (this.isJSBox) {
  195. val = $file.read("drive://MagicJS/magic.json").string;
  196. }
  197. try {
  198. // Node 和 JSBox数据处理
  199. if (this.isNode) val = val[key];
  200. if (this.isJSBox) val = JSON.parse(val)[key];
  201. // 带Session的情况
  202. if (!!session) {
  203. if (typeof val === "string") val = JSON.parse(val);
  204. val = !!val && typeof val === "object" ? val[session] : null;
  205. }
  206. } catch (err) {
  207. this.logError(err);
  208. val = !!session ? {} : null;
  209. this.del(key);
  210. }
  211. if (typeof val === "undefined") val = null;
  212. try {
  213. if (!!val && typeof val === "string") {
  214. var obj = JSON.parse(val);
  215. if (typeof obj == "object" && obj) {
  216. val = obj;
  217. }
  218. }
  219. } catch (err) {}
  220. this.logDebug(`READ DATA [${key}]${!!session ? `[${session}]` : ""}(${typeof val})`+'\n'+`${JSON.stringify(val)}`);
  221. return val;
  222. }
  223. write(key, val, session = "") {
  224. let data = !!session ? {} : "";
  225. // 读取原先存储的JSON格式数据
  226. if (!!session && (this.isSurge || this.isLoon)) {
  227. data = $persistentStore.read(key);
  228. } else if (!!session && this.isQuanX) {
  229. data = $prefs.valueForKey(key);
  230. } else if (this.isNode) {
  231. data = this.node.data;
  232. } else if (this.isJSBox) {
  233. data = JSON.parse($file.read("drive://MagicJS/magic.json").string);
  234. }
  235. if (!!session) {
  236. // 有Session,所有数据都是Object
  237. try {
  238. if (typeof data === "string") data = JSON.parse(data);
  239. data = typeof data === "object" && !!data ? data : {};
  240. } catch (err) {
  241. this.logError(err);
  242. this.del(key);
  243. data = {};
  244. }
  245. if (this.isJSBox || this.isNode) {
  246. // 构造数据
  247. if (!data[key] || typeof data[key] != "object") {
  248. data[key] = {};
  249. }
  250. if (!data[key].hasOwnProperty(session)) {
  251. data[key][session] = null;
  252. }
  253. // 写入或删除数据
  254. if (typeof val === "undefined") {
  255. delete data[key][session];
  256. } else {
  257. data[key][session] = val;
  258. }
  259. } else {
  260. // 写入或删除数据
  261. if (typeof val === "undefined") {
  262. delete data[session];
  263. } else {
  264. data[session] = val;
  265. }
  266. }
  267. }
  268. // 没有Session时
  269. else {
  270. if (this.isNode || this.isJSBox) {
  271. // 删除数据
  272. if (typeof val === "undefined") {
  273. delete data[key];
  274. } else {
  275. data[key] = val;
  276. }
  277. } else {
  278. // 删除数据
  279. if (typeof val === "undefined") {
  280. data = null;
  281. } else {
  282. data = val;
  283. }
  284. }
  285. }
  286. // 数据回写
  287. if (typeof data === "object") data = JSON.stringify(data);
  288. if (this.isSurge || this.isLoon) {
  289. $persistentStore.write(data, key);
  290. } else if (this.isQuanX) {
  291. $prefs.setValueForKey(data, key);
  292. } else if (this.isNode) {
  293. this.node.fs.writeFileSync("./magic.json", data);
  294. } else if (this.isJSBox) {
  295. $file.write({
  296. data: $data({
  297. string: data
  298. }),
  299. path: "drive://MagicJS/magic.json"
  300. });
  301. }
  302. this.logDebug(`WRITE DATA [${key}]${!!session ? `[${session}]` : ""}(${typeof val})`+'\n'+`${JSON.stringify(val)}`);
  303. }
  304. del(key, session = "") {
  305. this.logDebug(`DELETE KEY [${key}]${!!session ? `[${session}]` : ""}`);
  306. this.write(key, null, session);
  307. }
  308. /**
  309. * iOS系统通知
  310. * @param {*} title 通知标题
  311. * @param {*} subTitle 通知副标题
  312. * @param {*} body 通知内容
  313. * @param {*} opts 通知选项,目前支持传入超链接或Object
  314. * Surge不支持通知选项,Loon和QuantumultX支持打开URL和多媒体通知
  315. * opts "applestore://" 打开Apple Store
  316. * opts "https://www.apple.com.cn/" 打开Apple.com.cn
  317. * opts {'open-url': 'https://www.apple.com.cn/'} 打开Apple.com.cn
  318. * opts {'open-url': 'https://www.apple.com.cn/', 'media-url': 'https://raw.githubusercontent.com/Orz-3/mini/master/Apple.png'} 打开Apple.com.cn,显示一个苹果Logo
  319. */
  320. notify(title = this.scriptName, subTitle = "", body = "", opts = "") {
  321. let convertOptions = _opts => {
  322. let newOpts = {};
  323. if (typeof _opts === "string") {
  324. if(_opts.length > 0){
  325. if (this.isLoon){
  326. newOpts = {
  327. openUrl: _opts
  328. };
  329. }else if (this.isQuanX){
  330. newOpts = {
  331. "open-url": _opts
  332. };
  333. }else if (this.isSurge){
  334. newOpts = {
  335. url: _opts
  336. };
  337. }
  338. }
  339. } else if (typeof _opts === "object") {
  340. if (this.isLoon) {
  341. newOpts["openUrl"] = !!_opts["open-url"] ? _opts["open-url"] : "";
  342. newOpts["mediaUrl"] = !!_opts["media-url"] ? _opts["media-url"] : "";
  343. } else if (this.isQuanX) {
  344. newOpts = !!_opts["open-url"] || !!_opts["media-url"] ? _opts : {};
  345. } else if (this.isSurge) {
  346. let openUrl = _opts["open-url"] || _opts["openUrl"];
  347. newOpts = openUrl ? {
  348. url: openUrl
  349. } : {};
  350. }
  351. }
  352. return newOpts;
  353. };
  354. opts = convertOptions(opts);
  355. // 支持单个参数通知
  356. if (arguments.length == 1) {
  357. title = this.scriptName;
  358. subTitle = "", body = arguments[0];
  359. }
  360. // 生成通知日志
  361. this.logNotify('\n'+`title:${title}`+'\n'+`subTitle:${subTitle}`+'\n'+`body:${body}`+'\n'+`options:${typeof opts === "object" ? JSON.stringify(opts) : opts}`);
  362. if (this.isSurge) {
  363. $notification.post(title, subTitle, body, opts);
  364. } else if (this.isLoon) {
  365. if (!!opts) $notification.post(title, subTitle, body, opts); else $notification.post(title, subTitle, body);
  366. } else if (this.isQuanX) {
  367. $notify(title, subTitle, body, opts);
  368. } else if (this.isJSBox) {
  369. let push = {
  370. title: title,
  371. body: !!subTitle ? `${subTitle}`+'\n'+`${body}` : body
  372. };
  373. $push.schedule(push);
  374. }
  375. // 跨设备统一推送
  376. if (this._barkUrl && this._barkKey) {
  377. this.notifyBark(title, subTitle, body);
  378. }
  379. }
  380. /**
  381. * iOS系统通知,仅在DEBUG模式下推送
  382. * @param {*} title 通知标题
  383. * @param {*} subTitle 通知副标题
  384. * @param {*} body 通知内容
  385. * @param {*} opts 通知选项,目前支持传入超链接或Object
  386. * Surge不支持通知选项,Loon和QuantumultX支持打开URL和多媒体通知
  387. * opts "applestore://" 打开Apple Store
  388. * opts "https://www.apple.com.cn/" 打开Apple.com.cn
  389. * opts {'open-url': 'https://www.apple.com.cn/'} 打开Apple.com.cn
  390. * opts {'open-url': 'https://www.apple.com.cn/', 'media-url': 'https://raw.githubusercontent.com/Orz-3/mini/master/Apple.png'} 打开Apple.com.cn,显示一个苹果Logo
  391. */
  392. notifyDebug(title = this.scriptName, subTitle = "", body = "", opts = "") {
  393. if (this.logLevel === "DEBUG") {
  394. // 适配单个参数通知
  395. if (arguments.length == 1) {
  396. title = this.scriptName;
  397. subTitle = "", body = arguments[0];
  398. }
  399. this.notify(title, subTitle, body, opts);
  400. }
  401. }
  402. notifyBark(title = this.scriptName, subTitle = "", body = "", opts = "") {
  403. let options = {
  404. url: this._barkUrl,
  405. headers: {
  406. "Content-Type": "application/json; charset=utf-8"
  407. },
  408. body: {
  409. title: title,
  410. body: subTitle ? `${subTitle}`+'\n'+`${body}` : body,
  411. device_key: this._barkKey
  412. }
  413. };
  414. this.post(options, err => {});
  415. }
  416. log(msg, level = "INFO") {
  417. if (!(this.logLevels[this._logLevel] < this.logLevels[level.toUpperCase()])) console.log(`██[${this.scriptName}][${level}]`+''+`${msg}`+'\n');
  418. }
  419. logDebug(msg) {
  420. this.log(msg, "DEBUG");
  421. }
  422. logInfo(msg) {
  423. this.log(msg, "INFO");
  424. }
  425. logNotify(msg) {
  426. this.log(msg, "NOTIFY");
  427. }
  428. logWarning(msg) {
  429. this.log(msg, "WARNING");
  430. }
  431. logError(msg) {
  432. this.log(msg, "ERROR");
  433. }
  434. logRetry(msg) {
  435. this.log(msg, "RETRY");
  436. }
  437. /**
  438. * 对传入的Http Options根据不同环境进行适配
  439. * @param {*} options
  440. */
  441. adapterHttpOptions(options, method) {
  442. let _options = typeof options === "object" ? Object.assign({}, options) : {
  443. url: options,
  444. headers: {}
  445. };
  446. if (_options.hasOwnProperty("header") && !_options.hasOwnProperty("headers")) {
  447. _options["headers"] = _options["header"];
  448. delete _options["header"];
  449. }
  450. // // 将HTTP/2头部header字段统一转成
  451. // if (typeof _options.headers === "object" && !!s) {
  452. // for (let key in _options.headers) {
  453. // if (headerFieldMap[key]) {
  454. // _options.headers[headerFieldMap[key]] = _options.headers[key];
  455. // delete _options.headers[key];
  456. // }
  457. // }
  458. // }
  459. // 自动补完User-Agent,减少请求特征
  460. if (!!!_options.headers || typeof _options.headers !== "object" || !!!_options.headers["User-Agent"] && !!!_options.headers["user-agent"]) {
  461. if (!!!_options.headers || typeof _options.headers !== "object") _options.headers = {};
  462. if (this.isNode) _options.headers["User-Agent"] = this.pcUserAgent; else _options.headers["User-Agent"] = this.iOSUserAgent;
  463. }
  464. // 判断是否跳过脚本处理
  465. let skipScripting = false;
  466. if (typeof _options["opts"] === "object" && (_options["opts"]["hints"] === true || _options["opts"]["Skip-Scripting"] === true) || typeof _options["headers"] === "object" && _options["headers"]["X-Surge-Skip-Scripting"] === true) {
  467. skipScripting = true;
  468. }
  469. if (!skipScripting) {
  470. if (this.isSurge) _options.headers["X-Surge-Skip-Scripting"] = false; else if (this.isLoon) _options.headers["X-Requested-With"] = "XMLHttpRequest"; else if (this.isQuanX) {
  471. if (typeof _options["opts"] !== "object") _options.opts = {};
  472. _options.opts["hints"] = false;
  473. }
  474. }
  475. // 对请求数据做清理
  476. if (!this.isSurge || skipScripting) delete _options.headers["X-Surge-Skip-Scripting"];
  477. if (!this.isQuanX && _options.hasOwnProperty("opts")) delete _options["opts"];
  478. if (this.isQuanX && _options.hasOwnProperty("opts")) delete _options["opts"]["Skip-Scripting"];
  479. // GET请求将body转换成QueryString(beta)
  480. if (method === "GET" && !this.isNode && !!_options.body) {
  481. let qs = Object.keys(_options.body).map(key => {
  482. if (typeof _options.body === "undefined") return "";
  483. return `${encodeURIComponent(key)}=${encodeURIComponent(_options.body[key])}`;
  484. }).join("&");
  485. if (_options.url.indexOf("?") < 0) _options.url += "?";
  486. if (_options.url.lastIndexOf("&") + 1 != _options.url.length && _options.url.lastIndexOf("?") + 1 != _options.url.length) _options.url += "&";
  487. _options.url += qs;
  488. delete _options.body;
  489. }
  490. // 适配多环境
  491. if (this.isQuanX) {
  492. if (_options.hasOwnProperty("body") && typeof _options["body"] !== "string") _options["body"] = JSON.stringify(_options["body"]);
  493. _options["method"] = method;
  494. } else if (this.isNode) {
  495. delete _options.headers["Accept-Encoding"];
  496. if (typeof _options.body === "object") {
  497. if (method === "GET") {
  498. _options.qs = _options.body;
  499. delete _options.body;
  500. } else if (method === "POST") {
  501. _options["json"] = true;
  502. _options.body = _options.body;
  503. }
  504. }
  505. } else if (this.isJSBox) {
  506. _options["header"] = _options["headers"];
  507. delete _options["headers"];
  508. }
  509. return _options;
  510. }
  511. adapterHttpResponse(resp) {
  512. let _resp = {
  513. body: resp.body,
  514. headers: resp.headers,
  515. json: () => {
  516. return JSON.parse(_resp.body);
  517. }
  518. };
  519. if (resp.hasOwnProperty("statusCode") && resp.statusCode) {
  520. _resp["status"] = resp.statusCode;
  521. }
  522. return _resp;
  523. }
  524. /**
  525. * Http客户端发起GET请求
  526. * @param {*} options
  527. * @param {*} callback
  528. * options可配置参数headers和opts,用于判断由脚本发起的http请求是否跳过脚本处理。
  529. * 支持Surge和Quantumult X两种配置方式。
  530. * 以下几种配置会跳过脚本处理,options没有opts或opts的值不匹配,则不跳过脚本处理
  531. * {opts:{"hints": true}}
  532. * {opts:{"Skip-Scripting": true}}
  533. * {headers: {"X-Surge-Skip-Scripting": true}}
  534. */
  535. get(options, callback) {
  536. let _options = this.adapterHttpOptions(options, "GET");
  537. this.logDebug(`HTTP GET: ${JSON.stringify(_options)}`);
  538. if (this.isSurge || this.isLoon) {
  539. $httpClient.get(_options, callback);
  540. } else if (this.isQuanX) {
  541. $task.fetch(_options).then(resp => {
  542. resp["status"] = resp.statusCode;
  543. callback(null, resp, resp.body);
  544. }, reason => callback(reason.error, null, null));
  545. } else if (this.isNode) {
  546. this.node.request.get(_options, (err, resp, data) => {
  547. resp = this.adapterHttpResponse(resp);
  548. callback(err, resp, data);
  549. });
  550. } else if (this.isJSBox) {
  551. _options["handler"] = resp => {
  552. let err = resp.error ? JSON.stringify(resp.error) : undefined;
  553. let data = typeof resp.data === "object" ? JSON.stringify(resp.data) : resp.data;
  554. callback(err, resp.response, data);
  555. };
  556. $http.get(_options);
  557. }
  558. }
  559. getPromise(options) {
  560. return new Promise((resolve, reject) => {
  561. magicJS.get(options, (err, resp) => {
  562. if (err) {
  563. reject(err);
  564. } else {
  565. resolve(resp);
  566. }
  567. });
  568. });
  569. }
  570. /**
  571. * Http客户端发起POST请求
  572. * @param {*} options
  573. * @param {*} callback
  574. * options可配置参数headers和opts,用于判断由脚本发起的http请求是否跳过脚本处理。
  575. * 支持Surge和Quantumult X两种配置方式。
  576. * 以下几种配置会跳过脚本处理,options没有opts或opts的值不匹配,则不跳过脚本处理
  577. * {opts:{"hints": true}}
  578. * {opts:{"Skip-Scripting": true}}
  579. * {headers: {"X-Surge-Skip-Scripting": true}}
  580. */
  581. post(options, callback) {
  582. let _options = this.adapterHttpOptions(options, "POST");
  583. this.logDebug(`HTTP POST: ${JSON.stringify(_options)}`);
  584. if (this.isSurge || this.isLoon) {
  585. $httpClient.post(_options, callback);
  586. } else if (this.isQuanX) {
  587. $task.fetch(_options).then(resp => {
  588. resp["status"] = resp.statusCode;
  589. callback(null, resp, resp.body);
  590. }, reason => {
  591. callback(reason.error, null, null);
  592. });
  593. } else if (this.isNode) {
  594. let resp = this.node.request.post(_options, callback);
  595. resp["status"] = resp.statusCode;
  596. delete resp.statusCode;
  597. } else if (this.isJSBox) {
  598. _options["handler"] = resp => {
  599. let err = resp.error ? JSON.stringify(resp.error) : undefined;
  600. let data = typeof resp.data === "object" ? JSON.stringify(resp.data) : resp.data;
  601. callback(err, resp.response, data);
  602. };
  603. $http.post(_options, {});
  604. }
  605. }
  606. costTime() {
  607. let info = `${this.scriptName}执行完毕!`
  608. // if (this.isNode && this.isExecComm) {
  609. // info = `指令【${this.comm[1]}】执行完毕!`
  610. // }
  611. this._endTime = new Date().getTime();
  612. const ms = this._endTime - this._startTime;
  613. const costTime = ms / 1000;
  614. this.log(`${info}耗时【${costTime}】秒`);
  615. }
  616. done(value = {}) {
  617. // this._endTime = Date.now();
  618. // let span = (this._endTime - this._startTime) / 1e3;
  619. // this.log(`SCRIPT COMPLETED: ${span}S.`);
  620. this.costTime();
  621. if (typeof $done !== "undefined") {
  622. $done(value);
  623. }
  624. }
  625. isToday(day) {
  626. if (day == null) {
  627. return false;
  628. } else {
  629. let today = new Date();
  630. if (typeof day == "string") {
  631. day = new Date(day);
  632. }
  633. if (today.getFullYear() == day.getFullYear() && today.getMonth() == day.getMonth() && today.getDay() == day.getDay()) {
  634. return true;
  635. } else {
  636. return false;
  637. }
  638. }
  639. }
  640. isNumber(val) {
  641. return parseFloat(val).toString() === "NaN" ? false : true;
  642. }
  643. /**
  644. * 对await执行中出现的异常进行捕获并返回,避免写过多的try catch语句
  645. * 示例:let [err,val] = await magicJS.attempt(func(), 'defaultvalue');
  646. * 或者:let [err, [val1,val2]] = await magicJS.attempt(func(), ['defaultvalue1', 'defaultvalue2']);
  647. * @param {*} promise Promise 对象
  648. * @param {*} defaultValue 出现异常时返回的默认值
  649. * @returns 返回两个值,第一个值为异常,第二个值为执行结果
  650. */
  651. attempt(promise, defaultValue = null) {
  652. return promise.then(args => {
  653. return [ null, args ];
  654. }).catch(ex => {
  655. this.logError(ex);
  656. return [ ex, defaultValue ];
  657. });
  658. }
  659. /**
  660. * 重试方法
  661. * @param {*} fn 需要重试的函数
  662. * @param {number} [retries=5] 重试次数
  663. * @param {number} [interval=0] 每次重试间隔
  664. * @param {function} [callback=null] 函数没有异常时的回调,会将函数执行结果result传入callback,根据result的值进行判断,如果需要再次重试,在callback中throw一个异常,适用于函数本身没有异常但仍需重试的情况。
  665. * @returns 返回一个Promise对象
  666. */
  667. retry(fn, retries = 5, interval = 0, callback = null) {
  668. return (...args) => {
  669. return new Promise((resolve, reject) => {
  670. function _retry(...args) {
  671. Promise.resolve().then(() => fn.apply(this, args)).then(result => {
  672. if (typeof callback === "function") {
  673. Promise.resolve().then(() => callback(result)).then(() => {
  674. resolve(result);
  675. }).catch(ex => {
  676. if (retries >= 1) {
  677. if (interval > 0) setTimeout(() => _retry.apply(this, args), interval); else _retry.apply(this, args);
  678. } else {
  679. reject(ex);
  680. }
  681. retries--;
  682. });
  683. } else {
  684. resolve(result);
  685. }
  686. }).catch(ex => {
  687. this.logRetry(ex);
  688. if (retries >= 1 && interval > 0) {
  689. setTimeout(() => _retry.apply(this, args), interval);
  690. } else if (retries >= 1) {
  691. _retry.apply(this, args);
  692. } else {
  693. reject(ex);
  694. }
  695. retries--;
  696. });
  697. }
  698. _retry.apply(this, args);
  699. });
  700. };
  701. }
  702. formatTime(time, fmt = "yyyy-MM-dd hh:mm:ss") {
  703. var o = {
  704. "M+": time.getMonth() + 1,
  705. "d+": time.getDate(),
  706. "h+": time.getHours(),
  707. "m+": time.getMinutes(),
  708. "s+": time.getSeconds(),
  709. "q+": Math.floor((time.getMonth() + 3) / 3),
  710. S: time.getMilliseconds()
  711. };
  712. if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (time.getFullYear() + "").substr(4 - RegExp.$1.length));
  713. for (let k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
  714. return fmt;
  715. }
  716. now() {
  717. return this.formatTime(new Date(), "yyyy-MM-dd hh:mm:ss");
  718. }
  719. today() {
  720. return this.formatTime(new Date(), "yyyy-MM-dd");
  721. }
  722. sleep(time) {
  723. return new Promise(resolve => setTimeout(resolve, time));
  724. }
  725. objToQueryStr(obj, encode) {
  726. let str = '';
  727. for (const key in obj) {
  728. let value = obj[key];
  729. if (value != null && value !== '') {
  730. if (typeof value === 'object') {
  731. value = JSON.stringify(value);
  732. } else if (encode) {
  733. value = encodeURIComponent(value);
  734. }
  735. str += `${key}=${value}&`;
  736. }
  737. }
  738. str = str.substring(0, str.length - 1);
  739. return str;
  740. }
  741. parseQueryStr(str) {
  742. let obj = {};
  743. if (str.indexOf("?") > -1) {
  744. str = str.split("?")[1];
  745. }
  746. let arr = str.split("&");
  747. for (let i = 0; i < arr.length; i++) {
  748. let kv = arr[i].split("=");
  749. obj[kv[0]] = kv[1];
  750. }
  751. return obj;
  752. }
  753. deepClone(obj, newObj) {
  754. newObj = newObj || {};
  755. for (let key in obj) {
  756. if (typeof obj[key] == 'object') {
  757. newObj[key] = (obj[key].constructor === Array) ? [] : {};
  758. this.deepClone(obj[key], newObj[key]);
  759. } else {
  760. newObj[key] = obj[key];
  761. }
  762. }
  763. return newObj;
  764. }
  765. convertToObject(input) {
  766. if (typeof input === "string") {
  767. let retObj = {};
  768. try {
  769. retObj = JSON.parse(input);
  770. const resultType = typeof retObj;
  771. if (resultType !== "object" || retObj instanceof Array || resultType === "boolean" || retObj === null) {
  772. retObj = {};
  773. }
  774. } catch {
  775. }
  776. return retObj;
  777. }
  778. else if (input instanceof Array || input === null || typeof input === "undefined" || input !== input || typeof input === "boolean") {
  779. return {};
  780. } else {
  781. return input;
  782. }
  783. };
  784. }(scriptName);
  785. }