chavy.boxjs.js 41 KB

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