ToolKit.js 52 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338
  1. /**
  2. * 根据自己的习惯整合各个开发者而形成的工具包(@NobyDa, @chavyleung)
  3. * 兼容surge,quantumult x,loon,node环境
  4. * 并且加入一些好用的方法
  5. * 方法如下:
  6. * isEmpty: 判断字符串是否是空(undefined,null,空串)
  7. * getRequestUrl: 获取请求的url(目前仅支持surge和quanx)
  8. * getResponseBody: 获取响应体(目前仅支持surge和quanx)
  9. * boxJsJsonBuilder:构建最简默认boxjs配置json
  10. * randomString: 生成随机字符串
  11. * autoComplete: 自动补齐字符串
  12. * customReplace: 自定义替换
  13. * hash: 字符串做hash
  14. *
  15. * ⚠️当开启当且仅当执行失败的时候通知选项,请在执行失败的地方执行execFail()
  16. *
  17. * @param scriptName 脚本名,用于通知时候的标题
  18. * @param scriptId 每个脚本唯一的id,用于存储持久化的时候加入key
  19. * @param options 传入一些参数,目前参数如下;
  20. * [email protected]:6166(这个是默认值,本人surge调试脚本用,可自行修改)
  21. * target_boxjs_json_path=/Users/lowking/Desktop/Scripts/lowking.boxjs.json(生成boxjs配置的目标文件路径)
  22. * @constructor
  23. */
  24. function ToolKit(scriptName, scriptId, options) {
  25. return new (class {
  26. constructor(scriptName, scriptId, options) {
  27. this.tgEscapeCharMapping = { '&': '&', '#': '#' }
  28. this.userAgent = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0.2 Safari/605.1.15`
  29. this.prefix = `lk`
  30. this.name = scriptName
  31. this.id = scriptId
  32. this.data = null
  33. this.dataFile = this.getRealPath(`${this.prefix}${this.id}.dat`)
  34. this.boxJsJsonFile = this.getRealPath(`${this.prefix}${this.id}.boxjs.json`)
  35. //surge http api等一些扩展参数
  36. this.options = options
  37. //命令行入参
  38. this.isExecComm = false
  39. //默认脚本开关
  40. this.isEnableLog = this.getVal(`${this.prefix}IsEnableLog${this.id}`)
  41. this.isEnableLog = this.isEmpty(this.isEnableLog) ? true : JSON.parse(this.isEnableLog)
  42. this.isNotifyOnlyFail = this.getVal(`${this.prefix}NotifyOnlyFail${this.id}`)
  43. this.isNotifyOnlyFail = this.isEmpty(this.isNotifyOnlyFail) ? false : JSON.parse(this.isNotifyOnlyFail)
  44. //tg通知开关
  45. this.isEnableTgNotify = this.getVal(`${this.prefix}IsEnableTgNotify${this.id}`)
  46. this.isEnableTgNotify = this.isEmpty(this.isEnableTgNotify) ? false : JSON.parse(this.isEnableTgNotify)
  47. this.tgNotifyUrl = this.getVal(`${this.prefix}TgNotifyUrl${this.id}`)
  48. this.isEnableTgNotify = this.isEnableTgNotify ? !this.isEmpty(this.tgNotifyUrl) : this.isEnableTgNotify
  49. //计时部分
  50. this.costTotalStringKey = `${this.prefix}CostTotalString${this.id}`
  51. this.costTotalString = this.getVal(this.costTotalStringKey)
  52. this.costTotalString = this.isEmpty(this.costTotalString) ? `0,0` : this.costTotalString.replace("\"", "")
  53. this.costTotalMs = this.costTotalString.split(",")[0]
  54. this.execCount = this.costTotalString.split(",")[1]
  55. this.costTotalMs = this.isEmpty(this.costTotalMs) ? 0 : parseInt(this.costTotalMs)
  56. this.execCount = this.isEmpty(this.execCount) ? 0 : parseInt(this.execCount)
  57. this.logSeparator = '\n██'
  58. this.now = new Date()
  59. this.startTime = this.now.getTime()
  60. this.node = (() => {
  61. if (this.isNode()) {
  62. const request = require('request')
  63. return ({ request })
  64. } else {
  65. return (null)
  66. }
  67. })()
  68. this.execStatus = true
  69. this.notifyInfo = []
  70. this.log(`${this.name}, 开始执行!`)
  71. this.initCache()
  72. this.checkRecordRequestBody()
  73. this.execComm()
  74. }
  75. checkRecordRequestBody() {
  76. if (!this.isRequest()) {
  77. return;
  78. }
  79. const reqBody = $request.body;
  80. if (!reqBody) {
  81. return;
  82. }
  83. const path = $request.path;
  84. let cacheKey = this.id + "#" + path.replace("/", "_");
  85. cacheKey = cacheKey.replace("?", "#");
  86. if (this.isQuanX()) $prefs.setValueForKey(reqBody, cacheKey);
  87. if (this.isLoon() || this.isSurge()) $persistentStore.write(reqBody, cacheKey);
  88. if (this.isNode()) {
  89. this.node.fs.writeFileSync(
  90. `${cacheKey}.json`,
  91. reqBody,
  92. {
  93. flag: "w"
  94. },
  95. (err) => console.log(err)
  96. );
  97. }
  98. }
  99. getRequestBody() {
  100. const path = $request.path;
  101. let cacheKey = this.id + "#" + path.replace("/", "_");
  102. cacheKey = cacheKey.replace("?", "#");
  103. if (this.isSurge() || this.isLoon()) {
  104. return $persistentStore.read(cacheKey);
  105. }
  106. if (this.isQuanX()) {
  107. return $prefs.valueForKey(cacheKey);
  108. }
  109. if (this.isNode()) {
  110. const fpath = `${cacheKey}.json`;
  111. if (!this.node.fs.existsSync(fpath)) {
  112. return JSON.parse(
  113. this.node.fs.readFileSync(fpath)
  114. );
  115. }
  116. }
  117. }
  118. // persistence
  119. // initialize cache
  120. initCache() {
  121. const pKey = this.getPersistKey();
  122. if (this.isQuanX()) this.cache = JSON.parse($prefs.valueForKey(pKey) || "{}");
  123. if (this.isLoon() || this.isSurge())
  124. this.cache = JSON.parse($persistentStore.read(pKey) || "{}");
  125. if (this.isNode()) {
  126. // create a json for root cache
  127. let fpath = "root.json";
  128. if (!this.node.fs.existsSync(fpath)) {
  129. this.node.fs.writeFileSync(
  130. fpath,
  131. JSON.stringify({}),
  132. {
  133. flag: "wx"
  134. },
  135. (err) => console.log(err)
  136. );
  137. }
  138. this.root = {};
  139. // create a json file with the given name if not exists
  140. fpath = `${pKey}.json`;
  141. if (!this.node.fs.existsSync(fpath)) {
  142. this.node.fs.writeFileSync(
  143. fpath,
  144. JSON.stringify({}),
  145. {
  146. flag: "wx"
  147. },
  148. (err) => console.log(err)
  149. );
  150. this.cache = {};
  151. } else {
  152. this.cache = JSON.parse(
  153. this.node.fs.readFileSync(`${pKey}.json`)
  154. );
  155. }
  156. }
  157. }
  158. getPersistKey() {
  159. return `${this.id}#privateCache`;
  160. }
  161. // store cache
  162. persistCache() {
  163. const pKey = this.getPersistKey();
  164. const data = JSON.stringify(this.cache, null, 2);
  165. if (this.isQuanX()) $prefs.setValueForKey(data, pKey);
  166. if (this.isLoon() || this.isSurge()) $persistentStore.write(data, pKey);
  167. if (this.isNode()) {
  168. this.node.fs.writeFileSync(
  169. `${pKey}.json`,
  170. data,
  171. {
  172. flag: "w"
  173. },
  174. (err) => console.log(err)
  175. );
  176. this.node.fs.writeFileSync(
  177. "root.json",
  178. JSON.stringify(this.root, null, 2),
  179. {
  180. flag: "w"
  181. },
  182. (err) => console.log(err)
  183. );
  184. }
  185. }
  186. write(data, key) {
  187. this.log(`SET ${key}`);
  188. if (key.indexOf("#") !== -1) {
  189. key = key.substr(1);
  190. if (isSurge || this.isLoon()) {
  191. return $persistentStore.write(data, key);
  192. }
  193. if (this.isQuanX()) {
  194. return $prefs.setValueForKey(data, key);
  195. }
  196. if (this.isNode()) {
  197. this.root[key] = data;
  198. }
  199. } else {
  200. this.cache[key] = data;
  201. }
  202. this.persistCache();
  203. }
  204. read(key) {
  205. this.log(`READ ${key}`);
  206. if (key.indexOf("#") !== -1) {
  207. key = key.substr(1);
  208. if (this.isSurge() || this.isLoon()) {
  209. return $persistentStore.read(key);
  210. }
  211. if (this.isQuanX()) {
  212. return $prefs.valueForKey(key);
  213. }
  214. if (this.isNode()) {
  215. return this.root[key];
  216. }
  217. } else {
  218. return this.cache[key];
  219. }
  220. }
  221. delete(key) {
  222. this.log(`DELETE ${key}`);
  223. if (key.indexOf("#") !== -1) {
  224. key = key.substr(1);
  225. if (this.isSurge() || this.isLoon()) {
  226. return $persistentStore.write(null, key);
  227. }
  228. if (this.isQuanX()) {
  229. return $prefs.removeValueForKey(key);
  230. }
  231. if (this.isNode()) {
  232. delete this.root[key];
  233. }
  234. } else {
  235. delete this.cache[key];
  236. }
  237. this.persistCache();
  238. }
  239. //当执行命令的目录不是脚本所在目录时,自动把文件路径改成指令传入的路径并返回完整文件路径
  240. getRealPath(fileName) {
  241. if (this.isNode()) {
  242. let targetPath = process.argv.slice(1, 2)[0].split("/")
  243. targetPath[targetPath.length - 1] = fileName
  244. return targetPath.join("/")
  245. }
  246. return fileName
  247. }
  248. /**
  249. * http://boxjs.com/ => http://boxjs.com
  250. * http://boxjs.com/app/jd => http://boxjs.com
  251. */
  252. getUrlHost(url) {
  253. return url.slice(0, url.indexOf('/', 8))
  254. }
  255. /**
  256. * http://boxjs.com/ =>
  257. * http://boxjs.com/api/getdata => /api/getdata
  258. */
  259. getUrlPath(url) {
  260. // 如果以结尾, 去掉最后一个/
  261. const end = url.lastIndexOf('/') === url.length - 1 ? -1 : undefined
  262. // slice第二个参数传 undefined 会直接截到最后
  263. // indexOf第二个参数用来跳过前面的 "https://"
  264. return url.slice(url.indexOf('/', 8), end)
  265. }
  266. async execComm() {
  267. //支持node命令,实现发送手机测试
  268. if (this.isNode()) {
  269. this.comm = process.argv.slice(1)
  270. let isHttpApiErr = false
  271. if (this.comm[1] == "p") {
  272. this.isExecComm = true
  273. //phone
  274. this.log(`开始执行指令【${this.comm[1]}】=> 发送到手机测试脚本!`);
  275. if (this.isEmpty(this.options) || this.isEmpty(this.options.httpApi)) {
  276. this.log(`未设置options,使用默认值`)
  277. //设置默认值
  278. if (this.isEmpty(this.options)) {
  279. this.options = {}
  280. }
  281. this.options.httpApi = `[email protected]:6166`
  282. } else {
  283. //判断格式
  284. if (!/.*?@.*?:[0-9]+/.test(this.options.httpApi)) {
  285. isHttpApiErr = true
  286. this.log(`❌httpApi格式错误!格式:[email protected]:6166`)
  287. this.done()
  288. }
  289. }
  290. if (!isHttpApiErr) {
  291. this.callApi(this.comm[2])
  292. }
  293. }
  294. }
  295. }
  296. callApi(timeout) {
  297. // 直接用接收到文件路径,解决在不同目录下都可以使用 node xxxx/xxx.js p 指令发送脚本给手机执行
  298. // let fname = this.getCallerFileNameAndLine().split(":")[0].replace("[", "")
  299. let fname = this.comm[0]
  300. this.log(`获取【${fname}】内容传给手机`)
  301. let scriptStr = ''
  302. this.fs = this.fs ? this.fs : require('fs')
  303. this.path = this.path ? this.path : require('path')
  304. const curDirDataFilePath = this.path.resolve(fname)
  305. const rootDirDataFilePath = this.path.resolve(process.cwd(), fname)
  306. const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath)
  307. const isRootDirDataFile = !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath)
  308. if (isCurDirDataFile || isRootDirDataFile) {
  309. const datPath = isCurDirDataFile ? curDirDataFilePath : rootDirDataFilePath
  310. try {
  311. scriptStr = this.fs.readFileSync(datPath)
  312. } catch (e) {
  313. scriptStr = ''
  314. }
  315. } else {
  316. scriptStr = ''
  317. }
  318. let options = {
  319. url: `http://${this.options.httpApi.split("@")[1]}/v1/scripting/evaluate`,
  320. headers: {
  321. "X-Key": `${this.options.httpApi.split("@")[0]}`
  322. },
  323. body: {
  324. "script_text": `${scriptStr}`,
  325. "mock_type": "cron",
  326. "timeout": (!this.isEmpty(timeout) && timeout > 5) ? timeout : 5
  327. },
  328. json: true
  329. }
  330. this.post(options, (_error, _response, _data) => {
  331. this.log(`已将脚本【${fname}】发给手机!`)
  332. this.done()
  333. })
  334. }
  335. getCallerFileNameAndLine() {
  336. let error
  337. try {
  338. throw Error('')
  339. } catch (err) {
  340. error = err
  341. }
  342. const stack = error.stack
  343. const stackArr = stack.split('\n')
  344. let callerLogIndex = 1
  345. if (callerLogIndex !== 0) {
  346. const callerStackLine = stackArr[callerLogIndex]
  347. this.path = this.path ? this.path : require('path')
  348. return `[${callerStackLine.substring(callerStackLine.lastIndexOf(this.path.sep) + 1, callerStackLine.lastIndexOf(':'))}]`
  349. } else {
  350. return '[-]'
  351. }
  352. }
  353. getFunName(fun) {
  354. var ret = fun.toString()
  355. ret = ret.substr('function '.length)
  356. ret = ret.substr(0, ret.indexOf('('))
  357. return ret
  358. }
  359. boxJsJsonBuilder(info, param) {
  360. if (this.isNode()) {
  361. let boxjsJsonPath = "/Users/lowking/Desktop/Scripts/lowking.boxjs.json"
  362. // 从传入参数param读取配置的boxjs的json文件路径
  363. if (param && param.hasOwnProperty("target_boxjs_json_path")) {
  364. boxjsJsonPath = param["target_boxjs_json_path"]
  365. }
  366. if (!this.fs.existsSync(boxjsJsonPath)) {
  367. return
  368. }
  369. if (!this.isJsonObject(info) || !this.isJsonObject(param)) {
  370. this.log("构建BoxJsJson传入参数格式错误,请传入json对象")
  371. return
  372. }
  373. this.log('using node')
  374. let needAppendKeys = ["settings", "keys"]
  375. const domain = 'https://raw.githubusercontent.com/Orz-3'
  376. let boxJsJson = {}
  377. let scritpUrl = '#lk{script_url}'
  378. if (param && param.hasOwnProperty('script_url')) {
  379. scritpUrl = this.isEmpty(param['script_url']) ? "#lk{script_url}" : param['script_url']
  380. }
  381. boxJsJson.id = `${this.prefix}${this.id}`
  382. boxJsJson.name = this.name
  383. boxJsJson.desc_html = `⚠️使用说明</br>详情【<a href='${scritpUrl}?raw=true'><font class='red--text'>点我查看</font></a>】`
  384. boxJsJson.icons = [`${domain}/mini/master/Alpha/${this.id.toLocaleLowerCase()}.png`, `${domain}/mini/master/Color/${this.id.toLocaleLowerCase()}.png`]
  385. boxJsJson.keys = []
  386. boxJsJson.settings = [
  387. {
  388. "id": `${this.prefix}IsEnableLog${this.id}`,
  389. "name": "开启/关闭日志",
  390. "val": true,
  391. "type": "boolean",
  392. "desc": "默认开启"
  393. },
  394. {
  395. "id": `${this.prefix}NotifyOnlyFail${this.id}`,
  396. "name": "只当执行失败才通知",
  397. "val": false,
  398. "type": "boolean",
  399. "desc": "默认关闭"
  400. },
  401. {
  402. "id": `${this.prefix}IsEnableTgNotify${this.id}`,
  403. "name": "开启/关闭Telegram通知",
  404. "val": false,
  405. "type": "boolean",
  406. "desc": "默认关闭"
  407. },
  408. {
  409. "id": `${this.prefix}TgNotifyUrl${this.id}`,
  410. "name": "Telegram通知地址",
  411. "val": "",
  412. "type": "text",
  413. "desc": "Tg的通知地址,如:https://api.telegram.org/bot-token/sendMessage?chat_id=-100140&parse_mode=Markdown&text="
  414. }
  415. ]
  416. boxJsJson.author = "#lk{author}"
  417. boxJsJson.repo = "#lk{repo}"
  418. boxJsJson.script = `${scritpUrl}?raw=true`
  419. // 除了settings和keys追加,其他的都覆盖
  420. if (!this.isEmpty(info)) {
  421. for (let i in needAppendKeys) {
  422. let key = needAppendKeys[i]
  423. if (!this.isEmpty(info[key])) {
  424. // 处理传入的每项设置
  425. if (key === 'settings') {
  426. for (let i = 0; i < info[key].length; i++) {
  427. let input = info[key][i]
  428. for (let j = 0; j < boxJsJson.settings.length; j++) {
  429. let def = boxJsJson.settings[j]
  430. if (input.id === def.id) {
  431. // id相同,就使用外部传入的配置
  432. boxJsJson.settings.splice(j, 1)
  433. }
  434. }
  435. }
  436. }
  437. boxJsJson[key] = boxJsJson[key].concat(info[key])
  438. }
  439. delete info[key]
  440. }
  441. }
  442. Object.assign(boxJsJson, info)
  443. if (this.isNode()) {
  444. this.fs = this.fs ? this.fs : require('fs')
  445. this.path = this.path ? this.path : require('path')
  446. const curDirDataFilePath = this.path.resolve(this.boxJsJsonFile)
  447. const rootDirDataFilePath = this.path.resolve(process.cwd(), this.boxJsJsonFile)
  448. const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath)
  449. const isRootDirDataFile = !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath)
  450. const jsondata = JSON.stringify(boxJsJson, null, '\t')
  451. if (isCurDirDataFile) {
  452. this.fs.writeFileSync(curDirDataFilePath, jsondata)
  453. } else if (isRootDirDataFile) {
  454. this.fs.writeFileSync(rootDirDataFilePath, jsondata)
  455. } else {
  456. this.fs.writeFileSync(curDirDataFilePath, jsondata)
  457. }
  458. // 写到项目的boxjs订阅json中
  459. let boxjsJson = JSON.parse(this.fs.readFileSync(boxjsJsonPath))
  460. if (boxjsJson.hasOwnProperty("apps") && Array.isArray(boxjsJson["apps"]) && boxjsJson["apps"].length > 0) {
  461. let apps = boxjsJson.apps
  462. let targetIdx = apps.indexOf(apps.filter((app) => {
  463. return app.id == boxJsJson.id
  464. })[0])
  465. if (targetIdx >= 0) {
  466. boxjsJson.apps[targetIdx] = boxJsJson
  467. } else {
  468. boxjsJson.apps.push(boxJsJson)
  469. }
  470. let ret = JSON.stringify(boxjsJson, null, 2)
  471. if (!this.isEmpty(param)) {
  472. for (const key in param) {
  473. let val = ''
  474. if (param.hasOwnProperty(key)) {
  475. val = param[key]
  476. } else if (key === 'author') {
  477. val = '@lowking'
  478. } else if (key === 'repo') {
  479. val = 'https://github.com/lowking/Scripts'
  480. }
  481. ret = ret.replace(`#lk{${key}}`, val)
  482. }
  483. }
  484. // 全部处理完毕检查是否有漏掉未配置的参数,进行提醒
  485. const regex = /(?:#lk\{)(.+?)(?=\})/
  486. let m = regex.exec(ret)
  487. if (m !== null) {
  488. this.log('生成BoxJs还有未配置的参数,请参考https://github.com/lowking/Scripts/blob/master/util/example/ToolKitDemo.js#L17-L18传入参数:\n')
  489. }
  490. let loseParamSet = new Set()
  491. while ((m = regex.exec(ret)) !== null) {
  492. loseParamSet.add(m[1])
  493. ret = ret.replace(`#lk{${m[1]}}`, ``)
  494. }
  495. loseParamSet.forEach(p => {
  496. console.log(`${p} `)
  497. })
  498. this.fs.writeFileSync(boxjsJsonPath, ret)
  499. }
  500. }
  501. }
  502. }
  503. isJsonObject(obj) {
  504. return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && !obj.length
  505. }
  506. appendNotifyInfo(info, type) {
  507. if (type == 1) {
  508. this.notifyInfo = info
  509. } else {
  510. this.notifyInfo.push(info)
  511. }
  512. }
  513. prependNotifyInfo(info) {
  514. this.notifyInfo.splice(0, 0, info)
  515. }
  516. execFail() {
  517. this.execStatus = false
  518. }
  519. isRequest() {
  520. return typeof $request != "undefined"
  521. }
  522. isSurge() {
  523. return typeof $httpClient != "undefined"
  524. }
  525. isQuanX() {
  526. return typeof $task != "undefined"
  527. }
  528. isLoon() {
  529. return typeof $loon != "undefined"
  530. }
  531. isJSBox() {
  532. return typeof $app != "undefined" && typeof $http != "undefined"
  533. }
  534. isStash() {
  535. return 'undefined' !== typeof $environment && $environment['stash-version']
  536. }
  537. isNode() {
  538. return typeof require == "function" && !this.isJSBox()
  539. }
  540. async sleep(time) {
  541. return new Promise((resolve) => setTimeout(resolve, time));
  542. }
  543. async wait(time) {
  544. return new Promise((resolve) => setTimeout(resolve, time));
  545. }
  546. async delay(time) {
  547. return new Promise((resolve) => setTimeout(resolve, time));
  548. }
  549. log(message) {
  550. if (this.isEnableLog) console.log(`${this.logSeparator}${message}`)
  551. }
  552. logErr(message) {
  553. this.execStatus = true
  554. if (this.isEnableLog) {
  555. console.log(`${this.logSeparator}${this.name}执行异常:`)
  556. console.log(message)
  557. console.log('\n' + `${message.message}`)
  558. }
  559. }
  560. msg(subtitle, message, openUrl, mediaUrl) {
  561. if (!this.isRequest() && this.isNotifyOnlyFail && this.execStatus) {
  562. //开启了当且仅当执行失败的时候通知,并且执行成功了,这时候不通知
  563. } else {
  564. if (this.isEmpty(message)) {
  565. if (Array.isArray(this.notifyInfo)) {
  566. message = this.notifyInfo.join("\n")
  567. } else {
  568. message = this.notifyInfo
  569. }
  570. }
  571. if (!this.isEmpty(message)) {
  572. if (this.isEnableTgNotify) {
  573. this.log(`${this.name}Tg通知开始`)
  574. //处理特殊字符
  575. for (let key in this.tgEscapeCharMapping) {
  576. if (!this.tgEscapeCharMapping.hasOwnProperty(key)) {
  577. continue
  578. }
  579. message = message.replace(key, this.tgEscapeCharMapping[key])
  580. }
  581. this.get({
  582. url: encodeURI(`${this.tgNotifyUrl}📌${this.name}` + '\n' + `${message}`)
  583. }, (_error, _statusCode, _body) => {
  584. this.log(`Tg通知完毕`)
  585. })
  586. } else {
  587. let options = {}
  588. const hasOpenUrl = !this.isEmpty(openUrl)
  589. const hasMediaUrl = !this.isEmpty(mediaUrl)
  590. if (this.isQuanX()) {
  591. if (hasOpenUrl) options["open-url"] = openUrl
  592. if (hasMediaUrl) options["media-url"] = mediaUrl
  593. $notify(this.name, subtitle, message, options)
  594. }
  595. if (this.isSurge() || this.isStash()) {
  596. if (hasOpenUrl) options["url"] = openUrl
  597. $notification.post(this.name, subtitle, message, options)
  598. }
  599. if (this.isNode()) this.log("⭐️" + this.name + "\n" + subtitle + "\n" + message)
  600. if (this.isJSBox()) $push.schedule({
  601. title: this.name,
  602. body: subtitle ? subtitle + "\n" + message : message
  603. })
  604. }
  605. }
  606. }
  607. }
  608. pushWxMsg(summary, content, url, callback = () => { }) {
  609. let data = {
  610. appToken: "AT_rTc93GQYIdMU8XLRnoJaSea8WkfhSzhX",
  611. content: content,
  612. summary: summary,
  613. contentType: 1,
  614. topicIds: [],
  615. uids: [
  616. "UID_6P4B00X6Zv8U2oKC0I2R09emxtqq"
  617. ],
  618. url: "",
  619. verifyPay: false
  620. };
  621. if (url) {
  622. data.url = url;
  623. }
  624. const headers = this.getJsonDoneHeaders();
  625. headers.Host = 'wxpusher.zjiecode.com';
  626. headers['Content-Type'] = 'application/json;charset=UTF-8';
  627. let options = {
  628. url: 'https://wxpusher.zjiecode.com/api/send/message',
  629. headers: headers,
  630. body: JSON.stringify(data),
  631. };
  632. this.post(options, callback);
  633. }
  634. getVal(key, defaultValue = "") {
  635. let value
  636. if (this.isSurge() || this.isLoon() || this.isStash()) {
  637. value = $persistentStore.read(key)
  638. } else if (this.isQuanX()) {
  639. value = $prefs.valueForKey(key)
  640. } else if (this.isNode()) {
  641. this.data = this.loadData()
  642. value = process.env[key] || this.data[key]
  643. } else {
  644. value = (this.data && this.data[key]) || null
  645. }
  646. return !value ? defaultValue : value
  647. }
  648. setVal(key, val) {
  649. if (this.isSurge() || this.isLoon() || this.isStash()) {
  650. return $persistentStore.write(val, key)
  651. } else if (this.isQuanX()) {
  652. return $prefs.setValueForKey(val, key)
  653. } else if (this.isNode()) {
  654. this.data = this.loadData()
  655. this.data[key] = val
  656. this.writeData()
  657. return true
  658. } else {
  659. return (this.data && this.data[key]) || null
  660. }
  661. }
  662. loadData() {
  663. if (this.isNode()) {
  664. this.fs = this.fs ? this.fs : require('fs')
  665. this.path = this.path ? this.path : require('path')
  666. const curDirDataFilePath = this.path.resolve(this.dataFile)
  667. const rootDirDataFilePath = this.path.resolve(process.cwd(), this.dataFile)
  668. const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath)
  669. const isRootDirDataFile = !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath)
  670. if (isCurDirDataFile || isRootDirDataFile) {
  671. const datPath = isCurDirDataFile ? curDirDataFilePath : rootDirDataFilePath
  672. try {
  673. return JSON.parse(this.fs.readFileSync(datPath))
  674. } catch (e) {
  675. return {}
  676. }
  677. } else return {}
  678. } else return {}
  679. }
  680. writeData() {
  681. if (this.isNode()) {
  682. this.fs = this.fs ? this.fs : require('fs')
  683. this.path = this.path ? this.path : require('path')
  684. const curDirDataFilePath = this.path.resolve(this.dataFile)
  685. const rootDirDataFilePath = this.path.resolve(process.cwd(), this.dataFile)
  686. const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath)
  687. const isRootDirDataFile = !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath)
  688. const jsondata = JSON.stringify(this.data)
  689. if (isCurDirDataFile) {
  690. this.fs.writeFileSync(curDirDataFilePath, jsondata)
  691. } else if (isRootDirDataFile) {
  692. this.fs.writeFileSync(rootDirDataFilePath, jsondata)
  693. } else {
  694. this.fs.writeFileSync(curDirDataFilePath, jsondata)
  695. }
  696. }
  697. }
  698. adapterStatus(response) {
  699. if (response) {
  700. if (response.status) {
  701. response["statusCode"] = response.status
  702. } else if (response.statusCode) {
  703. response["status"] = response.statusCode
  704. }
  705. }
  706. return response
  707. }
  708. get(options, callback = () => { }) {
  709. if (this.isQuanX()) {
  710. if (typeof options == "string") options = {
  711. url: options
  712. }
  713. options["method"] = "GET"
  714. $task.fetch(options).then(response => {
  715. callback(null, this.adapterStatus(response), response.body)
  716. }, reason => callback(reason.error, null, null))
  717. }
  718. if (this.isSurge() || this.isLoon() || this.isStash()) $httpClient.get(options, (error, response, body) => {
  719. callback(error, this.adapterStatus(response), body)
  720. })
  721. if (this.isNode()) {
  722. this.node.request(options, (error, response, body) => {
  723. callback(error, this.adapterStatus(response), body)
  724. })
  725. }
  726. if (this.isJSBox()) {
  727. if (typeof options == "string") options = {
  728. url: options
  729. }
  730. options["header"] = options["headers"]
  731. options["handler"] = function (resp) {
  732. let error = resp.error
  733. if (error) error = JSON.stringify(resp.error)
  734. let body = resp.data
  735. if (typeof body == "object") body = JSON.stringify(resp.data)
  736. callback(error, this.adapterStatus(resp.response), body)
  737. }
  738. $http.get(options)
  739. }
  740. }
  741. post(options, callback = () => { }) {
  742. if (this.isQuanX()) {
  743. if (typeof options == "string") options = {
  744. url: options
  745. }
  746. options["method"] = "POST"
  747. $task.fetch(options).then(response => {
  748. callback(null, this.adapterStatus(response), response.body)
  749. }, reason => callback(reason.error, null, null))
  750. }
  751. if (this.isSurge() || this.isLoon() || this.isStash()) {
  752. $httpClient.post(options, (error, response, body) => {
  753. callback(error, this.adapterStatus(response), body)
  754. })
  755. }
  756. if (this.isNode()) {
  757. this.node.request.post(options, (error, response, body) => {
  758. callback(error, this.adapterStatus(response), body)
  759. })
  760. }
  761. if (this.isJSBox()) {
  762. if (typeof options == "string") options = {
  763. url: options
  764. }
  765. options["header"] = options["headers"]
  766. options["handler"] = function (resp) {
  767. let error = resp.error
  768. if (error) error = JSON.stringify(resp.error)
  769. let body = resp.data
  770. if (typeof body == "object") body = JSON.stringify(resp.data)
  771. callback(error, this.adapterStatus(resp.response), body)
  772. }
  773. $http.post(options)
  774. }
  775. }
  776. put(options, callback = () => { }) {
  777. if (this.isQuanX()) {
  778. // no test
  779. if (typeof options == "string") options = {
  780. url: options
  781. }
  782. options["method"] = "PUT"
  783. $task.fetch(options).then(response => {
  784. callback(null, this.adapterStatus(response), response.body)
  785. }, reason => callback(reason.error, null, null))
  786. }
  787. if (this.isSurge() || this.isLoon() || this.isStash()) {
  788. options.method = "PUT"
  789. $httpClient.put(options, (error, response, body) => {
  790. callback(error, this.adapterStatus(response), body)
  791. })
  792. }
  793. if (this.isNode()) {
  794. options.method = "PUT"
  795. this.node.request.put(options, (error, response, body) => {
  796. callback(error, this.adapterStatus(response), body)
  797. })
  798. }
  799. if (this.isJSBox()) {
  800. // no test
  801. if (typeof options == "string") options = {
  802. url: options
  803. }
  804. options["header"] = options["headers"]
  805. options["handler"] = function (resp) {
  806. let error = resp.error
  807. if (error) error = JSON.stringify(resp.error)
  808. let body = resp.data
  809. if (typeof body == "object") body = JSON.stringify(resp.data)
  810. callback(error, this.adapterStatus(resp.response), body)
  811. }
  812. $http.post(options)
  813. }
  814. }
  815. costTime() {
  816. let info = `${this.name}执行完毕!`
  817. if (this.isNode() && this.isExecComm) {
  818. info = `指令【${this.comm[1]}】执行完毕!`
  819. }
  820. const endTime = new Date().getTime()
  821. const ms = endTime - this.startTime
  822. const costTime = ms / 1000
  823. this.execCount++
  824. this.costTotalMs += ms
  825. this.log(`${info}耗时【${costTime}】秒\n总共执行【${this.execCount}】次,平均耗时【${((this.costTotalMs / this.execCount) / 1000).toFixed(4)}】秒`)
  826. this.setVal(this.costTotalStringKey, JSON.stringify(`${this.costTotalMs},${this.execCount}`))
  827. // this.setVal(this.execCountKey, JSON.stringify(0))
  828. // this.setVal(this.costTotalMsKey, JSON.stringify(0))
  829. }
  830. done(value = {}) {
  831. this.costTime()
  832. if (this.isSurge() || this.isQuanX() || this.isLoon() || this.isStash()) {
  833. $done(value)
  834. }
  835. }
  836. getRequestUrl() {
  837. return $request.url
  838. }
  839. getResponseBody() {
  840. if ($response) {
  841. return $response.body
  842. }
  843. }
  844. isGetCookie(reg) {
  845. return !!($request.method != 'OPTIONS' && this.getRequestUrl().match(reg))
  846. }
  847. isEmpty(obj) {
  848. return typeof obj == "undefined" || obj == null || obj == "" || obj == "null" || obj == "undefined" || obj.length === 0
  849. }
  850. randomString(len) {
  851. len = len || 32
  852. var $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'
  853. var maxPos = $chars.length
  854. var pwd = ''
  855. for (let i = 0; i < len; i++) {
  856. pwd += $chars.charAt(Math.floor(Math.random() * maxPos))
  857. }
  858. return pwd
  859. }
  860. /**
  861. * 自动补齐字符串
  862. * @param str 原始字符串
  863. * @param prefix 前缀
  864. * @param suffix 后缀
  865. * @param fill 补齐用字符
  866. * @param len 目标补齐长度,不包含前后缀
  867. * @param direction 方向:0往后补齐
  868. * @param ifCode 是否打码
  869. * @param clen 打码长度
  870. * @param startIndex 起始坐标
  871. * @param cstr 打码字符
  872. * @returns {*}
  873. */
  874. autoComplete(str, prefix, suffix, fill, len, direction, ifCode, clen, startIndex, cstr) {
  875. str += ''
  876. if (str.length < len) {
  877. while (str.length < len) {
  878. if (direction == 0) {
  879. str += fill
  880. } else {
  881. str = fill + str
  882. }
  883. }
  884. }
  885. if (ifCode) {
  886. let temp = ''
  887. for (var i = 0; i < clen; i++) {
  888. temp += cstr
  889. }
  890. str = str.substring(0, startIndex) + temp + str.substring(clen + startIndex)
  891. }
  892. str = prefix + str + suffix
  893. return this.toDBC(str)
  894. }
  895. /**
  896. * @param str 源字符串 "#{code}, #{value}"
  897. * @param param 用于替换的数据,结构如下
  898. * @param prefix 前缀 "#{"
  899. * @param suffix 后缀 "}"
  900. * {
  901. * "code": 1,
  902. * "value": 2
  903. * }
  904. * 按上面的传入,输出为"1, 2"
  905. * 对应的#{code}用param里面code的值替换,#{value}也是
  906. * @returns {*|void|string}
  907. */
  908. customReplace(str, param, prefix, suffix) {
  909. try {
  910. if (this.isEmpty(prefix)) {
  911. prefix = "#{"
  912. }
  913. if (this.isEmpty(suffix)) {
  914. suffix = "}"
  915. }
  916. for (let i in param) {
  917. str = str.replace(`${prefix}${i}${suffix}`, param[i])
  918. }
  919. } catch (e) {
  920. this.logErr(e)
  921. }
  922. return str
  923. }
  924. toDBC(txtstring) {
  925. var tmp = ""
  926. for (var i = 0; i < txtstring.length; i++) {
  927. if (txtstring.charCodeAt(i) == 32) {
  928. tmp = tmp + String.fromCharCode(12288)
  929. } else if (txtstring.charCodeAt(i) < 127) {
  930. tmp = tmp + String.fromCharCode(txtstring.charCodeAt(i) + 65248)
  931. }
  932. }
  933. return tmp
  934. }
  935. hash(str) {
  936. let h = 0,
  937. i,
  938. chr
  939. for (i = 0; i < str.length; i++) {
  940. chr = str.charCodeAt(i)
  941. h = (h << 5) - h + chr
  942. h |= 0 // Convert to 32bit integer
  943. }
  944. return String(h)
  945. }
  946. /**
  947. * formatDate y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒
  948. */
  949. formatDate(date, format) {
  950. let o = {
  951. 'M+': date.getMonth() + 1,
  952. 'd+': date.getDate(),
  953. 'H+': date.getHours(),
  954. 'm+': date.getMinutes(),
  955. 's+': date.getSeconds(),
  956. 'q+': Math.floor((date.getMonth() + 3) / 3),
  957. 'S': date.getMilliseconds()
  958. }
  959. if (/(y+)/.test(format)) format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
  960. for (let k in o)
  961. if (new RegExp('(' + k + ')').test(format))
  962. format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))
  963. return format
  964. }
  965. /**
  966. * parseDate 字符串格式,默认'yyyy-MM-dd',支持如下:y、M、d、H、m、s、S,不支持w和q
  967. */
  968. parseDate(str, format) {
  969. format = format || 'yyyy-MM-dd';
  970. let obj = { y: 0, M: 1, d: 0, H: 0, h: 0, m: 0, s: 0, S: 0 };
  971. format.replace(/([^yMdHmsS]*?)(([yMdHmsS])\3*)([^yMdHmsS]*?)/g, function (m, $1, $2, $3, $4, idx, old) {
  972. str = str.replace(new RegExp($1 + '(\\d{' + $2.length + '})' + $4), function (_m, _$1) {
  973. obj[$3] = parseInt(_$1);
  974. return '';
  975. });
  976. return '';
  977. });
  978. obj.M--; // 月份是从0开始的,所以要减去1
  979. let date = new Date(obj.y, obj.M, obj.d, obj.H, obj.m, obj.s);
  980. if (obj.S !== 0) date.setMilliseconds(obj.S); // 如果设置了毫秒
  981. return date;
  982. }
  983. objToQueryStr(obj, encode) {
  984. let str = ''
  985. for (const key in obj) {
  986. let value = obj[key]
  987. if (value != null && value !== '') {
  988. if (typeof value === 'object') {
  989. value = JSON.stringify(value)
  990. } else if (encode) {
  991. value = encodeURIComponent(value)
  992. }
  993. str += `${key}=${value}&`
  994. }
  995. }
  996. str = str.substring(0, str.length - 1)
  997. return str
  998. }
  999. parseQueryStr(str) {
  1000. let obj = {}
  1001. if (str.indexOf("?") > -1) {
  1002. str = str.split("?")[1]
  1003. }
  1004. let arr = str.split("&")
  1005. for (let i = 0; i < arr.length; i++) {
  1006. let kv = arr[i].split("=")
  1007. obj[kv[0]] = kv[1]
  1008. }
  1009. return obj
  1010. }
  1011. deepClone(obj, newObj) {
  1012. newObj = newObj || {};
  1013. for (let key in obj) {
  1014. if (typeof obj[key] == 'object') {
  1015. newObj[key] = (obj[key].constructor === Array) ? [] : {}
  1016. this.deepClone(obj[key], newObj[key]);
  1017. } else {
  1018. newObj[key] = obj[key]
  1019. }
  1020. }
  1021. return newObj;
  1022. }
  1023. getBaseDoneHeaders(mixHeaders = {}) {
  1024. return Object.assign(
  1025. {
  1026. 'Access-Control-Allow-Origin': '*',
  1027. 'Access-Control-Allow-Methods': 'POST,GET,OPTIONS,PUT,DELETE',
  1028. 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
  1029. },
  1030. mixHeaders
  1031. )
  1032. }
  1033. getHtmlDoneHeaders() {
  1034. return this.getBaseDoneHeaders({
  1035. 'Content-Type': 'text/html;charset=UTF-8'
  1036. })
  1037. }
  1038. getJsonDoneHeaders() {
  1039. return this.getBaseDoneHeaders({
  1040. 'Content-Type': 'text/json; charset=utf-8',
  1041. 'Connection': 'keep-alive'
  1042. })
  1043. }
  1044. shallowClone(obj) {
  1045. let clone = {};
  1046. for (let key in obj) {
  1047. if (obj.hasOwnProperty(key)) {
  1048. clone[key] = obj[key];
  1049. }
  1050. }
  1051. return clone;
  1052. }
  1053. // 解析cookie字符串的函数
  1054. parseCookies(cookieString) {
  1055. let dict = {};
  1056. cookieString && cookieString.split(';').forEach(function(cookie) {
  1057. let parts = cookie.split('=');
  1058. dict[parts.shift().trim()] = decodeURI(parts.join('='));
  1059. });
  1060. return dict;
  1061. }
  1062. // 系列化为cookie字符串
  1063. serializeCookies(cookieData) {
  1064. const parts = [];
  1065. for(let key in cookieData){
  1066. let value = cookieData[key];
  1067. let cookiePart = `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
  1068. parts.push(cookiePart);
  1069. }
  1070. return parts.join('; ');
  1071. }
  1072. parseSetCookies(cookieString) {
  1073. const attribKeys = ['Expires', 'Max-Age', 'Domain', 'Path', 'HttpOnly', 'SameSite'];
  1074. const parts = cookieString.split(';');
  1075. let mainKey = null;
  1076. const retDict = {};
  1077. const retData = [];
  1078. parts.forEach(part => {
  1079. let pstr = part.trim();
  1080. let aKey = null;
  1081. let aValue = true;
  1082. if (pstr.includes('=')) {
  1083. let kvdata = pstr.split('=');
  1084. aKey = kvdata[0].trim();
  1085. aValue = kvdata[1].trim();
  1086. } else {
  1087. aKey = pstr;
  1088. }
  1089. if (attribKeys.includes(aKey)) {
  1090. if (retDict[mainKey]) {
  1091. retDict[mainKey][aKey] = aValue;
  1092. let attribs = retDict[mainKey].attribs;
  1093. attribs[aKey] = aValue;
  1094. }
  1095. } else {
  1096. mainKey = aKey;
  1097. let attribs = {};
  1098. if (mainKey.includes(',')) {
  1099. const keys = mainKey.split(',');
  1100. keys.forEach(key => {
  1101. const k = key.trim();
  1102. if (attribKeys.includes(k)) {
  1103. attribs[k] = true;
  1104. } else {
  1105. mainKey = k;
  1106. }
  1107. });
  1108. }
  1109. retDict[mainKey] = { name: mainKey, value: aValue, attribs: attribs };
  1110. retData.push(retDict[mainKey]);
  1111. }
  1112. });
  1113. return retData;
  1114. }
  1115. base64Encode(str){
  1116. let base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  1117. let out, i, len;
  1118. let c1, c2, c3;
  1119. len = str.length;
  1120. i = 0;
  1121. out = "";
  1122. while (i < len) {
  1123. c1 = str.charCodeAt(i++) & 0xff;
  1124. if (i == len) {
  1125. out += base64EncodeChars.charAt(c1 >> 2);
  1126. out += base64EncodeChars.charAt((c1 & 0x3) << 4);
  1127. out += "==";
  1128. break;
  1129. }
  1130. c2 = str.charCodeAt(i++);
  1131. if (i == len) {
  1132. out += base64EncodeChars.charAt(c1 >> 2);
  1133. out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
  1134. out += base64EncodeChars.charAt((c2 & 0xF) << 2);
  1135. out += "=";
  1136. break;
  1137. }
  1138. c3 = str.charCodeAt(i++);
  1139. out += base64EncodeChars.charAt(c1 >> 2);
  1140. out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
  1141. out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
  1142. out += base64EncodeChars.charAt(c3 & 0x3F);
  1143. }
  1144. return out;
  1145. }
  1146. base64Decode(input) {
  1147. const base64_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  1148. // 确保输入是一个正确的Base64编码字符串
  1149. if (/([^\s]+[^0-9a-zA-Z\+\/\=]|[^0-9a-zA-Z\+\/\=]\s+)/.test(input)) {
  1150. throw new Error('Invalid base64 input');
  1151. }
  1152. let str = input.replace(/\s/g, '');
  1153. let output = '';
  1154. let chr1, chr2, chr3;
  1155. let enc1, enc2, enc3, enc4;
  1156. let i = 0;
  1157. while (i < str.length) {
  1158. enc1 = base64_chars.indexOf(str.charAt(i++));
  1159. enc2 = base64_chars.indexOf(str.charAt(i++));
  1160. enc3 = base64_chars.indexOf(str.charAt(i++));
  1161. enc4 = base64_chars.indexOf(str.charAt(i++));
  1162. chr1 = (enc1 << 2) | (enc2 >> 4);
  1163. chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
  1164. chr3 = ((enc3 & 3) << 6) | enc4;
  1165. output = output + String.fromCharCode(chr1);
  1166. if (enc3 !== 64) {
  1167. output = output + String.fromCharCode(chr2);
  1168. }
  1169. if (enc4 !== 64) {
  1170. output = output + String.fromCharCode(chr3);
  1171. }
  1172. }
  1173. output = this.utf8Decode(output);
  1174. return output;
  1175. }
  1176. utf8Decode(str_data) {
  1177. let tmp_arr = [],
  1178. i = 0,
  1179. c1 = 0,
  1180. seqlen = 0;
  1181. str_data = str_data.replace(/\r\n/g, "\n");
  1182. while (i < str_data.length) {
  1183. c1 = str_data.charCodeAt(i) & 0xFF;
  1184. seqlen = 0;
  1185. // Single byte sequence (0xxxxxxx)
  1186. if (c1 <= 0xBF) {
  1187. c1 = (c1 & 0x7F);
  1188. seqlen = 1;
  1189. } else if (c1 <= 0xDF) {
  1190. c1 = (c1 & 0x1F);
  1191. seqlen = 2;
  1192. } else if (c1 <= 0xEF) {
  1193. c1 = (c1 & 0x0F);
  1194. seqlen = 3;
  1195. } else {
  1196. c1 = (c1 & 0x07);
  1197. seqlen = 4;
  1198. }
  1199. for (let ai = 1; ai < seqlen; ++ai) {
  1200. c1 = ((c1 << 0x06) | (str_data.charCodeAt(ai + i) & 0x3F));
  1201. }
  1202. if (seqlen === 4) {
  1203. c1 -= 0x10000;
  1204. tmp_arr.push(String.fromCharCode(0xD800 | ((c1 >> 10) & 0x3FF)));
  1205. tmp_arr.push(String.fromCharCode(0xDC00 | (c1 & 0x3FF)));
  1206. } else {
  1207. tmp_arr.push(String.fromCharCode(c1));
  1208. }
  1209. i += seqlen;
  1210. }
  1211. return tmp_arr.join("");
  1212. }
  1213. parseJwt(token) {
  1214. try {
  1215. const segments = token.split('.');
  1216. const base64HeaderUrl = segments[0];
  1217. const base64Header = base64HeaderUrl.replace(/-/g, '+').replace(/_/g, '/');
  1218. const jsonStrHeader = this.base64Decode(base64Header).replace(/\0/g, '');
  1219. const headerData = JSON.parse(jsonStrHeader);
  1220. const base64PayloadUrl = segments[1];
  1221. const base64Payload = base64PayloadUrl.replace(/-/g, '+').replace(/_/g, '/');
  1222. const jsonStrPayload = this.base64Decode(base64Payload).replace(/\0/g, '');
  1223. const payloadData = JSON.parse(jsonStrPayload);
  1224. return {
  1225. header: headerData,
  1226. payload: payloadData,
  1227. signature: segments[2],
  1228. };
  1229. } catch (e) {
  1230. this.log(e);
  1231. return null;
  1232. }
  1233. }
  1234. })(scriptName, scriptId, options)
  1235. }