chavy.boxjs.js 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912
  1. const $ = new Env('BoxJs')
  2. // 为 eval 准备的上下文环境
  3. const $eval_env = {}
  4. $.version = '0.12.9'
  5. $.versionType = 'beta'
  6. // 发出的请求需要需要 Surge、QuanX 的 rewrite
  7. $.isNeedRewrite = true
  8. /**
  9. * ===================================
  10. * 持久化属性: BoxJs 自有的数据结构
  11. * ===================================
  12. */
  13. // 存储`用户偏好`
  14. $.KEY_usercfgs = 'chavy_boxjs_userCfgs'
  15. // 存储`应用会话`
  16. $.KEY_sessions = 'chavy_boxjs_sessions'
  17. // 存储`页面缓存`
  18. $.KEY_web_cache = 'chavy_boxjs_web_cache'
  19. // 存储`应用订阅缓存`
  20. $.KEY_app_subCaches = 'chavy_boxjs_app_subCaches'
  21. // 存储`全局备份` (弃用, 改用 `chavy_boxjs_backups`)
  22. $.KEY_globalBaks = 'chavy_boxjs_globalBaks'
  23. // 存储`备份索引`
  24. $.KEY_backups = 'chavy_boxjs_backups'
  25. // 存储`当前会话` (配合切换会话, 记录当前切换到哪个会话)
  26. $.KEY_cursessions = 'chavy_boxjs_cur_sessions'
  27. /**
  28. * ===================================
  29. * 持久化属性: BoxJs 公开的数据结构
  30. * ===================================
  31. */
  32. // 存储用户访问`BoxJs`时使用的域名
  33. $.KEY_boxjs_host = 'boxjs_host'
  34. // 请求响应体 (返回至页面的结果)
  35. $.json = $.name // `接口`类请求的响应体
  36. $.html = $.name // `页面`类请求的响应体
  37. // 页面源码地址
  38. $.web = `https://cdn.jsdelivr.net/gh/chavyleung/scripts@${$.version
  39. }/box/chavy.boxjs.html?_=${new Date().getTime()}`
  40. // 版本说明地址 (Release Note)
  41. $.ver = `https://raw.githubusercontent.com/chavyleung/scripts/master/box/release/box.release.json`
  42. !(async () => {
  43. // 勿扰模式
  44. $.isMute = [true, 'true'].includes($.getdata('@chavy_boxjs_userCfgs.isMute'))
  45. // 请求路径
  46. $.path = getPath($request.url)
  47. // 请求类型: GET
  48. $.isGet = $request.method === 'GET'
  49. // 请求类型: POST
  50. $.isPost = $request.method === 'POST'
  51. // 请求类型: OPTIONS
  52. $.isOptions = $request.method === 'OPTIONS'
  53. // 请求类型: page、api、query
  54. $.type = 'page'
  55. // 查询请求: /query/xxx
  56. $.isQuery = $.isGet && /^\/query\/.*?/.test($.path)
  57. // 接口请求: /api/xxx
  58. $.isApi = $.isPost && /^\/api\/.*?/.test($.path)
  59. // 页面请求: /xxx
  60. $.isPage = $.isGet && !$.isQuery && !$.isApi
  61. // 升级用户数据
  62. upgradeUserData()
  63. // 升级备份数据
  64. upgradeGlobalBaks()
  65. // 处理预检请求
  66. if ($.isOptions) {
  67. $.type = 'options'
  68. await handleOptions()
  69. }
  70. // 处理`页面`请求
  71. else if ($.isPage) {
  72. $.type = 'page'
  73. await handlePage()
  74. }
  75. // 处理`查询`请求
  76. else if ($.isQuery) {
  77. $.type = 'query'
  78. await handleQuery()
  79. }
  80. // 处理`接口`请求
  81. else if ($.isApi) {
  82. $.type = 'api'
  83. await handleApi()
  84. }
  85. })()
  86. .catch((e) => $.logErr(e))
  87. .finally(() => doneBox())
  88. /**
  89. * http://boxjs.com/ => `http://boxjs.com`
  90. * http://boxjs.com/app/jd => `http://boxjs.com`
  91. */
  92. function getHost(url) {
  93. return url.slice(0, url.indexOf('/', 8))
  94. }
  95. /**
  96. * http://boxjs.com/ => ``
  97. * http://boxjs.com/api/getdata => `/api/getdata`
  98. */
  99. function getPath(url) {
  100. // 如果以`/`结尾, 去掉最后一个`/`
  101. const end = url.lastIndexOf('/') === url.length - 1 ? -1 : undefined
  102. // slice第二个参数传 undefined 会直接截到最后
  103. // indexOf第二个参数用来跳过前面的 "https://"
  104. return url.slice(url.indexOf('/', 8), end)
  105. }
  106. /**
  107. * ===================================
  108. * 处理前端请求
  109. * ===================================
  110. */
  111. /**
  112. * 处理`页面`请求
  113. */
  114. async function handlePage() {
  115. // 获取 BoxJs 数据
  116. const boxdata = getBoxData()
  117. boxdata.syscfgs.isDebugMode = false
  118. console.log(`[WARN] handlePage: $.web = : ${$.web}`)
  119. // 调试模式: 是否每次都获取新的页面
  120. const isDebugWeb = [true, 'true'].includes(
  121. $.getdata('@chavy_boxjs_userCfgs.isDebugWeb')
  122. ) || true;
  123. let debugger_web = $.getdata('@chavy_boxjs_userCfgs.debugger_web');
  124. debugger_web = "http://git.jojo21.cf/shawenguan/Quantumult-X/raw/master/Scripts/box/chavy.boxjs.html";
  125. const cache = $.getjson($.KEY_web_cache, null)
  126. // 如果没有开启调试模式,且当前版本与缓存版本一致,且直接取缓存
  127. if (!isDebugWeb && cache && cache.version === $.version) {
  128. $.html = cache.cache
  129. }
  130. // 如果开启了调试模式,并指定了 `debugger_web` 则从指定的地址获取页面
  131. else {
  132. if (isDebugWeb && debugger_web) {
  133. // 调试地址后面拼时间缀, 避免 GET 缓存
  134. const isQueryUrl = debugger_web.includes('?')
  135. $.web = `${debugger_web}${isQueryUrl ? '&' : '?'
  136. }_=${new Date().getTime()}`
  137. boxdata.syscfgs.isDebugMode = true
  138. console.log(`[WARN] 调试模式: $.web = : ${$.web}`)
  139. }
  140. // 如果调用这个方法来获取缓存, 且标记为`非调试模式`
  141. const getcache = () => {
  142. console.log(`[ERROR] 调试模式: 正在使用缓存的页面!`)
  143. boxdata.syscfgs.isDebugMode = false
  144. return $.getjson($.KEY_web_cache).cache
  145. }
  146. await $.http.get($.web).then(
  147. (resp) => {
  148. if (/<title>BoxJs<\/title>/.test(resp.body)) {
  149. // 返回页面源码, 并马上存储到持久化仓库
  150. $.html = resp.body
  151. const cache = { version: $.version, cache: $.html }
  152. console.log("$.KEY_web_cache=" + $.KEY_web_cache);
  153. $.setjson(cache, $.KEY_web_cache)
  154. } else {
  155. // 如果返回的页面源码不是预期的, 则从持久化仓库中获取
  156. $.html = getcache()
  157. }
  158. },
  159. // 如果获取页面源码失败, 则从持久化仓库中获取
  160. () => ($.html = getcache())
  161. )
  162. }
  163. // 根据偏好设置, 替换首屏颜色 (如果是`auto`则交由页面自适应)
  164. const theme = $.getdata('@chavy_boxjs_userCfgs.theme')
  165. if (theme === 'light') {
  166. $.html = $.html.replace('#121212', '#fff')
  167. } else if (theme === 'dark') {
  168. $.html = $.html.replace('#fff', '#121212')
  169. }
  170. /**
  171. * 后端渲染数据, 感谢 https://t.me/eslint 提供帮助
  172. *
  173. * 如果直接渲染到 box: null 会出现双向绑定问题
  174. * 所以先渲染到 `boxServerData: null` 再由前端 `this.box = this.boxServerData` 实现双向绑定
  175. */
  176. $.html = $.html.replace(
  177. 'boxServerData: null',
  178. 'boxServerData:' + JSON.stringify(boxdata)
  179. )
  180. // 调试模式支持 vue Devtools (只有在同时开启调试模式和指定了调试地址才生效)
  181. // vue.min.js 生效时, 会导致 @click="window.open()" 报 "window" is not defined 错误
  182. if (isDebugWeb && debugger_web) {
  183. $.html = $.html.replace('vue.min.js', 'vue.js')
  184. }
  185. }
  186. /**
  187. * 处理`查询`请求
  188. */
  189. async function handleQuery() {
  190. const [, query] = $.path.split('/query')
  191. if (/^\/boxdata/.test(query)) {
  192. $.json = getBoxData()
  193. } else if (/^\/baks/.test(query)) {
  194. const [, backupId] = query.split('/baks/')
  195. $.json = $.getjson(backupId)
  196. } else if (/^\/versions$/.test(query)) {
  197. await getVersions(true)
  198. } else if (/^\/data/.test(query)) {
  199. // TODO 记录每次查询的 key 至 usercfgs.viewkeys
  200. const [, dataKey] = query.split('/data/')
  201. $.json = {
  202. key: dataKey,
  203. val: $.getdata(dataKey)
  204. }
  205. }
  206. }
  207. /**
  208. * 处理 API 请求
  209. */
  210. async function handleApi() {
  211. const [, api] = $.path.split('/api')
  212. console.log("handleApi:" + api);
  213. if (api === '/save') {
  214. await apiSave()
  215. } else if (api === '/addAppSub') {
  216. await apiAddAppSub()
  217. } else if (api === '/reloadAppSub') {
  218. await apiReloadAppSub()
  219. } else if (api === '/delGlobalBak') {
  220. await apiDelGlobalBak()
  221. } else if (api === '/updateGlobalBak') {
  222. await apiUpdateGlobalBak()
  223. } else if (api === '/saveGlobalBak') {
  224. await apiSaveGlobalBak()
  225. } else if (api === '/impGlobalBak') {
  226. await apiImpGlobalBak()
  227. } else if (api === '/revertGlobalBak') {
  228. await apiRevertGlobalBak()
  229. } else if (api === '/runScript') {
  230. await apiRunScript()
  231. } else if (api === '/saveData') {
  232. await apiSaveData()
  233. }
  234. }
  235. async function handleOptions() { }
  236. /**
  237. * ===================================
  238. * 获取基础数据
  239. * ===================================
  240. */
  241. function getBoxData() {
  242. const datas = {}
  243. const usercfgs = getUserCfgs()
  244. const sessions = getAppSessions()
  245. const curSessions = getCurSessions()
  246. const sysapps = getSystemApps()
  247. const syscfgs = getSystemCfgs()
  248. const appSubCaches = getAppSubCaches()
  249. const globalbaks = getGlobalBaks()
  250. // 把 `内置应用`和`订阅应用` 里需要持久化属性放到`datas`
  251. sysapps.forEach((app) => Object.assign(datas, getAppDatas(app)))
  252. usercfgs.appsubs.forEach((sub) => {
  253. const subcache = appSubCaches[sub.url]
  254. if (subcache && subcache.apps && Array.isArray(subcache.apps)) {
  255. subcache.apps.forEach((app) => Object.assign(datas, getAppDatas(app)))
  256. }
  257. })
  258. const box = {
  259. datas,
  260. usercfgs,
  261. sessions,
  262. curSessions,
  263. sysapps,
  264. syscfgs,
  265. appSubCaches,
  266. globalbaks
  267. }
  268. return box
  269. }
  270. /**
  271. * 获取系统配置
  272. */
  273. function getSystemCfgs() {
  274. // prettier-ignore
  275. return {
  276. env: $.isStash() ? 'Stash' : $.isShadowrocket() ? 'Shadowrocket' : $.isLoon() ? 'Loon' : $.isQuanX() ? 'QuanX' : $.isSurge() ? 'Surge' : 'Node',
  277. version: $.version,
  278. versionType: $.versionType,
  279. envs: [
  280. { id: 'Surge', icons: ['https://raw.githubusercontent.com/Orz-3/mini/none/surge.png', 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/surge.png'] },
  281. { id: 'QuanX', icons: ['https://raw.githubusercontent.com/Orz-3/mini/none/quanX.png', 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/quantumultx.png'] },
  282. { id: 'Loon', icons: ['https://raw.githubusercontent.com/Orz-3/mini/none/loon.png', 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/loon.png'] },
  283. { id: 'Shadowrocket', icons: ['https://raw.githubusercontent.com/Orz-3/mini/master/Alpha/shadowrocket.png', 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/shadowrocket.png'] },
  284. { id: 'Stash', icons: ['https://raw.githubusercontent.com/Orz-3/mini/master/Alpha/stash.png', 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/stash.png'] }
  285. ],
  286. chavy: { id: 'ChavyLeung', icon: 'https://avatars3.githubusercontent.com/u/29748519', repo: 'https://github.com/chavyleung/scripts' },
  287. senku: { id: 'GideonSenku', icon: 'https://avatars1.githubusercontent.com/u/39037656', repo: 'https://github.com/GideonSenku' },
  288. id77: { id: 'id77', icon: 'https://avatars0.githubusercontent.com/u/9592236', repo: 'https://github.com/id77' },
  289. orz3: { id: 'Orz-3', icon: 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/Orz-3.png', repo: 'https://github.com/Orz-3/' },
  290. boxjs: { id: 'BoxJs', show: false, icon: 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/box.png', icons: ['https://raw.githubusercontent.com/Orz-3/mini/master/Alpha/box.png', 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/box.png'], repo: 'https://github.com/chavyleung/scripts' },
  291. defaultIcons: ['https://raw.githubusercontent.com/Orz-3/mini/master/Alpha/appstore.png', 'https://raw.githubusercontent.com/Orz-3/mini/master/Color/appstore.png']
  292. }
  293. }
  294. /**
  295. * 获取内置应用
  296. */
  297. function getSystemApps() {
  298. // prettier-ignore
  299. const sysapps = [
  300. {
  301. id: 'BoxSetting',
  302. name: '偏好设置',
  303. descs: ['可手动执行一些抹掉数据的脚本', '可设置明暗两种主题下的主色调', '可设置壁纸清单'],
  304. keys: [
  305. '@chavy_boxjs_userCfgs.httpapi',
  306. '@chavy_boxjs_userCfgs.bgimg',
  307. '@chavy_boxjs_userCfgs.http_backend',
  308. '@chavy_boxjs_userCfgs.color_dark_primary',
  309. '@chavy_boxjs_userCfgs.color_light_primary'
  310. ],
  311. settings: [
  312. { id: '@chavy_boxjs_userCfgs.httpapis', name: 'HTTP-API (Surge)', val: '', type: 'textarea', placeholder: ',[email protected]:6166', autoGrow: true, rows: 2, persistentHint: true, desc: '示例: ,[email protected]:6166! 注意: 以逗号开头, 逗号分隔多个地址, 可加回车' },
  313. { id: '@chavy_boxjs_userCfgs.httpapi_timeout', name: 'HTTP-API Timeout (Surge)', val: 20, type: 'number', persistentHint: true, desc: '如果脚本作者指定了超时时间, 会优先使用脚本指定的超时时间.' },
  314. { id: '@chavy_boxjs_userCfgs.http_backend', name: 'HTTP Backend (Quantumult X)', val: '', type: 'text', placeholder: 'http://127.0.0.1:9999', persistentHint: true, desc: '示例: http://127.0.0.1:9999 ! 注意: 必须是以 http 开头的完整路径, 不能是 / 结尾' },
  315. { id: '@chavy_boxjs_userCfgs.bgimgs', name: '背景图片清单', val: '无,\n跟随系统,跟随系统\nlight,http://api.btstu.cn/sjbz/zsy.php\ndark,https://uploadbeta.com/api/pictures/random\n妹子,http://api.btstu.cn/sjbz/zsy.php', type: 'textarea', placeholder: '无,{回车} 跟随系统,跟随系统{回车} light,图片地址{回车} dark,图片地址{回车} 妹子,图片地址', persistentHint: true, autoGrow: true, rows: 2, desc: '逗号分隔名字和链接, 回车分隔多个地址' },
  316. { id: '@chavy_boxjs_userCfgs.bgimg', name: '背景图片', val: '', type: 'text', placeholder: 'http://api.btstu.cn/sjbz/zsy.php', persistentHint: true, desc: '输入背景图标的在线链接' },
  317. { id: '@chavy_boxjs_userCfgs.changeBgImgEnterDefault', name: '手势进入壁纸模式默认背景图片', val: '', type: 'text', placeholder: '填写上面背景图片清单的值', persistentHint: true, desc: '' },
  318. { id: '@chavy_boxjs_userCfgs.changeBgImgOutDefault', name: '手势退出壁纸模式默认背景图片', val: '', type: 'text', placeholder: '填写上面背景图片清单的值', persistentHint: true, desc: '' },
  319. { id: '@chavy_boxjs_userCfgs.color_light_primary', name: '明亮色调', canvas: true, val: '#F7BB0E', type: 'colorpicker', desc: '' },
  320. { id: '@chavy_boxjs_userCfgs.color_dark_primary', name: '暗黑色调', canvas: true, val: '#2196F3', type: 'colorpicker', desc: '' }
  321. ],
  322. scripts: [
  323. {
  324. name: "抹掉:所有缓存",
  325. script: "https://raw.githubusercontent.com/chavyleung/scripts/master/box/scripts/boxjs.revert.caches.js"
  326. },
  327. {
  328. name: "抹掉:收藏应用",
  329. script: "https://raw.githubusercontent.com/chavyleung/scripts/master/box/scripts/boxjs.revert.usercfgs.favapps.js"
  330. },
  331. {
  332. name: "抹掉:用户偏好",
  333. script: "https://raw.githubusercontent.com/chavyleung/scripts/master/box/scripts/boxjs.revert.usercfgs.js"
  334. },
  335. {
  336. name: "抹掉:所有会话",
  337. script: "https://raw.githubusercontent.com/chavyleung/scripts/master/box/scripts/boxjs.revert.usercfgs.sessions.js"
  338. },
  339. {
  340. name: "抹掉:所有备份",
  341. script: "https://raw.githubusercontent.com/chavyleung/scripts/master/box/scripts/boxjs.revert.baks.js"
  342. },
  343. {
  344. name: "抹掉:BoxJs (注意备份)",
  345. script: "https://raw.githubusercontent.com/chavyleung/scripts/master/box/scripts/boxjs.revert.boxjs.js"
  346. }
  347. ],
  348. author: '@chavyleung',
  349. repo: 'https://github.com/chavyleung/scripts/blob/master/box/switcher/box.switcher.js',
  350. icons: [
  351. 'https://raw.githubusercontent.com/chavyleung/scripts/master/box/icons/BoxSetting.mini.png',
  352. 'https://raw.githubusercontent.com/chavyleung/scripts/master/box/icons/BoxSetting.png'
  353. ]
  354. },
  355. {
  356. id: 'BoxSwitcher',
  357. name: '会话切换',
  358. desc: '打开静默运行后, 切换会话将不再发出系统通知 \n注: 不影响日志记录',
  359. keys: [],
  360. settings: [{ id: 'CFG_BoxSwitcher_isSilent', name: '静默运行', val: false, type: 'boolean', desc: '切换会话时不发出系统通知!' }],
  361. author: '@chavyleung',
  362. repo: 'https://github.com/chavyleung/scripts/blob/master/box/switcher/box.switcher.js',
  363. icons: [
  364. 'https://raw.githubusercontent.com/chavyleung/scripts/master/box/icons/BoxSwitcher.mini.png',
  365. 'https://raw.githubusercontent.com/chavyleung/scripts/master/box/icons/BoxSwitcher.png'
  366. ],
  367. script: 'https://raw.githubusercontent.com/chavyleung/scripts/master/box/switcher/box.switcher.js'
  368. },
  369. {
  370. "id": "BoxGist",
  371. "name": "Gist备份",
  372. "keys": ["@gist.token", "@gist.username"],
  373. "author": "@dompling",
  374. "repo": "https://github.com/dompling/Script/tree/master/gist",
  375. "icons": [
  376. "https://raw.githubusercontent.com/Former-Years/icon/master/github-bf.png",
  377. "https://raw.githubusercontent.com/Former-Years/icon/master/github-bf.png"
  378. ],
  379. "descs_html": [
  380. "脚本由 <a href='https://github.com/dompling' target='_blank'>@dompling</a> 提供, 感谢!",
  381. "<br />",
  382. "<b>Token</b> 获取方式:",
  383. "<span style='margin-left: 40px'>头像菜单 -></span>",
  384. "<span style='margin-left: 40px'>Settings -></span>",
  385. "<span style='margin-left: 40px'>Developer settings -></span>",
  386. "<span style='margin-left: 40px'>Personal access tokens -></span>",
  387. "<span style='margin-left: 40px'>Generate new token -></span>",
  388. "<span style='margin-left: 40px'>在里面找到 gist 勾选提交</span>"
  389. ],
  390. "scripts": [
  391. {
  392. "name": "备份 Gist",
  393. "script": "https://raw.githubusercontent.com/dompling/Script/master/gist/backup.js"
  394. },
  395. {
  396. "name": "从 Gist 恢复",
  397. "script": "https://raw.githubusercontent.com/dompling/Script/master/gist/restore.js"
  398. }
  399. ],
  400. "settings": [
  401. {
  402. "id": "@gist.username",
  403. "name": "用户名",
  404. "val": null,
  405. "type": "text",
  406. "placeholder": "github 用户名",
  407. "desc": "必填"
  408. },
  409. {
  410. "id": "@gist.token",
  411. "name": "Personal access tokens",
  412. "val": null,
  413. "type": "text",
  414. "placeholder": "github personal access tokens",
  415. "desc": "必填"
  416. }
  417. ]
  418. }
  419. ]
  420. return sysapps
  421. }
  422. /**
  423. * 获取用户配置
  424. */
  425. function getUserCfgs() {
  426. const defcfgs = {
  427. favapps: [],
  428. appsubs: [],
  429. viewkeys: [],
  430. isPinedSearchBar: true,
  431. httpapi: '[email protected]:6166',
  432. http_backend: ''
  433. }
  434. const usercfgs = Object.assign(defcfgs, $.getjson($.KEY_usercfgs, {}))
  435. // 处理异常数据:删除所有为 null 的订阅
  436. if (usercfgs.appsubs.includes(null)) {
  437. usercfgs.appsubs = usercfgs.appsubs.filter((sub) => sub)
  438. $.setjson(usercfgs, $.KEY_usercfgs)
  439. }
  440. return usercfgs
  441. }
  442. /**
  443. * 获取`应用订阅`缓存
  444. */
  445. function getAppSubCaches() {
  446. return $.getjson($.KEY_app_subCaches, {})
  447. }
  448. /**
  449. * 获取全局备份列表
  450. */
  451. function getGlobalBaks() {
  452. let backups = $.getjson($.KEY_backups, [])
  453. // 处理异常数据:删除所有为 null 的备份
  454. if (backups.includes(null)) {
  455. backups = backups.filter((bak) => bak)
  456. $.setjson(backups, $.KEY_backups)
  457. }
  458. return backups
  459. }
  460. /**
  461. * 获取版本清单
  462. */
  463. function getVersions() {
  464. return $.http.get($.ver).then(
  465. (resp) => {
  466. try {
  467. $.json = $.toObj(resp.body)
  468. } catch {
  469. $.json = {}
  470. }
  471. },
  472. () => ($.json = {})
  473. )
  474. }
  475. /**
  476. * 获取用户应用
  477. */
  478. function getUserApps() {
  479. // TODO 用户可在 BoxJs 中自定义应用, 格式与应用订阅一致
  480. return []
  481. }
  482. /**
  483. * 获取应用会话
  484. */
  485. function getAppSessions() {
  486. return $.getjson($.KEY_sessions, []) || []
  487. }
  488. /**
  489. * 获取当前切换到哪个会话
  490. */
  491. function getCurSessions() {
  492. return $.getjson($.KEY_cursessions, {}) || {}
  493. }
  494. /**
  495. * ===================================
  496. * 接口类函数
  497. * ===================================
  498. */
  499. function getAppDatas(app) {
  500. const datas = {}
  501. const nulls = [null, undefined, 'null', 'undefined']
  502. if (app.keys && Array.isArray(app.keys)) {
  503. app.keys.forEach((key) => {
  504. const val = $.getdata(key)
  505. datas[key] = nulls.includes(val) ? null : val
  506. })
  507. }
  508. if (app.settings && Array.isArray(app.settings)) {
  509. app.settings.forEach((setting) => {
  510. const key = setting.id
  511. const val = $.getdata(key)
  512. datas[key] = nulls.includes(val) ? null : val
  513. })
  514. }
  515. return datas
  516. }
  517. async function apiSave() {
  518. const data = $.toObj($request.body)
  519. if (Array.isArray(data)) {
  520. data.forEach((dat) => $.setdata(dat.val, dat.key))
  521. } else {
  522. $.setdata(data.val, data.key)
  523. }
  524. $.json = getBoxData()
  525. }
  526. async function apiAddAppSub() {
  527. const sub = $.toObj($request.body)
  528. // 添加订阅
  529. const usercfgs = getUserCfgs()
  530. usercfgs.appsubs.push(sub)
  531. $.setjson(usercfgs, $.KEY_usercfgs)
  532. // 加载订阅缓存
  533. await reloadAppSubCache(sub.url)
  534. $.json = getBoxData()
  535. }
  536. async function apiReloadAppSub() {
  537. const sub = $.toObj($request.body)
  538. if (sub) {
  539. await reloadAppSubCache(sub.url)
  540. } else {
  541. await reloadAppSubCaches()
  542. }
  543. $.json = getBoxData()
  544. }
  545. async function apiDelGlobalBak() {
  546. const backup = $.toObj($request.body)
  547. const backups = $.getjson($.KEY_backups, [])
  548. const bakIdx = backups.findIndex((b) => b.id === backup.id)
  549. if (bakIdx > -1) {
  550. backups.splice(bakIdx, 1)
  551. $.setdata('', backup.id)
  552. $.setjson(backups, $.KEY_backups)
  553. }
  554. $.json = getBoxData()
  555. }
  556. async function apiUpdateGlobalBak() {
  557. const { id: backupId, name: backupName } = $.toObj($request.body)
  558. const backups = $.getjson($.KEY_backups, [])
  559. const backup = backups.find((b) => b.id === backupId)
  560. if (backup) {
  561. backup.name = backupName
  562. $.setjson(backups, $.KEY_backups)
  563. }
  564. $.json = getBoxData()
  565. }
  566. async function apiRevertGlobalBak() {
  567. const { id: bakcupId } = $.toObj($request.body)
  568. const backup = $.getjson(bakcupId)
  569. if (backup) {
  570. const {
  571. chavy_boxjs_sysCfgs,
  572. chavy_boxjs_sysApps,
  573. chavy_boxjs_sessions,
  574. chavy_boxjs_userCfgs,
  575. chavy_boxjs_cur_sessions,
  576. chavy_boxjs_app_subCaches,
  577. ...datas
  578. } = backup
  579. $.setdata(JSON.stringify(chavy_boxjs_sessions), $.KEY_sessions)
  580. $.setdata(JSON.stringify(chavy_boxjs_userCfgs), $.KEY_usercfgs)
  581. $.setdata(JSON.stringify(chavy_boxjs_cur_sessions), $.KEY_cursessions)
  582. $.setdata(JSON.stringify(chavy_boxjs_app_subCaches), $.KEY_app_subCaches)
  583. const isNull = (val) =>
  584. [undefined, null, 'null', 'undefined', ''].includes(val)
  585. Object.keys(datas).forEach((datkey) =>
  586. $.setdata(isNull(datas[datkey]) ? '' : `${datas[datkey]}`, datkey)
  587. )
  588. }
  589. const boxdata = getBoxData()
  590. $.json = boxdata
  591. }
  592. async function apiSaveGlobalBak() {
  593. const backups = $.getjson($.KEY_backups, [])
  594. const boxdata = getBoxData()
  595. const backup = $.toObj($request.body)
  596. const backupData = {}
  597. backupData['chavy_boxjs_userCfgs'] = boxdata.usercfgs
  598. backupData['chavy_boxjs_sessions'] = boxdata.sessions
  599. backupData['chavy_boxjs_cur_sessions'] = boxdata.curSessions
  600. backupData['chavy_boxjs_app_subCaches'] = boxdata.appSubCaches
  601. Object.assign(backupData, boxdata.datas)
  602. backups.push(backup)
  603. $.setjson(backups, $.KEY_backups)
  604. $.setjson(backupData, backup.id)
  605. $.json = getBoxData()
  606. }
  607. async function apiImpGlobalBak() {
  608. const backups = $.getjson($.KEY_backups, [])
  609. const backup = $.toObj($request.body)
  610. const backupData = backup.bak
  611. delete backup.bak
  612. backups.push(backup)
  613. $.setjson(backups, $.KEY_backups)
  614. $.setjson(backupData, backup.id)
  615. $.json = getBoxData()
  616. }
  617. async function apiRunScript() {
  618. // 取消勿扰模式
  619. $.isMute = false
  620. const opts = $.toObj($request.body)
  621. const httpapi = $.getdata('@chavy_boxjs_userCfgs.httpapi')
  622. const ishttpapi = /.*?@.*?:[0-9]+/.test(httpapi)
  623. let script_text = null
  624. $.log("apiRunScript:" + $request.body)
  625. if (opts.isRemote) {
  626. await $.getScript(opts.url).then((script) => (script_text = script))
  627. } else {
  628. script_text = opts.script
  629. }
  630. if (
  631. $.isSurge() &&
  632. !$.isLoon() &&
  633. !$.isShadowrocket() &&
  634. !$.isStash() &&
  635. ishttpapi
  636. ) {
  637. const runOpts = { timeout: opts.timeout }
  638. await $.runScript(script_text, runOpts).then(
  639. (resp) => ($.json = JSON.parse(resp))
  640. )
  641. } else {
  642. await new Promise((resolve) => {
  643. $eval_env.resolve = resolve
  644. // 避免被执行脚本误认为是 rewrite 环境
  645. // 所以需要 `$request = undefined`
  646. $eval_env.request = $request
  647. $request = undefined
  648. // 重写 console.log, 把日志记录到 $eval_env.cached_logs
  649. $eval_env.cached_logs = []
  650. console.cloned_log = console.log
  651. console.log = (l) => {
  652. console.cloned_log(l)
  653. $eval_env.cached_logs.push(l)
  654. }
  655. // 重写脚本内的 $done, 调用 $done() 即是调用 $eval_env.resolve()
  656. script_text = script_text.replace(/\$done/g, '$eval_env.resolve')
  657. script_text = script_text.replace(/\$\.done/g, '$eval_env.resolve')
  658. try {
  659. eval(script_text)
  660. } catch (e) {
  661. $eval_env.cached_logs.push(e)
  662. resolve()
  663. }
  664. })
  665. // 还原 console.log
  666. console.log = console.cloned_log
  667. // 还原 $request
  668. $request = $eval_env.request
  669. // 返回数据
  670. $.json = {
  671. result: '',
  672. output: $eval_env.cached_logs.join('\n')
  673. }
  674. }
  675. }
  676. async function apiSaveData() {
  677. const { key: dataKey, val: dataVal } = $.toObj($request.body)
  678. $.setdata(dataVal, dataKey)
  679. $.json = {
  680. key: dataKey,
  681. val: $.getdata(dataKey)
  682. }
  683. }
  684. /**
  685. * ===================================
  686. * 工具类函数
  687. * ===================================
  688. */
  689. function reloadAppSubCache(url) {
  690. // 地址后面拼时间缀, 避免 GET 缓存
  691. const requrl = `${url}${url.includes('?') ? '&' : '?'
  692. }_=${new Date().getTime()}`
  693. return $.http.get(requrl).then((resp) => {
  694. try {
  695. const subcaches = getAppSubCaches()
  696. subcaches[url] = $.toObj(resp.body)
  697. subcaches[url].updateTime = new Date()
  698. $.setjson(subcaches, $.KEY_app_subCaches)
  699. $.log(`更新订阅, 成功! ${url}`)
  700. } catch (e) {
  701. $.logErr(e)
  702. $.log(`更新订阅, 失败! ${url}`)
  703. }
  704. })
  705. }
  706. async function reloadAppSubCaches() {
  707. $.msg($.name, '更新订阅: 开始!')
  708. const reloadActs = []
  709. const usercfgs = getUserCfgs()
  710. usercfgs.appsubs.forEach((sub) => {
  711. reloadActs.push(reloadAppSubCache(sub.url))
  712. })
  713. await Promise.all(reloadActs)
  714. $.log(`全部订阅, 完成!`)
  715. const endTime = new Date().getTime()
  716. const costTime = (endTime - $.startTime) / 1000
  717. $.msg($.name, `更新订阅: 完成! 🕛 ${costTime} 秒`)
  718. }
  719. function upgradeUserData() {
  720. const usercfgs = getUserCfgs()
  721. // 如果存在`usercfgs.appsubCaches`则需要升级数据
  722. const isNeedUpgrade = !!usercfgs.appsubCaches
  723. if (isNeedUpgrade) {
  724. // 迁移订阅缓存至独立的持久化空间
  725. $.setjson(usercfgs.appsubCaches, $.KEY_app_subCaches)
  726. // 移除用户偏好中的订阅缓存
  727. delete usercfgs.appsubCaches
  728. usercfgs.appsubs.forEach((sub) => {
  729. delete sub._raw
  730. delete sub.apps
  731. delete sub.isErr
  732. delete sub.updateTime
  733. })
  734. }
  735. if (isNeedUpgrade) {
  736. $.setjson(usercfgs, $.KEY_usercfgs)
  737. }
  738. }
  739. /**
  740. * 升级备份数据
  741. *
  742. * 升级前: 把所有备份都存到一个持久化空间
  743. * 升级后: 把每个备份都独立存到一个空间, `$.KEY_backups` 仅记录必要的数据索引
  744. */
  745. function upgradeGlobalBaks() {
  746. let oldbaks = $.getdata($.KEY_globalBaks)
  747. let newbaks = $.getjson($.KEY_backups, [])
  748. const isEmpty = (bak) => [undefined, null, ''].includes(bak)
  749. const isExistsInNew = (backupId) => newbaks.find((bak) => bak.id === backupId)
  750. // 存在旧备份数据时, 升级备份数据格式
  751. if (!isEmpty(oldbaks)) {
  752. oldbaks = JSON.parse(oldbaks)
  753. oldbaks.forEach((bak) => {
  754. if (isEmpty(bak)) return
  755. if (isEmpty(bak.bak)) return
  756. if (isExistsInNew(bak.id)) return
  757. console.log(`正在迁移: ${bak.name}`)
  758. const backupId = bak.id
  759. const backupData = bak.bak
  760. // 删除旧的备份数据, 仅保留索引信息
  761. delete bak.bak
  762. newbaks.push(bak)
  763. // 提取旧备份数据, 存入独立的持久化空间
  764. $.setjson(backupData, backupId)
  765. })
  766. $.setjson(newbaks, $.KEY_backups)
  767. }
  768. // 清空所有旧备份的数据
  769. $.setdata('', $.KEY_globalBaks)
  770. }
  771. /**
  772. * ===================================
  773. * 结束类函数
  774. * ===================================
  775. */
  776. function doneBox() {
  777. // 记录当前使用哪个域名访问
  778. $.setdata(getHost($request.url), $.KEY_boxjs_host)
  779. if ($.isOptions) doneOptions()
  780. else if ($.isPage) donePage()
  781. else if ($.isQuery) doneQuery()
  782. else if ($.isApi) doneApi()
  783. else $.done()
  784. }
  785. function getBaseDoneHeaders(mixHeaders = {}) {
  786. return Object.assign(
  787. {
  788. 'Access-Control-Allow-Origin': '*',
  789. 'Access-Control-Allow-Methods': 'POST,GET,OPTIONS,PUT,DELETE',
  790. 'Access-Control-Allow-Headers':
  791. 'Origin, X-Requested-With, Content-Type, Accept'
  792. },
  793. mixHeaders
  794. )
  795. }
  796. function getHtmlDoneHeaders() {
  797. return getBaseDoneHeaders({
  798. 'Content-Type': 'text/html;charset=UTF-8'
  799. })
  800. }
  801. function getJsonDoneHeaders() {
  802. return getBaseDoneHeaders({
  803. 'Content-Type': 'text/json; charset=utf-8'
  804. })
  805. }
  806. function doneOptions() {
  807. const headers = getBaseDoneHeaders()
  808. if ($.isQuanX()) $.done({ headers })
  809. else $.done({ response: { headers } })
  810. }
  811. function donePage() {
  812. const headers = getHtmlDoneHeaders()
  813. if ($.isQuanX()) $.done({ status: 'HTTP/1.1 200', headers, body: $.html })
  814. else $.done({ response: { status: 200, headers, body: $.html } })
  815. }
  816. function doneQuery() {
  817. $.json = $.toStr($.json)
  818. const headers = getJsonDoneHeaders()
  819. if ($.isQuanX()) $.done({ status: 'HTTP/1.1 200', headers, body: $.json })
  820. else $.done({ response: { status: 200, headers, body: $.json } })
  821. }
  822. function doneApi() {
  823. $.json = $.toStr($.json)
  824. const headers = getJsonDoneHeaders()
  825. if ($.isQuanX()) $.done({ status: 'HTTP/1.1 200', headers, body: $.json })
  826. else $.done({ response: { status: 200, headers, body: $.json } })
  827. }
  828. /**
  829. * GistBox by https://github.com/Peng-YM
  830. */
  831. // prettier-ignore
  832. function GistBox(e) { const t = function (e, t = {}) { const { isQX: s, isLoon: n, isSurge: o } = function () { const e = "undefined" != typeof $task, t = "undefined" != typeof $loon, s = "undefined" != typeof $httpClient && !this.isLoon, n = "function" == typeof require && "undefined" != typeof $jsbox; return { isQX: e, isLoon: t, isSurge: s, isNode: "function" == typeof require && !n, isJSBox: n } }(), r = {}; return ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"].forEach(i => r[i.toLowerCase()] = (r => (function (r, i) { (i = "string" == typeof i ? { url: i } : i).url = e ? e + i.url : i.url; const a = (i = { ...t, ...i }).timeout, u = { onRequest: () => { }, onResponse: e => e, onTimeout: () => { }, ...i.events }; let c, d; u.onRequest(r, i), c = s ? $task.fetch({ method: r, ...i }) : new Promise((e, t) => { (o || n ? $httpClient : require("request"))[r.toLowerCase()](i, (s, n, o) => { s ? t(s) : e({ statusCode: n.status || n.statusCode, headers: n.headers, body: o }) }) }); const f = a ? new Promise((e, t) => { d = setTimeout(() => (u.onTimeout(), t(`${r} URL: ${i.url} exceeds the timeout ${a} ms`)), a) }) : null; return (f ? Promise.race([f, c]).then(e => (clearTimeout(d), e)) : c).then(e => u.onResponse(e)) })(i, r))), r }("https://api.github.com", { headers: { Authorization: `token ${e}`, "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.141 Safari/537.36" }, events: { onResponse: e => String(e.statusCode).startsWith("4") ? Promise.reject(`ERROR: ${JSON.parse(e.body).message}`) : e } }), s = e => `boxjs.bak.${e}.json`, n = e => e.match(/boxjs\.bak\.(\d+)\.json/)[1]; return new class { async findDatabase() { return t.get("/gists").then(e => { const t = JSON.parse(e.body); for (let e of t) if ("BoxJs Gist" === e.description) return e.id; return -1 }) } async createDatabase(e) { e instanceof Array || (e = [e]); const n = {}; return e.forEach(e => { n[s(e.time)] = { content: e.content } }), t.post({ url: "/gists", body: JSON.stringify({ description: "BoxJs Gist", public: !1, files: n }) }).then(e => JSON.parse(e.body).id) } async deleteDatabase(e) { return t.delete(`/gists/${e}`) } async getBackups(e) { const s = await t.get(`/gists/${e}`).then(e => JSON.parse(e.body)), { files: o } = s, r = []; for (let e of Object.keys(o)) r.push({ time: n(e), url: o[e].raw_url }); return r } async addBackups(e, t) { t instanceof Array || (t = [t]); const n = {}; return t.forEach(e => n[s(e.time)] = { content: e.content }), this.updateBackups(e, n) } async deleteBackups(e, t) { t instanceof Array || (t = [t]); const n = {}; return t.forEach(e => n[s(e)] = {}), this.updateBackups(e, n) } async updateBackups(e, s) { return t.patch({ url: `/gists/${e}`, body: JSON.stringify({ files: s }) }) } } }
  833. /**
  834. * EnvJs
  835. */
  836. // prettier-ignore
  837. function Env(t, s) { class e { constructor(t) { this.env = t } send(t, s = "GET") { t = "string" == typeof t ? { url: t } : t; let e = this.get; return "POST" === s && (e = this.post), new Promise((s, i) => { e.call(this, t, (t, e, r) => { t ? i(t) : s(e) }) }) } get(t) { return this.send.call(this.env, t) } post(t) { return this.send.call(this.env, t, "POST") } } return new class { constructor(t, s) { this.name = t, this.http = new e(this), this.data = null, this.dataFile = "box.dat", this.logs = [], this.isMute = !1, this.isNeedRewrite = !1, this.logSeparator = "\n", this.encoding = "utf-8", this.startTime = (new Date).getTime(), Object.assign(this, s), this.log("", `\ud83d\udd14${this.name}, \u5f00\u59cb!`) } isNode() { return "undefined" != typeof module && !!module.exports } isQuanX() { return "undefined" != typeof $task } isSurge() { return "undefined" != typeof $environment && $environment["surge-version"] } isLoon() { return "undefined" != typeof $loon } isShadowrocket() { return "undefined" != typeof $rocket } isStash() { return "undefined" != typeof $environment && $environment["stash-version"] } toObj(t, s = null) { try { return JSON.parse(t) } catch { return s } } toStr(t, s = null) { try { return JSON.stringify(t) } catch { return s } } getjson(t, s) { let e = s; const i = this.getdata(t); if (i) try { e = JSON.parse(this.getdata(t)) } catch { } return e } setjson(t, s) { try { return this.setdata(JSON.stringify(t), s) } catch { return !1 } } getScript(t) { return new Promise(s => { this.get({ url: t }, (t, e, i) => s(i)) }) } runScript(t, s) { return new Promise(e => { let i = this.getdata("@chavy_boxjs_userCfgs.httpapi"); i = i ? i.replace(/\n/g, "").trim() : i; let r = this.getdata("@chavy_boxjs_userCfgs.httpapi_timeout"); r = r ? 1 * r : 20, r = s && s.timeout ? s.timeout : r; const [o, h] = i.split("@"), a = { url: `http://${h}/v1/scripting/evaluate`, body: { script_text: t, mock_type: "cron", timeout: r }, headers: { "X-Key": o, Accept: "*/*" }, timeout: r }; this.post(a, (t, s, i) => e(i)) }).catch(t => this.logErr(t)) } loaddata() { if (!this.isNode()) return {}; { this.fs = this.fs ? this.fs : require("fs"), this.path = this.path ? this.path : require("path"); const t = this.path.resolve(this.dataFile), s = this.path.resolve(process.cwd(), this.dataFile), e = this.fs.existsSync(t), i = !e && this.fs.existsSync(s); if (!e && !i) return {}; { const i = e ? t : s; try { return JSON.parse(this.fs.readFileSync(i)) } catch (t) { return {} } } } } writedata() { if (this.isNode()) { this.fs = this.fs ? this.fs : require("fs"), this.path = this.path ? this.path : require("path"); const t = this.path.resolve(this.dataFile), s = this.path.resolve(process.cwd(), this.dataFile), e = this.fs.existsSync(t), i = !e && this.fs.existsSync(s), r = JSON.stringify(this.data); e ? this.fs.writeFileSync(t, r) : i ? this.fs.writeFileSync(s, r) : this.fs.writeFileSync(t, r) } } lodash_get(t, s, e) { const i = s.replace(/\[(\d+)\]/g, ".$1").split("."); let r = t; for (const t of i) if (r = Object(r)[t], void 0 === r) return e; return r } lodash_set(t, s, e) { return Object(t) !== t ? t : (Array.isArray(s) || (s = s.toString().match(/[^.[\]]+/g) || []), s.slice(0, -1).reduce((t, e, i) => Object(t[e]) === t[e] ? t[e] : t[e] = Math.abs(s[i + 1]) >> 0 == +s[i + 1] ? [] : {}, t)[s[s.length - 1]] = e, t) } getdata(t) { let s = this.getval(t); if (/^@/.test(t)) { const [, e, i] = /^@(.*?)\.(.*?)$/.exec(t), r = e ? this.getval(e) : ""; if (r) try { const t = JSON.parse(r); s = t ? this.lodash_get(t, i, "") : s } catch (t) { s = "" } } return s } setdata(t, s) { let e = !1; if (/^@/.test(s)) { const [, i, r] = /^@(.*?)\.(.*?)$/.exec(s), o = this.getval(i), h = i ? "null" === o ? null : o || "{}" : "{}"; try { const s = JSON.parse(h); this.lodash_set(s, r, t), e = this.setval(JSON.stringify(s), i) } catch (s) { const o = {}; this.lodash_set(o, r, t), e = this.setval(JSON.stringify(o), i) } } else e = this.setval(t, s); return e } getval(t) { return this.isSurge() || this.isShadowrocket() || this.isLoon() || this.isStash() ? $persistentStore.read(t) : this.isQuanX() ? $prefs.valueForKey(t) : this.isNode() ? (this.data = this.loaddata(), this.data[t]) : this.data && this.data[t] || null } setval(t, s) { return this.isSurge() || this.isShadowrocket() || this.isLoon() || this.isStash() ? $persistentStore.write(t, s) : this.isQuanX() ? $prefs.setValueForKey(t, s) : this.isNode() ? (this.data = this.loaddata(), this.data[s] = t, this.writedata(), !0) : this.data && this.data[s] || null } initGotEnv(t) { this.got = this.got ? this.got : require("got"), this.cktough = this.cktough ? this.cktough : require("tough-cookie"), this.ckjar = this.ckjar ? this.ckjar : new this.cktough.CookieJar, t && (t.headers = t.headers ? t.headers : {}, void 0 === t.headers.Cookie && void 0 === t.cookieJar && (t.cookieJar = this.ckjar)) } get(t, s = (() => { })) { if (t.headers && (delete t.headers["Content-Type"], delete t.headers["Content-Length"]), this.isSurge() || this.isShadowrocket() || this.isLoon() || this.isStash()) this.isSurge() && this.isNeedRewrite && (t.headers = t.headers || {}, Object.assign(t.headers, { "X-Surge-Skip-Scripting": !1 })), $httpClient.get(t, (t, e, i) => { !t && e && (e.body = i, e.statusCode = e.status ? e.status : e.statusCode, e.status = e.statusCode), s(t, e, i) }); else if (this.isQuanX()) this.isNeedRewrite && (t.opts = t.opts || {}, Object.assign(t.opts, { hints: !1 })), $task.fetch(t).then(t => { const { statusCode: e, statusCode: i, headers: r, body: o } = t; s(null, { status: e, statusCode: i, headers: r, body: o }, o) }, t => s(t && t.error || "UndefinedError")); else if (this.isNode()) { let e = require("iconv-lite"); this.initGotEnv(t), this.got(t).on("redirect", (t, s) => { try { if (t.headers["set-cookie"]) { const e = t.headers["set-cookie"].map(this.cktough.Cookie.parse).toString(); e && this.ckjar.setCookieSync(e, null), s.cookieJar = this.ckjar } } catch (t) { this.logErr(t) } }).then(t => { const { statusCode: i, statusCode: r, headers: o, rawBody: h } = t, a = e.decode(h, this.encoding); s(null, { status: i, statusCode: r, headers: o, rawBody: h, body: a }, a) }, t => { const { message: i, response: r } = t; s(i, r, r && e.decode(r.rawBody, this.encoding)) }) } } post(t, s = (() => { })) { const e = t.method ? t.method.toLocaleLowerCase() : "post"; if (t.body && t.headers && !t.headers["Content-Type"] && (t.headers["Content-Type"] = "application/x-www-form-urlencoded"), t.headers && delete t.headers["Content-Length"], this.isSurge() || this.isShadowrocket() || this.isLoon() || this.isStash()) this.isSurge() && this.isNeedRewrite && (t.headers = t.headers || {}, Object.assign(t.headers, { "X-Surge-Skip-Scripting": !1 })), $httpClient[e](t, (t, e, i) => { !t && e && (e.body = i, e.statusCode = e.status ? e.status : e.statusCode, e.status = e.statusCode), s(t, e, i) }); else if (this.isQuanX()) t.method = e, this.isNeedRewrite && (t.opts = t.opts || {}, Object.assign(t.opts, { hints: !1 })), $task.fetch(t).then(t => { const { statusCode: e, statusCode: i, headers: r, body: o } = t; s(null, { status: e, statusCode: i, headers: r, body: o }, o) }, t => s(t && t.error || "UndefinedError")); else if (this.isNode()) { let i = require("iconv-lite"); this.initGotEnv(t); const { url: r, ...o } = t; this.got[e](r, o).then(t => { const { statusCode: e, statusCode: r, headers: o, rawBody: h } = t, a = i.decode(h, this.encoding); s(null, { status: e, statusCode: r, headers: o, rawBody: h, body: a }, a) }, t => { const { message: e, response: r } = t; s(e, r, r && i.decode(r.rawBody, this.encoding)) }) } } time(t, s = null) { const e = s ? new Date(s) : new Date; let i = { "M+": e.getMonth() + 1, "d+": e.getDate(), "H+": e.getHours(), "m+": e.getMinutes(), "s+": e.getSeconds(), "q+": Math.floor((e.getMonth() + 3) / 3), S: e.getMilliseconds() }; /(y+)/.test(t) && (t = t.replace(RegExp.$1, (e.getFullYear() + "").substr(4 - RegExp.$1.length))); for (let s in i) new RegExp("(" + s + ")").test(t) && (t = t.replace(RegExp.$1, 1 == RegExp.$1.length ? i[s] : ("00" + i[s]).substr(("" + i[s]).length))); return t } queryStr(t) { let s = ""; for (const e in t) { let i = t[e]; null != i && "" !== i && ("object" == typeof i && (i = JSON.stringify(i)), s += `${e}=${i}&`) } return s = s.substring(0, s.length - 1), s } msg(s = t, e = "", i = "", r) { const o = t => { if (!t) return t; if ("string" == typeof t) return this.isLoon() || this.isShadowrocket() ? t : this.isQuanX() ? { "open-url": t } : this.isSurge() || this.isStash() ? { url: t } : void 0; if ("object" == typeof t) { if (this.isLoon()) { let s = t.openUrl || t.url || t["open-url"], e = t.mediaUrl || t["media-url"]; return { openUrl: s, mediaUrl: e } } if (this.isQuanX()) { let s = t["open-url"] || t.url || t.openUrl, e = t["media-url"] || t.mediaUrl, i = t["update-pasteboard"] || t.updatePasteboard; return { "open-url": s, "media-url": e, "update-pasteboard": i } } if (this.isSurge() || this.isShadowrocket() || this.isStash()) { let s = t.url || t.openUrl || t["open-url"]; return { url: s } } } }; if (this.isMute || (this.isSurge() || this.isShadowrocket() || this.isLoon() || this.isStash() ? $notification.post(s, e, i, o(r)) : this.isQuanX() && $notify(s, e, i, o(r))), !this.isMuteLog) { let t = ["", "==============\ud83d\udce3\u7cfb\u7edf\u901a\u77e5\ud83d\udce3=============="]; t.push(s), e && t.push(e), i && t.push(i), console.log(t.join("\n")), this.logs = this.logs.concat(t) } } log(...t) { t.length > 0 && (this.logs = [...this.logs, ...t]), console.log(t.join(this.logSeparator)) } logErr(t, s) { const e = !(this.isSurge() || this.isShadowrocket() || this.isQuanX() || this.isLoon() || this.isStash()); e ? this.log("", `\u2757\ufe0f${this.name}, \u9519\u8bef!`, t.stack) : this.log("", `\u2757\ufe0f${this.name}, \u9519\u8bef!`, t) } wait(t) { return new Promise(s => setTimeout(s, t)) } done(t = {}) { const s = (new Date).getTime(), e = (s - this.startTime) / 1e3; this.log("", `\ud83d\udd14${this.name}, \u7ed3\u675f! \ud83d\udd5b ${e} \u79d2`), this.log(), this.isSurge() || this.isShadowrocket() || this.isQuanX() || this.isLoon() || this.isStash() ? $done(t) : this.isNode() && process.exit(1) } }(t, s) }