123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338 |
- /**
- * 根据自己的习惯整合各个开发者而形成的工具包(@NobyDa, @chavyleung)
- * 兼容surge,quantumult x,loon,node环境
- * 并且加入一些好用的方法
- * 方法如下:
- * isEmpty: 判断字符串是否是空(undefined,null,空串)
- * getRequestUrl: 获取请求的url(目前仅支持surge和quanx)
- * getResponseBody: 获取响应体(目前仅支持surge和quanx)
- * boxJsJsonBuilder:构建最简默认boxjs配置json
- * randomString: 生成随机字符串
- * autoComplete: 自动补齐字符串
- * customReplace: 自定义替换
- * hash: 字符串做hash
- *
- * ⚠️当开启当且仅当执行失败的时候通知选项,请在执行失败的地方执行execFail()
- *
- * @param scriptName 脚本名,用于通知时候的标题
- * @param scriptId 每个脚本唯一的id,用于存储持久化的时候加入key
- * @param options 传入一些参数,目前参数如下;
- * [email protected]:6166(这个是默认值,本人surge调试脚本用,可自行修改)
- * target_boxjs_json_path=/Users/lowking/Desktop/Scripts/lowking.boxjs.json(生成boxjs配置的目标文件路径)
- * @constructor
- */
- function ToolKit(scriptName, scriptId, options) {
- return new (class {
- constructor(scriptName, scriptId, options) {
- this.tgEscapeCharMapping = { '&': '&', '#': '#' }
- 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`
- this.prefix = `lk`
- this.name = scriptName
- this.id = scriptId
- this.data = null
- this.dataFile = this.getRealPath(`${this.prefix}${this.id}.dat`)
- this.boxJsJsonFile = this.getRealPath(`${this.prefix}${this.id}.boxjs.json`)
- //surge http api等一些扩展参数
- this.options = options
- //命令行入参
- this.isExecComm = false
- //默认脚本开关
- this.isEnableLog = this.getVal(`${this.prefix}IsEnableLog${this.id}`)
- this.isEnableLog = this.isEmpty(this.isEnableLog) ? true : JSON.parse(this.isEnableLog)
- this.isNotifyOnlyFail = this.getVal(`${this.prefix}NotifyOnlyFail${this.id}`)
- this.isNotifyOnlyFail = this.isEmpty(this.isNotifyOnlyFail) ? false : JSON.parse(this.isNotifyOnlyFail)
- //tg通知开关
- this.isEnableTgNotify = this.getVal(`${this.prefix}IsEnableTgNotify${this.id}`)
- this.isEnableTgNotify = this.isEmpty(this.isEnableTgNotify) ? false : JSON.parse(this.isEnableTgNotify)
- this.tgNotifyUrl = this.getVal(`${this.prefix}TgNotifyUrl${this.id}`)
- this.isEnableTgNotify = this.isEnableTgNotify ? !this.isEmpty(this.tgNotifyUrl) : this.isEnableTgNotify
- //计时部分
- this.costTotalStringKey = `${this.prefix}CostTotalString${this.id}`
- this.costTotalString = this.getVal(this.costTotalStringKey)
- this.costTotalString = this.isEmpty(this.costTotalString) ? `0,0` : this.costTotalString.replace("\"", "")
- this.costTotalMs = this.costTotalString.split(",")[0]
- this.execCount = this.costTotalString.split(",")[1]
- this.costTotalMs = this.isEmpty(this.costTotalMs) ? 0 : parseInt(this.costTotalMs)
- this.execCount = this.isEmpty(this.execCount) ? 0 : parseInt(this.execCount)
- this.logSeparator = '\n██'
- this.now = new Date()
- this.startTime = this.now.getTime()
- this.node = (() => {
- if (this.isNode()) {
- const request = require('request')
- return ({ request })
- } else {
- return (null)
- }
- })()
- this.execStatus = true
- this.notifyInfo = []
- this.log(`${this.name}, 开始执行!`)
- this.initCache()
- this.checkRecordRequestBody()
- this.execComm()
- }
- checkRecordRequestBody() {
- if (!this.isRequest()) {
- return;
- }
- const reqBody = $request.body;
- if (!reqBody) {
- return;
- }
- const path = $request.path;
- let cacheKey = this.id + "#" + path.replace("/", "_");
- cacheKey = cacheKey.replace("?", "#");
- if (this.isQuanX()) $prefs.setValueForKey(reqBody, cacheKey);
- if (this.isLoon() || this.isSurge()) $persistentStore.write(reqBody, cacheKey);
- if (this.isNode()) {
- this.node.fs.writeFileSync(
- `${cacheKey}.json`,
- reqBody,
- {
- flag: "w"
- },
- (err) => console.log(err)
- );
- }
- }
- getRequestBody() {
- const path = $request.path;
- let cacheKey = this.id + "#" + path.replace("/", "_");
- cacheKey = cacheKey.replace("?", "#");
- if (this.isSurge() || this.isLoon()) {
- return $persistentStore.read(cacheKey);
- }
- if (this.isQuanX()) {
- return $prefs.valueForKey(cacheKey);
- }
- if (this.isNode()) {
- const fpath = `${cacheKey}.json`;
- if (!this.node.fs.existsSync(fpath)) {
- return JSON.parse(
- this.node.fs.readFileSync(fpath)
- );
- }
- }
- }
- // persistence
- // initialize cache
- initCache() {
- const pKey = this.getPersistKey();
- if (this.isQuanX()) this.cache = JSON.parse($prefs.valueForKey(pKey) || "{}");
- if (this.isLoon() || this.isSurge())
- this.cache = JSON.parse($persistentStore.read(pKey) || "{}");
- if (this.isNode()) {
- // create a json for root cache
- let fpath = "root.json";
- if (!this.node.fs.existsSync(fpath)) {
- this.node.fs.writeFileSync(
- fpath,
- JSON.stringify({}),
- {
- flag: "wx"
- },
- (err) => console.log(err)
- );
- }
- this.root = {};
- // create a json file with the given name if not exists
- fpath = `${pKey}.json`;
- if (!this.node.fs.existsSync(fpath)) {
- this.node.fs.writeFileSync(
- fpath,
- JSON.stringify({}),
- {
- flag: "wx"
- },
- (err) => console.log(err)
- );
- this.cache = {};
- } else {
- this.cache = JSON.parse(
- this.node.fs.readFileSync(`${pKey}.json`)
- );
- }
- }
- }
- getPersistKey() {
- return `${this.id}#privateCache`;
- }
- // store cache
- persistCache() {
- const pKey = this.getPersistKey();
- const data = JSON.stringify(this.cache, null, 2);
- if (this.isQuanX()) $prefs.setValueForKey(data, pKey);
- if (this.isLoon() || this.isSurge()) $persistentStore.write(data, pKey);
- if (this.isNode()) {
- this.node.fs.writeFileSync(
- `${pKey}.json`,
- data,
- {
- flag: "w"
- },
- (err) => console.log(err)
- );
- this.node.fs.writeFileSync(
- "root.json",
- JSON.stringify(this.root, null, 2),
- {
- flag: "w"
- },
- (err) => console.log(err)
- );
- }
- }
- write(data, key) {
- this.log(`SET ${key}`);
- if (key.indexOf("#") !== -1) {
- key = key.substr(1);
- if (isSurge || this.isLoon()) {
- return $persistentStore.write(data, key);
- }
- if (this.isQuanX()) {
- return $prefs.setValueForKey(data, key);
- }
- if (this.isNode()) {
- this.root[key] = data;
- }
- } else {
- this.cache[key] = data;
- }
- this.persistCache();
- }
- read(key) {
- this.log(`READ ${key}`);
- if (key.indexOf("#") !== -1) {
- key = key.substr(1);
- if (this.isSurge() || this.isLoon()) {
- return $persistentStore.read(key);
- }
- if (this.isQuanX()) {
- return $prefs.valueForKey(key);
- }
- if (this.isNode()) {
- return this.root[key];
- }
- } else {
- return this.cache[key];
- }
- }
- delete(key) {
- this.log(`DELETE ${key}`);
- if (key.indexOf("#") !== -1) {
- key = key.substr(1);
- if (this.isSurge() || this.isLoon()) {
- return $persistentStore.write(null, key);
- }
- if (this.isQuanX()) {
- return $prefs.removeValueForKey(key);
- }
- if (this.isNode()) {
- delete this.root[key];
- }
- } else {
- delete this.cache[key];
- }
- this.persistCache();
- }
- //当执行命令的目录不是脚本所在目录时,自动把文件路径改成指令传入的路径并返回完整文件路径
- getRealPath(fileName) {
- if (this.isNode()) {
- let targetPath = process.argv.slice(1, 2)[0].split("/")
- targetPath[targetPath.length - 1] = fileName
- return targetPath.join("/")
- }
- return fileName
- }
- /**
- * http://boxjs.com/ => http://boxjs.com
- * http://boxjs.com/app/jd => http://boxjs.com
- */
- getUrlHost(url) {
- return url.slice(0, url.indexOf('/', 8))
- }
- /**
- * http://boxjs.com/ =>
- * http://boxjs.com/api/getdata => /api/getdata
- */
- getUrlPath(url) {
- // 如果以结尾, 去掉最后一个/
- const end = url.lastIndexOf('/') === url.length - 1 ? -1 : undefined
- // slice第二个参数传 undefined 会直接截到最后
- // indexOf第二个参数用来跳过前面的 "https://"
- return url.slice(url.indexOf('/', 8), end)
- }
- async execComm() {
- //支持node命令,实现发送手机测试
- if (this.isNode()) {
- this.comm = process.argv.slice(1)
- let isHttpApiErr = false
- if (this.comm[1] == "p") {
- this.isExecComm = true
- //phone
- this.log(`开始执行指令【${this.comm[1]}】=> 发送到手机测试脚本!`);
- if (this.isEmpty(this.options) || this.isEmpty(this.options.httpApi)) {
- this.log(`未设置options,使用默认值`)
- //设置默认值
- if (this.isEmpty(this.options)) {
- this.options = {}
- }
- this.options.httpApi = `[email protected]:6166`
- } else {
- //判断格式
- if (!/.*?@.*?:[0-9]+/.test(this.options.httpApi)) {
- isHttpApiErr = true
- this.log(`❌httpApi格式错误!格式:[email protected]:6166`)
- this.done()
- }
- }
- if (!isHttpApiErr) {
- this.callApi(this.comm[2])
- }
- }
- }
- }
- callApi(timeout) {
- // 直接用接收到文件路径,解决在不同目录下都可以使用 node xxxx/xxx.js p 指令发送脚本给手机执行
- // let fname = this.getCallerFileNameAndLine().split(":")[0].replace("[", "")
- let fname = this.comm[0]
- this.log(`获取【${fname}】内容传给手机`)
- let scriptStr = ''
- this.fs = this.fs ? this.fs : require('fs')
- this.path = this.path ? this.path : require('path')
- const curDirDataFilePath = this.path.resolve(fname)
- const rootDirDataFilePath = this.path.resolve(process.cwd(), fname)
- const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath)
- const isRootDirDataFile = !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath)
- if (isCurDirDataFile || isRootDirDataFile) {
- const datPath = isCurDirDataFile ? curDirDataFilePath : rootDirDataFilePath
- try {
- scriptStr = this.fs.readFileSync(datPath)
- } catch (e) {
- scriptStr = ''
- }
- } else {
- scriptStr = ''
- }
- let options = {
- url: `http://${this.options.httpApi.split("@")[1]}/v1/scripting/evaluate`,
- headers: {
- "X-Key": `${this.options.httpApi.split("@")[0]}`
- },
- body: {
- "script_text": `${scriptStr}`,
- "mock_type": "cron",
- "timeout": (!this.isEmpty(timeout) && timeout > 5) ? timeout : 5
- },
- json: true
- }
- this.post(options, (_error, _response, _data) => {
- this.log(`已将脚本【${fname}】发给手机!`)
- this.done()
- })
- }
- getCallerFileNameAndLine() {
- let error
- try {
- throw Error('')
- } catch (err) {
- error = err
- }
- const stack = error.stack
- const stackArr = stack.split('\n')
- let callerLogIndex = 1
- if (callerLogIndex !== 0) {
- const callerStackLine = stackArr[callerLogIndex]
- this.path = this.path ? this.path : require('path')
- return `[${callerStackLine.substring(callerStackLine.lastIndexOf(this.path.sep) + 1, callerStackLine.lastIndexOf(':'))}]`
- } else {
- return '[-]'
- }
- }
- getFunName(fun) {
- var ret = fun.toString()
- ret = ret.substr('function '.length)
- ret = ret.substr(0, ret.indexOf('('))
- return ret
- }
- boxJsJsonBuilder(info, param) {
- if (this.isNode()) {
- let boxjsJsonPath = "/Users/lowking/Desktop/Scripts/lowking.boxjs.json"
- // 从传入参数param读取配置的boxjs的json文件路径
- if (param && param.hasOwnProperty("target_boxjs_json_path")) {
- boxjsJsonPath = param["target_boxjs_json_path"]
- }
- if (!this.fs.existsSync(boxjsJsonPath)) {
- return
- }
- if (!this.isJsonObject(info) || !this.isJsonObject(param)) {
- this.log("构建BoxJsJson传入参数格式错误,请传入json对象")
- return
- }
- this.log('using node')
- let needAppendKeys = ["settings", "keys"]
- const domain = 'https://raw.githubusercontent.com/Orz-3'
- let boxJsJson = {}
- let scritpUrl = '#lk{script_url}'
- if (param && param.hasOwnProperty('script_url')) {
- scritpUrl = this.isEmpty(param['script_url']) ? "#lk{script_url}" : param['script_url']
- }
- boxJsJson.id = `${this.prefix}${this.id}`
- boxJsJson.name = this.name
- boxJsJson.desc_html = `⚠️使用说明</br>详情【<a href='${scritpUrl}?raw=true'><font class='red--text'>点我查看</font></a>】`
- boxJsJson.icons = [`${domain}/mini/master/Alpha/${this.id.toLocaleLowerCase()}.png`, `${domain}/mini/master/Color/${this.id.toLocaleLowerCase()}.png`]
- boxJsJson.keys = []
- boxJsJson.settings = [
- {
- "id": `${this.prefix}IsEnableLog${this.id}`,
- "name": "开启/关闭日志",
- "val": true,
- "type": "boolean",
- "desc": "默认开启"
- },
- {
- "id": `${this.prefix}NotifyOnlyFail${this.id}`,
- "name": "只当执行失败才通知",
- "val": false,
- "type": "boolean",
- "desc": "默认关闭"
- },
- {
- "id": `${this.prefix}IsEnableTgNotify${this.id}`,
- "name": "开启/关闭Telegram通知",
- "val": false,
- "type": "boolean",
- "desc": "默认关闭"
- },
- {
- "id": `${this.prefix}TgNotifyUrl${this.id}`,
- "name": "Telegram通知地址",
- "val": "",
- "type": "text",
- "desc": "Tg的通知地址,如:https://api.telegram.org/bot-token/sendMessage?chat_id=-100140&parse_mode=Markdown&text="
- }
- ]
- boxJsJson.author = "#lk{author}"
- boxJsJson.repo = "#lk{repo}"
- boxJsJson.script = `${scritpUrl}?raw=true`
- // 除了settings和keys追加,其他的都覆盖
- if (!this.isEmpty(info)) {
- for (let i in needAppendKeys) {
- let key = needAppendKeys[i]
- if (!this.isEmpty(info[key])) {
- // 处理传入的每项设置
- if (key === 'settings') {
- for (let i = 0; i < info[key].length; i++) {
- let input = info[key][i]
- for (let j = 0; j < boxJsJson.settings.length; j++) {
- let def = boxJsJson.settings[j]
- if (input.id === def.id) {
- // id相同,就使用外部传入的配置
- boxJsJson.settings.splice(j, 1)
- }
- }
- }
- }
- boxJsJson[key] = boxJsJson[key].concat(info[key])
- }
- delete info[key]
- }
- }
- Object.assign(boxJsJson, info)
- if (this.isNode()) {
- this.fs = this.fs ? this.fs : require('fs')
- this.path = this.path ? this.path : require('path')
- const curDirDataFilePath = this.path.resolve(this.boxJsJsonFile)
- const rootDirDataFilePath = this.path.resolve(process.cwd(), this.boxJsJsonFile)
- const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath)
- const isRootDirDataFile = !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath)
- const jsondata = JSON.stringify(boxJsJson, null, '\t')
- if (isCurDirDataFile) {
- this.fs.writeFileSync(curDirDataFilePath, jsondata)
- } else if (isRootDirDataFile) {
- this.fs.writeFileSync(rootDirDataFilePath, jsondata)
- } else {
- this.fs.writeFileSync(curDirDataFilePath, jsondata)
- }
- // 写到项目的boxjs订阅json中
- let boxjsJson = JSON.parse(this.fs.readFileSync(boxjsJsonPath))
- if (boxjsJson.hasOwnProperty("apps") && Array.isArray(boxjsJson["apps"]) && boxjsJson["apps"].length > 0) {
- let apps = boxjsJson.apps
- let targetIdx = apps.indexOf(apps.filter((app) => {
- return app.id == boxJsJson.id
- })[0])
- if (targetIdx >= 0) {
- boxjsJson.apps[targetIdx] = boxJsJson
- } else {
- boxjsJson.apps.push(boxJsJson)
- }
- let ret = JSON.stringify(boxjsJson, null, 2)
- if (!this.isEmpty(param)) {
- for (const key in param) {
- let val = ''
- if (param.hasOwnProperty(key)) {
- val = param[key]
- } else if (key === 'author') {
- val = '@lowking'
- } else if (key === 'repo') {
- val = 'https://github.com/lowking/Scripts'
- }
- ret = ret.replace(`#lk{${key}}`, val)
- }
- }
- // 全部处理完毕检查是否有漏掉未配置的参数,进行提醒
- const regex = /(?:#lk\{)(.+?)(?=\})/
- let m = regex.exec(ret)
- if (m !== null) {
- this.log('生成BoxJs还有未配置的参数,请参考https://github.com/lowking/Scripts/blob/master/util/example/ToolKitDemo.js#L17-L18传入参数:\n')
- }
- let loseParamSet = new Set()
- while ((m = regex.exec(ret)) !== null) {
- loseParamSet.add(m[1])
- ret = ret.replace(`#lk{${m[1]}}`, ``)
- }
- loseParamSet.forEach(p => {
- console.log(`${p} `)
- })
- this.fs.writeFileSync(boxjsJsonPath, ret)
- }
- }
- }
- }
- isJsonObject(obj) {
- return typeof (obj) == "object" && Object.prototype.toString.call(obj).toLowerCase() == "[object object]" && !obj.length
- }
- appendNotifyInfo(info, type) {
- if (type == 1) {
- this.notifyInfo = info
- } else {
- this.notifyInfo.push(info)
- }
- }
- prependNotifyInfo(info) {
- this.notifyInfo.splice(0, 0, info)
- }
- execFail() {
- this.execStatus = false
- }
- isRequest() {
- return typeof $request != "undefined"
- }
- isSurge() {
- return typeof $httpClient != "undefined"
- }
- isQuanX() {
- return typeof $task != "undefined"
- }
- isLoon() {
- return typeof $loon != "undefined"
- }
- isJSBox() {
- return typeof $app != "undefined" && typeof $http != "undefined"
- }
- isStash() {
- return 'undefined' !== typeof $environment && $environment['stash-version']
- }
- isNode() {
- return typeof require == "function" && !this.isJSBox()
- }
- async sleep(time) {
- return new Promise((resolve) => setTimeout(resolve, time));
- }
- async wait(time) {
- return new Promise((resolve) => setTimeout(resolve, time));
- }
- async delay(time) {
- return new Promise((resolve) => setTimeout(resolve, time));
- }
- log(message) {
- if (this.isEnableLog) console.log(`${this.logSeparator}${message}`)
- }
- logErr(message) {
- this.execStatus = true
- if (this.isEnableLog) {
- console.log(`${this.logSeparator}${this.name}执行异常:`)
- console.log(message)
- console.log('\n' + `${message.message}`)
- }
- }
- msg(subtitle, message, openUrl, mediaUrl) {
- if (!this.isRequest() && this.isNotifyOnlyFail && this.execStatus) {
- //开启了当且仅当执行失败的时候通知,并且执行成功了,这时候不通知
- } else {
- if (this.isEmpty(message)) {
- if (Array.isArray(this.notifyInfo)) {
- message = this.notifyInfo.join("\n")
- } else {
- message = this.notifyInfo
- }
- }
- if (!this.isEmpty(message)) {
- if (this.isEnableTgNotify) {
- this.log(`${this.name}Tg通知开始`)
- //处理特殊字符
- for (let key in this.tgEscapeCharMapping) {
- if (!this.tgEscapeCharMapping.hasOwnProperty(key)) {
- continue
- }
- message = message.replace(key, this.tgEscapeCharMapping[key])
- }
- this.get({
- url: encodeURI(`${this.tgNotifyUrl}📌${this.name}` + '\n' + `${message}`)
- }, (_error, _statusCode, _body) => {
- this.log(`Tg通知完毕`)
- })
- } else {
- let options = {}
- const hasOpenUrl = !this.isEmpty(openUrl)
- const hasMediaUrl = !this.isEmpty(mediaUrl)
- if (this.isQuanX()) {
- if (hasOpenUrl) options["open-url"] = openUrl
- if (hasMediaUrl) options["media-url"] = mediaUrl
- $notify(this.name, subtitle, message, options)
- }
- if (this.isSurge() || this.isStash()) {
- if (hasOpenUrl) options["url"] = openUrl
- $notification.post(this.name, subtitle, message, options)
- }
- if (this.isNode()) this.log("⭐️" + this.name + "\n" + subtitle + "\n" + message)
- if (this.isJSBox()) $push.schedule({
- title: this.name,
- body: subtitle ? subtitle + "\n" + message : message
- })
- }
- }
- }
- }
- pushWxMsg(summary, content, url, callback = () => { }) {
- let data = {
- appToken: "AT_rTc93GQYIdMU8XLRnoJaSea8WkfhSzhX",
- content: content,
- summary: summary,
- contentType: 1,
- topicIds: [],
- uids: [
- "UID_6P4B00X6Zv8U2oKC0I2R09emxtqq"
- ],
- url: "",
- verifyPay: false
- };
- if (url) {
- data.url = url;
- }
- const headers = this.getJsonDoneHeaders();
- headers.Host = 'wxpusher.zjiecode.com';
- headers['Content-Type'] = 'application/json;charset=UTF-8';
- let options = {
- url: 'https://wxpusher.zjiecode.com/api/send/message',
- headers: headers,
- body: JSON.stringify(data),
- };
- this.post(options, callback);
- }
- getVal(key, defaultValue = "") {
- let value
- if (this.isSurge() || this.isLoon() || this.isStash()) {
- value = $persistentStore.read(key)
- } else if (this.isQuanX()) {
- value = $prefs.valueForKey(key)
- } else if (this.isNode()) {
- this.data = this.loadData()
- value = process.env[key] || this.data[key]
- } else {
- value = (this.data && this.data[key]) || null
- }
- return !value ? defaultValue : value
- }
- setVal(key, val) {
- if (this.isSurge() || this.isLoon() || this.isStash()) {
- return $persistentStore.write(val, key)
- } else if (this.isQuanX()) {
- return $prefs.setValueForKey(val, key)
- } else if (this.isNode()) {
- this.data = this.loadData()
- this.data[key] = val
- this.writeData()
- return true
- } else {
- return (this.data && this.data[key]) || null
- }
- }
- loadData() {
- if (this.isNode()) {
- this.fs = this.fs ? this.fs : require('fs')
- this.path = this.path ? this.path : require('path')
- const curDirDataFilePath = this.path.resolve(this.dataFile)
- const rootDirDataFilePath = this.path.resolve(process.cwd(), this.dataFile)
- const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath)
- const isRootDirDataFile = !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath)
- if (isCurDirDataFile || isRootDirDataFile) {
- const datPath = isCurDirDataFile ? curDirDataFilePath : rootDirDataFilePath
- try {
- return JSON.parse(this.fs.readFileSync(datPath))
- } catch (e) {
- return {}
- }
- } else return {}
- } else return {}
- }
- writeData() {
- if (this.isNode()) {
- this.fs = this.fs ? this.fs : require('fs')
- this.path = this.path ? this.path : require('path')
- const curDirDataFilePath = this.path.resolve(this.dataFile)
- const rootDirDataFilePath = this.path.resolve(process.cwd(), this.dataFile)
- const isCurDirDataFile = this.fs.existsSync(curDirDataFilePath)
- const isRootDirDataFile = !isCurDirDataFile && this.fs.existsSync(rootDirDataFilePath)
- const jsondata = JSON.stringify(this.data)
- if (isCurDirDataFile) {
- this.fs.writeFileSync(curDirDataFilePath, jsondata)
- } else if (isRootDirDataFile) {
- this.fs.writeFileSync(rootDirDataFilePath, jsondata)
- } else {
- this.fs.writeFileSync(curDirDataFilePath, jsondata)
- }
- }
- }
- adapterStatus(response) {
- if (response) {
- if (response.status) {
- response["statusCode"] = response.status
- } else if (response.statusCode) {
- response["status"] = response.statusCode
- }
- }
- return response
- }
- get(options, callback = () => { }) {
- if (this.isQuanX()) {
- if (typeof options == "string") options = {
- url: options
- }
- options["method"] = "GET"
- $task.fetch(options).then(response => {
- callback(null, this.adapterStatus(response), response.body)
- }, reason => callback(reason.error, null, null))
- }
- if (this.isSurge() || this.isLoon() || this.isStash()) $httpClient.get(options, (error, response, body) => {
- callback(error, this.adapterStatus(response), body)
- })
- if (this.isNode()) {
- this.node.request(options, (error, response, body) => {
- callback(error, this.adapterStatus(response), body)
- })
- }
- if (this.isJSBox()) {
- if (typeof options == "string") options = {
- url: options
- }
- options["header"] = options["headers"]
- options["handler"] = function (resp) {
- let error = resp.error
- if (error) error = JSON.stringify(resp.error)
- let body = resp.data
- if (typeof body == "object") body = JSON.stringify(resp.data)
- callback(error, this.adapterStatus(resp.response), body)
- }
- $http.get(options)
- }
- }
- post(options, callback = () => { }) {
- if (this.isQuanX()) {
- if (typeof options == "string") options = {
- url: options
- }
- options["method"] = "POST"
- $task.fetch(options).then(response => {
- callback(null, this.adapterStatus(response), response.body)
- }, reason => callback(reason.error, null, null))
- }
- if (this.isSurge() || this.isLoon() || this.isStash()) {
- $httpClient.post(options, (error, response, body) => {
- callback(error, this.adapterStatus(response), body)
- })
- }
- if (this.isNode()) {
- this.node.request.post(options, (error, response, body) => {
- callback(error, this.adapterStatus(response), body)
- })
- }
- if (this.isJSBox()) {
- if (typeof options == "string") options = {
- url: options
- }
- options["header"] = options["headers"]
- options["handler"] = function (resp) {
- let error = resp.error
- if (error) error = JSON.stringify(resp.error)
- let body = resp.data
- if (typeof body == "object") body = JSON.stringify(resp.data)
- callback(error, this.adapterStatus(resp.response), body)
- }
- $http.post(options)
- }
- }
- put(options, callback = () => { }) {
- if (this.isQuanX()) {
- // no test
- if (typeof options == "string") options = {
- url: options
- }
- options["method"] = "PUT"
- $task.fetch(options).then(response => {
- callback(null, this.adapterStatus(response), response.body)
- }, reason => callback(reason.error, null, null))
- }
- if (this.isSurge() || this.isLoon() || this.isStash()) {
- options.method = "PUT"
- $httpClient.put(options, (error, response, body) => {
- callback(error, this.adapterStatus(response), body)
- })
- }
- if (this.isNode()) {
- options.method = "PUT"
- this.node.request.put(options, (error, response, body) => {
- callback(error, this.adapterStatus(response), body)
- })
- }
- if (this.isJSBox()) {
- // no test
- if (typeof options == "string") options = {
- url: options
- }
- options["header"] = options["headers"]
- options["handler"] = function (resp) {
- let error = resp.error
- if (error) error = JSON.stringify(resp.error)
- let body = resp.data
- if (typeof body == "object") body = JSON.stringify(resp.data)
- callback(error, this.adapterStatus(resp.response), body)
- }
- $http.post(options)
- }
- }
- costTime() {
- let info = `${this.name}执行完毕!`
- if (this.isNode() && this.isExecComm) {
- info = `指令【${this.comm[1]}】执行完毕!`
- }
- const endTime = new Date().getTime()
- const ms = endTime - this.startTime
- const costTime = ms / 1000
- this.execCount++
- this.costTotalMs += ms
- this.log(`${info}耗时【${costTime}】秒\n总共执行【${this.execCount}】次,平均耗时【${((this.costTotalMs / this.execCount) / 1000).toFixed(4)}】秒`)
- this.setVal(this.costTotalStringKey, JSON.stringify(`${this.costTotalMs},${this.execCount}`))
- // this.setVal(this.execCountKey, JSON.stringify(0))
- // this.setVal(this.costTotalMsKey, JSON.stringify(0))
- }
- done(value = {}) {
- this.costTime()
- if (this.isSurge() || this.isQuanX() || this.isLoon() || this.isStash()) {
- $done(value)
- }
- }
- getRequestUrl() {
- return $request.url
- }
- getResponseBody() {
- if ($response) {
- return $response.body
- }
- }
- isGetCookie(reg) {
- return !!($request.method != 'OPTIONS' && this.getRequestUrl().match(reg))
- }
- isEmpty(obj) {
- return typeof obj == "undefined" || obj == null || obj == "" || obj == "null" || obj == "undefined" || obj.length === 0
- }
- randomString(len) {
- len = len || 32
- var $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'
- var maxPos = $chars.length
- var pwd = ''
- for (let i = 0; i < len; i++) {
- pwd += $chars.charAt(Math.floor(Math.random() * maxPos))
- }
- return pwd
- }
- /**
- * 自动补齐字符串
- * @param str 原始字符串
- * @param prefix 前缀
- * @param suffix 后缀
- * @param fill 补齐用字符
- * @param len 目标补齐长度,不包含前后缀
- * @param direction 方向:0往后补齐
- * @param ifCode 是否打码
- * @param clen 打码长度
- * @param startIndex 起始坐标
- * @param cstr 打码字符
- * @returns {*}
- */
- autoComplete(str, prefix, suffix, fill, len, direction, ifCode, clen, startIndex, cstr) {
- str += ''
- if (str.length < len) {
- while (str.length < len) {
- if (direction == 0) {
- str += fill
- } else {
- str = fill + str
- }
- }
- }
- if (ifCode) {
- let temp = ''
- for (var i = 0; i < clen; i++) {
- temp += cstr
- }
- str = str.substring(0, startIndex) + temp + str.substring(clen + startIndex)
- }
- str = prefix + str + suffix
- return this.toDBC(str)
- }
- /**
- * @param str 源字符串 "#{code}, #{value}"
- * @param param 用于替换的数据,结构如下
- * @param prefix 前缀 "#{"
- * @param suffix 后缀 "}"
- * {
- * "code": 1,
- * "value": 2
- * }
- * 按上面的传入,输出为"1, 2"
- * 对应的#{code}用param里面code的值替换,#{value}也是
- * @returns {*|void|string}
- */
- customReplace(str, param, prefix, suffix) {
- try {
- if (this.isEmpty(prefix)) {
- prefix = "#{"
- }
- if (this.isEmpty(suffix)) {
- suffix = "}"
- }
- for (let i in param) {
- str = str.replace(`${prefix}${i}${suffix}`, param[i])
- }
- } catch (e) {
- this.logErr(e)
- }
- return str
- }
- toDBC(txtstring) {
- var tmp = ""
- for (var i = 0; i < txtstring.length; i++) {
- if (txtstring.charCodeAt(i) == 32) {
- tmp = tmp + String.fromCharCode(12288)
- } else if (txtstring.charCodeAt(i) < 127) {
- tmp = tmp + String.fromCharCode(txtstring.charCodeAt(i) + 65248)
- }
- }
- return tmp
- }
- hash(str) {
- let h = 0,
- i,
- chr
- for (i = 0; i < str.length; i++) {
- chr = str.charCodeAt(i)
- h = (h << 5) - h + chr
- h |= 0 // Convert to 32bit integer
- }
- return String(h)
- }
- /**
- * formatDate y:年 M:月 d:日 q:季 H:时 m:分 s:秒 S:毫秒
- */
- formatDate(date, format) {
- let o = {
- 'M+': date.getMonth() + 1,
- 'd+': date.getDate(),
- 'H+': date.getHours(),
- 'm+': date.getMinutes(),
- 's+': date.getSeconds(),
- 'q+': Math.floor((date.getMonth() + 3) / 3),
- 'S': date.getMilliseconds()
- }
- if (/(y+)/.test(format)) format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
- for (let k in o)
- if (new RegExp('(' + k + ')').test(format))
- format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length))
- return format
- }
- /**
- * parseDate 字符串格式,默认'yyyy-MM-dd',支持如下:y、M、d、H、m、s、S,不支持w和q
- */
- parseDate(str, format) {
- format = format || 'yyyy-MM-dd';
- let obj = { y: 0, M: 1, d: 0, H: 0, h: 0, m: 0, s: 0, S: 0 };
- format.replace(/([^yMdHmsS]*?)(([yMdHmsS])\3*)([^yMdHmsS]*?)/g, function (m, $1, $2, $3, $4, idx, old) {
- str = str.replace(new RegExp($1 + '(\\d{' + $2.length + '})' + $4), function (_m, _$1) {
- obj[$3] = parseInt(_$1);
- return '';
- });
- return '';
- });
- obj.M--; // 月份是从0开始的,所以要减去1
- let date = new Date(obj.y, obj.M, obj.d, obj.H, obj.m, obj.s);
- if (obj.S !== 0) date.setMilliseconds(obj.S); // 如果设置了毫秒
- return date;
- }
- objToQueryStr(obj, encode) {
- let str = ''
- for (const key in obj) {
- let value = obj[key]
- if (value != null && value !== '') {
- if (typeof value === 'object') {
- value = JSON.stringify(value)
- } else if (encode) {
- value = encodeURIComponent(value)
- }
- str += `${key}=${value}&`
- }
- }
- str = str.substring(0, str.length - 1)
- return str
- }
- parseQueryStr(str) {
- let obj = {}
- if (str.indexOf("?") > -1) {
- str = str.split("?")[1]
- }
- let arr = str.split("&")
- for (let i = 0; i < arr.length; i++) {
- let kv = arr[i].split("=")
- obj[kv[0]] = kv[1]
- }
- return obj
- }
- deepClone(obj, newObj) {
- newObj = newObj || {};
- for (let key in obj) {
- if (typeof obj[key] == 'object') {
- newObj[key] = (obj[key].constructor === Array) ? [] : {}
- this.deepClone(obj[key], newObj[key]);
- } else {
- newObj[key] = obj[key]
- }
- }
- return newObj;
- }
- getBaseDoneHeaders(mixHeaders = {}) {
- return Object.assign(
- {
- 'Access-Control-Allow-Origin': '*',
- 'Access-Control-Allow-Methods': 'POST,GET,OPTIONS,PUT,DELETE',
- 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Type, Accept'
- },
- mixHeaders
- )
- }
- getHtmlDoneHeaders() {
- return this.getBaseDoneHeaders({
- 'Content-Type': 'text/html;charset=UTF-8'
- })
- }
- getJsonDoneHeaders() {
- return this.getBaseDoneHeaders({
- 'Content-Type': 'text/json; charset=utf-8',
- 'Connection': 'keep-alive'
- })
- }
- shallowClone(obj) {
- let clone = {};
- for (let key in obj) {
- if (obj.hasOwnProperty(key)) {
- clone[key] = obj[key];
- }
- }
- return clone;
- }
- // 解析cookie字符串的函数
- parseCookies(cookieString) {
- let dict = {};
- cookieString && cookieString.split(';').forEach(function(cookie) {
- let parts = cookie.split('=');
- dict[parts.shift().trim()] = decodeURI(parts.join('='));
- });
- return dict;
- }
- // 系列化为cookie字符串
- serializeCookies(cookieData) {
- const parts = [];
- for(let key in cookieData){
- let value = cookieData[key];
- let cookiePart = `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
- parts.push(cookiePart);
- }
- return parts.join('; ');
- }
- parseSetCookies(cookieString) {
- const attribKeys = ['Expires', 'Max-Age', 'Domain', 'Path', 'HttpOnly', 'SameSite'];
- const parts = cookieString.split(';');
- let mainKey = null;
- const retDict = {};
- const retData = [];
- parts.forEach(part => {
- let pstr = part.trim();
- let aKey = null;
- let aValue = true;
- if (pstr.includes('=')) {
- let kvdata = pstr.split('=');
- aKey = kvdata[0].trim();
- aValue = kvdata[1].trim();
- } else {
- aKey = pstr;
- }
- if (attribKeys.includes(aKey)) {
- if (retDict[mainKey]) {
- retDict[mainKey][aKey] = aValue;
- let attribs = retDict[mainKey].attribs;
- attribs[aKey] = aValue;
- }
- } else {
- mainKey = aKey;
- let attribs = {};
- if (mainKey.includes(',')) {
- const keys = mainKey.split(',');
- keys.forEach(key => {
- const k = key.trim();
- if (attribKeys.includes(k)) {
- attribs[k] = true;
- } else {
- mainKey = k;
- }
- });
- }
- retDict[mainKey] = { name: mainKey, value: aValue, attribs: attribs };
- retData.push(retDict[mainKey]);
- }
- });
- return retData;
- }
- base64Encode(str){
- let base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
- let out, i, len;
- let c1, c2, c3;
- len = str.length;
- i = 0;
- out = "";
- while (i < len) {
- c1 = str.charCodeAt(i++) & 0xff;
- if (i == len) {
- out += base64EncodeChars.charAt(c1 >> 2);
- out += base64EncodeChars.charAt((c1 & 0x3) << 4);
- out += "==";
- break;
- }
- c2 = str.charCodeAt(i++);
- if (i == len) {
- out += base64EncodeChars.charAt(c1 >> 2);
- out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
- out += base64EncodeChars.charAt((c2 & 0xF) << 2);
- out += "=";
- break;
- }
- c3 = str.charCodeAt(i++);
- out += base64EncodeChars.charAt(c1 >> 2);
- out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
- out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
- out += base64EncodeChars.charAt(c3 & 0x3F);
- }
- return out;
- }
- base64Decode(input) {
- const base64_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
- // 确保输入是一个正确的Base64编码字符串
- if (/([^\s]+[^0-9a-zA-Z\+\/\=]|[^0-9a-zA-Z\+\/\=]\s+)/.test(input)) {
- throw new Error('Invalid base64 input');
- }
- let str = input.replace(/\s/g, '');
- let output = '';
- let chr1, chr2, chr3;
- let enc1, enc2, enc3, enc4;
- let i = 0;
- while (i < str.length) {
- enc1 = base64_chars.indexOf(str.charAt(i++));
- enc2 = base64_chars.indexOf(str.charAt(i++));
- enc3 = base64_chars.indexOf(str.charAt(i++));
- enc4 = base64_chars.indexOf(str.charAt(i++));
- chr1 = (enc1 << 2) | (enc2 >> 4);
- chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
- chr3 = ((enc3 & 3) << 6) | enc4;
- output = output + String.fromCharCode(chr1);
- if (enc3 !== 64) {
- output = output + String.fromCharCode(chr2);
- }
- if (enc4 !== 64) {
- output = output + String.fromCharCode(chr3);
- }
- }
- output = this.utf8Decode(output);
- return output;
- }
- utf8Decode(str_data) {
- let tmp_arr = [],
- i = 0,
- c1 = 0,
- seqlen = 0;
- str_data = str_data.replace(/\r\n/g, "\n");
- while (i < str_data.length) {
- c1 = str_data.charCodeAt(i) & 0xFF;
- seqlen = 0;
- // Single byte sequence (0xxxxxxx)
- if (c1 <= 0xBF) {
- c1 = (c1 & 0x7F);
- seqlen = 1;
- } else if (c1 <= 0xDF) {
- c1 = (c1 & 0x1F);
- seqlen = 2;
- } else if (c1 <= 0xEF) {
- c1 = (c1 & 0x0F);
- seqlen = 3;
- } else {
- c1 = (c1 & 0x07);
- seqlen = 4;
- }
- for (let ai = 1; ai < seqlen; ++ai) {
- c1 = ((c1 << 0x06) | (str_data.charCodeAt(ai + i) & 0x3F));
- }
- if (seqlen === 4) {
- c1 -= 0x10000;
- tmp_arr.push(String.fromCharCode(0xD800 | ((c1 >> 10) & 0x3FF)));
- tmp_arr.push(String.fromCharCode(0xDC00 | (c1 & 0x3FF)));
- } else {
- tmp_arr.push(String.fromCharCode(c1));
- }
- i += seqlen;
- }
- return tmp_arr.join("");
- }
- parseJwt(token) {
- try {
- const segments = token.split('.');
- const base64HeaderUrl = segments[0];
- const base64Header = base64HeaderUrl.replace(/-/g, '+').replace(/_/g, '/');
- const jsonStrHeader = this.base64Decode(base64Header).replace(/\0/g, '');
- const headerData = JSON.parse(jsonStrHeader);
- const base64PayloadUrl = segments[1];
- const base64Payload = base64PayloadUrl.replace(/-/g, '+').replace(/_/g, '/');
- const jsonStrPayload = this.base64Decode(base64Payload).replace(/\0/g, '');
- const payloadData = JSON.parse(jsonStrPayload);
- return {
- header: headerData,
- payload: payloadData,
- signature: segments[2],
- };
- } catch (e) {
- this.log(e);
- return null;
- }
- }
- })(scriptName, scriptId, options)
- }
|