├── .editorconfig ├── .gitignore ├── .vscode └── settings.json ├── ENV.js ├── README.md ├── Script ├── Block100SVIPCrack.js ├── baimiao.js ├── camscanner.js ├── clicker.js ├── clicker_cdnv4.js ├── drops.js ├── endless.js ├── fuckEid.js ├── id77_JDLM.js ├── id77_QRTools.js ├── jd_goods_info.js ├── jd_hd.js ├── jd_union.js ├── jdapp_to_union.js ├── smzdm_to_jd.js ├── syncJDCookie.js ├── time.js ├── unHttpOnly.js ├── uploadJDWSKey.js ├── uploadJXXCXCK.js └── zhihu.js ├── box.json ├── boxJS ├── README.md ├── chavy.boxjs.html └── chavy.boxjs.js ├── icon ├── JDLM.png ├── console.png ├── ex.png ├── id77.png ├── jdGuaranteedPrice.png ├── jdWuLiu.png ├── jd_car.png ├── link.png ├── sim.png ├── wyx.png └── yiLi.png ├── img └── yiLi_qr.png ├── other └── JD_DailyBonus.js ├── package-lock.json ├── package.json ├── popup ├── index.html └── static │ ├── css │ └── main.e6cd4111.css │ └── js │ └── main.19fde459.js ├── rewrite ├── Youtube_CC.conf ├── ad.conf ├── cookie.conf ├── id77_JDCookie.conf ├── json1.json ├── json2.json ├── other.conf ├── to.js └── vip.conf ├── rule └── Email.list └── task ├── clearTaskFileData.js ├── dataToTaskFile.js ├── delICloudData.js ├── delMitmData.js ├── ex.cookie.js ├── ex.task.js ├── iCloudDataToTaskFile.js ├── integrateDataToICloudFile.js ├── jdWuLiu copy.js ├── jdWuLiu.js ├── jegotrip.cookie.js ├── jegotrip.js ├── ql_delTask.js ├── ql_task.js ├── thg.cookie.js ├── thg.js ├── xmSports.js ├── yiLi.cookie.js └── yiLi.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = false 8 | insert_final_newline = false 9 | 10 | [*.js] 11 | quote_type = single -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /JDZhiBo.py 2 | /auto_bean_plus.py 3 | node_modules 4 | yiLiTips* 5 | test* -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "px-to-rem.px-per-rem": 14 3 | } -------------------------------------------------------------------------------- /ENV.js: -------------------------------------------------------------------------------- 1 | function Env(name, opts) { 2 | class Http { 3 | constructor(env) { 4 | this.env = env; 5 | } 6 | 7 | send(opts, method = 'GET') { 8 | opts = typeof opts === 'string' ? { url: opts } : opts; 9 | let sender = this.get; 10 | if (method === 'POST') { 11 | sender = this.post; 12 | } 13 | return new Promise((resolve, reject) => { 14 | sender.call(this, opts, (err, resp, body) => { 15 | if (err) reject(err); 16 | else resolve(resp); 17 | }); 18 | }); 19 | } 20 | 21 | get(opts) { 22 | return this.send.call(this.env, opts); 23 | } 24 | 25 | post(opts) { 26 | return this.send.call(this.env, opts, 'POST'); 27 | } 28 | } 29 | 30 | return new (class { 31 | constructor(name, opts = {}) { 32 | this.name = name; 33 | this.http = new Http(this); 34 | this.data = null; 35 | this.dataFile = 'box.dat'; 36 | this.logs = []; 37 | this.isMute = false; 38 | this.noLogKey = opts.noLogKey || ''; 39 | this.noLog = opts.noLog; 40 | this.isNeedRewrite = false; 41 | this.logSeparator = '\n'; 42 | this.startTime = new Date().getTime(); 43 | Object.assign(this, opts); 44 | this.log('', `🔔${this.name}, 开始!`); 45 | } 46 | 47 | isNode() { 48 | return 'undefined' !== typeof module && !!module.exports; 49 | } 50 | 51 | isQuanX() { 52 | return 'undefined' !== typeof $task; 53 | } 54 | 55 | isSurge() { 56 | return 'undefined' !== typeof $httpClient && 'undefined' === typeof $loon; 57 | } 58 | 59 | isLoon() { 60 | return 'undefined' !== typeof $loon; 61 | } 62 | 63 | isShadowrocket() { 64 | return 'undefined' !== typeof $rocket; 65 | } 66 | 67 | toObj(str, defaultValue = null) { 68 | try { 69 | return JSON.parse(str); 70 | } catch { 71 | return defaultValue; 72 | } 73 | } 74 | 75 | toStr(obj, defaultValue = null) { 76 | try { 77 | return JSON.stringify(obj); 78 | } catch { 79 | return defaultValue; 80 | } 81 | } 82 | 83 | getJson(key, defaultValue) { 84 | let json = defaultValue; 85 | const val = this.getData(key); 86 | if (val) { 87 | try { 88 | json = JSON.parse(this.getData(key)); 89 | } catch {} 90 | } 91 | return json; 92 | } 93 | 94 | setJson(val, key) { 95 | try { 96 | return this.setData(JSON.stringify(val), key); 97 | } catch { 98 | return false; 99 | } 100 | } 101 | 102 | getScript(url) { 103 | return new Promise((resolve) => { 104 | this.get({ url }, (err, resp, body) => resolve(body)); 105 | }); 106 | } 107 | 108 | runScript(script, runOpts) { 109 | return new Promise((resolve) => { 110 | let httpApi = this.getData('@chavy_boxjs_userCfgs.httpApi'); 111 | httpApi = httpApi ? httpApi.replace(/\n/g, '').trim() : httpApi; 112 | let httpApi_timeout = this.getData( 113 | '@chavy_boxjs_userCfgs.httpApi_timeout' 114 | ); 115 | httpApi_timeout = httpApi_timeout ? httpApi_timeout * 1 : 20; 116 | httpApi_timeout = 117 | runOpts && runOpts.timeout ? runOpts.timeout : httpApi_timeout; 118 | const [key, addr] = httpApi.split('@'); 119 | const opts = { 120 | url: `http://${addr}/v1/scripting/evaluate`, 121 | body: { 122 | script_text: script, 123 | mock_type: 'cron', 124 | timeout: httpApi_timeout, 125 | }, 126 | headers: { 'X-Key': key, Accept: '*/*' }, 127 | }; 128 | this.post(opts, (err, resp, body) => resolve(body)); 129 | }).catch((e) => this.logErr(e)); 130 | } 131 | 132 | loadData() { 133 | if (this.isNode()) { 134 | this.fs = this.fs ? this.fs : require('fs'); 135 | this.path = this.path ? this.path : require('path'); 136 | const curDirDataFilePath = this.path.resolve(this.dataFile); 137 | const rootDirDataFilePath = this.path.resolve( 138 | process.cwd(), 139 | this.dataFile 140 | ); 141 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 142 | const isRootDirDataFile = 143 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 144 | if (isCurDirDataFile || isRootDirDataFile) { 145 | const datPath = isCurDirDataFile 146 | ? curDirDataFilePath 147 | : rootDirDataFilePath; 148 | try { 149 | return JSON.parse(this.fs.readFileSync(datPath)); 150 | } catch (e) { 151 | return {}; 152 | } 153 | } else return {}; 154 | } else return {}; 155 | } 156 | 157 | writeData() { 158 | if (this.isNode()) { 159 | this.fs = this.fs ? this.fs : require('fs'); 160 | this.path = this.path ? this.path : require('path'); 161 | const curDirDataFilePath = this.path.resolve(this.dataFile); 162 | const rootDirDataFilePath = this.path.resolve( 163 | process.cwd(), 164 | this.dataFile 165 | ); 166 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 167 | const isRootDirDataFile = 168 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 169 | const jsonData = JSON.stringify(this.data); 170 | if (isCurDirDataFile) { 171 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 172 | } else if (isRootDirDataFile) { 173 | this.fs.writeFileSync(rootDirDataFilePath, jsonData); 174 | } else { 175 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 176 | } 177 | } 178 | } 179 | 180 | lodash_get(source, path, defaultValue = undefined) { 181 | const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.'); 182 | let result = source; 183 | for (const p of paths) { 184 | result = Object(result)[p]; 185 | if (result === undefined) { 186 | return defaultValue; 187 | } 188 | } 189 | return result; 190 | } 191 | 192 | lodash_set(obj, path, value) { 193 | if (Object(obj) !== obj) return obj; 194 | if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || []; 195 | path 196 | .slice(0, -1) 197 | .reduce( 198 | (a, c, i) => 199 | Object(a[c]) === a[c] 200 | ? a[c] 201 | : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {}), 202 | obj 203 | )[path[path.length - 1]] = value; 204 | return obj; 205 | } 206 | 207 | getData(key) { 208 | let val = this.getVal(key); 209 | // 如果以 @ 210 | if (/^@/.test(key)) { 211 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 212 | const objVal = objKey ? this.getVal(objKey) : ''; 213 | if (objVal) { 214 | try { 215 | const objedVal = JSON.parse(objVal); 216 | val = objedVal ? this.lodash_get(objedVal, paths, '') : val; 217 | } catch (e) { 218 | val = ''; 219 | } 220 | } 221 | } 222 | return val; 223 | } 224 | 225 | setData(val, key) { 226 | let isSuc = false; 227 | if (/^@/.test(key)) { 228 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 229 | const objdat = this.getVal(objKey); 230 | const objVal = objKey 231 | ? objdat === 'null' 232 | ? null 233 | : objdat || '{}' 234 | : '{}'; 235 | try { 236 | const objedVal = JSON.parse(objVal); 237 | this.lodash_set(objedVal, paths, val); 238 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 239 | } catch (e) { 240 | const objedVal = {}; 241 | this.lodash_set(objedVal, paths, val); 242 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 243 | } 244 | } else { 245 | isSuc = this.setVal(val, key); 246 | } 247 | return isSuc; 248 | } 249 | 250 | getVal(key) { 251 | if (this.isSurge() || this.isLoon()) { 252 | return $persistentStore.read(key); 253 | } else if (this.isQuanX()) { 254 | return $prefs.valueForKey(key); 255 | } else if (this.isNode()) { 256 | this.data = this.loadData(); 257 | return this.data[key]; 258 | } else { 259 | return (this.data && this.data[key]) || null; 260 | } 261 | } 262 | 263 | setVal(val, key) { 264 | if (this.isSurge() || this.isLoon()) { 265 | return $persistentStore.write(val, key); 266 | } else if (this.isQuanX()) { 267 | return $prefs.setValueForKey(val, key); 268 | } else if (this.isNode()) { 269 | this.data = this.loadData(); 270 | this.data[key] = val; 271 | this.writeData(); 272 | return true; 273 | } else { 274 | return (this.data && this.data[key]) || null; 275 | } 276 | } 277 | 278 | initGotEnv(opts) { 279 | this.got = this.got ? this.got : require('got'); 280 | this.ckTough = this.ckTough ? this.ckTough : require('tough-cookie'); 281 | this.ckJar = this.ckJar ? this.ckJar : new this.ckTough.CookieJar(); 282 | if (opts) { 283 | opts.headers = opts.headers ? opts.headers : {}; 284 | if (undefined === opts.headers.Cookie && undefined === opts.cookieJar) { 285 | opts.cookieJar = this.ckJar; 286 | } 287 | } 288 | } 289 | 290 | get(opts, callback = () => {}) { 291 | if (opts.headers) { 292 | delete opts.headers['Content-Type']; 293 | delete opts.headers['Content-Length']; 294 | } 295 | if (this.isSurge() || this.isLoon()) { 296 | if (this.isSurge() && this.isNeedRewrite) { 297 | opts.headers = opts.headers || {}; 298 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 299 | } 300 | $httpClient.get(opts, (err, resp, body) => { 301 | if (!err && resp) { 302 | resp.body = body; 303 | resp.statusCode = resp.status; 304 | } 305 | callback(err, resp, body); 306 | }); 307 | } else if (this.isQuanX()) { 308 | if (this.isNeedRewrite) { 309 | opts.opts = opts.opts || {}; 310 | Object.assign(opts.opts, { hints: false }); 311 | } 312 | $task.fetch(opts).then( 313 | (resp) => { 314 | const { statusCode: status, statusCode, headers, body } = resp; 315 | callback(null, { status, statusCode, headers, body }, body); 316 | }, 317 | (err) => callback(err) 318 | ); 319 | } else if (this.isNode()) { 320 | this.initGotEnv(opts); 321 | this.got(opts) 322 | .on('redirect', (resp, nextOpts) => { 323 | try { 324 | if (resp.headers['set-cookie']) { 325 | const ck = resp.headers['set-cookie'] 326 | .map(this.ckTough.Cookie.parse) 327 | .toString(); 328 | if (ck) { 329 | this.ckJar.setCookieSync(ck, null); 330 | } 331 | nextOpts.cookieJar = this.ckJar; 332 | } 333 | } catch (e) { 334 | this.logErr(e); 335 | } 336 | // this.ckJar.setCookieSync(resp.headers['set-cookie'].map(Cookie.parse).toString()) 337 | }) 338 | .then( 339 | (resp) => { 340 | const { statusCode: status, statusCode, headers, body } = resp; 341 | callback(null, { status, statusCode, headers, body }, body); 342 | }, 343 | (err) => { 344 | const { message: error, response: resp } = err; 345 | callback(error, resp, resp && resp.body); 346 | } 347 | ); 348 | } 349 | } 350 | 351 | post(opts, callback = () => {}) { 352 | const method = opts.method ? opts.method.toLocaleLowerCase() : 'post'; 353 | // 如果指定了请求体, 但没指定`Content-Type`, 则自动生成 354 | // if (opts.body && // opts.headers && !opts.headers['Content-Type']) { 355 | opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 356 | // } 357 | if (opts.headers) delete opts.headers['Content-Length']; 358 | if (this.isSurge() || this.isLoon()) { 359 | if (this.isSurge() && this.isNeedRewrite) { 360 | opts.headers = opts.headers || {}; 361 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 362 | } 363 | $httpClient[method](opts, (err, resp, body) => { 364 | if (!err && resp) { 365 | resp.body = body; 366 | resp.statusCode = resp.status; 367 | } 368 | callback(err, resp, body); 369 | }); 370 | } else if (this.isQuanX()) { 371 | opts.method = method; 372 | if (this.isNeedRewrite) { 373 | opts.opts = opts.opts || {}; 374 | Object.assign(opts.opts, { hints: false }); 375 | } 376 | $task.fetch(opts).then( 377 | (resp) => { 378 | const { statusCode: status, statusCode, headers, body } = resp; 379 | callback(null, { status, statusCode, headers, body }, body); 380 | }, 381 | (err) => callback(err) 382 | ); 383 | } else if (this.isNode()) { 384 | this.initGotEnv(opts); 385 | const { url, ..._opts } = opts; 386 | this.got[method](url, _opts).then( 387 | (resp) => { 388 | const { statusCode: status, statusCode, headers, body } = resp; 389 | callback(null, { status, statusCode, headers, body }, body); 390 | }, 391 | (err) => { 392 | const { message: error, response: resp } = err; 393 | callback(error, resp, resp && resp.body); 394 | } 395 | ); 396 | } 397 | } 398 | /** 399 | * 400 | * 示例:$.time('yyyy-MM-dd qq HH:mm:ss.S') 401 | * :$.time('yyyyMMddHHmmssS') 402 | * y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒 403 | * 其中y可选0-4位占位符、S可选0-1位占位符,其余可选0-2位占位符 404 | * @param {string} fmt 格式化参数 405 | * @param {number} 可选: 根据指定时间戳返回格式化日期 406 | * 407 | */ 408 | time(fmt, ts = null) { 409 | const date = ts ? new Date(ts) : new Date(); 410 | let o = { 411 | 'M+': date.getMonth() + 1, 412 | 'd+': date.getDate(), 413 | 'H+': date.getHours(), 414 | 'm+': date.getMinutes(), 415 | 's+': date.getSeconds(), 416 | 'q+': Math.floor((date.getMonth() + 3) / 3), 417 | S: date.getMilliseconds(), 418 | }; 419 | if (/(y+)/.test(fmt)) 420 | fmt = fmt.replace( 421 | RegExp.$1, 422 | (date.getFullYear() + '').substr(4 - RegExp.$1.length) 423 | ); 424 | for (let k in o) 425 | if (new RegExp('(' + k + ')').test(fmt)) 426 | fmt = fmt.replace( 427 | RegExp.$1, 428 | RegExp.$1.length == 1 429 | ? o[k] 430 | : ('00' + o[k]).substr(('' + o[k]).length) 431 | ); 432 | return fmt; 433 | } 434 | 435 | /** 436 | * 系统通知 437 | * 438 | * > 通知参数: 同时支持 QuanX 和 Loon 两种格式, EnvJs根据运行环境自动转换, Surge 环境不支持多媒体通知 439 | * 440 | * 示例: 441 | * $.msg(title, subt, desc, 'twitter://') 442 | * $.msg(title, subt, desc, { 'open-url': 'twitter://', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 443 | * $.msg(title, subt, desc, { 'open-url': 'https://bing.com', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 444 | * 445 | * @param {*} title 标题 446 | * @param {*} subt 副标题 447 | * @param {*} desc 通知详情 448 | * @param {*} opts 通知参数 449 | * 450 | */ 451 | msg(title = name, subt = '', desc = '', opts) { 452 | const toEnvOpts = (rawOpts) => { 453 | if (!rawOpts) return rawOpts; 454 | if (typeof rawOpts === 'string') { 455 | if (this.isLoon()) return rawOpts; 456 | else if (this.isQuanX()) return { 'open-url': rawOpts }; 457 | else if (this.isSurge()) return { url: rawOpts }; 458 | else return undefined; 459 | } else if (typeof rawOpts === 'object') { 460 | if (this.isLoon()) { 461 | let openUrl = rawOpts.openUrl || rawOpts.url || rawOpts['open-url']; 462 | let mediaUrl = rawOpts.mediaUrl || rawOpts['media-url']; 463 | return { openUrl, mediaUrl }; 464 | } else if (this.isQuanX()) { 465 | let openUrl = rawOpts['open-url'] || rawOpts.url || rawOpts.openUrl; 466 | let mediaUrl = rawOpts['media-url'] || rawOpts.mediaUrl; 467 | let updatePasteboard = 468 | rawOpts['update-pasteboard'] || rawOpts.updatePasteboard; 469 | return { 470 | 'open-url': openUrl, 471 | 'media-url': mediaUrl, 472 | 'update-pasteboard': updatePasteboard, 473 | }; 474 | } else if (this.isSurge()) { 475 | let openUrl = rawOpts.url || rawOpts.openUrl || rawOpts['open-url']; 476 | return { url: openUrl }; 477 | } 478 | } else { 479 | return undefined; 480 | } 481 | }; 482 | if (!this.isMute) { 483 | if (this.isSurge() || this.isLoon()) { 484 | $notification.post(title, subt, desc, toEnvOpts(opts)); 485 | } else if (this.isQuanX()) { 486 | $notify(title, subt, desc, toEnvOpts(opts)); 487 | } 488 | } 489 | if (!this.isMuteLog) { 490 | let logs = ['', '==============📣系统通知📣==============']; 491 | logs.push(title); 492 | subt ? logs.push(subt) : ''; 493 | desc ? logs.push(desc) : ''; 494 | console.log(logs.join('\n')); 495 | this.logs = this.logs.concat(logs); 496 | } 497 | } 498 | 499 | log(...logs) { 500 | if ( 501 | this.noLog || 502 | (this.noLogKey && 503 | (this.getData(this.noLogKey) || 'N').toLocaleUpperCase() === 'Y') 504 | ) { 505 | return; 506 | } 507 | if (logs.length > 0) { 508 | this.logs = [...this.logs, ...logs]; 509 | } 510 | console.log(logs.join(this.logSeparator)); 511 | } 512 | 513 | logErr(err, msg) { 514 | const isPrintSack = !this.isSurge() && !this.isQuanX() && !this.isLoon(); 515 | if (!isPrintSack) { 516 | this.log('', `❗️${this.name}, 错误!`, err); 517 | } else { 518 | this.log('', `❗️${this.name}, 错误!`, err.stack); 519 | } 520 | } 521 | 522 | wait(time) { 523 | return new Promise((resolve) => setTimeout(resolve, time)); 524 | } 525 | 526 | done(val = {}) { 527 | const endTime = new Date().getTime(); 528 | const costTime = (endTime - this.startTime) / 1000; 529 | this.log('', `🔔${this.name}, 结束! 🕛 ${costTime} 秒`); 530 | this.log(); 531 | if (this.isSurge() || this.isQuanX() || this.isLoon()) { 532 | $done(val); 533 | } 534 | } 535 | })(name, opts); 536 | } 537 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 自用脚本、配置 2 | 3 | QuantumultX 可以直接使用,所有配置以及脚本。 4 | 5 | Task 同时兼容 Surge、Loon, 由于个人精力有限,仅测试 QuantumultX。 6 | 7 | 为了 Task 脚本兼容多个 App,使用了[env.js](https://github.com/chavyleung/scripts/blob/master/Env.js) 8 | 9 | 特别感谢[@Orz-3](https://github.com/Orz-3/mini)提供图标库 10 | 11 | ⚠️ 免责声明: 12 | 13 | 1. 脚本仅用于学习研究,不保证其合法性、准确性、有效性,请根据情况自行判断,本人对此不承担任何保证责任。 14 | 2. 由于脚本仅用于学习研究,您必须在下载后 24 小时内将所有内容从您的计算机或手机或任何存储设备中完全删除,若违反规定引起任何事件本人对此均不负责。 15 | 3. 请勿将脚本用于任何商业或非法目的,若违反规定请自行对此负责。 16 | 4. 脚本涉及应用与本人无关,本人对因此引起的任何隐私泄漏或其他后果不承担任何责任。 17 | 5. 本人对任何脚本引发的问题概不负责,包括但不限于由脚本错误引起的任何损失和损害。 18 | 6. 如果任何单位或个人认为脚本可能涉嫌侵犯其权利,应及时通知并提供身份证明,所有权证明,我们将在收到认证文件确认后删除此脚本。 19 | 7. 所有直接或间接使用、查看脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了脚本,即视为您已接受此免责声明。 20 | -------------------------------------------------------------------------------- /Script/Block100SVIPCrack.js: -------------------------------------------------------------------------------- 1 | /* 2 | * 备份自用 3 | * 4 | 脚本功能:拦截100解锁永久会员 5 | 软件版本:3.0.2 6 | 下载地址:http://t.cn/A6MLFAJf 7 | 脚本作者:Hausd0rff 8 | 特别鸣谢:感谢@凉意大佬提供的数据 9 | 更新时间:2022-02-08 10 | 电报频道:https://t.me/yqc_123 11 | 问题反馈:https://t.me/yqc_777 12 | 使用声明:⚠️此脚本仅供学习与交流, 13 | 请勿转载与贩卖!⚠️⚠️⚠️ 14 | ******************************* 15 | [rewrite_local] 16 | # > 拦截100解锁永久超级会员 17 | ^https?:\/\/tagit\.hyhuo\.com\/cypt\/block100\/get_vip_info$ url script-response-body https://raw.githubusercontent.com/yqc007/QuantumultX/master/Block100FVIPCrack.js 18 | [mitm] 19 | hostname = tagit.hyhuo.com 20 | * 21 | * 22 | */ 23 | 24 | 25 | var body = $response.body; 26 | 27 | body = "lvCQG8cCxqficLk+LttK+OvjY+kGEoGHRWop15GMRVg1TU8oQTFHsNCJIEJMEYYfDjqpfM0sxeXRILHsoullvHqzmN6X7HmMRHqOjr3G0AXp2FtlU91l2+2ZbtUpL8p2cc6Y6JdCOUiADpqc4GZktNpGoED1rMVltIjdbhLGVgO0tYaNtQ/dV52tpmn+Lcm+/3pCU8/wXdnCfkkMB0QZc6psJavFUF6dLfDRzagLuxiwgOQmNQraUG99e4YLDmoQ"; 28 | 29 | $done({ body }); -------------------------------------------------------------------------------- /Script/baimiao.js: -------------------------------------------------------------------------------- 1 | /* 2 | 登陆时写入OCR&翻译无限制次数权限 3 | 已不再维护,使用微软家的 Translator 4 | [rewrite_local] 5 | # 白描 6 | ^https:\/\/baimiao\.uzero\.cn\/api\/v2\.user\/(logout|appLaunchWithUser|loginByWeixin) url script-response-body https://raw.githubusercontent.com/id77/QuantumultX/master/Script/baimiao.js 7 | 8 | [mitm] 9 | hostname = api.xiaolanben.com 10 | */ 11 | 12 | let obj = JSON.parse($response.body); 13 | obj.value.vip = true; 14 | obj.value.recognize.remainBatch = -100; 15 | obj.value.recognize.remainNormal = -100; 16 | obj.value.recognize.remainTranslate = -100; 17 | obj.value.recognize.recognizeTranslateAll = 1; 18 | 19 | $done({ body: JSON.stringify(obj) }); 20 | -------------------------------------------------------------------------------- /Script/camscanner.js: -------------------------------------------------------------------------------- 1 | let body = JSON.parse($response.body) 2 | body.data.psnl_vip_property = {"renew_method": "appstore", 3 | "initial_tm": "1614867690", 4 | "svip": 1, 5 | "auto_renewal": true, 6 | "ms_first_pay": 0, 7 | "pending": 0, 8 | "group2_paid": 0, 9 | "inherited_flag": 0, 10 | "nxt_renew_tm": "9915126887", 11 | "level_info": { 12 | "level": 1, 13 | "days": 1, 14 | "end_days": 30 15 | }, 16 | "group1_paid": 1, 17 | "ys_first_pay": 0, 18 | "renew_type": "year", 19 | "expiry": 9915130487, 20 | "grade": 2, 21 | "last_payment_method": "appstore", 22 | "product_id": "com.intsig.camscanner.premiums.oneyear.autorenewable.svip.low"} 23 | $done({body:JSON.stringify(body)}) 24 | -------------------------------------------------------------------------------- /Script/drops.js: -------------------------------------------------------------------------------- 1 | var obj = JSON.parse($response.body); 2 | 3 | obj= { 4 | "purchases": [ 5 | { 6 | "topUpId": "iapPremiumYearly70FreeTrial", 7 | "provider": "apple", 8 | "receipt": "MIIULAYJKoZIhvcNAQcCoIIUHTCCFBkCAQExCzAJBgUrDgMCGgUAMIIDzQYJKoZIhvcNAQcBoIIDvgSCA7oxggO2MAoCARQCAQEEAgwAMAsCARkCAQEEAwIBAzAMAgEKAgEBBAQWAjQrMAwCAQ4CAQEEBAICAIkwDQIBCwIBAQQFAgMTIfwwDQIBDQIBAQQFAgMB1YgwDgIBAQIBAQQGAgQ4AD+TMA4CAQkCAQEEBgIEUDI1MjAOAgEQAgEBBAYCBDGIVhowEAIBDwIBAQQIAgZVhlh8FGUwEQIBAwIBAQQJDAczMS4xMi4wMBECARMCAQEECQwHMzEuMTIuMDAUAgEAAgEBBAwMClByb2R1Y3Rpb24wGAIBBAIBAgQQthX8KtETb7VYNLeOXqvzgDAcAgEFAgEBBBQF7usNDLV6JkGwLKjs5MYNIo3bTjAeAgEIAgEBBBYWFDIwMTktMDQtMjFUMTg6Mjg6NTlaMB4CAQwCAQEEFhYUMjAxOS0wNC0yMVQxODoyODo1OVowHgIBEgIBAQQWFhQyMDE5LTA0LTIxVDE3OjU5OjM3WjAlAgECAgEBBB0MG2NvbS5wbGFuYmxhYnMuZHJvcHMuaXRhbGlhbjBGAgEHAgEBBD5KvFdEpBoIn92kAmekzisa7LgVEfTLEFvYGOpK5aRsW4i70RzYl9vlpDFd1iF7Gft3wEjeSBnjBaDfyRpHajBIAgEGAgEBBEBaSa7eVaPTsa1hSAKuIqu8z5KlH/VpRatbPKSkorwXo8ewSCIBlBQFVgcoVfPpeWJdpNEnbQg8Rba1Jj+SMywgMIIBkAIBEQIBAQSCAYYxggGCMAsCAgatAgEBBAIMADALAgIGsAIBAQQCFgAwCwICBrICAQEEAgwAMAsCAgazAgEBBAIMADALAgIGtAIBAQQCDAAwCwICBrUCAQEEAgwAMAsCAga2AgEBBAIMADAMAgIGpQIBAQQDAgEBMAwCAgarAgEBBAMCAQMwDAICBrECAQEEAwIBATAMAgIGtwIBAQQDAgEAMA8CAgauAgEBBAYCBFSfj5EwEgICBq8CAQEECQIHAesgks4I4TAaAgIGpwIBAQQRDA81NDAwMDAzNzA0NjEwMTMwGgICBqkCAQEEEQwPNTQwMDAwMzcwNDYxMDEzMB8CAgaoAgEBBBYWFDIwMTktMDQtMjFUMTg6Mjg6NThaMB8CAgaqAgEBBBYWFDIwMTktMDQtMjFUMTg6Mjg6NThaMB8CAgasAgEBBBYWFDIwMTktMDQtMjRUMTg6Mjg6NThaMC0CAgamAgEBBCQMInByZW1pdW1feWVhcmx5XzcwX2ZyZWV0cmlhbF9pbnRfdjKggg5lMIIFfDCCBGSgAwIBAgIIDutXh+eeCY0wDQYJKoZIhvcNAQEFBQAwgZYxCzAJBgNVBAYTAlVTMRMwEQYDVQQKDApBcHBsZSBJbmMuMSwwKgYDVQQLDCNBcHBsZSBXb3JsZHdpZGUgRGV2ZWxvcGVyIFJlbGF0aW9uczFEMEIGA1UEAww7QXBwbGUgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTUxMTEzMDIxNTA5WhcNMjMwMjA3MjE0ODQ3WjCBiTE3MDUGA1UEAwwuTWFjIEFwcCBTdG9yZSBhbmQgaVR1bmVzIFN0b3JlIFJlY2VpcHQgU2lnbmluZzEsMCoGA1UECwwjQXBwbGUgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMxEzARBgNVBAoMCkFwcGxlIEluYy4xCzAJBgNVBAYTAlVTMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApc+B/SWigVvWh+0j2jMcjuIjwKXEJss9xp/sSg1Vhv+kAteXyjlUbX1/slQYncQsUnGOZHuCzom6SdYI5bSIcc8/W0YuxsQduAOpWKIEPiF41du30I4SjYNMWypoN5PC8r0exNKhDEpYUqsS4+3dH5gVkDUtwswSyo1IgfdYeFRr6IwxNh9KBgxHVPM3kLiykol9X6SFSuHAnOC6pLuCl2P0K5PB/T5vysH1PKmPUhrAJQp2Dt7+mf7/wmv1W16sc1FJCFaJzEOQzI6BAtCgl7ZcsaFpaYeQEGgmJjm4HRBzsApdxXPQ33Y72C3ZiB7j7AfP4o7Q0/omVYHv4gNJIwIDAQABo4IB1zCCAdMwPwYIKwYBBQUHAQEEMzAxMC8GCCsGAQUFBzABhiNodHRwOi8vb2NzcC5hcHBsZS5jb20vb2NzcDAzLXd3ZHIwNDAdBgNVHQ4EFgQUkaSc/MR2t5+givRN9Y82Xe0rBIUwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBSIJxcJqbYYYIvs67r2R1nFUlSjtzCCAR4GA1UdIASCARUwggERMIIBDQYKKoZIhvdjZAUGATCB/jCBwwYIKwYBBQUHAgIwgbYMgbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjA2BggrBgEFBQcCARYqaHR0cDovL3d3dy5hcHBsZS5jb20vY2VydGlmaWNhdGVhdXRob3JpdHkvMA4GA1UdDwEB/wQEAwIHgDAQBgoqhkiG92NkBgsBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEADaYb0y4941srB25ClmzT6IxDMIJf4FzRjb69D70a/CWS24yFw4BZ3+Pi1y4FFKwN27a4/vw1LnzLrRdrjn8f5He5sWeVtBNephmGdvhaIJXnY4wPc/zo7cYfrpn4ZUhcoOAoOsAQNy25oAQ5H3O5yAX98t5/GioqbisB/KAgXNnrfSemM/j1mOC+RNuxTGf8bgpPyeIGqNKX86eOa1GiWoR1ZdEWBGLjwV/1CKnPaNmSAMnBjLP4jQBkulhgwHyvj3XKablbKtYdaG6YQvVMpzcZm8w7HHoZQ/Ojbb9IYAYMNpIr7N4YtRHaLSPQjvygaZwXG56AezlHRTBhL8cTqDCCBCIwggMKoAMCAQICCAHevMQ5baAQMA0GCSqGSIb3DQEBBQUAMGIxCzAJBgNVBAYTAlVTMRMwEQYDVQQKEwpBcHBsZSBJbmMuMSYwJAYDVQQLEx1BcHBsZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEWMBQGA1UEAxMNQXBwbGUgUm9vdCBDQTAeFw0xMzAyMDcyMTQ4NDdaFw0yMzAyMDcyMTQ4NDdaMIGWMQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUgSW5jLjEsMCoGA1UECwwjQXBwbGUgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMxRDBCBgNVBAMMO0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVsYXRpb25zIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyjhUpstWqsgkOUjpjO7sX7h/JpG8NFN6znxjgGF3ZF6lByO2Of5QLRVWWHAtfsRuwUqFPi/w3oQaoVfJr3sY/2r6FRJJFQgZrKrbKjLtlmNoUhU9jIrsv2sYleADrAF9lwVnzg6FlTdq7Qm2rmfNUWSfxlzRvFduZzWAdjakh4FuOI/YKxVOeyXYWr9Og8GN0pPVGnG1YJydM05V+RJYDIa4Fg3B5XdFjVBIuist5JSF4ejEncZopbCj/Gd+cLoCWUt3QpE5ufXN4UzvwDtIjKblIV39amq7pxY1YNLmrfNGKcnow4vpecBqYWcVsvD95Wi8Yl9uz5nd7xtj/pJlqwIDAQABo4GmMIGjMB0GA1UdDgQWBBSIJxcJqbYYYIvs67r2R1nFUlSjtzAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFCvQaUeUdgn+9GuNLkCm90dNfwheMC4GA1UdHwQnMCUwI6AhoB+GHWh0dHA6Ly9jcmwuYXBwbGUuY29tL3Jvb3QuY3JsMA4GA1UdDwEB/wQEAwIBhjAQBgoqhkiG92NkBgIBBAIFADANBgkqhkiG9w0BAQUFAAOCAQEAT8/vWb4s9bJsL4/uE4cy6AU1qG6LfclpDLnZF7x3LNRn4v2abTpZXN+DAb2yriphcrGvzcNFMI+jgw3OHUe08ZOKo3SbpMOYcoc7Pq9FC5JUuTK7kBhTawpOELbZHVBsIYAKiU5XjGtbPD2m/d73DSMdC0omhz+6kZJMpBkSGW1X9XpYh3toiuSGjErr4kkUqqXdVQCprrtLMK7hoLG8KYDmCXflvjSiAcp/3OIK5ju4u+y6YpXzBWNBgs0POx1MlaTbq/nJlelP5E3nJpmB6bz5tCnSAXpm4S6M9iGKxfh44YGuv9OQnamt86/9OBqWZzAcUaVc7HGKgrRsDwwVHzCCBLswggOjoAMCAQICAQIwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENBMB4XDTA2MDQyNTIxNDAzNloXDTM1MDIwOTIxNDAzNlowYjELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAkBgNVBAsTHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYwFAYDVQQDEw1BcHBsZSBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5JGpCR+R2x5HUOsF7V55hC3rNqJXTFXsixmJ3vlLbPUHqyIwAugYPvhQCdN/QaiY+dHKZpwkaxHQo7vkGyrDH5WeegykR4tb1BY3M8vED03OFGnRyRly9V0O1X9fm/IlA7pVj01dDfFkNSMVSxVZHbOU9/acns9QusFYUGePCLQg98usLCBvcLY/ATCMt0PPD5098ytJKBrI/s61uQ7ZXhzWyz21Oq30Dw4AkguxIRYudNU8DdtiFqujcZJHU1XBry9Bs/j743DN5qNMRX4fTGtQlkGJxHRiCxCDQYczioGxMFjsWgQyjGizjx3eZXP/Z15lvEnYdp8zFGWhd5TJLQIDAQABo4IBejCCAXYwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFCvQaUeUdgn+9GuNLkCm90dNfwheMB8GA1UdIwQYMBaAFCvQaUeUdgn+9GuNLkCm90dNfwheMIIBEQYDVR0gBIIBCDCCAQQwggEABgkqhkiG92NkBQEwgfIwKgYIKwYBBQUHAgEWHmh0dHBzOi8vd3d3LmFwcGxlLmNvbS9hcHBsZWNhLzCBwwYIKwYBBQUHAgIwgbYagbNSZWxpYW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBhc3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBwcmFjdGljZSBzdGF0ZW1lbnRzLjANBgkqhkiG9w0BAQUFAAOCAQEAXDaZTC14t+2Mm9zzd5vydtJ3ME/BH4WDhRuZPUc38qmbQI4s1LGQEti+9HOb7tJkD8t5TzTYoj75eP9ryAfsfTmDi1Mg0zjEsb+aTwpr/yv8WacFCXwXQFYRHnTTt4sjO0ej1W8k4uvRt3DfD0XhJ8rxbXjt57UXF6jcfiI1yiXV2Q/Wa9SiJCMR96Gsj3OBYMYbWwkvkrL4REjwYDieFfU9JmcgijNq9w2Cz97roy/5U2pbZMBjM3f3OgcsVuvaDyEO2rpzGU+12TZ/wYdV2aeZuTJC+9jVcZ5+oVK3G72TQiQSKscPHbZNnF5jyEuAF1CqitXa5PzQCQc3sHV1ITGCAcswggHHAgEBMIGjMIGWMQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUgSW5jLjEsMCoGA1UECwwjQXBwbGUgV29ybGR3aWRlIERldmVsb3BlciBSZWxhdGlvbnMxRDBCBgNVBAMMO0FwcGxlIFdvcmxkd2lkZSBEZXZlbG9wZXIgUmVsYXRpb25zIENlcnRpZmljYXRpb24gQXV0aG9yaXR5AggO61eH554JjTAJBgUrDgMCGgUAMA0GCSqGSIb3DQEBAQUABIIBAE3m0zOR8kURDoZSvO0HXrFeC6NX37l2el4l1xVCdlE1fw5Zre5A6IhjhHQzxr+vSTDUNf+aNG1enN4GT/GkVQC8xNBWLn5Y9goeF35rELLEzDGyXHyv3SjgJ0v/aoG+PwLK0rvrrLOEBOgazvP1k2BUt1BMGQ3vx/TZpjlxLV7Jv0WaLd6xNeP+2zTX4AGgssXUy+7J1Sa05W95vrK13vtAlbfbLqatwqwzVgRvnU42skGhvxZsQdiD9SUsdeufhe3SLzF11VagbRKs/jGkU+4guSpuDtsSWQHkbsCRApjKniYI22ZLZraFsgzHhYdadTh6oRh7373+J7lZIpQvhHs=", 9 | "status": "valid", 10 | "purchaseDate": 1555871338000, 11 | "expirationDate": 4080738538000, 12 | "transactionId": "540000370461013" 13 | } 14 | ] 15 | }; 16 | 17 | $done({body: JSON.stringify(obj)}); 18 | 19 | // Descriptions 20 | 21 | -------------------------------------------------------------------------------- /Script/endless.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 名称:endless.js 3 | * 地址: 4 | * 5 | * 制作:senku 6 | * 7 | ******** 以下为 tamperJS 自动生成的 rewrite 相关信息,可能需要根据情况适当调整 ******** 8 | 9 | https://www.google.com/search?q=sss&ie=UTF-8&oe=UTF-8&hl=zh-hans-us&client=safari 10 | 11 | [rewrite] 12 | # google 翻页 (senku) 13 | https?:\/\/www\.google\.([a-z.]*)\/search.*(?|<\/body>/.test(body)) { 27 | body = body.replace( 28 | '', 29 | ` 30 | 31 | ` 179 | ); 180 | 181 | console.log('添加 tamperJS:endless.js'); 182 | } 183 | 184 | $done({ body }); 185 | -------------------------------------------------------------------------------- /Script/fuckEid.js: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | # 屏蔽eid 手机跟踪 4 | https://.*.jd.com url script-request-body https://raw.githubusercontent.com/id77/QuantumultX/master/Script/fuckEid.js 5 | 6 | **/ 7 | 8 | const $ = new Env('Fuck Eid'); 9 | 10 | let modifiedHeaders = $request.headers; 11 | let modifiedBody = $request.body; 12 | // modifiedHeaders['User-Agent'] = modifiedHeaders['User-Agent'].replace( 13 | // /"devicefinger":".*"/g, 14 | // '"devicefinger":""' 15 | // ); 16 | modifiedHeaders['Cookie'] = modifiedHeaders['Cookie'].replace( 17 | /("devicefinger":")([^"]*)/g, 18 | '"devicefinger":""' 19 | ); 20 | 21 | if (modifiedBody) { 22 | // 校验参数,不能替换 23 | // modifiedBody = modifiedBody.replace(/(eid=)([^&]*)/g, handleReplace); 24 | } 25 | 26 | $.done({ headers: modifiedHeaders, body: modifiedBody }); 27 | 28 | function handleReplace(match, p1, p2) { 29 | return ( 30 | p1 + 31 | p2 32 | .split('') 33 | .sort(function () { 34 | return Math.random() - 0.5; 35 | }) 36 | .join('') 37 | ); 38 | } 39 | 40 | // https://github.com/chavyleung/scripts/blob/master/Env.js 41 | // prettier-ignore 42 | function Env(name, opts) { 43 | class Http { 44 | constructor(env) { 45 | this.env = env; 46 | } 47 | 48 | send(opts, method = 'GET') { 49 | opts = typeof opts === 'string' ? { url: opts } : opts; 50 | let sender = this.get; 51 | if (method === 'POST') { 52 | sender = this.post; 53 | } 54 | return new Promise((resolve, reject) => { 55 | sender.call(this, opts, (err, resp, body) => { 56 | if (err) reject(err); 57 | else resolve(resp); 58 | }); 59 | }); 60 | } 61 | 62 | get(opts) { 63 | return this.send.call(this.env, opts); 64 | } 65 | 66 | post(opts) { 67 | return this.send.call(this.env, opts, 'POST'); 68 | } 69 | } 70 | 71 | return new (class { 72 | constructor(name, opts = {}) { 73 | this.name = name; 74 | this.http = new Http(this); 75 | this.data = null; 76 | this.dataFile = 'box.dat'; 77 | this.logs = []; 78 | this.isMute = false; 79 | this.noLogKey = opts.noLogKey || ''; 80 | this.noLog = opts.noLog; 81 | this.isNeedRewrite = false; 82 | this.logSeparator = '\n'; 83 | this.startTime = new Date().getTime(); 84 | Object.assign(this, opts); 85 | this.log('', `🔔${this.name}, 开始!`); 86 | } 87 | 88 | isNode() { 89 | return 'undefined' !== typeof module && !!module.exports; 90 | } 91 | 92 | isQuanX() { 93 | return 'undefined' !== typeof $task; 94 | } 95 | 96 | isSurge() { 97 | return 'undefined' !== typeof $httpClient && 'undefined' === typeof $loon; 98 | } 99 | 100 | isLoon() { 101 | return 'undefined' !== typeof $loon; 102 | } 103 | 104 | isShadowrocket() { 105 | return 'undefined' !== typeof $rocket; 106 | } 107 | 108 | toObj(str, defaultValue = null) { 109 | try { 110 | return JSON.parse(str); 111 | } catch { 112 | return defaultValue; 113 | } 114 | } 115 | 116 | toStr(obj, defaultValue = null) { 117 | try { 118 | return JSON.stringify(obj); 119 | } catch { 120 | return defaultValue; 121 | } 122 | } 123 | 124 | getJson(key, defaultValue) { 125 | let json = defaultValue; 126 | const val = this.getData(key); 127 | if (val) { 128 | try { 129 | json = JSON.parse(this.getData(key)); 130 | } catch {} 131 | } 132 | return json; 133 | } 134 | 135 | setJson(val, key) { 136 | try { 137 | return this.setData(JSON.stringify(val), key); 138 | } catch { 139 | return false; 140 | } 141 | } 142 | 143 | getScript(url) { 144 | return new Promise((resolve) => { 145 | this.get({ url }, (err, resp, body) => resolve(body)); 146 | }); 147 | } 148 | 149 | runScript(script, runOpts) { 150 | return new Promise((resolve) => { 151 | let httpApi = this.getData('@chavy_boxjs_userCfgs.httpApi'); 152 | httpApi = httpApi ? httpApi.replace(/\n/g, '').trim() : httpApi; 153 | let httpApi_timeout = this.getData( 154 | '@chavy_boxjs_userCfgs.httpApi_timeout' 155 | ); 156 | httpApi_timeout = httpApi_timeout ? httpApi_timeout * 1 : 20; 157 | httpApi_timeout = 158 | runOpts && runOpts.timeout ? runOpts.timeout : httpApi_timeout; 159 | const [key, addr] = httpApi.split('@'); 160 | const opts = { 161 | url: `http://${addr}/v1/scripting/evaluate`, 162 | body: { 163 | script_text: script, 164 | mock_type: 'cron', 165 | timeout: httpApi_timeout, 166 | }, 167 | headers: { 'X-Key': key, Accept: '*/*' }, 168 | }; 169 | this.post(opts, (err, resp, body) => resolve(body)); 170 | }).catch((e) => this.logErr(e)); 171 | } 172 | 173 | loadData() { 174 | if (this.isNode()) { 175 | this.fs = this.fs ? this.fs : require('fs'); 176 | this.path = this.path ? this.path : require('path'); 177 | const curDirDataFilePath = this.path.resolve(this.dataFile); 178 | const rootDirDataFilePath = this.path.resolve( 179 | process.cwd(), 180 | this.dataFile 181 | ); 182 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 183 | const isRootDirDataFile = 184 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 185 | if (isCurDirDataFile || isRootDirDataFile) { 186 | const datPath = isCurDirDataFile 187 | ? curDirDataFilePath 188 | : rootDirDataFilePath; 189 | try { 190 | return JSON.parse(this.fs.readFileSync(datPath)); 191 | } catch (e) { 192 | return {}; 193 | } 194 | } else return {}; 195 | } else return {}; 196 | } 197 | 198 | writeData() { 199 | if (this.isNode()) { 200 | this.fs = this.fs ? this.fs : require('fs'); 201 | this.path = this.path ? this.path : require('path'); 202 | const curDirDataFilePath = this.path.resolve(this.dataFile); 203 | const rootDirDataFilePath = this.path.resolve( 204 | process.cwd(), 205 | this.dataFile 206 | ); 207 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 208 | const isRootDirDataFile = 209 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 210 | const jsonData = JSON.stringify(this.data); 211 | if (isCurDirDataFile) { 212 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 213 | } else if (isRootDirDataFile) { 214 | this.fs.writeFileSync(rootDirDataFilePath, jsonData); 215 | } else { 216 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 217 | } 218 | } 219 | } 220 | 221 | lodash_get(source, path, defaultValue = undefined) { 222 | const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.'); 223 | let result = source; 224 | for (const p of paths) { 225 | result = Object(result)[p]; 226 | if (result === undefined) { 227 | return defaultValue; 228 | } 229 | } 230 | return result; 231 | } 232 | 233 | lodash_set(obj, path, value) { 234 | if (Object(obj) !== obj) return obj; 235 | if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || []; 236 | path 237 | .slice(0, -1) 238 | .reduce( 239 | (a, c, i) => 240 | Object(a[c]) === a[c] 241 | ? a[c] 242 | : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {}), 243 | obj 244 | )[path[path.length - 1]] = value; 245 | return obj; 246 | } 247 | 248 | getData(key) { 249 | let val = this.getVal(key); 250 | // 如果以 @ 251 | if (/^@/.test(key)) { 252 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 253 | const objVal = objKey ? this.getVal(objKey) : ''; 254 | if (objVal) { 255 | try { 256 | const objedVal = JSON.parse(objVal); 257 | val = objedVal ? this.lodash_get(objedVal, paths, '') : val; 258 | } catch (e) { 259 | val = ''; 260 | } 261 | } 262 | } 263 | return val; 264 | } 265 | 266 | setData(val, key) { 267 | let isSuc = false; 268 | if (/^@/.test(key)) { 269 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 270 | const objdat = this.getVal(objKey); 271 | const objVal = objKey 272 | ? objdat === 'null' 273 | ? null 274 | : objdat || '{}' 275 | : '{}'; 276 | try { 277 | const objedVal = JSON.parse(objVal); 278 | this.lodash_set(objedVal, paths, val); 279 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 280 | } catch (e) { 281 | const objedVal = {}; 282 | this.lodash_set(objedVal, paths, val); 283 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 284 | } 285 | } else { 286 | isSuc = this.setVal(val, key); 287 | } 288 | return isSuc; 289 | } 290 | 291 | getVal(key) { 292 | if (this.isSurge() || this.isLoon()) { 293 | return $persistentStore.read(key); 294 | } else if (this.isQuanX()) { 295 | return $prefs.valueForKey(key); 296 | } else if (this.isNode()) { 297 | this.data = this.loadData(); 298 | return this.data[key]; 299 | } else { 300 | return (this.data && this.data[key]) || null; 301 | } 302 | } 303 | 304 | setVal(val, key) { 305 | if (this.isSurge() || this.isLoon()) { 306 | return $persistentStore.write(val, key); 307 | } else if (this.isQuanX()) { 308 | return $prefs.setValueForKey(val, key); 309 | } else if (this.isNode()) { 310 | this.data = this.loadData(); 311 | this.data[key] = val; 312 | this.writeData(); 313 | return true; 314 | } else { 315 | return (this.data && this.data[key]) || null; 316 | } 317 | } 318 | 319 | initGotEnv(opts) { 320 | this.got = this.got ? this.got : require('got'); 321 | this.ckTough = this.ckTough ? this.ckTough : require('tough-cookie'); 322 | this.ckJar = this.ckJar ? this.ckJar : new this.ckTough.CookieJar(); 323 | if (opts) { 324 | opts.headers = opts.headers ? opts.headers : {}; 325 | if (undefined === opts.headers.Cookie && undefined === opts.cookieJar) { 326 | opts.cookieJar = this.ckJar; 327 | } 328 | } 329 | } 330 | 331 | get(opts, callback = () => {}) { 332 | if (opts.headers) { 333 | delete opts.headers['Content-Type']; 334 | delete opts.headers['Content-Length']; 335 | delete opts.headers['Host']; 336 | } 337 | if (this.isSurge() || this.isLoon()) { 338 | if (this.isSurge() && this.isNeedRewrite) { 339 | opts.headers = opts.headers || {}; 340 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 341 | } 342 | $httpClient.get(opts, (err, resp, body) => { 343 | if (!err && resp) { 344 | resp.body = body; 345 | resp.statusCode = resp.status; 346 | } 347 | callback(err, resp, body); 348 | }); 349 | } else if (this.isQuanX()) { 350 | if (this.isNeedRewrite) { 351 | opts.opts = opts.opts || {}; 352 | Object.assign(opts.opts, { hints: false }); 353 | } 354 | $task.fetch(opts).then( 355 | (resp) => { 356 | const { statusCode: status, statusCode, headers, body } = resp; 357 | callback(null, { status, statusCode, headers, body }, body); 358 | }, 359 | (err) => callback(err) 360 | ); 361 | } else if (this.isNode()) { 362 | this.initGotEnv(opts); 363 | this.got(opts) 364 | .on('redirect', (resp, nextOpts) => { 365 | try { 366 | if (resp.headers['set-cookie']) { 367 | const ck = resp.headers['set-cookie'] 368 | .map(this.ckTough.Cookie.parse) 369 | .toString(); 370 | if (ck) { 371 | this.ckJar.setCookieSync(ck, null); 372 | } 373 | nextOpts.cookieJar = this.ckJar; 374 | } 375 | } catch (e) { 376 | this.logErr(e); 377 | } 378 | // this.ckJar.setCookieSync(resp.headers['set-cookie'].map(Cookie.parse).toString()) 379 | }) 380 | .then( 381 | (resp) => { 382 | const { statusCode: status, statusCode, headers, body } = resp; 383 | callback(null, { status, statusCode, headers, body }, body); 384 | }, 385 | (err) => { 386 | const { message: error, response: resp } = err; 387 | callback(error, resp, resp && resp.body); 388 | } 389 | ); 390 | } 391 | } 392 | 393 | post(opts, callback = () => {}) { 394 | const method = opts.method ? opts.method.toLocaleLowerCase() : 'post'; 395 | // 如果指定了请求体, 但没指定`Content-Type`, 则自动生成 396 | if (opts.body && opts.headers && !opts.headers['Content-Type']) { 397 | opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 398 | } 399 | if (opts.headers) { 400 | delete opts.headers['Host']; 401 | delete opts.headers['Content-Length']; 402 | }; 403 | if (this.isSurge() || this.isLoon()) { 404 | if (this.isSurge() && this.isNeedRewrite) { 405 | opts.headers = opts.headers || {}; 406 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 407 | } 408 | $httpClient[method](opts, (err, resp, body) => { 409 | if (!err && resp) { 410 | resp.body = body; 411 | resp.statusCode = resp.status; 412 | } 413 | callback(err, resp, body); 414 | }); 415 | } else if (this.isQuanX()) { 416 | opts.method = method; 417 | if (this.isNeedRewrite) { 418 | opts.opts = opts.opts || {}; 419 | Object.assign(opts.opts, { hints: false }); 420 | } 421 | $task.fetch(opts).then( 422 | (resp) => { 423 | const { statusCode: status, statusCode, headers, body } = resp; 424 | callback(null, { status, statusCode, headers, body }, body); 425 | }, 426 | (err) => callback(err) 427 | ); 428 | } else if (this.isNode()) { 429 | this.initGotEnv(opts); 430 | const { url, ..._opts } = opts; 431 | this.got[method](url, _opts).then( 432 | (resp) => { 433 | const { statusCode: status, statusCode, headers, body } = resp; 434 | callback(null, { status, statusCode, headers, body }, body); 435 | }, 436 | (err) => { 437 | const { message: error, response: resp } = err; 438 | callback(error, resp, resp && resp.body); 439 | } 440 | ); 441 | } 442 | } 443 | /** 444 | * 445 | * 示例:$.time('yyyy-MM-dd qq HH:mm:ss.S') 446 | * :$.time('yyyyMMddHHmmssS') 447 | * y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒 448 | * 其中y可选0-4位占位符、S可选0-1位占位符,其余可选0-2位占位符 449 | * @param {string} fmt 格式化参数 450 | * @param {number} 可选: 根据指定时间戳返回格式化日期 451 | * 452 | */ 453 | time(fmt, ts = null) { 454 | const date = ts ? new Date(ts) : new Date(); 455 | let o = { 456 | 'M+': date.getMonth() + 1, 457 | 'd+': date.getDate(), 458 | 'H+': date.getHours(), 459 | 'm+': date.getMinutes(), 460 | 's+': date.getSeconds(), 461 | 'q+': Math.floor((date.getMonth() + 3) / 3), 462 | S: date.getMilliseconds(), 463 | }; 464 | if (/(y+)/.test(fmt)) 465 | fmt = fmt.replace( 466 | RegExp.$1, 467 | (date.getFullYear() + '').substr(4 - RegExp.$1.length) 468 | ); 469 | for (let k in o) 470 | if (new RegExp('(' + k + ')').test(fmt)) 471 | fmt = fmt.replace( 472 | RegExp.$1, 473 | RegExp.$1.length == 1 474 | ? o[k] 475 | : ('00' + o[k]).substr(('' + o[k]).length) 476 | ); 477 | return fmt; 478 | } 479 | 480 | /** 481 | * 系统通知 482 | * 483 | * > 通知参数: 同时支持 QuanX 和 Loon 两种格式, EnvJs根据运行环境自动转换, Surge 环境不支持多媒体通知 484 | * 485 | * 示例: 486 | * $.msg(title, subt, desc, 'twitter://') 487 | * $.msg(title, subt, desc, { 'open-url': 'twitter://', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 488 | * $.msg(title, subt, desc, { 'open-url': 'https://bing.com', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 489 | * 490 | * @param {*} title 标题 491 | * @param {*} subt 副标题 492 | * @param {*} desc 通知详情 493 | * @param {*} opts 通知参数 494 | * 495 | */ 496 | msg(title = name, subt = '', desc = '', opts) { 497 | const toEnvOpts = (rawOpts) => { 498 | if (!rawOpts) return rawOpts; 499 | if (typeof rawOpts === 'string') { 500 | if (this.isLoon()) return rawOpts; 501 | else if (this.isQuanX()) return { 'open-url': rawOpts }; 502 | else if (this.isSurge()) return { url: rawOpts }; 503 | else return undefined; 504 | } else if (typeof rawOpts === 'object') { 505 | if (this.isLoon()) { 506 | let openUrl = rawOpts.openUrl || rawOpts.url || rawOpts['open-url']; 507 | let mediaUrl = rawOpts.mediaUrl || rawOpts['media-url']; 508 | return { openUrl, mediaUrl }; 509 | } else if (this.isQuanX()) { 510 | let openUrl = rawOpts['open-url'] || rawOpts.url || rawOpts.openUrl; 511 | let mediaUrl = rawOpts['media-url'] || rawOpts.mediaUrl; 512 | let updatePasteboard = 513 | rawOpts['update-pasteboard'] || rawOpts.updatePasteboard; 514 | return { 515 | 'open-url': openUrl, 516 | 'media-url': mediaUrl, 517 | 'update-pasteboard': updatePasteboard, 518 | }; 519 | } else if (this.isSurge()) { 520 | let openUrl = rawOpts.url || rawOpts.openUrl || rawOpts['open-url']; 521 | return { url: openUrl }; 522 | } 523 | } else { 524 | return undefined; 525 | } 526 | }; 527 | if (!this.isMute) { 528 | if (this.isSurge() || this.isLoon()) { 529 | $notification.post(title, subt, desc, toEnvOpts(opts)); 530 | } else if (this.isQuanX()) { 531 | $notify(title, subt, desc, toEnvOpts(opts)); 532 | } 533 | } 534 | if (!this.isMuteLog) { 535 | let logs = ['', '==============📣系统通知📣==============']; 536 | logs.push(title); 537 | subt ? logs.push(subt) : ''; 538 | desc ? logs.push(desc) : ''; 539 | console.log(logs.join('\n')); 540 | this.logs = this.logs.concat(logs); 541 | } 542 | } 543 | 544 | log(...logs) { 545 | if (this.noLog || (this.noLogKey && (this.getData(this.noLogKey) || 'N').toLocaleUpperCase() === 'Y')) { 546 | return; 547 | } 548 | if (logs.length > 0) { 549 | this.logs = [...this.logs, ...logs]; 550 | } 551 | console.log(logs.join(this.logSeparator)); 552 | } 553 | 554 | logErr(err, msg) { 555 | const isPrintSack = !this.isSurge() && !this.isQuanX() && !this.isLoon(); 556 | if (!isPrintSack) { 557 | this.log('', `❗️${this.name}, 错误!`, err); 558 | } else { 559 | this.log('', `❗️${this.name}, 错误!`, err.stack); 560 | } 561 | } 562 | 563 | wait(time) { 564 | return new Promise((resolve) => setTimeout(resolve, time)); 565 | } 566 | 567 | done(val = {}) { 568 | const endTime = new Date().getTime(); 569 | const costTime = (endTime - this.startTime) / 1000; 570 | this.log('', `🔔${this.name}, 结束! 🕛 ${costTime} 秒`); 571 | this.log(); 572 | if (this.isSurge() || this.isQuanX() || this.isLoon()) { 573 | $done(val); 574 | } 575 | } 576 | })(name, opts); 577 | } 578 | -------------------------------------------------------------------------------- /Script/jd_goods_info.js: -------------------------------------------------------------------------------- 1 | /* 2 | [rewrite_local] 3 | ^https?://api\.m\.jd\.com/client\.action\?functionId=(wareBusiness|lite_wareBusiness) url script-response-body https://raw.githubusercontent.com/id77/QuantumultX/master/Script/jd_goods_info.js 4 | 5 | [mitm] 6 | hostname = *.jd.com, 7 | */ 8 | const $ = new Env('京东商品信息'); 9 | Array.prototype.insert = function (index, item) { 10 | this.splice(index, 0, item); 11 | }; 12 | 13 | let obj = JSON.parse($response.body); 14 | 15 | const floors = obj.floors; 16 | const lowerWord = { 17 | bId: 'eCustom_flo_199', 18 | cf: { 19 | bgc: '#ffffff', 20 | spl: 'empty', 21 | }, 22 | data: { 23 | ad: { 24 | adword: '💡', 25 | color: '#f23030', 26 | adLink: 'smzdm://', 27 | hasFold: true, 28 | adLinkContent: '点击前往色魔张大妈比价找券>', 29 | newALContent: true, 30 | class: 'com.jd.app.server.warecoresoa.domain.AdWordInfo.AdWordInfo', 31 | textColor: '#8C8C8C', 32 | }, 33 | }, 34 | sortId: 13, 35 | mId: 'bpAdword', 36 | refId: 'eAdword_0000000028', 37 | }; 38 | let bestIndex = 0; 39 | for (let index = 0; index < floors.length; index++) { 40 | const element = floors[index]; 41 | if (element.mId == lowerWord.mId) { 42 | bestIndex = index + 1; 43 | break; 44 | } else { 45 | if (element.sortId > lowerWord.sortId) { 46 | bestIndex = index; 47 | break; 48 | } 49 | } 50 | } 51 | floors.insert(bestIndex - 1, lowerWord); 52 | 53 | $.done({ body: JSON.stringify(obj) }); 54 | 55 | function Env(name, opts) { 56 | class Http { 57 | constructor(env) { 58 | this.env = env; 59 | } 60 | 61 | send(opts, method = 'GET') { 62 | opts = typeof opts === 'string' ? { url: opts } : opts; 63 | let sender = this.get; 64 | if (method === 'POST') { 65 | sender = this.post; 66 | } 67 | return new Promise((resolve, reject) => { 68 | sender.call(this, opts, (err, resp, body) => { 69 | if (err) reject(err); 70 | else resolve(resp); 71 | }); 72 | }); 73 | } 74 | 75 | get(opts) { 76 | return this.send.call(this.env, opts); 77 | } 78 | 79 | post(opts) { 80 | return this.send.call(this.env, opts, 'POST'); 81 | } 82 | } 83 | 84 | return new (class { 85 | constructor(name, opts = {}) { 86 | this.name = name; 87 | this.http = new Http(this); 88 | this.data = null; 89 | this.dataFile = 'box.dat'; 90 | this.logs = []; 91 | this.isMute = false; 92 | this.noLogKey = opts.noLogKey || ''; 93 | this.noLog = opts.noLog; 94 | this.isNeedRewrite = false; 95 | this.logSeparator = '\n'; 96 | this.startTime = new Date().getTime(); 97 | Object.assign(this, opts); 98 | this.log('', `🔔${this.name}, 开始!`); 99 | } 100 | 101 | isNode() { 102 | return 'undefined' !== typeof module && !!module.exports; 103 | } 104 | 105 | isQuanX() { 106 | return 'undefined' !== typeof $task; 107 | } 108 | 109 | isSurge() { 110 | return 'undefined' !== typeof $httpClient && 'undefined' === typeof $loon; 111 | } 112 | 113 | isLoon() { 114 | return 'undefined' !== typeof $loon; 115 | } 116 | 117 | isShadowrocket() { 118 | return 'undefined' !== typeof $rocket; 119 | } 120 | 121 | toObj(str, defaultValue = null) { 122 | try { 123 | return JSON.parse(str); 124 | } catch { 125 | return defaultValue; 126 | } 127 | } 128 | 129 | toStr(obj, defaultValue = null) { 130 | try { 131 | return JSON.stringify(obj); 132 | } catch { 133 | return defaultValue; 134 | } 135 | } 136 | 137 | getJson(key, defaultValue) { 138 | let json = defaultValue; 139 | const val = this.getData(key); 140 | if (val) { 141 | try { 142 | json = JSON.parse(this.getData(key)); 143 | } catch {} 144 | } 145 | return json; 146 | } 147 | 148 | setJson(val, key) { 149 | try { 150 | return this.setData(JSON.stringify(val), key); 151 | } catch { 152 | return false; 153 | } 154 | } 155 | 156 | getScript(url) { 157 | return new Promise((resolve) => { 158 | this.get({ url }, (err, resp, body) => resolve(body)); 159 | }); 160 | } 161 | 162 | runScript(script, runOpts) { 163 | return new Promise((resolve) => { 164 | let httpApi = this.getData('@chavy_boxjs_userCfgs.httpApi'); 165 | httpApi = httpApi ? httpApi.replace(/\n/g, '').trim() : httpApi; 166 | let httpApi_timeout = this.getData( 167 | '@chavy_boxjs_userCfgs.httpApi_timeout' 168 | ); 169 | httpApi_timeout = httpApi_timeout ? httpApi_timeout * 1 : 20; 170 | httpApi_timeout = 171 | runOpts && runOpts.timeout ? runOpts.timeout : httpApi_timeout; 172 | const [key, addr] = httpApi.split('@'); 173 | const opts = { 174 | url: `http://${addr}/v1/scripting/evaluate`, 175 | body: { 176 | script_text: script, 177 | mock_type: 'cron', 178 | timeout: httpApi_timeout, 179 | }, 180 | headers: { 'X-Key': key, Accept: '*/*' }, 181 | }; 182 | this.post(opts, (err, resp, body) => resolve(body)); 183 | }).catch((e) => this.logErr(e)); 184 | } 185 | 186 | loadData() { 187 | if (this.isNode()) { 188 | this.fs = this.fs ? this.fs : require('fs'); 189 | this.path = this.path ? this.path : require('path'); 190 | const curDirDataFilePath = this.path.resolve(this.dataFile); 191 | const rootDirDataFilePath = this.path.resolve( 192 | process.cwd(), 193 | this.dataFile 194 | ); 195 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 196 | const isRootDirDataFile = 197 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 198 | if (isCurDirDataFile || isRootDirDataFile) { 199 | const datPath = isCurDirDataFile 200 | ? curDirDataFilePath 201 | : rootDirDataFilePath; 202 | try { 203 | return JSON.parse(this.fs.readFileSync(datPath)); 204 | } catch (e) { 205 | return {}; 206 | } 207 | } else return {}; 208 | } else return {}; 209 | } 210 | 211 | writeData() { 212 | if (this.isNode()) { 213 | this.fs = this.fs ? this.fs : require('fs'); 214 | this.path = this.path ? this.path : require('path'); 215 | const curDirDataFilePath = this.path.resolve(this.dataFile); 216 | const rootDirDataFilePath = this.path.resolve( 217 | process.cwd(), 218 | this.dataFile 219 | ); 220 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 221 | const isRootDirDataFile = 222 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 223 | const jsonData = JSON.stringify(this.data); 224 | if (isCurDirDataFile) { 225 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 226 | } else if (isRootDirDataFile) { 227 | this.fs.writeFileSync(rootDirDataFilePath, jsonData); 228 | } else { 229 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 230 | } 231 | } 232 | } 233 | 234 | lodash_get(source, path, defaultValue = undefined) { 235 | const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.'); 236 | let result = source; 237 | for (const p of paths) { 238 | result = Object(result)[p]; 239 | if (result === undefined) { 240 | return defaultValue; 241 | } 242 | } 243 | return result; 244 | } 245 | 246 | lodash_set(obj, path, value) { 247 | if (Object(obj) !== obj) return obj; 248 | if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || []; 249 | path 250 | .slice(0, -1) 251 | .reduce( 252 | (a, c, i) => 253 | Object(a[c]) === a[c] 254 | ? a[c] 255 | : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {}), 256 | obj 257 | )[path[path.length - 1]] = value; 258 | return obj; 259 | } 260 | 261 | getData(key) { 262 | let val = this.getVal(key); 263 | // 如果以 @ 264 | if (/^@/.test(key)) { 265 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 266 | const objVal = objKey ? this.getVal(objKey) : ''; 267 | if (objVal) { 268 | try { 269 | const objedVal = JSON.parse(objVal); 270 | val = objedVal ? this.lodash_get(objedVal, paths, '') : val; 271 | } catch (e) { 272 | val = ''; 273 | } 274 | } 275 | } 276 | return val; 277 | } 278 | 279 | setData(val, key) { 280 | let isSuc = false; 281 | if (/^@/.test(key)) { 282 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 283 | const objdat = this.getVal(objKey); 284 | const objVal = objKey 285 | ? objdat === 'null' 286 | ? null 287 | : objdat || '{}' 288 | : '{}'; 289 | try { 290 | const objedVal = JSON.parse(objVal); 291 | this.lodash_set(objedVal, paths, val); 292 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 293 | } catch (e) { 294 | const objedVal = {}; 295 | this.lodash_set(objedVal, paths, val); 296 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 297 | } 298 | } else { 299 | isSuc = this.setVal(val, key); 300 | } 301 | return isSuc; 302 | } 303 | 304 | getVal(key) { 305 | if (this.isSurge() || this.isLoon()) { 306 | return $persistentStore.read(key); 307 | } else if (this.isQuanX()) { 308 | return $prefs.valueForKey(key); 309 | } else if (this.isNode()) { 310 | this.data = this.loadData(); 311 | return this.data[key]; 312 | } else { 313 | return (this.data && this.data[key]) || null; 314 | } 315 | } 316 | 317 | setVal(val, key) { 318 | if (this.isSurge() || this.isLoon()) { 319 | return $persistentStore.write(val, key); 320 | } else if (this.isQuanX()) { 321 | return $prefs.setValueForKey(val, key); 322 | } else if (this.isNode()) { 323 | this.data = this.loadData(); 324 | this.data[key] = val; 325 | this.writeData(); 326 | return true; 327 | } else { 328 | return (this.data && this.data[key]) || null; 329 | } 330 | } 331 | 332 | initGotEnv(opts) { 333 | this.got = this.got ? this.got : require('got'); 334 | this.ckTough = this.ckTough ? this.ckTough : require('tough-cookie'); 335 | this.ckJar = this.ckJar ? this.ckJar : new this.ckTough.CookieJar(); 336 | if (opts) { 337 | opts.headers = opts.headers ? opts.headers : {}; 338 | if (undefined === opts.headers.Cookie && undefined === opts.cookieJar) { 339 | opts.cookieJar = this.ckJar; 340 | } 341 | } 342 | } 343 | 344 | get(opts, callback = () => {}) { 345 | if (opts.headers) { 346 | delete opts.headers['Content-Type']; 347 | delete opts.headers['Content-Length']; 348 | } 349 | if (this.isSurge() || this.isLoon()) { 350 | if (this.isSurge() && this.isNeedRewrite) { 351 | opts.headers = opts.headers || {}; 352 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 353 | } 354 | $httpClient.get(opts, (err, resp, body) => { 355 | if (!err && resp) { 356 | resp.body = body; 357 | resp.statusCode = resp.status; 358 | } 359 | callback(err, resp, body); 360 | }); 361 | } else if (this.isQuanX()) { 362 | if (this.isNeedRewrite) { 363 | opts.opts = opts.opts || {}; 364 | Object.assign(opts.opts, { hints: false }); 365 | } 366 | $task.fetch(opts).then( 367 | (resp) => { 368 | const { statusCode: status, statusCode, headers, body } = resp; 369 | callback(null, { status, statusCode, headers, body }, body); 370 | }, 371 | (err) => callback(err) 372 | ); 373 | } else if (this.isNode()) { 374 | this.initGotEnv(opts); 375 | this.got(opts) 376 | .on('redirect', (resp, nextOpts) => { 377 | try { 378 | if (resp.headers['set-cookie']) { 379 | const ck = resp.headers['set-cookie'] 380 | .map(this.ckTough.Cookie.parse) 381 | .toString(); 382 | if (ck) { 383 | this.ckJar.setCookieSync(ck, null); 384 | } 385 | nextOpts.cookieJar = this.ckJar; 386 | } 387 | } catch (e) { 388 | this.logErr(e); 389 | } 390 | // this.ckJar.setCookieSync(resp.headers['set-cookie'].map(Cookie.parse).toString()) 391 | }) 392 | .then( 393 | (resp) => { 394 | const { statusCode: status, statusCode, headers, body } = resp; 395 | callback(null, { status, statusCode, headers, body }, body); 396 | }, 397 | (err) => { 398 | const { message: error, response: resp } = err; 399 | callback(error, resp, resp && resp.body); 400 | } 401 | ); 402 | } 403 | } 404 | 405 | post(opts, callback = () => {}) { 406 | const method = opts.method ? opts.method.toLocaleLowerCase() : 'post'; 407 | // 如果指定了请求体, 但没指定`Content-Type`, 则自动生成 408 | // if (opts.body && // opts.headers && !opts.headers['Content-Type']) { 409 | opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 410 | // } 411 | if (opts.headers) delete opts.headers['Content-Length']; 412 | if (this.isSurge() || this.isLoon()) { 413 | if (this.isSurge() && this.isNeedRewrite) { 414 | opts.headers = opts.headers || {}; 415 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 416 | } 417 | $httpClient[method](opts, (err, resp, body) => { 418 | if (!err && resp) { 419 | resp.body = body; 420 | resp.statusCode = resp.status; 421 | } 422 | callback(err, resp, body); 423 | }); 424 | } else if (this.isQuanX()) { 425 | opts.method = method; 426 | if (this.isNeedRewrite) { 427 | opts.opts = opts.opts || {}; 428 | Object.assign(opts.opts, { hints: false }); 429 | } 430 | $task.fetch(opts).then( 431 | (resp) => { 432 | const { statusCode: status, statusCode, headers, body } = resp; 433 | callback(null, { status, statusCode, headers, body }, body); 434 | }, 435 | (err) => callback(err) 436 | ); 437 | } else if (this.isNode()) { 438 | this.initGotEnv(opts); 439 | const { url, ..._opts } = opts; 440 | this.got[method](url, _opts).then( 441 | (resp) => { 442 | const { statusCode: status, statusCode, headers, body } = resp; 443 | callback(null, { status, statusCode, headers, body }, body); 444 | }, 445 | (err) => { 446 | const { message: error, response: resp } = err; 447 | callback(error, resp, resp && resp.body); 448 | } 449 | ); 450 | } 451 | } 452 | /** 453 | * 454 | * 示例:$.time('yyyy-MM-dd qq HH:mm:ss.S') 455 | * :$.time('yyyyMMddHHmmssS') 456 | * y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒 457 | * 其中y可选0-4位占位符、S可选0-1位占位符,其余可选0-2位占位符 458 | * @param {string} fmt 格式化参数 459 | * @param {number} 可选: 根据指定时间戳返回格式化日期 460 | * 461 | */ 462 | time(fmt, ts = null) { 463 | const date = ts ? new Date(ts) : new Date(); 464 | let o = { 465 | 'M+': date.getMonth() + 1, 466 | 'd+': date.getDate(), 467 | 'H+': date.getHours(), 468 | 'm+': date.getMinutes(), 469 | 's+': date.getSeconds(), 470 | 'q+': Math.floor((date.getMonth() + 3) / 3), 471 | S: date.getMilliseconds(), 472 | }; 473 | if (/(y+)/.test(fmt)) 474 | fmt = fmt.replace( 475 | RegExp.$1, 476 | (date.getFullYear() + '').substr(4 - RegExp.$1.length) 477 | ); 478 | for (let k in o) 479 | if (new RegExp('(' + k + ')').test(fmt)) 480 | fmt = fmt.replace( 481 | RegExp.$1, 482 | RegExp.$1.length == 1 483 | ? o[k] 484 | : ('00' + o[k]).substr(('' + o[k]).length) 485 | ); 486 | return fmt; 487 | } 488 | 489 | /** 490 | * 系统通知 491 | * 492 | * > 通知参数: 同时支持 QuanX 和 Loon 两种格式, EnvJs根据运行环境自动转换, Surge 环境不支持多媒体通知 493 | * 494 | * 示例: 495 | * $.msg(title, subt, desc, 'twitter://') 496 | * $.msg(title, subt, desc, { 'open-url': 'twitter://', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 497 | * $.msg(title, subt, desc, { 'open-url': 'https://bing.com', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 498 | * 499 | * @param {*} title 标题 500 | * @param {*} subt 副标题 501 | * @param {*} desc 通知详情 502 | * @param {*} opts 通知参数 503 | * 504 | */ 505 | msg(title = name, subt = '', desc = '', opts) { 506 | const toEnvOpts = (rawOpts) => { 507 | if (!rawOpts) return rawOpts; 508 | if (typeof rawOpts === 'string') { 509 | if (this.isLoon()) return rawOpts; 510 | else if (this.isQuanX()) return { 'open-url': rawOpts }; 511 | else if (this.isSurge()) return { url: rawOpts }; 512 | else return undefined; 513 | } else if (typeof rawOpts === 'object') { 514 | if (this.isLoon()) { 515 | let openUrl = rawOpts.openUrl || rawOpts.url || rawOpts['open-url']; 516 | let mediaUrl = rawOpts.mediaUrl || rawOpts['media-url']; 517 | return { openUrl, mediaUrl }; 518 | } else if (this.isQuanX()) { 519 | let openUrl = rawOpts['open-url'] || rawOpts.url || rawOpts.openUrl; 520 | let mediaUrl = rawOpts['media-url'] || rawOpts.mediaUrl; 521 | let updatePasteboard = 522 | rawOpts['update-pasteboard'] || rawOpts.updatePasteboard; 523 | return { 524 | 'open-url': openUrl, 525 | 'media-url': mediaUrl, 526 | 'update-pasteboard': updatePasteboard, 527 | }; 528 | } else if (this.isSurge()) { 529 | let openUrl = rawOpts.url || rawOpts.openUrl || rawOpts['open-url']; 530 | return { url: openUrl }; 531 | } 532 | } else { 533 | return undefined; 534 | } 535 | }; 536 | if (!this.isMute) { 537 | if (this.isSurge() || this.isLoon()) { 538 | $notification.post(title, subt, desc, toEnvOpts(opts)); 539 | } else if (this.isQuanX()) { 540 | $notify(title, subt, desc, toEnvOpts(opts)); 541 | } 542 | } 543 | if (!this.isMuteLog) { 544 | let logs = ['', '==============📣系统通知📣==============']; 545 | logs.push(title); 546 | subt ? logs.push(subt) : ''; 547 | desc ? logs.push(desc) : ''; 548 | console.log(logs.join('\n')); 549 | this.logs = this.logs.concat(logs); 550 | } 551 | } 552 | 553 | log(...logs) { 554 | if ( 555 | this.noLog || 556 | (this.noLogKey && 557 | (this.getData(this.noLogKey) || 'N').toLocaleUpperCase() === 'Y') 558 | ) { 559 | return; 560 | } 561 | if (logs.length > 0) { 562 | this.logs = [...this.logs, ...logs]; 563 | } 564 | console.log(logs.join(this.logSeparator)); 565 | } 566 | 567 | logErr(err, msg) { 568 | const isPrintSack = !this.isSurge() && !this.isQuanX() && !this.isLoon(); 569 | if (!isPrintSack) { 570 | this.log('', `❗️${this.name}, 错误!`, err); 571 | } else { 572 | this.log('', `❗️${this.name}, 错误!`, err.stack); 573 | } 574 | } 575 | 576 | wait(time) { 577 | return new Promise((resolve) => setTimeout(resolve, time)); 578 | } 579 | 580 | done(val = {}) { 581 | const endTime = new Date().getTime(); 582 | const costTime = (endTime - this.startTime) / 1000; 583 | this.log('', `🔔${this.name}, 结束! 🕛 ${costTime} 秒`); 584 | this.log(); 585 | if (this.isSurge() || this.isQuanX() || this.isLoon()) { 586 | $done(val); 587 | } 588 | } 589 | })(name, opts); 590 | } 591 | -------------------------------------------------------------------------------- /Script/jd_union.js: -------------------------------------------------------------------------------- 1 | /* 2 | [rewrite_local] 3 | # 京东联盟 4 | https:\/\/union\.jd\.com\/proManager\/index\?pageNo=1&keywords=\d+ url script-response-body jd_union.js 5 | # 京东联盟短链接 6 | https:\/\/union\.jd\.com\/api\/receivecode\/getCode url script-response-body jd_union.js 7 | [mitm] 8 | hostname = *.jd.com, 9 | */ 10 | 11 | let body = $response.body; 12 | let url = $request.url.replace(/https?:\/\/|\?.*/g, ''); 13 | 14 | if (url.includes('/api/receivecode/getCode')) { 15 | body = JSON.parse(body); 16 | 17 | if (body.code !== 200) { 18 | console.log(`转链失败:${body.message}`); 19 | } 20 | const { shortCode, rqCode } = body.data.data; 21 | $notify('京东联盟', '', `转链成功:${shortCode}`, { 22 | 'open-url': shortCode, 23 | 'media-url': rqCode, 24 | }); 25 | 26 | $done(); 27 | } 28 | 29 | if (url.includes('/proManager/index')) { 30 | html = body; 31 | html = 32 | html.replace(/(<\/html>)/g, '') + 33 | ` 34 | 45 | 46 | `; 47 | 48 | $done({ body: html }); 49 | } 50 | -------------------------------------------------------------------------------- /Script/smzdm_to_jd.js: -------------------------------------------------------------------------------- 1 | /* 2 | [rewrite_local] 3 | https:\/\/dg\.k\.jd\.com\/ksdk\/scheme\.json url script-response-body https://raw.githubusercontent.com/id77/QuantumultX/master/Script/smzdm_to_jd.js 4 | 5 | [mitm] 6 | hostname = *.*.jd.com, 7 | */ 8 | const $ = new Env('色魔张大妈京东scheme'); 9 | let obj = JSON.parse($response.body); 10 | const scheme = $.getData('id77_JDLM_openUrl'); 11 | const skuId = scheme 12 | ? scheme.replace(/(.*skuId%22%3A%22)(\d+)(%22.*)/, '$2') 13 | : ''; 14 | 15 | if (skuId && obj.appScheme.includes(skuId)) { 16 | obj.appScheme = scheme; 17 | } 18 | 19 | $.done({ body: JSON.stringify(obj) }); 20 | 21 | function Env(name, opts) { 22 | class Http { 23 | constructor(env) { 24 | this.env = env; 25 | } 26 | 27 | send(opts, method = 'GET') { 28 | opts = typeof opts === 'string' ? { url: opts } : opts; 29 | let sender = this.get; 30 | if (method === 'POST') { 31 | sender = this.post; 32 | } 33 | return new Promise((resolve, reject) => { 34 | sender.call(this, opts, (err, resp, body) => { 35 | if (err) reject(err); 36 | else resolve(resp); 37 | }); 38 | }); 39 | } 40 | 41 | get(opts) { 42 | return this.send.call(this.env, opts); 43 | } 44 | 45 | post(opts) { 46 | return this.send.call(this.env, opts, 'POST'); 47 | } 48 | } 49 | 50 | return new (class { 51 | constructor(name, opts = {}) { 52 | this.name = name; 53 | this.http = new Http(this); 54 | this.data = null; 55 | this.dataFile = 'box.dat'; 56 | this.logs = []; 57 | this.isMute = false; 58 | this.noLogKey = opts.noLogKey || ''; 59 | this.noLog = opts.noLog; 60 | this.isNeedRewrite = false; 61 | this.logSeparator = '\n'; 62 | this.startTime = new Date().getTime(); 63 | Object.assign(this, opts); 64 | this.log('', `🔔${this.name}, 开始!`); 65 | } 66 | 67 | isNode() { 68 | return 'undefined' !== typeof module && !!module.exports; 69 | } 70 | 71 | isQuanX() { 72 | return 'undefined' !== typeof $task; 73 | } 74 | 75 | isSurge() { 76 | return 'undefined' !== typeof $httpClient && 'undefined' === typeof $loon; 77 | } 78 | 79 | isLoon() { 80 | return 'undefined' !== typeof $loon; 81 | } 82 | 83 | isShadowrocket() { 84 | return 'undefined' !== typeof $rocket; 85 | } 86 | 87 | toObj(str, defaultValue = null) { 88 | try { 89 | return JSON.parse(str); 90 | } catch { 91 | return defaultValue; 92 | } 93 | } 94 | 95 | toStr(obj, defaultValue = null) { 96 | try { 97 | return JSON.stringify(obj); 98 | } catch { 99 | return defaultValue; 100 | } 101 | } 102 | 103 | getJson(key, defaultValue) { 104 | let json = defaultValue; 105 | const val = this.getData(key); 106 | if (val) { 107 | try { 108 | json = JSON.parse(this.getData(key)); 109 | } catch {} 110 | } 111 | return json; 112 | } 113 | 114 | setJson(val, key) { 115 | try { 116 | return this.setData(JSON.stringify(val), key); 117 | } catch { 118 | return false; 119 | } 120 | } 121 | 122 | getScript(url) { 123 | return new Promise((resolve) => { 124 | this.get({ url }, (err, resp, body) => resolve(body)); 125 | }); 126 | } 127 | 128 | runScript(script, runOpts) { 129 | return new Promise((resolve) => { 130 | let httpApi = this.getData('@chavy_boxjs_userCfgs.httpApi'); 131 | httpApi = httpApi ? httpApi.replace(/\n/g, '').trim() : httpApi; 132 | let httpApi_timeout = this.getData( 133 | '@chavy_boxjs_userCfgs.httpApi_timeout' 134 | ); 135 | httpApi_timeout = httpApi_timeout ? httpApi_timeout * 1 : 20; 136 | httpApi_timeout = 137 | runOpts && runOpts.timeout ? runOpts.timeout : httpApi_timeout; 138 | const [key, addr] = httpApi.split('@'); 139 | const opts = { 140 | url: `http://${addr}/v1/scripting/evaluate`, 141 | body: { 142 | script_text: script, 143 | mock_type: 'cron', 144 | timeout: httpApi_timeout, 145 | }, 146 | headers: { 'X-Key': key, Accept: '*/*' }, 147 | }; 148 | this.post(opts, (err, resp, body) => resolve(body)); 149 | }).catch((e) => this.logErr(e)); 150 | } 151 | 152 | loadData() { 153 | if (this.isNode()) { 154 | this.fs = this.fs ? this.fs : require('fs'); 155 | this.path = this.path ? this.path : require('path'); 156 | const curDirDataFilePath = this.path.resolve(this.dataFile); 157 | const rootDirDataFilePath = this.path.resolve( 158 | process.cwd(), 159 | this.dataFile 160 | ); 161 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 162 | const isRootDirDataFile = 163 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 164 | if (isCurDirDataFile || isRootDirDataFile) { 165 | const datPath = isCurDirDataFile 166 | ? curDirDataFilePath 167 | : rootDirDataFilePath; 168 | try { 169 | return JSON.parse(this.fs.readFileSync(datPath)); 170 | } catch (e) { 171 | return {}; 172 | } 173 | } else return {}; 174 | } else return {}; 175 | } 176 | 177 | writeData() { 178 | if (this.isNode()) { 179 | this.fs = this.fs ? this.fs : require('fs'); 180 | this.path = this.path ? this.path : require('path'); 181 | const curDirDataFilePath = this.path.resolve(this.dataFile); 182 | const rootDirDataFilePath = this.path.resolve( 183 | process.cwd(), 184 | this.dataFile 185 | ); 186 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 187 | const isRootDirDataFile = 188 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 189 | const jsonData = JSON.stringify(this.data); 190 | if (isCurDirDataFile) { 191 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 192 | } else if (isRootDirDataFile) { 193 | this.fs.writeFileSync(rootDirDataFilePath, jsonData); 194 | } else { 195 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 196 | } 197 | } 198 | } 199 | 200 | lodash_get(source, path, defaultValue = undefined) { 201 | const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.'); 202 | let result = source; 203 | for (const p of paths) { 204 | result = Object(result)[p]; 205 | if (result === undefined) { 206 | return defaultValue; 207 | } 208 | } 209 | return result; 210 | } 211 | 212 | lodash_set(obj, path, value) { 213 | if (Object(obj) !== obj) return obj; 214 | if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || []; 215 | path 216 | .slice(0, -1) 217 | .reduce( 218 | (a, c, i) => 219 | Object(a[c]) === a[c] 220 | ? a[c] 221 | : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {}), 222 | obj 223 | )[path[path.length - 1]] = value; 224 | return obj; 225 | } 226 | 227 | getData(key) { 228 | let val = this.getVal(key); 229 | // 如果以 @ 230 | if (/^@/.test(key)) { 231 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 232 | const objVal = objKey ? this.getVal(objKey) : ''; 233 | if (objVal) { 234 | try { 235 | const objedVal = JSON.parse(objVal); 236 | val = objedVal ? this.lodash_get(objedVal, paths, '') : val; 237 | } catch (e) { 238 | val = ''; 239 | } 240 | } 241 | } 242 | return val; 243 | } 244 | 245 | setData(val, key) { 246 | let isSuc = false; 247 | if (/^@/.test(key)) { 248 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 249 | const objdat = this.getVal(objKey); 250 | const objVal = objKey 251 | ? objdat === 'null' 252 | ? null 253 | : objdat || '{}' 254 | : '{}'; 255 | try { 256 | const objedVal = JSON.parse(objVal); 257 | this.lodash_set(objedVal, paths, val); 258 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 259 | } catch (e) { 260 | const objedVal = {}; 261 | this.lodash_set(objedVal, paths, val); 262 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 263 | } 264 | } else { 265 | isSuc = this.setVal(val, key); 266 | } 267 | return isSuc; 268 | } 269 | 270 | getVal(key) { 271 | if (this.isSurge() || this.isLoon()) { 272 | return $persistentStore.read(key); 273 | } else if (this.isQuanX()) { 274 | return $prefs.valueForKey(key); 275 | } else if (this.isNode()) { 276 | this.data = this.loadData(); 277 | return this.data[key]; 278 | } else { 279 | return (this.data && this.data[key]) || null; 280 | } 281 | } 282 | 283 | setVal(val, key) { 284 | if (this.isSurge() || this.isLoon()) { 285 | return $persistentStore.write(val, key); 286 | } else if (this.isQuanX()) { 287 | return $prefs.setValueForKey(val, key); 288 | } else if (this.isNode()) { 289 | this.data = this.loadData(); 290 | this.data[key] = val; 291 | this.writeData(); 292 | return true; 293 | } else { 294 | return (this.data && this.data[key]) || null; 295 | } 296 | } 297 | 298 | initGotEnv(opts) { 299 | this.got = this.got ? this.got : require('got'); 300 | this.ckTough = this.ckTough ? this.ckTough : require('tough-cookie'); 301 | this.ckJar = this.ckJar ? this.ckJar : new this.ckTough.CookieJar(); 302 | if (opts) { 303 | opts.headers = opts.headers ? opts.headers : {}; 304 | if (undefined === opts.headers.Cookie && undefined === opts.cookieJar) { 305 | opts.cookieJar = this.ckJar; 306 | } 307 | } 308 | } 309 | 310 | get(opts, callback = () => {}) { 311 | if (opts.headers) { 312 | delete opts.headers['Content-Type']; 313 | delete opts.headers['Content-Length']; 314 | } 315 | if (this.isSurge() || this.isLoon()) { 316 | if (this.isSurge() && this.isNeedRewrite) { 317 | opts.headers = opts.headers || {}; 318 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 319 | } 320 | $httpClient.get(opts, (err, resp, body) => { 321 | if (!err && resp) { 322 | resp.body = body; 323 | resp.statusCode = resp.status; 324 | } 325 | callback(err, resp, body); 326 | }); 327 | } else if (this.isQuanX()) { 328 | if (this.isNeedRewrite) { 329 | opts.opts = opts.opts || {}; 330 | Object.assign(opts.opts, { hints: false }); 331 | } 332 | $task.fetch(opts).then( 333 | (resp) => { 334 | const { statusCode: status, statusCode, headers, body } = resp; 335 | callback(null, { status, statusCode, headers, body }, body); 336 | }, 337 | (err) => callback(err) 338 | ); 339 | } else if (this.isNode()) { 340 | this.initGotEnv(opts); 341 | this.got(opts) 342 | .on('redirect', (resp, nextOpts) => { 343 | try { 344 | if (resp.headers['set-cookie']) { 345 | const ck = resp.headers['set-cookie'] 346 | .map(this.ckTough.Cookie.parse) 347 | .toString(); 348 | if (ck) { 349 | this.ckJar.setCookieSync(ck, null); 350 | } 351 | nextOpts.cookieJar = this.ckJar; 352 | } 353 | } catch (e) { 354 | this.logErr(e); 355 | } 356 | // this.ckJar.setCookieSync(resp.headers['set-cookie'].map(Cookie.parse).toString()) 357 | }) 358 | .then( 359 | (resp) => { 360 | const { statusCode: status, statusCode, headers, body } = resp; 361 | callback(null, { status, statusCode, headers, body }, body); 362 | }, 363 | (err) => { 364 | const { message: error, response: resp } = err; 365 | callback(error, resp, resp && resp.body); 366 | } 367 | ); 368 | } 369 | } 370 | 371 | post(opts, callback = () => {}) { 372 | const method = opts.method ? opts.method.toLocaleLowerCase() : 'post'; 373 | // 如果指定了请求体, 但没指定`Content-Type`, 则自动生成 374 | // if (opts.body && // opts.headers && !opts.headers['Content-Type']) { 375 | opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 376 | // } 377 | if (opts.headers) delete opts.headers['Content-Length']; 378 | if (this.isSurge() || this.isLoon()) { 379 | if (this.isSurge() && this.isNeedRewrite) { 380 | opts.headers = opts.headers || {}; 381 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 382 | } 383 | $httpClient[method](opts, (err, resp, body) => { 384 | if (!err && resp) { 385 | resp.body = body; 386 | resp.statusCode = resp.status; 387 | } 388 | callback(err, resp, body); 389 | }); 390 | } else if (this.isQuanX()) { 391 | opts.method = method; 392 | if (this.isNeedRewrite) { 393 | opts.opts = opts.opts || {}; 394 | Object.assign(opts.opts, { hints: false }); 395 | } 396 | $task.fetch(opts).then( 397 | (resp) => { 398 | const { statusCode: status, statusCode, headers, body } = resp; 399 | callback(null, { status, statusCode, headers, body }, body); 400 | }, 401 | (err) => callback(err) 402 | ); 403 | } else if (this.isNode()) { 404 | this.initGotEnv(opts); 405 | const { url, ..._opts } = opts; 406 | this.got[method](url, _opts).then( 407 | (resp) => { 408 | const { statusCode: status, statusCode, headers, body } = resp; 409 | callback(null, { status, statusCode, headers, body }, body); 410 | }, 411 | (err) => { 412 | const { message: error, response: resp } = err; 413 | callback(error, resp, resp && resp.body); 414 | } 415 | ); 416 | } 417 | } 418 | /** 419 | * 420 | * 示例:$.time('yyyy-MM-dd qq HH:mm:ss.S') 421 | * :$.time('yyyyMMddHHmmssS') 422 | * y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒 423 | * 其中y可选0-4位占位符、S可选0-1位占位符,其余可选0-2位占位符 424 | * @param {string} fmt 格式化参数 425 | * @param {number} 可选: 根据指定时间戳返回格式化日期 426 | * 427 | */ 428 | time(fmt, ts = null) { 429 | const date = ts ? new Date(ts) : new Date(); 430 | let o = { 431 | 'M+': date.getMonth() + 1, 432 | 'd+': date.getDate(), 433 | 'H+': date.getHours(), 434 | 'm+': date.getMinutes(), 435 | 's+': date.getSeconds(), 436 | 'q+': Math.floor((date.getMonth() + 3) / 3), 437 | S: date.getMilliseconds(), 438 | }; 439 | if (/(y+)/.test(fmt)) 440 | fmt = fmt.replace( 441 | RegExp.$1, 442 | (date.getFullYear() + '').substr(4 - RegExp.$1.length) 443 | ); 444 | for (let k in o) 445 | if (new RegExp('(' + k + ')').test(fmt)) 446 | fmt = fmt.replace( 447 | RegExp.$1, 448 | RegExp.$1.length == 1 449 | ? o[k] 450 | : ('00' + o[k]).substr(('' + o[k]).length) 451 | ); 452 | return fmt; 453 | } 454 | 455 | /** 456 | * 系统通知 457 | * 458 | * > 通知参数: 同时支持 QuanX 和 Loon 两种格式, EnvJs根据运行环境自动转换, Surge 环境不支持多媒体通知 459 | * 460 | * 示例: 461 | * $.msg(title, subt, desc, 'twitter://') 462 | * $.msg(title, subt, desc, { 'open-url': 'twitter://', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 463 | * $.msg(title, subt, desc, { 'open-url': 'https://bing.com', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 464 | * 465 | * @param {*} title 标题 466 | * @param {*} subt 副标题 467 | * @param {*} desc 通知详情 468 | * @param {*} opts 通知参数 469 | * 470 | */ 471 | msg(title = name, subt = '', desc = '', opts) { 472 | const toEnvOpts = (rawOpts) => { 473 | if (!rawOpts) return rawOpts; 474 | if (typeof rawOpts === 'string') { 475 | if (this.isLoon()) return rawOpts; 476 | else if (this.isQuanX()) return { 'open-url': rawOpts }; 477 | else if (this.isSurge()) return { url: rawOpts }; 478 | else return undefined; 479 | } else if (typeof rawOpts === 'object') { 480 | if (this.isLoon()) { 481 | let openUrl = rawOpts.openUrl || rawOpts.url || rawOpts['open-url']; 482 | let mediaUrl = rawOpts.mediaUrl || rawOpts['media-url']; 483 | return { openUrl, mediaUrl }; 484 | } else if (this.isQuanX()) { 485 | let openUrl = rawOpts['open-url'] || rawOpts.url || rawOpts.openUrl; 486 | let mediaUrl = rawOpts['media-url'] || rawOpts.mediaUrl; 487 | let updatePasteboard = 488 | rawOpts['update-pasteboard'] || rawOpts.updatePasteboard; 489 | return { 490 | 'open-url': openUrl, 491 | 'media-url': mediaUrl, 492 | 'update-pasteboard': updatePasteboard, 493 | }; 494 | } else if (this.isSurge()) { 495 | let openUrl = rawOpts.url || rawOpts.openUrl || rawOpts['open-url']; 496 | return { url: openUrl }; 497 | } 498 | } else { 499 | return undefined; 500 | } 501 | }; 502 | if (!this.isMute) { 503 | if (this.isSurge() || this.isLoon()) { 504 | $notification.post(title, subt, desc, toEnvOpts(opts)); 505 | } else if (this.isQuanX()) { 506 | $notify(title, subt, desc, toEnvOpts(opts)); 507 | } 508 | } 509 | if (!this.isMuteLog) { 510 | let logs = ['', '==============📣系统通知📣==============']; 511 | logs.push(title); 512 | subt ? logs.push(subt) : ''; 513 | desc ? logs.push(desc) : ''; 514 | console.log(logs.join('\n')); 515 | this.logs = this.logs.concat(logs); 516 | } 517 | } 518 | 519 | log(...logs) { 520 | if ( 521 | this.noLog || 522 | (this.noLogKey && 523 | (this.getData(this.noLogKey) || 'N').toLocaleUpperCase() === 'Y') 524 | ) { 525 | return; 526 | } 527 | if (logs.length > 0) { 528 | this.logs = [...this.logs, ...logs]; 529 | } 530 | console.log(logs.join(this.logSeparator)); 531 | } 532 | 533 | logErr(err, msg) { 534 | const isPrintSack = !this.isSurge() && !this.isQuanX() && !this.isLoon(); 535 | if (!isPrintSack) { 536 | this.log('', `❗️${this.name}, 错误!`, err); 537 | } else { 538 | this.log('', `❗️${this.name}, 错误!`, err.stack); 539 | } 540 | } 541 | 542 | wait(time) { 543 | return new Promise((resolve) => setTimeout(resolve, time)); 544 | } 545 | 546 | done(val = {}) { 547 | const endTime = new Date().getTime(); 548 | const costTime = (endTime - this.startTime) / 1000; 549 | this.log('', `🔔${this.name}, 结束! 🕛 ${costTime} 秒`); 550 | this.log(); 551 | if (this.isSurge() || this.isQuanX() || this.isLoon()) { 552 | $done(val); 553 | } 554 | } 555 | })(name, opts); 556 | } 557 | -------------------------------------------------------------------------------- /Script/syncJDCookie.js: -------------------------------------------------------------------------------- 1 | const $ = new Env('同步 Docker JDCookie'); 2 | 3 | $.api = $.getData('id77_api_sync_cookie'); 4 | 5 | !(async () => { 6 | try { 7 | $.cookieArr = []; 8 | await getCookie(); 9 | 10 | if (!$.resData) { 11 | $.desc = '获取数据失败'; 12 | } 13 | 14 | if ($.resData.length > 0) { 15 | $.cookieArr = $.resData; 16 | $.desc = `同步${$.cookieArr.length}条`; 17 | } else { 18 | $.desc = '没有新数据'; 19 | } 20 | 21 | $.setData(JSON.stringify($.cookieArr, null, 2), 'CookiesJD'); 22 | await showMsg(); 23 | } catch (error) { 24 | $.msg($.name, '', '同步失败,请重试 ⚠️'); 25 | } 26 | })() 27 | .catch((e) => $.logErr(e)) 28 | .finally(() => $.done()); 29 | 30 | function getCookie() { 31 | return new Promise((resolve) => { 32 | $.get({ url: $.api }, (err, resp, data) => { 33 | try { 34 | if (data && data.length > 0) { 35 | $.resData = JSON.parse(data); 36 | } 37 | } catch (e) { 38 | $.logErr(e, resp); 39 | } finally { 40 | resolve(); 41 | } 42 | }); 43 | }); 44 | } 45 | 46 | function showMsg() { 47 | return new Promise((resolve) => { 48 | $.msg($.name, $.subt, $.desc || '服务不可用'); 49 | resolve(); 50 | }); 51 | } 52 | 53 | // https://github.com/chavyleung/scripts/blob/master/Env.js 54 | // prettier-ignore 55 | 56 | // https://github.com/chavyleung/scripts/blob/master/Env.js 57 | // prettier-ignore 58 | function Env(name, opts) { 59 | class Http { 60 | constructor(env) { 61 | this.env = env; 62 | } 63 | 64 | send(opts, method = 'GET') { 65 | opts = typeof opts === 'string' ? { url: opts } : opts; 66 | let sender = this.get; 67 | if (method === 'POST') { 68 | sender = this.post; 69 | } 70 | return new Promise((resolve, reject) => { 71 | sender.call(this, opts, (err, resp, body) => { 72 | if (err) reject(err); 73 | else resolve(resp); 74 | }); 75 | }); 76 | } 77 | 78 | get(opts) { 79 | return this.send.call(this.env, opts); 80 | } 81 | 82 | post(opts) { 83 | return this.send.call(this.env, opts, 'POST'); 84 | } 85 | } 86 | 87 | return new (class { 88 | constructor(name, opts = {}) { 89 | this.name = name; 90 | this.http = new Http(this); 91 | this.data = null; 92 | this.dataFile = 'box.dat'; 93 | this.logs = []; 94 | this.isMute = false; 95 | this.noLogKey = opts.noLogKey || ''; 96 | this.noLog = opts.noLog; 97 | this.isNeedRewrite = false; 98 | this.logSeparator = '\n'; 99 | this.startTime = new Date().getTime(); 100 | Object.assign(this, opts); 101 | this.log('', `🔔${this.name}, 开始!`); 102 | } 103 | 104 | isNode() { 105 | return 'undefined' !== typeof module && !!module.exports; 106 | } 107 | 108 | isQuanX() { 109 | return 'undefined' !== typeof $task; 110 | } 111 | 112 | isSurge() { 113 | return 'undefined' !== typeof $httpClient && 'undefined' === typeof $loon; 114 | } 115 | 116 | isLoon() { 117 | return 'undefined' !== typeof $loon; 118 | } 119 | 120 | isShadowrocket() { 121 | return 'undefined' !== typeof $rocket; 122 | } 123 | 124 | toObj(str, defaultValue = null) { 125 | try { 126 | return JSON.parse(str); 127 | } catch { 128 | return defaultValue; 129 | } 130 | } 131 | 132 | toStr(obj, defaultValue = null) { 133 | try { 134 | return JSON.stringify(obj); 135 | } catch { 136 | return defaultValue; 137 | } 138 | } 139 | 140 | getJson(key, defaultValue) { 141 | let json = defaultValue; 142 | const val = this.getData(key); 143 | if (val) { 144 | try { 145 | json = JSON.parse(this.getData(key)); 146 | } catch {} 147 | } 148 | return json; 149 | } 150 | 151 | setJson(val, key) { 152 | try { 153 | return this.setData(JSON.stringify(val), key); 154 | } catch { 155 | return false; 156 | } 157 | } 158 | 159 | getScript(url) { 160 | return new Promise((resolve) => { 161 | this.get({ url }, (err, resp, body) => resolve(body)); 162 | }); 163 | } 164 | 165 | runScript(script, runOpts) { 166 | return new Promise((resolve) => { 167 | let httpApi = this.getData('@chavy_boxjs_userCfgs.httpApi'); 168 | httpApi = httpApi ? httpApi.replace(/\n/g, '').trim() : httpApi; 169 | let httpApi_timeout = this.getData( 170 | '@chavy_boxjs_userCfgs.httpApi_timeout' 171 | ); 172 | httpApi_timeout = httpApi_timeout ? httpApi_timeout * 1 : 20; 173 | httpApi_timeout = 174 | runOpts && runOpts.timeout ? runOpts.timeout : httpApi_timeout; 175 | const [key, addr] = httpApi.split('@'); 176 | const opts = { 177 | url: `http://${addr}/v1/scripting/evaluate`, 178 | body: { 179 | script_text: script, 180 | mock_type: 'cron', 181 | timeout: httpApi_timeout, 182 | }, 183 | headers: { 'X-Key': key, Accept: '*/*' }, 184 | }; 185 | this.post(opts, (err, resp, body) => resolve(body)); 186 | }).catch((e) => this.logErr(e)); 187 | } 188 | 189 | loadData() { 190 | if (this.isNode()) { 191 | this.fs = this.fs ? this.fs : require('fs'); 192 | this.path = this.path ? this.path : require('path'); 193 | const curDirDataFilePath = this.path.resolve(this.dataFile); 194 | const rootDirDataFilePath = this.path.resolve( 195 | process.cwd(), 196 | this.dataFile 197 | ); 198 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 199 | const isRootDirDataFile = 200 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 201 | if (isCurDirDataFile || isRootDirDataFile) { 202 | const datPath = isCurDirDataFile 203 | ? curDirDataFilePath 204 | : rootDirDataFilePath; 205 | try { 206 | return JSON.parse(this.fs.readFileSync(datPath)); 207 | } catch (e) { 208 | return {}; 209 | } 210 | } else return {}; 211 | } else return {}; 212 | } 213 | 214 | writeData() { 215 | if (this.isNode()) { 216 | this.fs = this.fs ? this.fs : require('fs'); 217 | this.path = this.path ? this.path : require('path'); 218 | const curDirDataFilePath = this.path.resolve(this.dataFile); 219 | const rootDirDataFilePath = this.path.resolve( 220 | process.cwd(), 221 | this.dataFile 222 | ); 223 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 224 | const isRootDirDataFile = 225 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 226 | const jsonData = JSON.stringify(this.data); 227 | if (isCurDirDataFile) { 228 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 229 | } else if (isRootDirDataFile) { 230 | this.fs.writeFileSync(rootDirDataFilePath, jsonData); 231 | } else { 232 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 233 | } 234 | } 235 | } 236 | 237 | lodash_get(source, path, defaultValue = undefined) { 238 | const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.'); 239 | let result = source; 240 | for (const p of paths) { 241 | result = Object(result)[p]; 242 | if (result === undefined) { 243 | return defaultValue; 244 | } 245 | } 246 | return result; 247 | } 248 | 249 | lodash_set(obj, path, value) { 250 | if (Object(obj) !== obj) return obj; 251 | if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || []; 252 | path 253 | .slice(0, -1) 254 | .reduce( 255 | (a, c, i) => 256 | Object(a[c]) === a[c] 257 | ? a[c] 258 | : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {}), 259 | obj 260 | )[path[path.length - 1]] = value; 261 | return obj; 262 | } 263 | 264 | getData(key) { 265 | let val = this.getVal(key); 266 | // 如果以 @ 267 | if (/^@/.test(key)) { 268 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 269 | const objVal = objKey ? this.getVal(objKey) : ''; 270 | if (objVal) { 271 | try { 272 | const objedVal = JSON.parse(objVal); 273 | val = objedVal ? this.lodash_get(objedVal, paths, '') : val; 274 | } catch (e) { 275 | val = ''; 276 | } 277 | } 278 | } 279 | return val; 280 | } 281 | 282 | setData(val, key) { 283 | let isSuc = false; 284 | if (/^@/.test(key)) { 285 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 286 | const objdat = this.getVal(objKey); 287 | const objVal = objKey 288 | ? objdat === 'null' 289 | ? null 290 | : objdat || '{}' 291 | : '{}'; 292 | try { 293 | const objedVal = JSON.parse(objVal); 294 | this.lodash_set(objedVal, paths, val); 295 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 296 | } catch (e) { 297 | const objedVal = {}; 298 | this.lodash_set(objedVal, paths, val); 299 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 300 | } 301 | } else { 302 | isSuc = this.setVal(val, key); 303 | } 304 | return isSuc; 305 | } 306 | 307 | getVal(key) { 308 | if (this.isSurge() || this.isLoon()) { 309 | return $persistentStore.read(key); 310 | } else if (this.isQuanX()) { 311 | return $prefs.valueForKey(key); 312 | } else if (this.isNode()) { 313 | this.data = this.loadData(); 314 | return this.data[key]; 315 | } else { 316 | return (this.data && this.data[key]) || null; 317 | } 318 | } 319 | 320 | setVal(val, key) { 321 | if (this.isSurge() || this.isLoon()) { 322 | return $persistentStore.write(val, key); 323 | } else if (this.isQuanX()) { 324 | return $prefs.setValueForKey(val, key); 325 | } else if (this.isNode()) { 326 | this.data = this.loadData(); 327 | this.data[key] = val; 328 | this.writeData(); 329 | return true; 330 | } else { 331 | return (this.data && this.data[key]) || null; 332 | } 333 | } 334 | 335 | initGotEnv(opts) { 336 | this.got = this.got ? this.got : require('got'); 337 | this.ckTough = this.ckTough ? this.ckTough : require('tough-cookie'); 338 | this.ckJar = this.ckJar ? this.ckJar : new this.ckTough.CookieJar(); 339 | if (opts) { 340 | opts.headers = opts.headers ? opts.headers : {}; 341 | if (undefined === opts.headers.Cookie && undefined === opts.cookieJar) { 342 | opts.cookieJar = this.ckJar; 343 | } 344 | } 345 | } 346 | 347 | get(opts, callback = () => {}) { 348 | if (opts.headers) { 349 | delete opts.headers['Content-Type']; 350 | delete opts.headers['Content-Length']; 351 | delete opts.headers['Host']; 352 | } 353 | if (this.isSurge() || this.isLoon()) { 354 | if (this.isSurge() && this.isNeedRewrite) { 355 | opts.headers = opts.headers || {}; 356 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 357 | } 358 | $httpClient.get(opts, (err, resp, body) => { 359 | if (!err && resp) { 360 | resp.body = body; 361 | resp.statusCode = resp.status; 362 | } 363 | callback(err, resp, body); 364 | }); 365 | } else if (this.isQuanX()) { 366 | if (this.isNeedRewrite) { 367 | opts.opts = opts.opts || {}; 368 | Object.assign(opts.opts, { hints: false }); 369 | } 370 | $task.fetch(opts).then( 371 | (resp) => { 372 | const { statusCode: status, statusCode, headers, body } = resp; 373 | callback(null, { status, statusCode, headers, body }, body); 374 | }, 375 | (err) => callback(err) 376 | ); 377 | } else if (this.isNode()) { 378 | this.initGotEnv(opts); 379 | this.got(opts) 380 | .on('redirect', (resp, nextOpts) => { 381 | try { 382 | if (resp.headers['set-cookie']) { 383 | const ck = resp.headers['set-cookie'] 384 | .map(this.ckTough.Cookie.parse) 385 | .toString(); 386 | if (ck) { 387 | this.ckJar.setCookieSync(ck, null); 388 | } 389 | nextOpts.cookieJar = this.ckJar; 390 | } 391 | } catch (e) { 392 | this.logErr(e); 393 | } 394 | // this.ckJar.setCookieSync(resp.headers['set-cookie'].map(Cookie.parse).toString()) 395 | }) 396 | .then( 397 | (resp) => { 398 | const { statusCode: status, statusCode, headers, body } = resp; 399 | callback(null, { status, statusCode, headers, body }, body); 400 | }, 401 | (err) => { 402 | const { message: error, response: resp } = err; 403 | callback(error, resp, resp && resp.body); 404 | } 405 | ); 406 | } 407 | } 408 | 409 | post(opts, callback = () => {}) { 410 | const method = opts.method ? opts.method.toLocaleLowerCase() : 'post'; 411 | // 如果指定了请求体, 但没指定`Content-Type`, 则自动生成 412 | if (opts.body && opts.headers && !opts.headers['Content-Type']) { 413 | opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 414 | } 415 | if (opts.headers) { 416 | delete opts.headers['Host']; 417 | delete opts.headers['Content-Length']; 418 | }; 419 | if (this.isSurge() || this.isLoon()) { 420 | if (this.isSurge() && this.isNeedRewrite) { 421 | opts.headers = opts.headers || {}; 422 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 423 | } 424 | $httpClient[method](opts, (err, resp, body) => { 425 | if (!err && resp) { 426 | resp.body = body; 427 | resp.statusCode = resp.status; 428 | } 429 | callback(err, resp, body); 430 | }); 431 | } else if (this.isQuanX()) { 432 | opts.method = method; 433 | if (this.isNeedRewrite) { 434 | opts.opts = opts.opts || {}; 435 | Object.assign(opts.opts, { hints: false }); 436 | } 437 | $task.fetch(opts).then( 438 | (resp) => { 439 | const { statusCode: status, statusCode, headers, body } = resp; 440 | callback(null, { status, statusCode, headers, body }, body); 441 | }, 442 | (err) => callback(err) 443 | ); 444 | } else if (this.isNode()) { 445 | this.initGotEnv(opts); 446 | const { url, ..._opts } = opts; 447 | this.got[method](url, _opts).then( 448 | (resp) => { 449 | const { statusCode: status, statusCode, headers, body } = resp; 450 | callback(null, { status, statusCode, headers, body }, body); 451 | }, 452 | (err) => { 453 | const { message: error, response: resp } = err; 454 | callback(error, resp, resp && resp.body); 455 | } 456 | ); 457 | } 458 | } 459 | /** 460 | * 461 | * 示例:$.time('yyyy-MM-dd qq HH:mm:ss.S') 462 | * :$.time('yyyyMMddHHmmssS') 463 | * y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒 464 | * 其中y可选0-4位占位符、S可选0-1位占位符,其余可选0-2位占位符 465 | * @param {string} fmt 格式化参数 466 | * @param {number} 可选: 根据指定时间戳返回格式化日期 467 | * 468 | */ 469 | time(fmt, ts = null) { 470 | const date = ts ? new Date(ts) : new Date(); 471 | let o = { 472 | 'M+': date.getMonth() + 1, 473 | 'd+': date.getDate(), 474 | 'H+': date.getHours(), 475 | 'm+': date.getMinutes(), 476 | 's+': date.getSeconds(), 477 | 'q+': Math.floor((date.getMonth() + 3) / 3), 478 | S: date.getMilliseconds(), 479 | }; 480 | if (/(y+)/.test(fmt)) 481 | fmt = fmt.replace( 482 | RegExp.$1, 483 | (date.getFullYear() + '').substr(4 - RegExp.$1.length) 484 | ); 485 | for (let k in o) 486 | if (new RegExp('(' + k + ')').test(fmt)) 487 | fmt = fmt.replace( 488 | RegExp.$1, 489 | RegExp.$1.length == 1 490 | ? o[k] 491 | : ('00' + o[k]).substr(('' + o[k]).length) 492 | ); 493 | return fmt; 494 | } 495 | 496 | /** 497 | * 系统通知 498 | * 499 | * > 通知参数: 同时支持 QuanX 和 Loon 两种格式, EnvJs根据运行环境自动转换, Surge 环境不支持多媒体通知 500 | * 501 | * 示例: 502 | * $.msg(title, subt, desc, 'twitter://') 503 | * $.msg(title, subt, desc, { 'open-url': 'twitter://', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 504 | * $.msg(title, subt, desc, { 'open-url': 'https://bing.com', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 505 | * 506 | * @param {*} title 标题 507 | * @param {*} subt 副标题 508 | * @param {*} desc 通知详情 509 | * @param {*} opts 通知参数 510 | * 511 | */ 512 | msg(title = name, subt = '', desc = '', opts) { 513 | const toEnvOpts = (rawOpts) => { 514 | if (!rawOpts) return rawOpts; 515 | if (typeof rawOpts === 'string') { 516 | if (this.isLoon()) return rawOpts; 517 | else if (this.isQuanX()) return { 'open-url': rawOpts }; 518 | else if (this.isSurge()) return { url: rawOpts }; 519 | else return undefined; 520 | } else if (typeof rawOpts === 'object') { 521 | if (this.isLoon()) { 522 | let openUrl = rawOpts.openUrl || rawOpts.url || rawOpts['open-url']; 523 | let mediaUrl = rawOpts.mediaUrl || rawOpts['media-url']; 524 | return { openUrl, mediaUrl }; 525 | } else if (this.isQuanX()) { 526 | let openUrl = rawOpts['open-url'] || rawOpts.url || rawOpts.openUrl; 527 | let mediaUrl = rawOpts['media-url'] || rawOpts.mediaUrl; 528 | let updatePasteboard = 529 | rawOpts['update-pasteboard'] || rawOpts.updatePasteboard; 530 | return { 531 | 'open-url': openUrl, 532 | 'media-url': mediaUrl, 533 | 'update-pasteboard': updatePasteboard, 534 | }; 535 | } else if (this.isSurge()) { 536 | let openUrl = rawOpts.url || rawOpts.openUrl || rawOpts['open-url']; 537 | return { url: openUrl }; 538 | } 539 | } else { 540 | return undefined; 541 | } 542 | }; 543 | if (!this.isMute) { 544 | if (this.isSurge() || this.isLoon()) { 545 | $notification.post(title, subt, desc, toEnvOpts(opts)); 546 | } else if (this.isQuanX()) { 547 | $notify(title, subt, desc, toEnvOpts(opts)); 548 | } 549 | } 550 | if (!this.isMuteLog) { 551 | let logs = ['', '==============📣系统通知📣==============']; 552 | logs.push(title); 553 | subt ? logs.push(subt) : ''; 554 | desc ? logs.push(desc) : ''; 555 | console.log(logs.join('\n')); 556 | this.logs = this.logs.concat(logs); 557 | } 558 | } 559 | 560 | log(...logs) { 561 | if (this.noLog || (this.noLogKey && (this.getData(this.noLogKey) || 'N').toLocaleUpperCase() === 'Y')) { 562 | return; 563 | } 564 | if (logs.length > 0) { 565 | this.logs = [...this.logs, ...logs]; 566 | } 567 | console.log(logs.join(this.logSeparator)); 568 | } 569 | 570 | logErr(err, msg) { 571 | const isPrintSack = !this.isSurge() && !this.isQuanX() && !this.isLoon(); 572 | if (!isPrintSack) { 573 | this.log('', `❗️${this.name}, 错误!`, err); 574 | } else { 575 | this.log('', `❗️${this.name}, 错误!`, err.stack); 576 | } 577 | } 578 | 579 | wait(time) { 580 | return new Promise((resolve) => setTimeout(resolve, time)); 581 | } 582 | 583 | done(val = {}) { 584 | const endTime = new Date().getTime(); 585 | const costTime = (endTime - this.startTime) / 1000; 586 | this.log('', `🔔${this.name}, 结束! 🕛 ${costTime} 秒`); 587 | this.log(); 588 | if (this.isSurge() || this.isQuanX() || this.isLoon()) { 589 | $done(val); 590 | } 591 | } 592 | })(name, opts); 593 | } 594 | -------------------------------------------------------------------------------- /Script/time.js: -------------------------------------------------------------------------------- 1 | function getTimeRemaining(endtime) { 2 | const total = Date.parse(endtime) - Date.parse(new Date()); 3 | const seconds = Math.floor((total / 1000) % 60); 4 | const minutes = Math.floor((total / 1000 / 60) % 60); 5 | const hours = Math.floor((total / (1000 * 60 * 60)) % 24); 6 | const days = Math.floor(total / (1000 * 60 * 60 * 24)); 7 | 8 | return { 9 | total, 10 | days, 11 | hours, 12 | minutes, 13 | seconds, 14 | }; 15 | } 16 | 17 | function initializeClock(className, endtime) { 18 | const $dom = clock.querySelector(className); 19 | 20 | function updateClock() { 21 | const t = getTimeRemaining(endtime); 22 | 23 | $dom.textContent = 24 | t.days + 25 | ':' + 26 | ('0' + t.hours).slice(-2) + 27 | ':' + 28 | ('0' + t.minutes).slice(-2) + 29 | ':' + 30 | ('0' + t.seconds).slice(-2); 31 | 32 | if (t.total <= 0) { 33 | clearInterval(timeinterval); 34 | } 35 | } 36 | 37 | updateClock(); 38 | const timeinterval = setInterval(updateClock, 1000); 39 | } 40 | 41 | const deadline = new Date(Date.parse(new Date()) + 15 * 24 * 60 * 60 * 1000); 42 | initializeClock('#_time i', deadline); 43 | -------------------------------------------------------------------------------- /Script/unHttpOnly.js: -------------------------------------------------------------------------------- 1 | const $ = new Env('移除HttpOnly'); 2 | 3 | const modifiedHeaders = $response.headers; 4 | let key = 'Set-Cookie'; 5 | let cookies = $response.headers[key]; 6 | if (!cookies) { 7 | key = 'set-cookie'; 8 | cookies = $response.headers[key]; 9 | } 10 | if (cookies) { 11 | cookies = cookies 12 | .replace(/HttpOnly/gi, '') 13 | .replace(/(Expires=.+?),/gi, '$1@') 14 | .split(', '); 15 | 16 | let _key = key; 17 | cookies.forEach((ck, i) => { 18 | // 利用空格设置多个 set-cookie 19 | _key += ' '; 20 | modifiedHeaders[_key] = ck.replace(/@/g, ','); 21 | }); 22 | } 23 | 24 | $.done({ headers: modifiedHeaders }); 25 | 26 | // https://github.com/chavyleung/scripts/blob/master/Env.js 27 | // prettier-ignore 28 | function Env(name, opts) { 29 | class Http { 30 | constructor(env) { 31 | this.env = env; 32 | } 33 | 34 | send(opts, method = 'GET') { 35 | opts = typeof opts === 'string' ? { url: opts } : opts; 36 | let sender = this.get; 37 | if (method === 'POST') { 38 | sender = this.post; 39 | } 40 | return new Promise((resolve, reject) => { 41 | sender.call(this, opts, (err, resp, body) => { 42 | if (err) reject(err); 43 | else resolve(resp); 44 | }); 45 | }); 46 | } 47 | 48 | get(opts) { 49 | return this.send.call(this.env, opts); 50 | } 51 | 52 | post(opts) { 53 | return this.send.call(this.env, opts, 'POST'); 54 | } 55 | } 56 | 57 | return new (class { 58 | constructor(name, opts = {}) { 59 | this.name = name; 60 | this.http = new Http(this); 61 | this.data = null; 62 | this.dataFile = 'box.dat'; 63 | this.logs = []; 64 | this.isMute = false; 65 | this.noLogKey = opts.noLogKey || ''; 66 | this.noLog = opts.noLog; 67 | this.isNeedRewrite = false; 68 | this.logSeparator = '\n'; 69 | this.startTime = new Date().getTime(); 70 | Object.assign(this, opts); 71 | this.log('', `🔔${this.name}, 开始!`); 72 | } 73 | 74 | isNode() { 75 | return 'undefined' !== typeof module && !!module.exports; 76 | } 77 | 78 | isQuanX() { 79 | return 'undefined' !== typeof $task; 80 | } 81 | 82 | isSurge() { 83 | return 'undefined' !== typeof $httpClient && 'undefined' === typeof $loon; 84 | } 85 | 86 | isLoon() { 87 | return 'undefined' !== typeof $loon; 88 | } 89 | 90 | isShadowrocket() { 91 | return 'undefined' !== typeof $rocket; 92 | } 93 | 94 | toObj(str, defaultValue = null) { 95 | try { 96 | return JSON.parse(str); 97 | } catch { 98 | return defaultValue; 99 | } 100 | } 101 | 102 | toStr(obj, defaultValue = null) { 103 | try { 104 | return JSON.stringify(obj); 105 | } catch { 106 | return defaultValue; 107 | } 108 | } 109 | 110 | getJson(key, defaultValue) { 111 | let json = defaultValue; 112 | const val = this.getData(key); 113 | if (val) { 114 | try { 115 | json = JSON.parse(this.getData(key)); 116 | } catch {} 117 | } 118 | return json; 119 | } 120 | 121 | setJson(val, key) { 122 | try { 123 | return this.setData(JSON.stringify(val), key); 124 | } catch { 125 | return false; 126 | } 127 | } 128 | 129 | getScript(url) { 130 | return new Promise((resolve) => { 131 | this.get({ url }, (err, resp, body) => resolve(body)); 132 | }); 133 | } 134 | 135 | runScript(script, runOpts) { 136 | return new Promise((resolve) => { 137 | let httpApi = this.getData('@chavy_boxjs_userCfgs.httpApi'); 138 | httpApi = httpApi ? httpApi.replace(/\n/g, '').trim() : httpApi; 139 | let httpApi_timeout = this.getData( 140 | '@chavy_boxjs_userCfgs.httpApi_timeout' 141 | ); 142 | httpApi_timeout = httpApi_timeout ? httpApi_timeout * 1 : 20; 143 | httpApi_timeout = 144 | runOpts && runOpts.timeout ? runOpts.timeout : httpApi_timeout; 145 | const [key, addr] = httpApi.split('@'); 146 | const opts = { 147 | url: `http://${addr}/v1/scripting/evaluate`, 148 | body: { 149 | script_text: script, 150 | mock_type: 'cron', 151 | timeout: httpApi_timeout, 152 | }, 153 | headers: { 'X-Key': key, Accept: '*/*' }, 154 | }; 155 | this.post(opts, (err, resp, body) => resolve(body)); 156 | }).catch((e) => this.logErr(e)); 157 | } 158 | 159 | loadData() { 160 | if (this.isNode()) { 161 | this.fs = this.fs ? this.fs : require('fs'); 162 | this.path = this.path ? this.path : require('path'); 163 | const curDirDataFilePath = this.path.resolve(this.dataFile); 164 | const rootDirDataFilePath = this.path.resolve( 165 | process.cwd(), 166 | this.dataFile 167 | ); 168 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 169 | const isRootDirDataFile = 170 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 171 | if (isCurDirDataFile || isRootDirDataFile) { 172 | const datPath = isCurDirDataFile 173 | ? curDirDataFilePath 174 | : rootDirDataFilePath; 175 | try { 176 | return JSON.parse(this.fs.readFileSync(datPath)); 177 | } catch (e) { 178 | return {}; 179 | } 180 | } else return {}; 181 | } else return {}; 182 | } 183 | 184 | writeData() { 185 | if (this.isNode()) { 186 | this.fs = this.fs ? this.fs : require('fs'); 187 | this.path = this.path ? this.path : require('path'); 188 | const curDirDataFilePath = this.path.resolve(this.dataFile); 189 | const rootDirDataFilePath = this.path.resolve( 190 | process.cwd(), 191 | this.dataFile 192 | ); 193 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 194 | const isRootDirDataFile = 195 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 196 | const jsonData = JSON.stringify(this.data); 197 | if (isCurDirDataFile) { 198 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 199 | } else if (isRootDirDataFile) { 200 | this.fs.writeFileSync(rootDirDataFilePath, jsonData); 201 | } else { 202 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 203 | } 204 | } 205 | } 206 | 207 | lodash_get(source, path, defaultValue = undefined) { 208 | const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.'); 209 | let result = source; 210 | for (const p of paths) { 211 | result = Object(result)[p]; 212 | if (result === undefined) { 213 | return defaultValue; 214 | } 215 | } 216 | return result; 217 | } 218 | 219 | lodash_set(obj, path, value) { 220 | if (Object(obj) !== obj) return obj; 221 | if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || []; 222 | path 223 | .slice(0, -1) 224 | .reduce( 225 | (a, c, i) => 226 | Object(a[c]) === a[c] 227 | ? a[c] 228 | : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {}), 229 | obj 230 | )[path[path.length - 1]] = value; 231 | return obj; 232 | } 233 | 234 | getData(key) { 235 | let val = this.getVal(key); 236 | // 如果以 @ 237 | if (/^@/.test(key)) { 238 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 239 | const objVal = objKey ? this.getVal(objKey) : ''; 240 | if (objVal) { 241 | try { 242 | const objedVal = JSON.parse(objVal); 243 | val = objedVal ? this.lodash_get(objedVal, paths, '') : val; 244 | } catch (e) { 245 | val = ''; 246 | } 247 | } 248 | } 249 | return val; 250 | } 251 | 252 | setData(val, key) { 253 | let isSuc = false; 254 | if (/^@/.test(key)) { 255 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 256 | const objdat = this.getVal(objKey); 257 | const objVal = objKey 258 | ? objdat === 'null' 259 | ? null 260 | : objdat || '{}' 261 | : '{}'; 262 | try { 263 | const objedVal = JSON.parse(objVal); 264 | this.lodash_set(objedVal, paths, val); 265 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 266 | } catch (e) { 267 | const objedVal = {}; 268 | this.lodash_set(objedVal, paths, val); 269 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 270 | } 271 | } else { 272 | isSuc = this.setVal(val, key); 273 | } 274 | return isSuc; 275 | } 276 | 277 | getVal(key) { 278 | if (this.isSurge() || this.isLoon()) { 279 | return $persistentStore.read(key); 280 | } else if (this.isQuanX()) { 281 | return $prefs.valueForKey(key); 282 | } else if (this.isNode()) { 283 | this.data = this.loadData(); 284 | return this.data[key]; 285 | } else { 286 | return (this.data && this.data[key]) || null; 287 | } 288 | } 289 | 290 | setVal(val, key) { 291 | if (this.isSurge() || this.isLoon()) { 292 | return $persistentStore.write(val, key); 293 | } else if (this.isQuanX()) { 294 | return $prefs.setValueForKey(val, key); 295 | } else if (this.isNode()) { 296 | this.data = this.loadData(); 297 | this.data[key] = val; 298 | this.writeData(); 299 | return true; 300 | } else { 301 | return (this.data && this.data[key]) || null; 302 | } 303 | } 304 | 305 | initGotEnv(opts) { 306 | this.got = this.got ? this.got : require('got'); 307 | this.ckTough = this.ckTough ? this.ckTough : require('tough-cookie'); 308 | this.ckJar = this.ckJar ? this.ckJar : new this.ckTough.CookieJar(); 309 | if (opts) { 310 | opts.headers = opts.headers ? opts.headers : {}; 311 | if (undefined === opts.headers.Cookie && undefined === opts.cookieJar) { 312 | opts.cookieJar = this.ckJar; 313 | } 314 | } 315 | } 316 | 317 | get(opts, callback = () => {}) { 318 | if (opts.headers) { 319 | delete opts.headers['Content-Type']; 320 | delete opts.headers['Content-Length']; 321 | delete opts.headers['Host']; 322 | } 323 | if (this.isSurge() || this.isLoon()) { 324 | if (this.isSurge() && this.isNeedRewrite) { 325 | opts.headers = opts.headers || {}; 326 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 327 | } 328 | $httpClient.get(opts, (err, resp, body) => { 329 | if (!err && resp) { 330 | resp.body = body; 331 | resp.statusCode = resp.status; 332 | } 333 | callback(err, resp, body); 334 | }); 335 | } else if (this.isQuanX()) { 336 | if (this.isNeedRewrite) { 337 | opts.opts = opts.opts || {}; 338 | Object.assign(opts.opts, { hints: false }); 339 | } 340 | $task.fetch(opts).then( 341 | (resp) => { 342 | const { statusCode: status, statusCode, headers, body } = resp; 343 | callback(null, { status, statusCode, headers, body }, body); 344 | }, 345 | (err) => callback(err) 346 | ); 347 | } else if (this.isNode()) { 348 | this.initGotEnv(opts); 349 | this.got(opts) 350 | .on('redirect', (resp, nextOpts) => { 351 | try { 352 | if (resp.headers['set-cookie']) { 353 | const ck = resp.headers['set-cookie'] 354 | .map(this.ckTough.Cookie.parse) 355 | .toString(); 356 | if (ck) { 357 | this.ckJar.setCookieSync(ck, null); 358 | } 359 | nextOpts.cookieJar = this.ckJar; 360 | } 361 | } catch (e) { 362 | this.logErr(e); 363 | } 364 | // this.ckJar.setCookieSync(resp.headers['set-cookie'].map(Cookie.parse).toString()) 365 | }) 366 | .then( 367 | (resp) => { 368 | const { statusCode: status, statusCode, headers, body } = resp; 369 | callback(null, { status, statusCode, headers, body }, body); 370 | }, 371 | (err) => { 372 | const { message: error, response: resp } = err; 373 | callback(error, resp, resp && resp.body); 374 | } 375 | ); 376 | } 377 | } 378 | 379 | post(opts, callback = () => {}) { 380 | const method = opts.method ? opts.method.toLocaleLowerCase() : 'post'; 381 | // 如果指定了请求体, 但没指定`Content-Type`, 则自动生成 382 | if (opts.body && opts.headers && !opts.headers['Content-Type']) { 383 | opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 384 | } 385 | if (opts.headers) { 386 | delete opts.headers['Host']; 387 | delete opts.headers['Content-Length']; 388 | }; 389 | if (this.isSurge() || this.isLoon()) { 390 | if (this.isSurge() && this.isNeedRewrite) { 391 | opts.headers = opts.headers || {}; 392 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 393 | } 394 | $httpClient[method](opts, (err, resp, body) => { 395 | if (!err && resp) { 396 | resp.body = body; 397 | resp.statusCode = resp.status; 398 | } 399 | callback(err, resp, body); 400 | }); 401 | } else if (this.isQuanX()) { 402 | opts.method = method; 403 | if (this.isNeedRewrite) { 404 | opts.opts = opts.opts || {}; 405 | Object.assign(opts.opts, { hints: false }); 406 | } 407 | $task.fetch(opts).then( 408 | (resp) => { 409 | const { statusCode: status, statusCode, headers, body } = resp; 410 | callback(null, { status, statusCode, headers, body }, body); 411 | }, 412 | (err) => callback(err) 413 | ); 414 | } else if (this.isNode()) { 415 | this.initGotEnv(opts); 416 | const { url, ..._opts } = opts; 417 | this.got[method](url, _opts).then( 418 | (resp) => { 419 | const { statusCode: status, statusCode, headers, body } = resp; 420 | callback(null, { status, statusCode, headers, body }, body); 421 | }, 422 | (err) => { 423 | const { message: error, response: resp } = err; 424 | callback(error, resp, resp && resp.body); 425 | } 426 | ); 427 | } 428 | } 429 | /** 430 | * 431 | * 示例:$.time('yyyy-MM-dd qq HH:mm:ss.S') 432 | * :$.time('yyyyMMddHHmmssS') 433 | * y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒 434 | * 其中y可选0-4位占位符、S可选0-1位占位符,其余可选0-2位占位符 435 | * @param {string} fmt 格式化参数 436 | * @param {number} 可选: 根据指定时间戳返回格式化日期 437 | * 438 | */ 439 | time(fmt, ts = null) { 440 | const date = ts ? new Date(ts) : new Date(); 441 | let o = { 442 | 'M+': date.getMonth() + 1, 443 | 'd+': date.getDate(), 444 | 'H+': date.getHours(), 445 | 'm+': date.getMinutes(), 446 | 's+': date.getSeconds(), 447 | 'q+': Math.floor((date.getMonth() + 3) / 3), 448 | S: date.getMilliseconds(), 449 | }; 450 | if (/(y+)/.test(fmt)) 451 | fmt = fmt.replace( 452 | RegExp.$1, 453 | (date.getFullYear() + '').substr(4 - RegExp.$1.length) 454 | ); 455 | for (let k in o) 456 | if (new RegExp('(' + k + ')').test(fmt)) 457 | fmt = fmt.replace( 458 | RegExp.$1, 459 | RegExp.$1.length == 1 460 | ? o[k] 461 | : ('00' + o[k]).substr(('' + o[k]).length) 462 | ); 463 | return fmt; 464 | } 465 | 466 | /** 467 | * 系统通知 468 | * 469 | * > 通知参数: 同时支持 QuanX 和 Loon 两种格式, EnvJs根据运行环境自动转换, Surge 环境不支持多媒体通知 470 | * 471 | * 示例: 472 | * $.msg(title, subt, desc, 'twitter://') 473 | * $.msg(title, subt, desc, { 'open-url': 'twitter://', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 474 | * $.msg(title, subt, desc, { 'open-url': 'https://bing.com', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 475 | * 476 | * @param {*} title 标题 477 | * @param {*} subt 副标题 478 | * @param {*} desc 通知详情 479 | * @param {*} opts 通知参数 480 | * 481 | */ 482 | msg(title = name, subt = '', desc = '', opts) { 483 | const toEnvOpts = (rawOpts) => { 484 | if (!rawOpts) return rawOpts; 485 | if (typeof rawOpts === 'string') { 486 | if (this.isLoon()) return rawOpts; 487 | else if (this.isQuanX()) return { 'open-url': rawOpts }; 488 | else if (this.isSurge()) return { url: rawOpts }; 489 | else return undefined; 490 | } else if (typeof rawOpts === 'object') { 491 | if (this.isLoon()) { 492 | let openUrl = rawOpts.openUrl || rawOpts.url || rawOpts['open-url']; 493 | let mediaUrl = rawOpts.mediaUrl || rawOpts['media-url']; 494 | return { openUrl, mediaUrl }; 495 | } else if (this.isQuanX()) { 496 | let openUrl = rawOpts['open-url'] || rawOpts.url || rawOpts.openUrl; 497 | let mediaUrl = rawOpts['media-url'] || rawOpts.mediaUrl; 498 | let updatePasteboard = 499 | rawOpts['update-pasteboard'] || rawOpts.updatePasteboard; 500 | return { 501 | 'open-url': openUrl, 502 | 'media-url': mediaUrl, 503 | 'update-pasteboard': updatePasteboard, 504 | }; 505 | } else if (this.isSurge()) { 506 | let openUrl = rawOpts.url || rawOpts.openUrl || rawOpts['open-url']; 507 | return { url: openUrl }; 508 | } 509 | } else { 510 | return undefined; 511 | } 512 | }; 513 | if (!this.isMute) { 514 | if (this.isSurge() || this.isLoon()) { 515 | $notification.post(title, subt, desc, toEnvOpts(opts)); 516 | } else if (this.isQuanX()) { 517 | $notify(title, subt, desc, toEnvOpts(opts)); 518 | } 519 | } 520 | if (!this.isMuteLog) { 521 | let logs = ['', '==============📣系统通知📣==============']; 522 | logs.push(title); 523 | subt ? logs.push(subt) : ''; 524 | desc ? logs.push(desc) : ''; 525 | console.log(logs.join('\n')); 526 | this.logs = this.logs.concat(logs); 527 | } 528 | } 529 | 530 | log(...logs) { 531 | if (this.noLog || (this.noLogKey && (this.getData(this.noLogKey) || 'N').toLocaleUpperCase() === 'Y')) { 532 | return; 533 | } 534 | if (logs.length > 0) { 535 | this.logs = [...this.logs, ...logs]; 536 | } 537 | console.log(logs.join(this.logSeparator)); 538 | } 539 | 540 | logErr(err, msg) { 541 | const isPrintSack = !this.isSurge() && !this.isQuanX() && !this.isLoon(); 542 | if (!isPrintSack) { 543 | this.log('', `❗️${this.name}, 错误!`, err); 544 | } else { 545 | this.log('', `❗️${this.name}, 错误!`, err.stack); 546 | } 547 | } 548 | 549 | wait(time) { 550 | return new Promise((resolve) => setTimeout(resolve, time)); 551 | } 552 | 553 | done(val = {}) { 554 | const endTime = new Date().getTime(); 555 | const costTime = (endTime - this.startTime) / 1000; 556 | this.log('', `🔔${this.name}, 结束! 🕛 ${costTime} 秒`); 557 | this.log(); 558 | if (this.isSurge() || this.isQuanX() || this.isLoon()) { 559 | $done(val); 560 | } 561 | } 562 | })(name, opts); 563 | } 564 | -------------------------------------------------------------------------------- /Script/zhihu.js: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | [rewrite_local] 4 | # 知乎(部分替换为手机百度 Quark 的 User-Agent) 5 | ^https:\/\/www\.zhihu\.com\/ url request-header (\r\n)User-Agent:.+(\r\n) request-header $1User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Version/9.0 Mobile/13B143 Safari/601.1 Quark/604.1 T7/10.7 SearchCraft/2.7.7 (Baidu; P1 9.0.0)$2 6 | # 知乎网页直接看 7 | ^https://www\.zhihu\.com/question/ url script-response-body https://raw.githubusercontent.com/id77/QuantumultX/master/Script/zhihu.js 8 | ^https://zhuanlan\.zhihu\.com/p/ url script-response-body https://raw.githubusercontent.com/id77/QuantumultX/master/Script/zhihu.js 9 | # 知乎网页去广告&推荐列表 10 | https://www\.zhihu\.com/api/v4/questions/\d+/related-readings url reject-200 11 | https://www\.zhihu\.com/api/v4/answers/\d+/related-readings url reject-200 12 | https://www\.zhihu\.com/api/v4/hot_recommendation url reject-200 13 | https://www\.zhihu\.com/commercial_api/banners_v3/mobile_banner url reject-200 14 | https://zhuanlan\.zhihu\.com/api/articles/\d+/recommendation url reject-200 15 | 16 | 17 | 18 | [mitm] 19 | hostname = www.zhihu.com, zhuanlan.zhihu.com 20 | */ 21 | 22 | let html = $response.body; 23 | let nonce = html.match(/nonce="[\w\-]*"/g)[1]; 24 | 25 | html = html.replace('apple-itunes-app', ''); 26 | 27 | html = 28 | html.replace(/(<\/html>)/, '') + 29 | ` 30 | 43 | 44 | 64 | 65 | `; 66 | 67 | $done({ body: html }); 68 | -------------------------------------------------------------------------------- /boxJS/README.md: -------------------------------------------------------------------------------- 1 | ### 自用boxJS 2 | 3 | 仅支持QuantumultX,需要开启 iCloud 4 | chavy.boxjs.js 放 Scripts 文件夹 5 | chavy.boxjs.html 放 Data 文件夹 6 | 7 | ``` 8 | hostname = boxjs.com, boxjs.net, *.boxjs.com, *.boxjs.net 9 | ^https?:\/\/(.+\.)?boxjs\.(com|net) url script-analyze-echo-response chavy.boxjs.js 10 | ``` 11 | -------------------------------------------------------------------------------- /icon/JDLM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/id77/QuantumultX/60f4ffb72ff9995e05c222f921349cc96646d8a1/icon/JDLM.png -------------------------------------------------------------------------------- /icon/console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/id77/QuantumultX/60f4ffb72ff9995e05c222f921349cc96646d8a1/icon/console.png -------------------------------------------------------------------------------- /icon/ex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/id77/QuantumultX/60f4ffb72ff9995e05c222f921349cc96646d8a1/icon/ex.png -------------------------------------------------------------------------------- /icon/id77.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/id77/QuantumultX/60f4ffb72ff9995e05c222f921349cc96646d8a1/icon/id77.png -------------------------------------------------------------------------------- /icon/jdGuaranteedPrice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/id77/QuantumultX/60f4ffb72ff9995e05c222f921349cc96646d8a1/icon/jdGuaranteedPrice.png -------------------------------------------------------------------------------- /icon/jdWuLiu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/id77/QuantumultX/60f4ffb72ff9995e05c222f921349cc96646d8a1/icon/jdWuLiu.png -------------------------------------------------------------------------------- /icon/jd_car.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/id77/QuantumultX/60f4ffb72ff9995e05c222f921349cc96646d8a1/icon/jd_car.png -------------------------------------------------------------------------------- /icon/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/id77/QuantumultX/60f4ffb72ff9995e05c222f921349cc96646d8a1/icon/link.png -------------------------------------------------------------------------------- /icon/sim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/id77/QuantumultX/60f4ffb72ff9995e05c222f921349cc96646d8a1/icon/sim.png -------------------------------------------------------------------------------- /icon/wyx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/id77/QuantumultX/60f4ffb72ff9995e05c222f921349cc96646d8a1/icon/wyx.png -------------------------------------------------------------------------------- /icon/yiLi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/id77/QuantumultX/60f4ffb72ff9995e05c222f921349cc96646d8a1/icon/yiLi.png -------------------------------------------------------------------------------- /img/yiLi_qr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/id77/QuantumultX/60f4ffb72ff9995e05c222f921349cc96646d8a1/img/yiLi_qr.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quantumultx", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@sindresorhus/is": { 8 | "version": "4.0.1", 9 | "resolved": "https://registry.nlark.com/@sindresorhus/is/download/@sindresorhus/is-4.0.1.tgz", 10 | "integrity": "sha1-0mcp24UPoye3ysxVIiUhlEBCJvU=" 11 | }, 12 | "@szmarczak/http-timer": { 13 | "version": "4.0.5", 14 | "resolved": "https://registry.npm.taobao.org/@szmarczak/http-timer/download/@szmarczak/http-timer-4.0.5.tgz", 15 | "integrity": "sha1-v71QIR6d+lG6B9pYoUzf0zMgUVI=", 16 | "requires": { 17 | "defer-to-connect": "^2.0.0" 18 | } 19 | }, 20 | "@types/cacheable-request": { 21 | "version": "6.0.1", 22 | "resolved": "https://registry.npm.taobao.org/@types/cacheable-request/download/@types/cacheable-request-6.0.1.tgz", 23 | "integrity": "sha1-XSLz3e0f06hMC761A5p0GcLJGXY=", 24 | "requires": { 25 | "@types/http-cache-semantics": "*", 26 | "@types/keyv": "*", 27 | "@types/node": "*", 28 | "@types/responselike": "*" 29 | } 30 | }, 31 | "@types/crypto-js": { 32 | "version": "4.0.1", 33 | "resolved": "https://registry.npm.taobao.org/@types/crypto-js/download/@types/crypto-js-4.0.1.tgz", 34 | "integrity": "sha1-OkvSRRiw5sWUDaTiZZ7rLvCAaWM=" 35 | }, 36 | "@types/http-cache-semantics": { 37 | "version": "4.0.0", 38 | "resolved": "https://registry.npm.taobao.org/@types/http-cache-semantics/download/@types/http-cache-semantics-4.0.0.tgz", 39 | "integrity": "sha1-kUB3lzaqJlVjXudW4kZ9eHz+iio=" 40 | }, 41 | "@types/keyv": { 42 | "version": "3.1.1", 43 | "resolved": "https://registry.npm.taobao.org/@types/keyv/download/@types/keyv-3.1.1.tgz?cache=0&sync_timestamp=1613379302011&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fkeyv%2Fdownload%2F%40types%2Fkeyv-3.1.1.tgz", 44 | "integrity": "sha1-5FpFMk/KnatxarEjDuJJyftSz6c=", 45 | "requires": { 46 | "@types/node": "*" 47 | } 48 | }, 49 | "@types/md5": { 50 | "version": "2.3.0", 51 | "resolved": "https://registry.npm.taobao.org/@types/md5/download/@types/md5-2.3.0.tgz", 52 | "integrity": "sha1-O2piMJEWD03HW+MXPiXyEQ3D+h8=", 53 | "requires": { 54 | "@types/node": "*" 55 | } 56 | }, 57 | "@types/node": { 58 | "version": "14.14.25", 59 | "resolved": "https://registry.npm.taobao.org/@types/node/download/@types/node-14.14.25.tgz", 60 | "integrity": "sha1-FZZ6e1d/+BOD+biIqmcF1D+7rpM=" 61 | }, 62 | "@types/responselike": { 63 | "version": "1.0.0", 64 | "resolved": "https://registry.npm.taobao.org/@types/responselike/download/@types/responselike-1.0.0.tgz?cache=0&sync_timestamp=1613384353624&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40types%2Fresponselike%2Fdownload%2F%40types%2Fresponselike-1.0.0.tgz", 65 | "integrity": "sha1-JR9P59FU0rrRJavhtCmyOv0mLik=", 66 | "requires": { 67 | "@types/node": "*" 68 | } 69 | }, 70 | "cacheable-lookup": { 71 | "version": "5.0.4", 72 | "resolved": "https://registry.npm.taobao.org/cacheable-lookup/download/cacheable-lookup-5.0.4.tgz?cache=0&sync_timestamp=1613567617958&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fcacheable-lookup%2Fdownload%2Fcacheable-lookup-5.0.4.tgz", 73 | "integrity": "sha1-WmuGWyxENXvj1evCpGewMnGacAU=" 74 | }, 75 | "cacheable-request": { 76 | "version": "7.0.1", 77 | "resolved": "https://registry.npm.taobao.org/cacheable-request/download/cacheable-request-7.0.1.tgz", 78 | "integrity": "sha1-BiAxwoViMngu1pSiV/o12pOUKlg=", 79 | "requires": { 80 | "clone-response": "^1.0.2", 81 | "get-stream": "^5.1.0", 82 | "http-cache-semantics": "^4.0.0", 83 | "keyv": "^4.0.0", 84 | "lowercase-keys": "^2.0.0", 85 | "normalize-url": "^4.1.0", 86 | "responselike": "^2.0.0" 87 | } 88 | }, 89 | "charenc": { 90 | "version": "0.0.2", 91 | "resolved": "http://registry.npm.taobao.org/charenc/download/charenc-0.0.2.tgz", 92 | "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" 93 | }, 94 | "clone-response": { 95 | "version": "1.0.2", 96 | "resolved": "http://registry.npm.taobao.org/clone-response/download/clone-response-1.0.2.tgz", 97 | "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", 98 | "requires": { 99 | "mimic-response": "^1.0.0" 100 | } 101 | }, 102 | "crypt": { 103 | "version": "0.0.2", 104 | "resolved": "http://registry.npm.taobao.org/crypt/download/crypt-0.0.2.tgz", 105 | "integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=" 106 | }, 107 | "crypto-js": { 108 | "version": "4.0.0", 109 | "resolved": "https://registry.npm.taobao.org/crypto-js/download/crypto-js-4.0.0.tgz", 110 | "integrity": "sha1-KQSrJnep0EKFai6i74DekuSjbcw=" 111 | }, 112 | "decompress-response": { 113 | "version": "6.0.0", 114 | "resolved": "https://registry.npm.taobao.org/decompress-response/download/decompress-response-6.0.0.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fdecompress-response%2Fdownload%2Fdecompress-response-6.0.0.tgz", 115 | "integrity": "sha1-yjh2Et234QS9FthaqwDV7PCcZvw=", 116 | "requires": { 117 | "mimic-response": "^3.1.0" 118 | }, 119 | "dependencies": { 120 | "mimic-response": { 121 | "version": "3.1.0", 122 | "resolved": "https://registry.npm.taobao.org/mimic-response/download/mimic-response-3.1.0.tgz", 123 | "integrity": "sha1-LR1Zr5wbEpgVrMwsRqAipc4fo8k=" 124 | } 125 | } 126 | }, 127 | "defer-to-connect": { 128 | "version": "2.0.1", 129 | "resolved": "https://registry.npm.taobao.org/defer-to-connect/download/defer-to-connect-2.0.1.tgz", 130 | "integrity": "sha1-gBa9tBQ+RjK3ejRJxiNid95SBYc=" 131 | }, 132 | "end-of-stream": { 133 | "version": "1.4.4", 134 | "resolved": "https://registry.npm.taobao.org/end-of-stream/download/end-of-stream-1.4.4.tgz", 135 | "integrity": "sha1-WuZKX0UFe682JuwU2gyl5LJDHrA=", 136 | "requires": { 137 | "once": "^1.4.0" 138 | } 139 | }, 140 | "get-stream": { 141 | "version": "5.2.0", 142 | "resolved": "https://registry.npm.taobao.org/get-stream/download/get-stream-5.2.0.tgz", 143 | "integrity": "sha1-SWaheV7lrOZecGxLe+txJX1uItM=", 144 | "requires": { 145 | "pump": "^3.0.0" 146 | } 147 | }, 148 | "got": { 149 | "version": "11.8.2", 150 | "resolved": "https://registry.npm.taobao.org/got/download/got-11.8.2.tgz?cache=0&sync_timestamp=1614332662884&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fgot%2Fdownload%2Fgot-11.8.2.tgz", 151 | "integrity": "sha1-ers5Weoowx81dvFXbB7/ziPzNZk=", 152 | "requires": { 153 | "@sindresorhus/is": "^4.0.0", 154 | "@szmarczak/http-timer": "^4.0.5", 155 | "@types/cacheable-request": "^6.0.1", 156 | "@types/responselike": "^1.0.0", 157 | "cacheable-lookup": "^5.0.3", 158 | "cacheable-request": "^7.0.1", 159 | "decompress-response": "^6.0.0", 160 | "http2-wrapper": "^1.0.0-beta.5.2", 161 | "lowercase-keys": "^2.0.0", 162 | "p-cancelable": "^2.0.0", 163 | "responselike": "^2.0.0" 164 | } 165 | }, 166 | "http-cache-semantics": { 167 | "version": "4.1.0", 168 | "resolved": "https://registry.npm.taobao.org/http-cache-semantics/download/http-cache-semantics-4.1.0.tgz", 169 | "integrity": "sha1-SekcXL82yblLz81xwj1SSex045A=" 170 | }, 171 | "http2-wrapper": { 172 | "version": "1.0.3", 173 | "resolved": "https://registry.npm.taobao.org/http2-wrapper/download/http2-wrapper-1.0.3.tgz", 174 | "integrity": "sha1-uPVeDB8l1OvQizsMLAeflZCACz0=", 175 | "requires": { 176 | "quick-lru": "^5.1.1", 177 | "resolve-alpn": "^1.0.0" 178 | } 179 | }, 180 | "is-buffer": { 181 | "version": "1.1.6", 182 | "resolved": "https://registry.npm.taobao.org/is-buffer/download/is-buffer-1.1.6.tgz?cache=0&sync_timestamp=1604429452232&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fis-buffer%2Fdownload%2Fis-buffer-1.1.6.tgz", 183 | "integrity": "sha1-76ouqdqg16suoTqXsritUf776L4=" 184 | }, 185 | "json-buffer": { 186 | "version": "3.0.1", 187 | "resolved": "http://registry.npm.taobao.org/json-buffer/download/json-buffer-3.0.1.tgz", 188 | "integrity": "sha1-kziAKjDTtmBfvgYT4JQAjKjAWhM=" 189 | }, 190 | "keyv": { 191 | "version": "4.0.3", 192 | "resolved": "https://registry.npm.taobao.org/keyv/download/keyv-4.0.3.tgz?cache=0&sync_timestamp=1600337463601&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fkeyv%2Fdownload%2Fkeyv-4.0.3.tgz", 193 | "integrity": "sha1-TzqpjeJUgDyvzSiWc0EI2qNeQlQ=", 194 | "requires": { 195 | "json-buffer": "3.0.1" 196 | } 197 | }, 198 | "lowercase-keys": { 199 | "version": "2.0.0", 200 | "resolved": "https://registry.npm.taobao.org/lowercase-keys/download/lowercase-keys-2.0.0.tgz", 201 | "integrity": "sha1-JgPni3tLAAbLyi+8yKMgJVislHk=" 202 | }, 203 | "md5": { 204 | "version": "2.3.0", 205 | "resolved": "https://registry.npm.taobao.org/md5/download/md5-2.3.0.tgz", 206 | "integrity": "sha1-w9qaaq46MLRreww0m4exENw72k8=", 207 | "requires": { 208 | "charenc": "0.0.2", 209 | "crypt": "0.0.2", 210 | "is-buffer": "~1.1.6" 211 | } 212 | }, 213 | "mimic-response": { 214 | "version": "1.0.1", 215 | "resolved": "https://registry.npm.taobao.org/mimic-response/download/mimic-response-1.0.1.tgz", 216 | "integrity": "sha1-SSNTiHju9CBjy4o+OweYeBSHqxs=" 217 | }, 218 | "normalize-url": { 219 | "version": "4.5.0", 220 | "resolved": "https://registry.npm.taobao.org/normalize-url/download/normalize-url-4.5.0.tgz", 221 | "integrity": "sha1-RTNUCH5sqWlXvY9br3U/WYIUISk=" 222 | }, 223 | "once": { 224 | "version": "1.4.0", 225 | "resolved": "http://registry.npm.taobao.org/once/download/once-1.4.0.tgz", 226 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 227 | "requires": { 228 | "wrappy": "1" 229 | } 230 | }, 231 | "p-cancelable": { 232 | "version": "2.1.0", 233 | "resolved": "https://registry.npm.taobao.org/p-cancelable/download/p-cancelable-2.1.0.tgz", 234 | "integrity": "sha1-TVHDuR9IPQKg0wB2UyH8o5PXWN0=" 235 | }, 236 | "psl": { 237 | "version": "1.8.0", 238 | "resolved": "https://registry.npm.taobao.org/psl/download/psl-1.8.0.tgz", 239 | "integrity": "sha1-kyb4vPsBOtzABf3/BWrM4CDlHCQ=" 240 | }, 241 | "pump": { 242 | "version": "3.0.0", 243 | "resolved": "http://registry.npm.taobao.org/pump/download/pump-3.0.0.tgz", 244 | "integrity": "sha1-tKIRaBW94vTh6mAjVOjHVWUQemQ=", 245 | "requires": { 246 | "end-of-stream": "^1.1.0", 247 | "once": "^1.3.1" 248 | } 249 | }, 250 | "punycode": { 251 | "version": "2.1.1", 252 | "resolved": "http://registry.npm.taobao.org/punycode/download/punycode-2.1.1.tgz", 253 | "integrity": "sha1-tYsBCsQMIsVldhbI0sLALHv0eew=" 254 | }, 255 | "quick-lru": { 256 | "version": "5.1.1", 257 | "resolved": "https://registry.npm.taobao.org/quick-lru/download/quick-lru-5.1.1.tgz?cache=0&sync_timestamp=1610610364837&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fquick-lru%2Fdownload%2Fquick-lru-5.1.1.tgz", 258 | "integrity": "sha1-NmST5rPkKjpoheLpnRj4D7eoyTI=" 259 | }, 260 | "resolve-alpn": { 261 | "version": "1.1.2", 262 | "resolved": "https://registry.npm.taobao.org/resolve-alpn/download/resolve-alpn-1.1.2.tgz?cache=0&sync_timestamp=1618513012147&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fresolve-alpn%2Fdownload%2Fresolve-alpn-1.1.2.tgz", 263 | "integrity": "sha1-MLYM+7DAuNyJeUD+E/4lWvzdTSg=" 264 | }, 265 | "responselike": { 266 | "version": "2.0.0", 267 | "resolved": "https://registry.npm.taobao.org/responselike/download/responselike-2.0.0.tgz", 268 | "integrity": "sha1-JjkbzDF091D5p56sxAoSpcQtdyM=", 269 | "requires": { 270 | "lowercase-keys": "^2.0.0" 271 | } 272 | }, 273 | "tough-cookie": { 274 | "version": "4.0.0", 275 | "resolved": "https://registry.npm.taobao.org/tough-cookie/download/tough-cookie-4.0.0.tgz?cache=0&sync_timestamp=1589682815640&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Ftough-cookie%2Fdownload%2Ftough-cookie-4.0.0.tgz", 276 | "integrity": "sha1-2CIjTuyogvmR8PkIgkrSYi3b7OQ=", 277 | "requires": { 278 | "psl": "^1.1.33", 279 | "punycode": "^2.1.1", 280 | "universalify": "^0.1.2" 281 | } 282 | }, 283 | "universalify": { 284 | "version": "0.1.2", 285 | "resolved": "https://registry.npm.taobao.org/universalify/download/universalify-0.1.2.tgz?cache=0&sync_timestamp=1603179967633&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Funiversalify%2Fdownload%2Funiversalify-0.1.2.tgz", 286 | "integrity": "sha1-tkb2m+OULavOzJ1mOcgNwQXvqmY=" 287 | }, 288 | "wrappy": { 289 | "version": "1.0.2", 290 | "resolved": "https://registry.nlark.com/wrappy/download/wrappy-1.0.2.tgz?cache=0&sync_timestamp=1619133505879&other_urls=https%3A%2F%2Fregistry.nlark.com%2Fwrappy%2Fdownload%2Fwrappy-1.0.2.tgz", 291 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quantumultx", 3 | "version": "1.0.0", 4 | "description": "自用脚本、配置", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/id77/QuantumultX.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/id77/QuantumultX/issues" 17 | }, 18 | "homepage": "https://github.com/id77/QuantumultX#readme", 19 | "dependencies": { 20 | "@types/crypto-js": "^4.0.1", 21 | "@types/md5": "^2.3.0", 22 | "crypto-js": "^4.0.0", 23 | "got": "^11.8.2", 24 | "md5": "^2.3.0", 25 | "tough-cookie": "^4.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /popup/index.html: -------------------------------------------------------------------------------- 1 | EasyClicker
-------------------------------------------------------------------------------- /popup/static/css/main.e6cd4111.css: -------------------------------------------------------------------------------- 1 | body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetica Neue",sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;--light-background-color:#fff;--dark-background-color:#1f1f1f;--main-background-color:var(--light-background-color);background-color:var(--main-background-color)!important}@media (prefers-color-scheme:dark){body{--main-background-color:var(--dark-background-color)}}body,html{width:100%;height:100%}code{font-family:source-code-pro,Menlo,Monaco,Consolas,"Courier New",monospace}*{-webkit-tap-highlight-color:rgba(0,0,0,0)} -------------------------------------------------------------------------------- /rewrite/Youtube_CC.conf: -------------------------------------------------------------------------------- 1 | # QX Youtube 无中文字幕机翻方案 2 | 3 | hostname= www.youtube.com 4 | 5 | // 简体;订阅链接后面加上 #out=Hant 后开启资源解析器 6 | // https://raw.githubusercontent.com/id77/QuantumultX/master/rewrite/Youtube_CC.conf#out=Hant 7 | 8 | https:\/\/www.youtube.com\/api\/timedtext\?.+&lang=(?!(zh|zh\-Hans)&)((?!&tlang=zh\-Hans).)*$ url request-header \sHTTP/1\.1(\r\n) request-header &tlang=zh-Hans HTTP/1.1$1 9 | 10 | 11 | // 繁体;订阅链接后面加上 #out=Hans 后开启资源解析器 12 | // https://raw.githubusercontent.com/id77/QuantumultX/master/rewrite/Youtube_CC.conf#out=Hans 13 | 14 | https:\/\/www.youtube.com\/api\/timedtext\?.+&lang=(?!zh\-Hant&)((?!&tlang=zh\-Hant).)*$ url request-header \sHTTP/1\.1(\r\n) request-header &tlang=zh-Hant HTTP/1.1$1 -------------------------------------------------------------------------------- /rewrite/ad.conf: -------------------------------------------------------------------------------- 1 | hostname= www.zhihu.com, zhuanlan.zhihu.com 2 | 3 | # 知乎网页去广告&推荐列表 4 | https://www\.zhihu\.com/api/v4/questions/\d+/related-readings url reject-200 5 | https://www\.zhihu\.com/api/v4/answers/\d+/related-readings url reject-200 6 | https://www\.zhihu\.com/api/v4/hot_recommendation url reject-200 7 | https://www\.zhihu\.com/commercial_api/banners_v3/mobile_banner url reject-200 8 | https://zhuanlan\.zhihu\.com/api/articles/\d+/recommendation url reject-200 9 | -------------------------------------------------------------------------------- /rewrite/cookie.conf: -------------------------------------------------------------------------------- 1 | hostname= *.youzan.com, api.m.jd.com, wq.jd.com, me-api.jd.com, *.video.qq.com, mall.oclean.com, h5.youzan.com, app*.jegotrip.com.cn, task.jegotrip.com.cn, *.easemob.com, club.yili.com, *.huami.com 2 | 3 | # 伊利乳品 4 | ^https:\/\/club\.yili\.com\/MALLIFChe\/MCSWSIAPI\.asmx\/Call url script-request-body https://raw.githubusercontent.com/id77/QuantumultX/master/task/yiLi.cookie.js 5 | 6 | 7 | # 无忧行 8 | https?:\/\/app.*\.jegotrip\.com\.cn\/.*getUser url script-response-body https://raw.githubusercontent.com/id77/QuantumultX/master/task/jegotrip.cookie.js 9 | https?:\/\/task\.jegotrip\.com\.cn\:8080\/app\/tasks\?userid url script-response-body https://raw.githubusercontent.com/id77/QuantumultX/master/task/jegotrip.cookie.js 10 | https:\/\/.+\.easemob\.com(\:443)?\/\d+\/jegotrip\/users\/ url script-response-body https://raw.githubusercontent.com/id77/QuantumultX/master/task/jegotrip.cookie.js 11 | # iklear 12 | ^https:\/\/shop42867343\.youzan\.com\/wscump\/checkin\/checkin\.json url script-request-header https://raw.githubusercontent.com/LJJJia/script/master/iklear/iklear_cookie.js 13 | ^https:\/\/h5\.youzan\.com\/wscump\/checkin\/checkin\.json\?.*?app_id=wx2a1c9c6949b2fe4e url script-request-header https://raw.githubusercontent.com/LJJJia/script/master/iklear/iklear_cookie.js 14 | # 京东 Get cookie 15 | # https://bean.m.jd.com/ 16 | https:\/\/api\.m\.jd\.com\/client\.action.*functionId=signBean url script-request-header https://raw.githubusercontent.com/NobyDa/Script/master/JD-DailyBonus/JD_DailyBonus.js 17 | # 京东多账号 cookie 18 | https:\/\/(me\-api|wq)\.jd\.com\/user_new\/info\/GetJDUserInfoUnion url script-request-header https://raw.githubusercontent.com/dompling/Script/master/jd/JD_extra_cookie.js 19 | # 腾讯视频签到,获取cookie成功后注释 20 | ^https:\/\/access.video.qq.com\/user\/auth_refresh url script-request-header https://raw.githubusercontent.com/chavyleung/scripts/master/videoqq/videoqq.cookie.js 21 | # 欧克林小程序 22 | ^https:\/\/mall\.oclean\.com\/API\/VshopProcess\.ashx\?action=TaskHome&clientType=5&client=5&openId= url script-request-header https://raw.githubusercontent.com/zZPiglet/Task/master/Oclean/Oclean_mini.js 23 | # 小米运动获取Token 24 | ^https:\/\/account(\-cn\d)?\.huami\.com\/v2\/client\/login url script-response-body https://raw.githubusercontent.com/id77/QuantumultX/master/task/xmSports.js -------------------------------------------------------------------------------- /rewrite/id77_JDCookie.conf: -------------------------------------------------------------------------------- 1 | hostname= me-api.jd.com 2 | 3 | https:\/\/me\-api\.jd\.com\/user_new\/info\/GetJDUserInfoUnion url script-echo-response https://raw.githubusercontent.com/id77/QuantumultX/master/Script/updateJDCookie.js -------------------------------------------------------------------------------- /rewrite/json1.json: -------------------------------------------------------------------------------- 1 | { 2 | "ActiveTask": { "dwIsCallActTask": 1 }, 3 | "Business": { "ddwBusinessTm": 1626192000, "ddwCoin": 0, "dwBussDayNum": 3 }, 4 | "Fund": { 5 | "ddwFundTargTm": 1626145817, 6 | "dwIsGetGift": 1, 7 | "dwIsShowFund": 0, 8 | "strGiftName": "100.00" 9 | }, 10 | "JxUserWelfare": { 11 | "dwCouponType": 0, 12 | "dwIsDisplayIcon": 0, 13 | "dwIsJxNewUser": 0, 14 | "dwIsJxNewer": 0, 15 | "dwIsPop": 0, 16 | "dwIsPrizeStatus": 0, 17 | "strCouponName": "", 18 | "strCouponUseLinkH5": "", 19 | "strCouponUseLinkXcx": "", 20 | "strIconImg": "", 21 | "strPrice": "" 22 | }, 23 | "LeadInfo": { "dwEggProg": 0, "dwLeadType": 0 }, 24 | "MarkList": { 25 | "build_food_full": "", 26 | "build_fun_full": "", 27 | "build_sea_full": "", 28 | "build_shop_full": "", 29 | "collect_coin_auth": "", 30 | "guide_guider_show": "true", 31 | "guide_receive_vistor": "", 32 | "guider_medal": "1", 33 | "guider_over_flag": "999", 34 | "guider_step": "none", 35 | "medal_guider_show": "true" 36 | }, 37 | "StoryInfo": { "Mermaid": null }, 38 | "buildInfo": { 39 | "buildList": [ 40 | { 41 | "PeoplePos": [3, 10, 17], 42 | "ddwEmployTourGuideTm": 0, 43 | "ddwOneceVistorAddCoin": 110, 44 | "ddwSpeedEndTm": 0, 45 | "dwBuildStyle": 5, 46 | "dwCanLvlUp": 0, 47 | "dwCoinCntDisplay": 20, 48 | "dwContain": 11, 49 | "dwHaveTourGuide": 0, 50 | "dwIntervalTm": 7, 51 | "dwIsSpeedIng": 0, 52 | "dwLvl": 11, 53 | "dwOnLoadPeople": 2, 54 | "dwRoomPeople": 1, 55 | "dwTourGuideOffWorkTm": 0, 56 | "strBuildIndex": "food" 57 | }, 58 | { 59 | "PeoplePos": [3, 11, 19], 60 | "ddwEmployTourGuideTm": 1626247085, 61 | "ddwOneceVistorAddCoin": 1200, 62 | "ddwSpeedEndTm": 0, 63 | "dwBuildStyle": 4, 64 | "dwCanLvlUp": 0, 65 | "dwCoinCntDisplay": 20, 66 | "dwContain": 10, 67 | "dwHaveTourGuide": 1, 68 | "dwIntervalTm": 8, 69 | "dwIsSpeedIng": 0, 70 | "dwLvl": 10, 71 | "dwOnLoadPeople": 1, 72 | "dwRoomPeople": 2, 73 | "dwTourGuideOffWorkTm": 1626250685, 74 | "strBuildIndex": "sea" 75 | }, 76 | { 77 | "PeoplePos": [3, 10, 17], 78 | "ddwEmployTourGuideTm": 1626247089, 79 | "ddwOneceVistorAddCoin": 1331, 80 | "ddwSpeedEndTm": 0, 81 | "dwBuildStyle": 5, 82 | "dwCanLvlUp": 0, 83 | "dwCoinCntDisplay": 20, 84 | "dwContain": 11, 85 | "dwHaveTourGuide": 1, 86 | "dwIntervalTm": 7, 87 | "dwIsSpeedIng": 0, 88 | "dwLvl": 11, 89 | "dwOnLoadPeople": 2, 90 | "dwRoomPeople": 1, 91 | "dwTourGuideOffWorkTm": 1626250689, 92 | "strBuildIndex": "shop" 93 | }, 94 | { 95 | "PeoplePos": [3, 10, 17], 96 | "ddwEmployTourGuideTm": 1626247092, 97 | "ddwOneceVistorAddCoin": 1089, 98 | "ddwSpeedEndTm": 0, 99 | "dwBuildStyle": 5, 100 | "dwCanLvlUp": 0, 101 | "dwCoinCntDisplay": 20, 102 | "dwContain": 11, 103 | "dwHaveTourGuide": 1, 104 | "dwIntervalTm": 7, 105 | "dwIsSpeedIng": 0, 106 | "dwLvl": 11, 107 | "dwOnLoadPeople": 2, 108 | "dwRoomPeople": 1, 109 | "dwTourGuideOffWorkTm": 1626250692, 110 | "strBuildIndex": "fun" 111 | } 112 | ], 113 | "ddwTourGuideEarnCoin": 0, 114 | "dwOnWayTm": 10, 115 | "dwRoomStayTm": 10, 116 | "dwTodaySpeedPeople": 553, 117 | "dwTourGuideBackTm": 5, 118 | "dwTourGuideComTm": 15, 119 | "strGuidePopName": "", 120 | "strGuidePopPic": "" 121 | }, 122 | "ddwCoinBalance": 87370, 123 | "ddwCurTime": 1626248395, 124 | "ddwRichBalance": 112739, 125 | "dwIsAllNew": 0, 126 | "dwIsDefaultPin": 0, 127 | "dwLandLvl": 2, 128 | "dwOfficeStyle": 2, 129 | "dwOfficeUnLock": 1, 130 | "dwTaskQuotiety": 21, 131 | "iRet": 0, 132 | "sErrMsg": "", 133 | "strHeadPic": "https://img13.360buyimg.com/img/s180x180_jfs/t1/183211/1/8546/5159/60c2be86E53c873a0/eb934529b1e26cd3.png", 134 | "strMyShareId": "AC914818DB8397FBC28C4078EF66AB97A9B13178467464642B5326A1F7E5DD90", 135 | "strNickName": "d2RoKip2Q2Y=" 136 | } 137 | -------------------------------------------------------------------------------- /rewrite/json2.json: -------------------------------------------------------------------------------- 1 | { 2 | "ActiveTask": { "dwIsCallActTask": 1 }, 3 | "Business": { "ddwBusinessTm": 1626192000, "ddwCoin": 0, "dwBussDayNum": 3 }, 4 | "Fund": { 5 | "ddwFundTargTm": 1626145817, 6 | "dwIsGetGift": 1, 7 | "dwIsShowFund": 0, 8 | "strGiftName": "100.00" 9 | }, 10 | "JxUserWelfare": { 11 | "dwCouponType": 0, 12 | "dwIsDisplayIcon": 0, 13 | "dwIsJxNewUser": 0, 14 | "dwIsJxNewer": 0, 15 | "dwIsPop": 0, 16 | "dwIsPrizeStatus": 0, 17 | "strCouponName": "", 18 | "strCouponUseLinkH5": "", 19 | "strCouponUseLinkXcx": "", 20 | "strIconImg": "", 21 | "strPrice": "" 22 | }, 23 | "LeadInfo": { "dwEggProg": 0, "dwLeadType": 0 }, 24 | "MarkList": { 25 | "build_food_full": "", 26 | "build_fun_full": "", 27 | "build_sea_full": "", 28 | "build_shop_full": "", 29 | "collect_coin_auth": "", 30 | "guide_guider_show": "true", 31 | "guide_receive_vistor": "", 32 | "guider_medal": "1", 33 | "guider_over_flag": "999", 34 | "guider_step": "none", 35 | "medal_guider_show": "true" 36 | }, 37 | "StoryInfo": { "Mermaid": null }, 38 | "buildInfo": { 39 | "buildList": [ 40 | { 41 | "PeoplePos": [3, 10, 17], 42 | "ddwEmployTourGuideTm": 0, 43 | "ddwOneceVistorAddCoin": 110, 44 | "ddwSpeedEndTm": 0, 45 | "dwBuildStyle": 5, 46 | "dwCanLvlUp": 0, 47 | "dwCoinCntDisplay": 20, 48 | "dwContain": 11, 49 | "dwHaveTourGuide": 0, 50 | "dwIntervalTm": 7, 51 | "dwIsSpeedIng": 0, 52 | "dwLvl": 11, 53 | "dwOnLoadPeople": 2, 54 | "dwRoomPeople": 1, 55 | "dwTourGuideOffWorkTm": 0, 56 | "strBuildIndex": "food" 57 | }, 58 | { 59 | "PeoplePos": [3, 11, 19], 60 | "ddwEmployTourGuideTm": 1626247085, 61 | "ddwOneceVistorAddCoin": 1200, 62 | "ddwSpeedEndTm": 0, 63 | "dwBuildStyle": 4, 64 | "dwCanLvlUp": 0, 65 | "dwCoinCntDisplay": 20, 66 | "dwContain": 10, 67 | "dwHaveTourGuide": 1, 68 | "dwIntervalTm": 8, 69 | "dwIsSpeedIng": 0, 70 | "dwLvl": 10, 71 | "dwOnLoadPeople": 1, 72 | "dwRoomPeople": 2, 73 | "dwTourGuideOffWorkTm": 1626250685, 74 | "strBuildIndex": "sea" 75 | }, 76 | { 77 | "PeoplePos": [3, 10, 17], 78 | "ddwEmployTourGuideTm": 1626247089, 79 | "ddwOneceVistorAddCoin": 1331, 80 | "ddwSpeedEndTm": 0, 81 | "dwBuildStyle": 5, 82 | "dwCanLvlUp": 0, 83 | "dwCoinCntDisplay": 20, 84 | "dwContain": 11, 85 | "dwHaveTourGuide": 1, 86 | "dwIntervalTm": 7, 87 | "dwIsSpeedIng": 0, 88 | "dwLvl": 11, 89 | "dwOnLoadPeople": 2, 90 | "dwRoomPeople": 1, 91 | "dwTourGuideOffWorkTm": 1626250689, 92 | "strBuildIndex": "shop" 93 | }, 94 | { 95 | "PeoplePos": [3, 10, 17], 96 | "ddwEmployTourGuideTm": 1626247092, 97 | "ddwOneceVistorAddCoin": 1089, 98 | "ddwSpeedEndTm": 0, 99 | "dwBuildStyle": 5, 100 | "dwCanLvlUp": 0, 101 | "dwCoinCntDisplay": 20, 102 | "dwContain": 11, 103 | "dwHaveTourGuide": 1, 104 | "dwIntervalTm": 7, 105 | "dwIsSpeedIng": 0, 106 | "dwLvl": 11, 107 | "dwOnLoadPeople": 2, 108 | "dwRoomPeople": 1, 109 | "dwTourGuideOffWorkTm": 1626250692, 110 | "strBuildIndex": "fun" 111 | } 112 | ], 113 | "ddwTourGuideEarnCoin": 0, 114 | "dwOnWayTm": 10, 115 | "dwRoomStayTm": 10, 116 | "dwTodaySpeedPeople": 553, 117 | "dwTourGuideBackTm": 5, 118 | "dwTourGuideComTm": 15, 119 | "strGuidePopName": "", 120 | "strGuidePopPic": "" 121 | }, 122 | "ddwCoinBalance": 87370, 123 | "ddwCurTime": 1626248395, 124 | "ddwRichBalance": 112739, 125 | "dwIsAllNew": 0, 126 | "dwIsDefaultPin": 0, 127 | "dwLandLvl": 2, 128 | "dwOfficeStyle": 2, 129 | "dwOfficeUnLock": 1, 130 | "dwTaskQuotiety": 21, 131 | "iRet": 0, 132 | "sErrMsg": "", 133 | "strHeadPic": "https://img13.360buyimg.com/img/s180x180_jfs/t1/183211/1/8546/5159/60c2be86E53c873a0/eb934529b1e26cd3.png", 134 | "strMyShareId": "AC914818DB8397FBC28C4078EF66AB97A9B13178467464642B5326A1F7E5DD90", 135 | "strNickName": "d2RoKip2Q2Y=" 136 | } 137 | -------------------------------------------------------------------------------- /rewrite/other.conf: -------------------------------------------------------------------------------- 1 | hostname= www.google.*, m.weibo.cn, www.zhihu.com, zhuanlan.zhihu.com, trade-acs.m.taobao.com, api.m.jd.com, xeq1kjnhr.m.jd.com, draw.jdfcloud.com, *.m.jd.com, testflight.apple.com, 2 | 3 | # TF 国区下载修正 4 | ^https?:\/\/testflight\.apple\.com\/v2\/accounts\/.*\/apps\/\d*\/builds\/\d*\/install url request-body storefrontId" : ".*", request-body storefrontId" : "143441-1,29", 5 | # cn => com 6 | https?://www.google.cn url 302 https://www.google.com 7 | # google 翻页 (senku) 8 | https?:\/\/www\.google\.([a-z.]*)\/search.*(? 拦截100解锁超级会员 6 | ^https?:\/\/tagit\.hyhuo\.com\/cypt\/block100\/get_vip_info$ url script-response-body https://raw.githubusercontent.com/yqc007/QuantumultX/master/Block100SVIPCrack.js 7 | 8 | # 扫描全能王 9 | ^https:\/\/(api|api-cs)\.intsig\.net\/purchase\/cs\/query_property\? url script-response-body https://raw.githubusercontent.com/id77/QuantumultX/master/Script/camscanner.js 10 | # workingcopy 11 | ^https:\/\/education\.github\.com\/api\/user$ url response-body "student":false response-body "student":true 12 | # 韩剧TV 13 | ^http:\/\/api\.hanju\.koudaibaobao\.com\/api\/series\/rslvV4 url response-body "quality":\d response-body "quality":10 14 | # 白描 15 | ^https:\/\/baimiao\.uzero\.cn\/api\/v2\.user\/(logout|appLaunchWithUser|loginByWeixin) url script-response-body https://raw.githubusercontent.com/id77/QuantumultX/master/Script/baimiao.js 16 | 17 | 18 | # 内购解锁 19 | # 需要ios13 复制放本地 申请试用-》恢复购买-〉取消订阅;禁用主机名 20 | # 通用解锁 试用取消 主机名buy.itunes.apple.com 21 | ^https:\/\/buy\.itunes\.apple\.com\/verifyReceipt$ url script-response-body https://raw.githubusercontent.com/langkhach270389/Quantumult-X-LK/master/Scripts/langkhach/verify_receipt.js 22 | # drops 23 | ^https:\/\/lambda\.us-east-1\.amazonaws\.com/.*/functions\/prod-4-syncPurchases\/invocations$ url script-response-body https://raw.githubusercontent.com/id77/QuantumultX/master/Script/drops.js -------------------------------------------------------------------------------- /rule/Email.list: -------------------------------------------------------------------------------- 1 | host-suffix, imap.gmail.com, 📮 邮箱 -------------------------------------------------------------------------------- /task/ex.cookie.js: -------------------------------------------------------------------------------- 1 | /** 2 | 通用重放脚本 3 | 4 | # Surge 5 | Rewrite: 重放抢兑 = type=http-request,pattern=URL_REGEX,script-path=https://raw.githubusercontent.com/id77/QuantumultX/master/task/ex.cookie.js 6 | # QuanX 7 | URL_REGEX url script-request-header https://raw.githubusercontent.com/id77/QuantumultX/master/task/ex.cookie.js 8 | # Loon 9 | http-request URL_REGEX script-path=https://raw.githubusercontent.com/id77/QuantumultX/master/task/ex.cookie.js 10 | 11 | 12 | tasks: 13 | https://raw.githubusercontent.com/id77/QuantumultX/master/task/ex.task.js 14 | 配合 boxjs 管理 15 | */ 16 | const $ = new Env('通用重放脚本'); 17 | $.EX_REQUEST = 'id77_ex_request'; 18 | 19 | !(async () => { 20 | const { headers, url, body, method } = $request; 21 | const session = {}; 22 | session.url = url; 23 | session.headers = headers; 24 | session.body = body; 25 | session.method = method; 26 | 27 | let key = $.EX_REQUEST; 28 | 29 | if ($.setData(JSON.stringify(session, null, 2), key)) { 30 | $.subt = `获取会话: 成功!`; 31 | } else { 32 | $.subt = `获取会话: 失败!`; 33 | } 34 | $.msg($.name, $.subt); 35 | })() 36 | .catch((e) => $.logErr(e)) 37 | .finally(() => $.done()); 38 | 39 | // https://github.com/chavyleung/scripts/blob/master/Env.js 40 | // prettier-ignore 41 | 42 | // https://github.com/chavyleung/scripts/blob/master/Env.js 43 | // prettier-ignore 44 | function Env(name, opts) { 45 | class Http { 46 | constructor(env) { 47 | this.env = env; 48 | } 49 | 50 | send(opts, method = 'GET') { 51 | opts = typeof opts === 'string' ? { url: opts } : opts; 52 | let sender = this.get; 53 | if (method === 'POST') { 54 | sender = this.post; 55 | } 56 | return new Promise((resolve, reject) => { 57 | sender.call(this, opts, (err, resp, body) => { 58 | if (err) reject(err); 59 | else resolve(resp); 60 | }); 61 | }); 62 | } 63 | 64 | get(opts) { 65 | return this.send.call(this.env, opts); 66 | } 67 | 68 | post(opts) { 69 | return this.send.call(this.env, opts, 'POST'); 70 | } 71 | } 72 | 73 | return new (class { 74 | constructor(name, opts = {}) { 75 | this.name = name; 76 | this.http = new Http(this); 77 | this.data = null; 78 | this.dataFile = 'box.dat'; 79 | this.logs = []; 80 | this.isMute = false; 81 | this.noLogKey = opts.noLogKey || ''; 82 | this.noLog = opts.noLog; 83 | this.isNeedRewrite = false; 84 | this.logSeparator = '\n'; 85 | this.startTime = new Date().getTime(); 86 | Object.assign(this, opts); 87 | this.log('', `🔔${this.name}, 开始!`); 88 | } 89 | 90 | isNode() { 91 | return 'undefined' !== typeof module && !!module.exports; 92 | } 93 | 94 | isQuanX() { 95 | return 'undefined' !== typeof $task; 96 | } 97 | 98 | isSurge() { 99 | return 'undefined' !== typeof $httpClient && 'undefined' === typeof $loon; 100 | } 101 | 102 | isLoon() { 103 | return 'undefined' !== typeof $loon; 104 | } 105 | 106 | isShadowrocket() { 107 | return 'undefined' !== typeof $rocket; 108 | } 109 | 110 | toObj(str, defaultValue = null) { 111 | try { 112 | return JSON.parse(str); 113 | } catch { 114 | return defaultValue; 115 | } 116 | } 117 | 118 | toStr(obj, defaultValue = null) { 119 | try { 120 | return JSON.stringify(obj); 121 | } catch { 122 | return defaultValue; 123 | } 124 | } 125 | 126 | getJson(key, defaultValue) { 127 | let json = defaultValue; 128 | const val = this.getData(key); 129 | if (val) { 130 | try { 131 | json = JSON.parse(this.getData(key)); 132 | } catch {} 133 | } 134 | return json; 135 | } 136 | 137 | setJson(val, key) { 138 | try { 139 | return this.setData(JSON.stringify(val), key); 140 | } catch { 141 | return false; 142 | } 143 | } 144 | 145 | getScript(url) { 146 | return new Promise((resolve) => { 147 | this.get({ url }, (err, resp, body) => resolve(body)); 148 | }); 149 | } 150 | 151 | runScript(script, runOpts) { 152 | return new Promise((resolve) => { 153 | let httpApi = this.getData('@chavy_boxjs_userCfgs.httpApi'); 154 | httpApi = httpApi ? httpApi.replace(/\n/g, '').trim() : httpApi; 155 | let httpApi_timeout = this.getData( 156 | '@chavy_boxjs_userCfgs.httpApi_timeout' 157 | ); 158 | httpApi_timeout = httpApi_timeout ? httpApi_timeout * 1 : 20; 159 | httpApi_timeout = 160 | runOpts && runOpts.timeout ? runOpts.timeout : httpApi_timeout; 161 | const [key, addr] = httpApi.split('@'); 162 | const opts = { 163 | url: `http://${addr}/v1/scripting/evaluate`, 164 | body: { 165 | script_text: script, 166 | mock_type: 'cron', 167 | timeout: httpApi_timeout, 168 | }, 169 | headers: { 'X-Key': key, Accept: '*/*' }, 170 | }; 171 | this.post(opts, (err, resp, body) => resolve(body)); 172 | }).catch((e) => this.logErr(e)); 173 | } 174 | 175 | loadData() { 176 | if (this.isNode()) { 177 | this.fs = this.fs ? this.fs : require('fs'); 178 | this.path = this.path ? this.path : require('path'); 179 | const curDirDataFilePath = this.path.resolve(this.dataFile); 180 | const rootDirDataFilePath = this.path.resolve( 181 | process.cwd(), 182 | this.dataFile 183 | ); 184 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 185 | const isRootDirDataFile = 186 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 187 | if (isCurDirDataFile || isRootDirDataFile) { 188 | const datPath = isCurDirDataFile 189 | ? curDirDataFilePath 190 | : rootDirDataFilePath; 191 | try { 192 | return JSON.parse(this.fs.readFileSync(datPath)); 193 | } catch (e) { 194 | return {}; 195 | } 196 | } else return {}; 197 | } else return {}; 198 | } 199 | 200 | writeData() { 201 | if (this.isNode()) { 202 | this.fs = this.fs ? this.fs : require('fs'); 203 | this.path = this.path ? this.path : require('path'); 204 | const curDirDataFilePath = this.path.resolve(this.dataFile); 205 | const rootDirDataFilePath = this.path.resolve( 206 | process.cwd(), 207 | this.dataFile 208 | ); 209 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 210 | const isRootDirDataFile = 211 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 212 | const jsonData = JSON.stringify(this.data); 213 | if (isCurDirDataFile) { 214 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 215 | } else if (isRootDirDataFile) { 216 | this.fs.writeFileSync(rootDirDataFilePath, jsonData); 217 | } else { 218 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 219 | } 220 | } 221 | } 222 | 223 | lodash_get(source, path, defaultValue = undefined) { 224 | const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.'); 225 | let result = source; 226 | for (const p of paths) { 227 | result = Object(result)[p]; 228 | if (result === undefined) { 229 | return defaultValue; 230 | } 231 | } 232 | return result; 233 | } 234 | 235 | lodash_set(obj, path, value) { 236 | if (Object(obj) !== obj) return obj; 237 | if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || []; 238 | path 239 | .slice(0, -1) 240 | .reduce( 241 | (a, c, i) => 242 | Object(a[c]) === a[c] 243 | ? a[c] 244 | : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {}), 245 | obj 246 | )[path[path.length - 1]] = value; 247 | return obj; 248 | } 249 | 250 | getData(key) { 251 | let val = this.getVal(key); 252 | // 如果以 @ 253 | if (/^@/.test(key)) { 254 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 255 | const objVal = objKey ? this.getVal(objKey) : ''; 256 | if (objVal) { 257 | try { 258 | const objedVal = JSON.parse(objVal); 259 | val = objedVal ? this.lodash_get(objedVal, paths, '') : val; 260 | } catch (e) { 261 | val = ''; 262 | } 263 | } 264 | } 265 | return val; 266 | } 267 | 268 | setData(val, key) { 269 | let isSuc = false; 270 | if (/^@/.test(key)) { 271 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 272 | const objdat = this.getVal(objKey); 273 | const objVal = objKey 274 | ? objdat === 'null' 275 | ? null 276 | : objdat || '{}' 277 | : '{}'; 278 | try { 279 | const objedVal = JSON.parse(objVal); 280 | this.lodash_set(objedVal, paths, val); 281 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 282 | } catch (e) { 283 | const objedVal = {}; 284 | this.lodash_set(objedVal, paths, val); 285 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 286 | } 287 | } else { 288 | isSuc = this.setVal(val, key); 289 | } 290 | return isSuc; 291 | } 292 | 293 | getVal(key) { 294 | if (this.isSurge() || this.isLoon()) { 295 | return $persistentStore.read(key); 296 | } else if (this.isQuanX()) { 297 | return $prefs.valueForKey(key); 298 | } else if (this.isNode()) { 299 | this.data = this.loadData(); 300 | return this.data[key]; 301 | } else { 302 | return (this.data && this.data[key]) || null; 303 | } 304 | } 305 | 306 | setVal(val, key) { 307 | if (this.isSurge() || this.isLoon()) { 308 | return $persistentStore.write(val, key); 309 | } else if (this.isQuanX()) { 310 | return $prefs.setValueForKey(val, key); 311 | } else if (this.isNode()) { 312 | this.data = this.loadData(); 313 | this.data[key] = val; 314 | this.writeData(); 315 | return true; 316 | } else { 317 | return (this.data && this.data[key]) || null; 318 | } 319 | } 320 | 321 | initGotEnv(opts) { 322 | this.got = this.got ? this.got : require('got'); 323 | this.ckTough = this.ckTough ? this.ckTough : require('tough-cookie'); 324 | this.ckJar = this.ckJar ? this.ckJar : new this.ckTough.CookieJar(); 325 | if (opts) { 326 | opts.headers = opts.headers ? opts.headers : {}; 327 | if (undefined === opts.headers.Cookie && undefined === opts.cookieJar) { 328 | opts.cookieJar = this.ckJar; 329 | } 330 | } 331 | } 332 | 333 | get(opts, callback = () => {}) { 334 | if (opts.headers) { 335 | delete opts.headers['Content-Type']; 336 | delete opts.headers['Content-Length']; 337 | delete opts.headers['Host']; 338 | } 339 | if (this.isSurge() || this.isLoon()) { 340 | if (this.isSurge() && this.isNeedRewrite) { 341 | opts.headers = opts.headers || {}; 342 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 343 | } 344 | $httpClient.get(opts, (err, resp, body) => { 345 | if (!err && resp) { 346 | resp.body = body; 347 | resp.statusCode = resp.status; 348 | } 349 | callback(err, resp, body); 350 | }); 351 | } else if (this.isQuanX()) { 352 | if (this.isNeedRewrite) { 353 | opts.opts = opts.opts || {}; 354 | Object.assign(opts.opts, { hints: false }); 355 | } 356 | $task.fetch(opts).then( 357 | (resp) => { 358 | const { statusCode: status, statusCode, headers, body } = resp; 359 | callback(null, { status, statusCode, headers, body }, body); 360 | }, 361 | (err) => callback(err) 362 | ); 363 | } else if (this.isNode()) { 364 | this.initGotEnv(opts); 365 | this.got(opts) 366 | .on('redirect', (resp, nextOpts) => { 367 | try { 368 | if (resp.headers['set-cookie']) { 369 | const ck = resp.headers['set-cookie'] 370 | .map(this.ckTough.Cookie.parse) 371 | .toString(); 372 | if (ck) { 373 | this.ckJar.setCookieSync(ck, null); 374 | } 375 | nextOpts.cookieJar = this.ckJar; 376 | } 377 | } catch (e) { 378 | this.logErr(e); 379 | } 380 | // this.ckJar.setCookieSync(resp.headers['set-cookie'].map(Cookie.parse).toString()) 381 | }) 382 | .then( 383 | (resp) => { 384 | const { statusCode: status, statusCode, headers, body } = resp; 385 | callback(null, { status, statusCode, headers, body }, body); 386 | }, 387 | (err) => { 388 | const { message: error, response: resp } = err; 389 | callback(error, resp, resp && resp.body); 390 | } 391 | ); 392 | } 393 | } 394 | 395 | post(opts, callback = () => {}) { 396 | const method = opts.method ? opts.method.toLocaleLowerCase() : 'post'; 397 | // 如果指定了请求体, 但没指定`Content-Type`, 则自动生成 398 | if (opts.body && opts.headers && !opts.headers['Content-Type']) { 399 | opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 400 | } 401 | if (opts.headers) { 402 | delete opts.headers['Host']; 403 | delete opts.headers['Content-Length']; 404 | }; 405 | if (this.isSurge() || this.isLoon()) { 406 | if (this.isSurge() && this.isNeedRewrite) { 407 | opts.headers = opts.headers || {}; 408 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 409 | } 410 | $httpClient[method](opts, (err, resp, body) => { 411 | if (!err && resp) { 412 | resp.body = body; 413 | resp.statusCode = resp.status; 414 | } 415 | callback(err, resp, body); 416 | }); 417 | } else if (this.isQuanX()) { 418 | opts.method = method; 419 | if (this.isNeedRewrite) { 420 | opts.opts = opts.opts || {}; 421 | Object.assign(opts.opts, { hints: false }); 422 | } 423 | $task.fetch(opts).then( 424 | (resp) => { 425 | const { statusCode: status, statusCode, headers, body } = resp; 426 | callback(null, { status, statusCode, headers, body }, body); 427 | }, 428 | (err) => callback(err) 429 | ); 430 | } else if (this.isNode()) { 431 | this.initGotEnv(opts); 432 | const { url, ..._opts } = opts; 433 | this.got[method](url, _opts).then( 434 | (resp) => { 435 | const { statusCode: status, statusCode, headers, body } = resp; 436 | callback(null, { status, statusCode, headers, body }, body); 437 | }, 438 | (err) => { 439 | const { message: error, response: resp } = err; 440 | callback(error, resp, resp && resp.body); 441 | } 442 | ); 443 | } 444 | } 445 | /** 446 | * 447 | * 示例:$.time('yyyy-MM-dd qq HH:mm:ss.S') 448 | * :$.time('yyyyMMddHHmmssS') 449 | * y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒 450 | * 其中y可选0-4位占位符、S可选0-1位占位符,其余可选0-2位占位符 451 | * @param {string} fmt 格式化参数 452 | * @param {number} 可选: 根据指定时间戳返回格式化日期 453 | * 454 | */ 455 | time(fmt, ts = null) { 456 | const date = ts ? new Date(ts) : new Date(); 457 | let o = { 458 | 'M+': date.getMonth() + 1, 459 | 'd+': date.getDate(), 460 | 'H+': date.getHours(), 461 | 'm+': date.getMinutes(), 462 | 's+': date.getSeconds(), 463 | 'q+': Math.floor((date.getMonth() + 3) / 3), 464 | S: date.getMilliseconds(), 465 | }; 466 | if (/(y+)/.test(fmt)) 467 | fmt = fmt.replace( 468 | RegExp.$1, 469 | (date.getFullYear() + '').substr(4 - RegExp.$1.length) 470 | ); 471 | for (let k in o) 472 | if (new RegExp('(' + k + ')').test(fmt)) 473 | fmt = fmt.replace( 474 | RegExp.$1, 475 | RegExp.$1.length == 1 476 | ? o[k] 477 | : ('00' + o[k]).substr(('' + o[k]).length) 478 | ); 479 | return fmt; 480 | } 481 | 482 | /** 483 | * 系统通知 484 | * 485 | * > 通知参数: 同时支持 QuanX 和 Loon 两种格式, EnvJs根据运行环境自动转换, Surge 环境不支持多媒体通知 486 | * 487 | * 示例: 488 | * $.msg(title, subt, desc, 'twitter://') 489 | * $.msg(title, subt, desc, { 'open-url': 'twitter://', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 490 | * $.msg(title, subt, desc, { 'open-url': 'https://bing.com', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 491 | * 492 | * @param {*} title 标题 493 | * @param {*} subt 副标题 494 | * @param {*} desc 通知详情 495 | * @param {*} opts 通知参数 496 | * 497 | */ 498 | msg(title = name, subt = '', desc = '', opts) { 499 | const toEnvOpts = (rawOpts) => { 500 | if (!rawOpts) return rawOpts; 501 | if (typeof rawOpts === 'string') { 502 | if (this.isLoon()) return rawOpts; 503 | else if (this.isQuanX()) return { 'open-url': rawOpts }; 504 | else if (this.isSurge()) return { url: rawOpts }; 505 | else return undefined; 506 | } else if (typeof rawOpts === 'object') { 507 | if (this.isLoon()) { 508 | let openUrl = rawOpts.openUrl || rawOpts.url || rawOpts['open-url']; 509 | let mediaUrl = rawOpts.mediaUrl || rawOpts['media-url']; 510 | return { openUrl, mediaUrl }; 511 | } else if (this.isQuanX()) { 512 | let openUrl = rawOpts['open-url'] || rawOpts.url || rawOpts.openUrl; 513 | let mediaUrl = rawOpts['media-url'] || rawOpts.mediaUrl; 514 | let updatePasteboard = 515 | rawOpts['update-pasteboard'] || rawOpts.updatePasteboard; 516 | return { 517 | 'open-url': openUrl, 518 | 'media-url': mediaUrl, 519 | 'update-pasteboard': updatePasteboard, 520 | }; 521 | } else if (this.isSurge()) { 522 | let openUrl = rawOpts.url || rawOpts.openUrl || rawOpts['open-url']; 523 | return { url: openUrl }; 524 | } 525 | } else { 526 | return undefined; 527 | } 528 | }; 529 | if (!this.isMute) { 530 | if (this.isSurge() || this.isLoon()) { 531 | $notification.post(title, subt, desc, toEnvOpts(opts)); 532 | } else if (this.isQuanX()) { 533 | $notify(title, subt, desc, toEnvOpts(opts)); 534 | } 535 | } 536 | if (!this.isMuteLog) { 537 | let logs = ['', '==============📣系统通知📣==============']; 538 | logs.push(title); 539 | subt ? logs.push(subt) : ''; 540 | desc ? logs.push(desc) : ''; 541 | console.log(logs.join('\n')); 542 | this.logs = this.logs.concat(logs); 543 | } 544 | } 545 | 546 | log(...logs) { 547 | if (this.noLog || (this.noLogKey && (this.getData(this.noLogKey) || 'N').toLocaleUpperCase() === 'Y')) { 548 | return; 549 | } 550 | if (logs.length > 0) { 551 | this.logs = [...this.logs, ...logs]; 552 | } 553 | console.log(logs.join(this.logSeparator)); 554 | } 555 | 556 | logErr(err, msg) { 557 | const isPrintSack = !this.isSurge() && !this.isQuanX() && !this.isLoon(); 558 | if (!isPrintSack) { 559 | this.log('', `❗️${this.name}, 错误!`, err); 560 | } else { 561 | this.log('', `❗️${this.name}, 错误!`, err.stack); 562 | } 563 | } 564 | 565 | wait(time) { 566 | return new Promise((resolve) => setTimeout(resolve, time)); 567 | } 568 | 569 | done(val = {}) { 570 | const endTime = new Date().getTime(); 571 | const costTime = (endTime - this.startTime) / 1000; 572 | this.log('', `🔔${this.name}, 结束! 🕛 ${costTime} 秒`); 573 | this.log(); 574 | if (this.isSurge() || this.isQuanX() || this.isLoon()) { 575 | $done(val); 576 | } 577 | } 578 | })(name, opts); 579 | } 580 | -------------------------------------------------------------------------------- /task/thg.cookie.js: -------------------------------------------------------------------------------- 1 | /** 2 | * THG - 小程序 3 | * 4 | * > 进入小程序点我的 5 | * 6 | * hostname = 抓主域名修改 7 | * 8 | * # Surge 9 | * Rewrite: THG = type=http-request,pattern=taieshop\/api\/CustomerFansInfo\/GetFansInfo,script-path=https://raw.githubusercontent.com/id77/QuantumultX/master/task/thg.cookie.js 10 | * Tasks: THG = type=cron,cronexp=10 0 * * *,script-path=https://raw.githubusercontent.com/id77/QuantumultX/master/task/thg.js,wake-system=true 11 | * 12 | * # QuanX 13 | * taieshop\/api\/CustomerFansInfo\/GetFansInfo url script-request-header https://raw.githubusercontent.com/id77/QuantumultX/master/task/thg.cookie.js 14 | * 10 0 * * * https://raw.githubusercontent.com/id77/QuantumultX/master/task/thg.js, tag=THG, img-url=https://raw.githubusercontent.com/id77/QuantumultX/master/icon/thg.png 15 | * 16 | * # Loon 17 | * http-request taieshop\/api\/CustomerFansInfo\/GetFansInfo script-path=https://raw.githubusercontent.com/id77/QuantumultX/master/task/thg.cookie.js 18 | * cron "10 0 * * *" script-path=https://raw.githubusercontent.com/id77/QuantumultX/master/task/thg.js 19 | * 20 | */ 21 | 22 | const $ = new Env('THG'); 23 | $.TOKEN_KEY = 'id77_thg_token'; 24 | 25 | !(async () => { 26 | const token = $request.headers.Authorization; 27 | if ($.setData(token, $.TOKEN_KEY)) { 28 | $.subt = `获取会话: 成功!`; 29 | } else { 30 | $.subt = `获取会话: 失败!`; 31 | } 32 | $.msg($.name, $.subt); 33 | })() 34 | .catch((e) => $.logErr(e)) 35 | .finally(() => $.done()); 36 | 37 | // https://github.com/chavyleung/scripts/blob/master/Env.js 38 | // prettier-ignore 39 | 40 | // https://github.com/chavyleung/scripts/blob/master/Env.js 41 | // prettier-ignore 42 | function Env(name, opts) { 43 | class Http { 44 | constructor(env) { 45 | this.env = env; 46 | } 47 | 48 | send(opts, method = 'GET') { 49 | opts = typeof opts === 'string' ? { url: opts } : opts; 50 | let sender = this.get; 51 | if (method === 'POST') { 52 | sender = this.post; 53 | } 54 | return new Promise((resolve, reject) => { 55 | sender.call(this, opts, (err, resp, body) => { 56 | if (err) reject(err); 57 | else resolve(resp); 58 | }); 59 | }); 60 | } 61 | 62 | get(opts) { 63 | return this.send.call(this.env, opts); 64 | } 65 | 66 | post(opts) { 67 | return this.send.call(this.env, opts, 'POST'); 68 | } 69 | } 70 | 71 | return new (class { 72 | constructor(name, opts = {}) { 73 | this.name = name; 74 | this.http = new Http(this); 75 | this.data = null; 76 | this.dataFile = 'box.dat'; 77 | this.logs = []; 78 | this.isMute = false; 79 | this.noLogKey = opts.noLogKey || ''; 80 | this.noLog = opts.noLog; 81 | this.isNeedRewrite = false; 82 | this.logSeparator = '\n'; 83 | this.startTime = new Date().getTime(); 84 | Object.assign(this, opts); 85 | this.log('', `🔔${this.name}, 开始!`); 86 | } 87 | 88 | isNode() { 89 | return 'undefined' !== typeof module && !!module.exports; 90 | } 91 | 92 | isQuanX() { 93 | return 'undefined' !== typeof $task; 94 | } 95 | 96 | isSurge() { 97 | return 'undefined' !== typeof $httpClient && 'undefined' === typeof $loon; 98 | } 99 | 100 | isLoon() { 101 | return 'undefined' !== typeof $loon; 102 | } 103 | 104 | isShadowrocket() { 105 | return 'undefined' !== typeof $rocket; 106 | } 107 | 108 | toObj(str, defaultValue = null) { 109 | try { 110 | return JSON.parse(str); 111 | } catch { 112 | return defaultValue; 113 | } 114 | } 115 | 116 | toStr(obj, defaultValue = null) { 117 | try { 118 | return JSON.stringify(obj); 119 | } catch { 120 | return defaultValue; 121 | } 122 | } 123 | 124 | getJson(key, defaultValue) { 125 | let json = defaultValue; 126 | const val = this.getData(key); 127 | if (val) { 128 | try { 129 | json = JSON.parse(this.getData(key)); 130 | } catch {} 131 | } 132 | return json; 133 | } 134 | 135 | setJson(val, key) { 136 | try { 137 | return this.setData(JSON.stringify(val), key); 138 | } catch { 139 | return false; 140 | } 141 | } 142 | 143 | getScript(url) { 144 | return new Promise((resolve) => { 145 | this.get({ url }, (err, resp, body) => resolve(body)); 146 | }); 147 | } 148 | 149 | runScript(script, runOpts) { 150 | return new Promise((resolve) => { 151 | let httpApi = this.getData('@chavy_boxjs_userCfgs.httpApi'); 152 | httpApi = httpApi ? httpApi.replace(/\n/g, '').trim() : httpApi; 153 | let httpApi_timeout = this.getData( 154 | '@chavy_boxjs_userCfgs.httpApi_timeout' 155 | ); 156 | httpApi_timeout = httpApi_timeout ? httpApi_timeout * 1 : 20; 157 | httpApi_timeout = 158 | runOpts && runOpts.timeout ? runOpts.timeout : httpApi_timeout; 159 | const [key, addr] = httpApi.split('@'); 160 | const opts = { 161 | url: `http://${addr}/v1/scripting/evaluate`, 162 | body: { 163 | script_text: script, 164 | mock_type: 'cron', 165 | timeout: httpApi_timeout, 166 | }, 167 | headers: { 'X-Key': key, Accept: '*/*' }, 168 | }; 169 | this.post(opts, (err, resp, body) => resolve(body)); 170 | }).catch((e) => this.logErr(e)); 171 | } 172 | 173 | loadData() { 174 | if (this.isNode()) { 175 | this.fs = this.fs ? this.fs : require('fs'); 176 | this.path = this.path ? this.path : require('path'); 177 | const curDirDataFilePath = this.path.resolve(this.dataFile); 178 | const rootDirDataFilePath = this.path.resolve( 179 | process.cwd(), 180 | this.dataFile 181 | ); 182 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 183 | const isRootDirDataFile = 184 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 185 | if (isCurDirDataFile || isRootDirDataFile) { 186 | const datPath = isCurDirDataFile 187 | ? curDirDataFilePath 188 | : rootDirDataFilePath; 189 | try { 190 | return JSON.parse(this.fs.readFileSync(datPath)); 191 | } catch (e) { 192 | return {}; 193 | } 194 | } else return {}; 195 | } else return {}; 196 | } 197 | 198 | writeData() { 199 | if (this.isNode()) { 200 | this.fs = this.fs ? this.fs : require('fs'); 201 | this.path = this.path ? this.path : require('path'); 202 | const curDirDataFilePath = this.path.resolve(this.dataFile); 203 | const rootDirDataFilePath = this.path.resolve( 204 | process.cwd(), 205 | this.dataFile 206 | ); 207 | const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath); 208 | const isRootDirDataFile = 209 | !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath); 210 | const jsonData = JSON.stringify(this.data); 211 | if (isCurDirDataFile) { 212 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 213 | } else if (isRootDirDataFile) { 214 | this.fs.writeFileSync(rootDirDataFilePath, jsonData); 215 | } else { 216 | this.fs.writeFileSync(curDirDataFilePath, jsonData); 217 | } 218 | } 219 | } 220 | 221 | lodash_get(source, path, defaultValue = undefined) { 222 | const paths = path.replace(/\[(\d+)\]/g, '.$1').split('.'); 223 | let result = source; 224 | for (const p of paths) { 225 | result = Object(result)[p]; 226 | if (result === undefined) { 227 | return defaultValue; 228 | } 229 | } 230 | return result; 231 | } 232 | 233 | lodash_set(obj, path, value) { 234 | if (Object(obj) !== obj) return obj; 235 | if (!Array.isArray(path)) path = path.toString().match(/[^.[\]]+/g) || []; 236 | path 237 | .slice(0, -1) 238 | .reduce( 239 | (a, c, i) => 240 | Object(a[c]) === a[c] 241 | ? a[c] 242 | : (a[c] = Math.abs(path[i + 1]) >> 0 === +path[i + 1] ? [] : {}), 243 | obj 244 | )[path[path.length - 1]] = value; 245 | return obj; 246 | } 247 | 248 | getData(key) { 249 | let val = this.getVal(key); 250 | // 如果以 @ 251 | if (/^@/.test(key)) { 252 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 253 | const objVal = objKey ? this.getVal(objKey) : ''; 254 | if (objVal) { 255 | try { 256 | const objedVal = JSON.parse(objVal); 257 | val = objedVal ? this.lodash_get(objedVal, paths, '') : val; 258 | } catch (e) { 259 | val = ''; 260 | } 261 | } 262 | } 263 | return val; 264 | } 265 | 266 | setData(val, key) { 267 | let isSuc = false; 268 | if (/^@/.test(key)) { 269 | const [, objKey, paths] = /^@(.*?)\.(.*?)$/.exec(key); 270 | const objdat = this.getVal(objKey); 271 | const objVal = objKey 272 | ? objdat === 'null' 273 | ? null 274 | : objdat || '{}' 275 | : '{}'; 276 | try { 277 | const objedVal = JSON.parse(objVal); 278 | this.lodash_set(objedVal, paths, val); 279 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 280 | } catch (e) { 281 | const objedVal = {}; 282 | this.lodash_set(objedVal, paths, val); 283 | isSuc = this.setVal(JSON.stringify(objedVal), objKey); 284 | } 285 | } else { 286 | isSuc = this.setVal(val, key); 287 | } 288 | return isSuc; 289 | } 290 | 291 | getVal(key) { 292 | if (this.isSurge() || this.isLoon()) { 293 | return $persistentStore.read(key); 294 | } else if (this.isQuanX()) { 295 | return $prefs.valueForKey(key); 296 | } else if (this.isNode()) { 297 | this.data = this.loadData(); 298 | return this.data[key]; 299 | } else { 300 | return (this.data && this.data[key]) || null; 301 | } 302 | } 303 | 304 | setVal(val, key) { 305 | if (this.isSurge() || this.isLoon()) { 306 | return $persistentStore.write(val, key); 307 | } else if (this.isQuanX()) { 308 | return $prefs.setValueForKey(val, key); 309 | } else if (this.isNode()) { 310 | this.data = this.loadData(); 311 | this.data[key] = val; 312 | this.writeData(); 313 | return true; 314 | } else { 315 | return (this.data && this.data[key]) || null; 316 | } 317 | } 318 | 319 | initGotEnv(opts) { 320 | this.got = this.got ? this.got : require('got'); 321 | this.ckTough = this.ckTough ? this.ckTough : require('tough-cookie'); 322 | this.ckJar = this.ckJar ? this.ckJar : new this.ckTough.CookieJar(); 323 | if (opts) { 324 | opts.headers = opts.headers ? opts.headers : {}; 325 | if (undefined === opts.headers.Cookie && undefined === opts.cookieJar) { 326 | opts.cookieJar = this.ckJar; 327 | } 328 | } 329 | } 330 | 331 | get(opts, callback = () => {}) { 332 | if (opts.headers) { 333 | delete opts.headers['Content-Type']; 334 | delete opts.headers['Content-Length']; 335 | delete opts.headers['Host']; 336 | } 337 | if (this.isSurge() || this.isLoon()) { 338 | if (this.isSurge() && this.isNeedRewrite) { 339 | opts.headers = opts.headers || {}; 340 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 341 | } 342 | $httpClient.get(opts, (err, resp, body) => { 343 | if (!err && resp) { 344 | resp.body = body; 345 | resp.statusCode = resp.status; 346 | } 347 | callback(err, resp, body); 348 | }); 349 | } else if (this.isQuanX()) { 350 | if (this.isNeedRewrite) { 351 | opts.opts = opts.opts || {}; 352 | Object.assign(opts.opts, { hints: false }); 353 | } 354 | $task.fetch(opts).then( 355 | (resp) => { 356 | const { statusCode: status, statusCode, headers, body } = resp; 357 | callback(null, { status, statusCode, headers, body }, body); 358 | }, 359 | (err) => callback(err) 360 | ); 361 | } else if (this.isNode()) { 362 | this.initGotEnv(opts); 363 | this.got(opts) 364 | .on('redirect', (resp, nextOpts) => { 365 | try { 366 | if (resp.headers['set-cookie']) { 367 | const ck = resp.headers['set-cookie'] 368 | .map(this.ckTough.Cookie.parse) 369 | .toString(); 370 | if (ck) { 371 | this.ckJar.setCookieSync(ck, null); 372 | } 373 | nextOpts.cookieJar = this.ckJar; 374 | } 375 | } catch (e) { 376 | this.logErr(e); 377 | } 378 | // this.ckJar.setCookieSync(resp.headers['set-cookie'].map(Cookie.parse).toString()) 379 | }) 380 | .then( 381 | (resp) => { 382 | const { statusCode: status, statusCode, headers, body } = resp; 383 | callback(null, { status, statusCode, headers, body }, body); 384 | }, 385 | (err) => { 386 | const { message: error, response: resp } = err; 387 | callback(error, resp, resp && resp.body); 388 | } 389 | ); 390 | } 391 | } 392 | 393 | post(opts, callback = () => {}) { 394 | const method = opts.method ? opts.method.toLocaleLowerCase() : 'post'; 395 | // 如果指定了请求体, 但没指定`Content-Type`, 则自动生成 396 | if (opts.body && opts.headers && !opts.headers['Content-Type']) { 397 | opts.headers['Content-Type'] = 'application/x-www-form-urlencoded'; 398 | } 399 | if (opts.headers) { 400 | delete opts.headers['Host']; 401 | delete opts.headers['Content-Length']; 402 | }; 403 | if (this.isSurge() || this.isLoon()) { 404 | if (this.isSurge() && this.isNeedRewrite) { 405 | opts.headers = opts.headers || {}; 406 | Object.assign(opts.headers, { 'X-Surge-Skip-Scripting': false }); 407 | } 408 | $httpClient[method](opts, (err, resp, body) => { 409 | if (!err && resp) { 410 | resp.body = body; 411 | resp.statusCode = resp.status; 412 | } 413 | callback(err, resp, body); 414 | }); 415 | } else if (this.isQuanX()) { 416 | opts.method = method; 417 | if (this.isNeedRewrite) { 418 | opts.opts = opts.opts || {}; 419 | Object.assign(opts.opts, { hints: false }); 420 | } 421 | $task.fetch(opts).then( 422 | (resp) => { 423 | const { statusCode: status, statusCode, headers, body } = resp; 424 | callback(null, { status, statusCode, headers, body }, body); 425 | }, 426 | (err) => callback(err) 427 | ); 428 | } else if (this.isNode()) { 429 | this.initGotEnv(opts); 430 | const { url, ..._opts } = opts; 431 | this.got[method](url, _opts).then( 432 | (resp) => { 433 | const { statusCode: status, statusCode, headers, body } = resp; 434 | callback(null, { status, statusCode, headers, body }, body); 435 | }, 436 | (err) => { 437 | const { message: error, response: resp } = err; 438 | callback(error, resp, resp && resp.body); 439 | } 440 | ); 441 | } 442 | } 443 | /** 444 | * 445 | * 示例:$.time('yyyy-MM-dd qq HH:mm:ss.S') 446 | * :$.time('yyyyMMddHHmmssS') 447 | * y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒 448 | * 其中y可选0-4位占位符、S可选0-1位占位符,其余可选0-2位占位符 449 | * @param {string} fmt 格式化参数 450 | * @param {number} 可选: 根据指定时间戳返回格式化日期 451 | * 452 | */ 453 | time(fmt, ts = null) { 454 | const date = ts ? new Date(ts) : new Date(); 455 | let o = { 456 | 'M+': date.getMonth() + 1, 457 | 'd+': date.getDate(), 458 | 'H+': date.getHours(), 459 | 'm+': date.getMinutes(), 460 | 's+': date.getSeconds(), 461 | 'q+': Math.floor((date.getMonth() + 3) / 3), 462 | S: date.getMilliseconds(), 463 | }; 464 | if (/(y+)/.test(fmt)) 465 | fmt = fmt.replace( 466 | RegExp.$1, 467 | (date.getFullYear() + '').substr(4 - RegExp.$1.length) 468 | ); 469 | for (let k in o) 470 | if (new RegExp('(' + k + ')').test(fmt)) 471 | fmt = fmt.replace( 472 | RegExp.$1, 473 | RegExp.$1.length == 1 474 | ? o[k] 475 | : ('00' + o[k]).substr(('' + o[k]).length) 476 | ); 477 | return fmt; 478 | } 479 | 480 | /** 481 | * 系统通知 482 | * 483 | * > 通知参数: 同时支持 QuanX 和 Loon 两种格式, EnvJs根据运行环境自动转换, Surge 环境不支持多媒体通知 484 | * 485 | * 示例: 486 | * $.msg(title, subt, desc, 'twitter://') 487 | * $.msg(title, subt, desc, { 'open-url': 'twitter://', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 488 | * $.msg(title, subt, desc, { 'open-url': 'https://bing.com', 'media-url': 'https://github.githubassets.com/images/modules/open_graph/github-mark.png' }) 489 | * 490 | * @param {*} title 标题 491 | * @param {*} subt 副标题 492 | * @param {*} desc 通知详情 493 | * @param {*} opts 通知参数 494 | * 495 | */ 496 | msg(title = name, subt = '', desc = '', opts) { 497 | const toEnvOpts = (rawOpts) => { 498 | if (!rawOpts) return rawOpts; 499 | if (typeof rawOpts === 'string') { 500 | if (this.isLoon()) return rawOpts; 501 | else if (this.isQuanX()) return { 'open-url': rawOpts }; 502 | else if (this.isSurge()) return { url: rawOpts }; 503 | else return undefined; 504 | } else if (typeof rawOpts === 'object') { 505 | if (this.isLoon()) { 506 | let openUrl = rawOpts.openUrl || rawOpts.url || rawOpts['open-url']; 507 | let mediaUrl = rawOpts.mediaUrl || rawOpts['media-url']; 508 | return { openUrl, mediaUrl }; 509 | } else if (this.isQuanX()) { 510 | let openUrl = rawOpts['open-url'] || rawOpts.url || rawOpts.openUrl; 511 | let mediaUrl = rawOpts['media-url'] || rawOpts.mediaUrl; 512 | let updatePasteboard = 513 | rawOpts['update-pasteboard'] || rawOpts.updatePasteboard; 514 | return { 515 | 'open-url': openUrl, 516 | 'media-url': mediaUrl, 517 | 'update-pasteboard': updatePasteboard, 518 | }; 519 | } else if (this.isSurge()) { 520 | let openUrl = rawOpts.url || rawOpts.openUrl || rawOpts['open-url']; 521 | return { url: openUrl }; 522 | } 523 | } else { 524 | return undefined; 525 | } 526 | }; 527 | if (!this.isMute) { 528 | if (this.isSurge() || this.isLoon()) { 529 | $notification.post(title, subt, desc, toEnvOpts(opts)); 530 | } else if (this.isQuanX()) { 531 | $notify(title, subt, desc, toEnvOpts(opts)); 532 | } 533 | } 534 | if (!this.isMuteLog) { 535 | let logs = ['', '==============📣系统通知📣==============']; 536 | logs.push(title); 537 | subt ? logs.push(subt) : ''; 538 | desc ? logs.push(desc) : ''; 539 | console.log(logs.join('\n')); 540 | this.logs = this.logs.concat(logs); 541 | } 542 | } 543 | 544 | log(...logs) { 545 | if (this.noLog || (this.noLogKey && (this.getData(this.noLogKey) || 'N').toLocaleUpperCase() === 'Y')) { 546 | return; 547 | } 548 | if (logs.length > 0) { 549 | this.logs = [...this.logs, ...logs]; 550 | } 551 | console.log(logs.join(this.logSeparator)); 552 | } 553 | 554 | logErr(err, msg) { 555 | const isPrintSack = !this.isSurge() && !this.isQuanX() && !this.isLoon(); 556 | if (!isPrintSack) { 557 | this.log('', `❗️${this.name}, 错误!`, err); 558 | } else { 559 | this.log('', `❗️${this.name}, 错误!`, err.stack); 560 | } 561 | } 562 | 563 | wait(time) { 564 | return new Promise((resolve) => setTimeout(resolve, time)); 565 | } 566 | 567 | done(val = {}) { 568 | const endTime = new Date().getTime(); 569 | const costTime = (endTime - this.startTime) / 1000; 570 | this.log('', `🔔${this.name}, 结束! 🕛 ${costTime} 秒`); 571 | this.log(); 572 | if (this.isSurge() || this.isQuanX() || this.isLoon()) { 573 | $done(val); 574 | } 575 | } 576 | })(name, opts); 577 | } 578 | --------------------------------------------------------------------------------