├── .github └── workflows │ └── mirror.yml ├── .gitignore ├── operator.js ├── test-express.js ├── README.md ├── Express.js ├── api-minified.js ├── example.js ├── API.js └── LICENSE /.github/workflows/mirror.yml: -------------------------------------------------------------------------------- 1 | name: 'GitHub Actions Mirror' 2 | 3 | on: 4 | [push, delete, workflow_dispatch] 5 | 6 | jobs: 7 | mirror_to_gitee: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: 'Checkout' 11 | uses: actions/checkout@v1 12 | - name: 'Mirror to gitee' 13 | uses: pixta-dev/repository-mirroring-action@v1 14 | with: 15 | target_repo_url: 16 | git@gitee.com:asvow/OpenAPI.git 17 | ssh_private_key: 18 | ${{ secrets.KEY }} 19 | 20 | mirror_to_gitlab: 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: 'Checkout' 24 | uses: actions/checkout@v1 25 | - name: 'Mirror to gitlab' 26 | uses: pixta-dev/repository-mirroring-action@v1 27 | with: 28 | target_repo_url: 29 | git@gitlab.com:asvow/OpenAPI.git 30 | ssh_private_key: 31 | ${{ secrets.KEY }} 32 | 33 | - name: Delete Workflow Runs 34 | uses: Mattraks/delete-workflow-runs@main 35 | with: 36 | token: ${{ github.token }} 37 | repository: ${{ github.repository }} 38 | retain_days: 0 39 | keep_minimum_runs: 0 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | *.json 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # IDEs and editors 34 | .idea 35 | .project 36 | .classpath 37 | .c9/ 38 | *.launch 39 | .settings/ 40 | *.sublime-workspace 41 | 42 | # IDE - VSCode 43 | .vscode/* 44 | !.vscode/settings.json 45 | !.vscode/tasks.json 46 | !.vscode/launch.json 47 | !.vscode/extensions.json 48 | 49 | # misc 50 | .sass-cache 51 | connect.lock 52 | typings 53 | 54 | # Logs 55 | logs 56 | *.log 57 | npm-debug.log* 58 | yarn-debug.log* 59 | yarn-error.log* 60 | 61 | 62 | # Dependency directories 63 | node_modules/ 64 | jspm_packages/ 65 | 66 | # Optional npm cache directory 67 | .npm 68 | 69 | # Optional eslint cache 70 | .eslintcache 71 | 72 | # Optional REPL history 73 | .node_repl_history 74 | 75 | # Output of 'npm pack' 76 | *.tgz 77 | 78 | # Yarn Integrity file 79 | .yarn-integrity 80 | 81 | # dotenv environment variables file 82 | .env 83 | 84 | # next.js build output 85 | .next 86 | 87 | # Lerna 88 | lerna-debug.log 89 | 90 | # System Files 91 | .DS_Store 92 | Thumbs.db 93 | -------------------------------------------------------------------------------- /operator.js: -------------------------------------------------------------------------------- 1 | const body = { 2 | Hello: "world", 3 | Outer: { 4 | A: "a", 5 | B: "b", 6 | }, 7 | TOBEDELETE: { 8 | A: { 9 | B: "NO", 10 | }, 11 | }, 12 | }; 13 | const $ = new Operator(body); 14 | let modified = $.set("Hello", "WOOOORLD") 15 | .patch("Outer", { PATCHED: "VAL" }) 16 | .delete("TOBEDELETE.A") 17 | .done(); 18 | console.log(modified); 19 | console.log(body); 20 | console.log($.get("Outer")); 21 | 22 | function Operator(origin) { 23 | const model = JSON.parse(JSON.stringify(origin)); // deep copy 24 | 25 | const modify = (expr, value, merge) => { 26 | const ERR = new Error("Cannot assign an attribute to a string!"); 27 | const _modify = (obj, nodes, value) => { 28 | const k = nodes[0]; 29 | if (typeof obj === "string") throw ERR; 30 | if (!obj.hasOwnProperty(k)) obj[k] = {}; 31 | if (nodes.length === 1) { 32 | if (merge && typeof obj[k] === "string") throw ERR; 33 | obj[k] = merge ? { ...obj[k], ...value } : value; 34 | return; 35 | } 36 | _modify(obj[k], nodes.slice(1), value); 37 | }; 38 | _modify(model, expr.split("."), value); 39 | }; 40 | 41 | this.get = (expr) => { 42 | const _get = (obj, nodes) => { 43 | if (typeof obj === "undefined") return undefined; 44 | if (nodes.length === 1) return obj[nodes[0]]; 45 | return _get(obj[nodes[0]], nodes.slice(1)); 46 | }; 47 | return _get(model, expr.split(".")); 48 | }; 49 | 50 | this.set = (expr, value) => { 51 | modify(expr, value, false); 52 | return this; 53 | }; 54 | 55 | this.patch = (expr, value) => { 56 | modify(expr, value, true); 57 | return this; 58 | }; 59 | 60 | this.delete = (expr) => { 61 | const _delete = (target, nodes) => { 62 | if (typeof target === "undefined") return; 63 | if (nodes.length == 1) { 64 | delete target[nodes[0]]; 65 | return; 66 | } 67 | _delete(target[nodes[0]], nodes.slice(1)); 68 | }; 69 | _delete(model, expr.split(".")); 70 | return this; 71 | }; 72 | 73 | this.done = () => { 74 | return model; 75 | }; 76 | } 77 | -------------------------------------------------------------------------------- /test-express.js: -------------------------------------------------------------------------------- 1 | const app = express({debug: true}); 2 | const $request = { 3 | url: "https://sub.store/users/10086/Peng-YM", 4 | method: "GET", 5 | headers: {} 6 | } 7 | const $done = (resp) => { 8 | console.log("=================== Response ==============================="); 9 | console.log(JSON.stringify(resp, null, 2)); 10 | } 11 | const $task = {}; 12 | 13 | app.all("/home", async function (req, res) { 14 | res.send("HELLO from express!"); 15 | }); 16 | app 17 | .route("/users/:userid") 18 | .post(function (req, res) { 19 | res.send(`POST USER: ${req.params.userid}`); 20 | }) 21 | .get(function (req, res) { 22 | res.send(`GET USER ${req.params.userid}`); 23 | }); 24 | app.get("/users/:userid/:name", function (req, res) { 25 | res.send(req.params.name); 26 | }) 27 | app.start(); 28 | 29 | // prettier-ignore 30 | function ENV(){const e="undefined"!=typeof $task,t="undefined"!=typeof $loon;return{isQX:e,isLoon:t,isSurge:"undefined"!=typeof $httpClient&&!t,isNode:"function"==typeof require,isRequest:"undefined"!=typeof $request}}function express({port:e,debug:t}={port:3e3,debug:!1}){const{isNode:n}=!t&&ENV(),s={"Content-Type":"text/plain;charset=UTF-8","Access-Control-Allow-Origin":"*","Access-Control-Allow-Methods":"POST,GET,OPTIONS,PATCH,PUT,DELETE","Access-Control-Allow-Headers":"Origin, X-Requested-With, Content-Type, Accept"};if(n){const t=require("express"),n=require("body-parser"),r=t();return r.use(n.json({verify:u})),r.use(n.urlencoded({verify:u,extended:!0})),r.use(n.raw({verify:u,type:"*/*"})),r.use((e,t,n)=>{t.set(s),n()}),r.start=(()=>{r.listen(e,()=>{$.info(`Express started on port: ${e}`)})}),r}const r=[],o=["GET","POST","PUT","DELETE","PATCH","OPTIONS","HEAD'","ALL"],i=(e,n=0)=>{let{method:s,url:o,headers:l,body:u}=e;t&&(console.log("=================== Dispatching Request ==============================="),console.log(JSON.stringify(e,null,2))),/json/i.test(l["Content-Type"])&&(u=JSON.parse(u)),s=s.toUpperCase();const{path:h,query:f}=function(e){const t=(e.match(/https?:\/\/[^\/]+(\/[^?]*)/)||[])[1]||"/",n=e.indexOf("?"),s={};if(-1!==n){let t=e.slice(e.indexOf("?")+1).split("&");for(let e=0;ey&&(T=r[p],y=e.split("/").length)}if(T){t&&console.log(`Pattern: ${T.pattern} matched`);const e=()=>{i(s,o,p)},n={method:s,url:o,path:h,query:f,params:d(T.pattern,h),headers:l,body:u},r=a(),c=e=>{r.status(500).json({status:"failed",message:`Internal Server Error: ${e}`})};if("AsyncFunction"===T.callback.constructor.name)T.callback(n,r,e).catch(c);else try{T.callback(n,r,e)}catch(e){c(e)}}else{a().status(404).json({status:"failed",message:"ERROR: 404 not found"})}},l={};return o.forEach(e=>{l[e.toLowerCase()]=((t,n)=>{r.push({method:e,pattern:t,callback:n})})}),l.route=(e=>{const t={};return o.forEach(n=>{t[n.toLowerCase()]=(s=>(r.push({method:n,pattern:e,callback:s}),t))}),t}),l.start=(()=>{i($request)}),l;function u(e,t,n,s){n&&n.length&&(e.rawBody=n.toString(s||"utf8"))}function a(){let e=200;const{isQX:t,isLoon:n,isSurge:r}=ENV(),o=s,i={200:"HTTP/1.1 200 OK",201:"HTTP/1.1 201 Created",302:"HTTP/1.1 302 Found",307:"HTTP/1.1 307 Temporary Redirect",308:"HTTP/1.1 308 Permanent Redirect",404:"HTTP/1.1 404 Not Found",500:"HTTP/1.1 500 Internal Server Error"};return new class{status(t){return e=t,this}send(s=""){const l={status:t?i[e]:e,body:s,headers:o};t?$done(l):(n||r)&&$done({response:l})}end(){this.send()}html(e){this.set("Content-Type","text/html;charset=UTF-8"),this.send(e)}json(e){this.set("Content-Type","application/json;charset=UTF-8"),this.send(JSON.stringify(e))}set(e,t){return o[e]=t,this}}}function c(e,t){if(e instanceof RegExp&&e.test(t))return!0;if("/"===e)return!0;if(-1===e.indexOf(":")){const n=t.split("/"),s=e.split("/");for(let e=0;e= build 316, 其他平台不会显示多媒体内容。 45 | $.notify("title", "subtitle", "content", {"media-url": "https://avatars.githubusercontent.com/u/88471740"}); 46 | 47 | // 通知开关 48 | $.isMute = ""; // 抑制通知发送(True or False, 缺省值为False) 49 | $.isMuteLog = ""; // 抑制通知输出(True or False, 缺省值为False) 50 | ``` 51 | 52 | #### Node.js通知参数 53 | Node.js环境中,需要使用`export`声明通知参数,也可以在脚本内直接赋值。参考[说明文档](https://asvow.com/param)。 54 | 55 | ```javascript 56 | // 作者信息(缺省值为笔者信息) 57 | $.author = ""; 58 | // 微信server酱 59 | $.SCKEY = ""; 60 | // pushplus(推送加) 61 | $.PUSH_PLUS_TOKEN = ""; 62 | $.PUSH_PLUS_USER = ""; 63 | // iOS Bark APP 64 | $.BARK_PUSH = ""; 65 | $.BARK_SOUND = ""; 66 | $.BARK_GROUP = ""; 67 | // Telegram 机器人 68 | $.TG_BOT_TOKEN = ""; 69 | $.TG_USER_ID = ""; 70 | $.TG_PROXY_HOST = ""; 71 | $.TG_PROXY_PORT = ""; 72 | $.TG_PROXY_AUTH = ""; 73 | $.TG_API_HOST = "api.telegram.org"; 74 | // 钉钉机器人 75 | $.DD_BOT_TOKEN = ""; 76 | $.DD_BOT_SECRET = ""; 77 | // 企业微信机器人 78 | $.QYWX_KEY = ""; 79 | // 企业微信应用消息推送 80 | $.QYWX_AM = ""; 81 | // iGot 82 | $.IGOT_PUSH_KEY = ""; 83 | // go-cqhttp 84 | $.GOBOT_URL = ""; 85 | $.GOBOT_TOKEN = ""; 86 | $.GOBOT_QQ = ""; 87 | ``` 88 | 89 | ### HTTP 90 | 91 | HTTP接口在Node.js使用request实现,需要用npm安装request。推荐通过如下操作安装到全局,并link。 92 | 93 | ```bash 94 | npm install -g request 95 | # 在工作目录 96 | npm link request 97 | ``` 98 | 99 | OpenAPI提供了全部HTTP方法,包括`["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"]`, 100 | 统一通过`$.http`调用: 101 | 102 | ```javascript 103 | // GET 104 | // 简单的请求一个URL 105 | $.http.get("https://postman-echo.com/get?foo1=bar1&foo2=bar2").then(resp => { 106 | // do something 107 | }); 108 | 109 | // PUT 110 | // 请求加入一些自定义参数 111 | $.http.put({ 112 | url: "https://postman-echo.com/put", 113 | body: "HELLO", // 设置请求body 114 | headers: { 115 | 'content-type': 'text/plain' 116 | }, // 设置请求头 117 | timeout: 200 // 设置请求超时为200ms, 118 | // 一些钩子函数 119 | events: { 120 | onRequest: (method, options) => { 121 | // 请求之前可以做一些操作,比如log,注意method和options无法修改 122 | }, 123 | onResponse: (resp) => { 124 | // 请求之后可以对resp做修改,记得返回resp! 125 | resp.body = JSON.parse(resp.body); 126 | return resp; 127 | }, 128 | onTimeout: () => { 129 | // timeout的处理,比如可以退出APP 130 | $.done(); 131 | } 132 | } 133 | }).then(response => { 134 | // do something 135 | }); 136 | ``` 137 | 138 | 或者你可以使用自定义参数的HTTP对象,实现一些自定义的配置。例如我们可以这样设置默认的baseURL以及默认的请求参数,比如: 139 | 140 | - headers 141 | - timeout 142 | - events 143 | 144 | 145 | 146 | ```javascript 147 | $.http = HTTP({ 148 | baseURL: "https://www.baidu.com", 149 | timeout: 500, 150 | headers: { 151 | "User-Agent": "OpenAPI" 152 | }, 153 | events: { 154 | onTimeout: () => $.error("OH NO!") 155 | } 156 | }); 157 | ``` 158 | 159 | 160 | ```javascript 161 | // 设置默认的baseURL为api.github.com,并设置鉴权token 162 | $.http = HTTP({ 163 | baseURL: "https://api.github.com", 164 | headers: { 165 | Authorization: `token MY_TOKEN`, 166 | "User-Agent": 167 | "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" 168 | } 169 | }) 170 | 171 | $.http.get("/gists").then(resp => { 172 | // do something 173 | }) 174 | ``` 175 | 176 | ### 持久化 177 | 178 | ```javascript 179 | $.write("VALUE", "KEY"); // 将VALUE保存到KEY中 180 | $.read("KEY"); // 将KEY的值取出 181 | $.delete("KEY"); // 删除KEY的值 182 | $.cache; // 当前脚本所有的缓存 183 | $.done(); 184 | ``` 185 | 186 | 持久化在OpenAPI中得到了巨大改进,在不同环境下,其表现如下: 187 | 188 | #### 1. QX & Loon & Surge 189 | 190 | 整个API对象共用一个`cache`,所以`$.write("VALUE", "KEY")`其实只是把值保存到`cache`中。然后`cache`会被以`name`属性作为key保存到`$prefs`或者`$persistentStore`中。这形成了每个脚本独自的缓存空间,避免key碰撞的情况产生。 191 | 192 | ```javascript 193 | const $1 = API("APP1"); 194 | const $2 = API("APP2"); 195 | 196 | // 这是安全的! 197 | $1.write("data", "key"); 198 | $2.write("data", "key"); 199 | ``` 200 | 201 | #### 2. Node.js 202 | 203 | Node.js环境中,`cache`会被保存到和脚本同级目录下的`name.json`中。 204 | 205 | **如果希望直接存取`$prefs`或者`$persistentStore`里面的缓存,可以通过在`KEY`前面加`#`号实现。** 此时Node.js环境则直接存取脚本同级目录下的`root.json`里面的缓存: 206 | 207 | ```javascript 208 | $.read("#KEY"); 209 | $.write(value, "#KEY"); 210 | ``` 211 | 212 | ### 其他 213 | 214 | #### 延时 215 | 216 | ```javascript 217 | $.wait(1000).then(()=>{ 218 | // 等待1000毫秒之后执行 219 | }) 220 | 221 | // 在任何Promise后面可以自定义delay 222 | $.http.get("http://www.baidu.com") 223 | .delay(1000) // 延迟1000毫秒 224 | .then(resp => { 225 | // do something with response. 226 | }) 227 | ``` 228 | 229 | 更全面的用法请查看`example.js`。 230 | -------------------------------------------------------------------------------- /Express.js: -------------------------------------------------------------------------------- 1 | function ENV() { 2 | const isQX = typeof $task !== "undefined"; 3 | const isLoon = typeof $loon !== "undefined"; 4 | const isSurge = typeof $httpClient !== "undefined" && !isLoon; 5 | const isNode = typeof require == "function"; 6 | const isRequest = typeof $request !== "undefined"; 7 | return {isQX, isLoon, isSurge, isNode, isRequest}; 8 | } 9 | 10 | function express({port, debug} = {port: 3000, debug: false}) { 11 | const {isNode} = debug ? false : ENV(); 12 | const DEFAULT_HEADERS = { 13 | "Content-Type": "text/plain;charset=UTF-8", 14 | "Access-Control-Allow-Origin": "*", 15 | "Access-Control-Allow-Methods": "POST,GET,OPTIONS,PATCH,PUT,DELETE", 16 | "Access-Control-Allow-Headers": 17 | "Origin, X-Requested-With, Content-Type, Accept", 18 | }; 19 | 20 | // node support 21 | if (isNode) { 22 | const express_ = require("express"); 23 | const bodyParser = require("body-parser"); 24 | const app = express_(); 25 | app.use(bodyParser.json({verify: rawBodySaver})); 26 | app.use(bodyParser.urlencoded({verify: rawBodySaver, extended: true})); 27 | app.use(bodyParser.raw({verify: rawBodySaver, type: "*/*"})); 28 | app.use((req, res, next) => { 29 | res.set(DEFAULT_HEADERS); 30 | next(); 31 | }); 32 | 33 | // adapter 34 | app.start = () => { 35 | app.listen(port, () => { 36 | $.info(`Express started on port: ${port}`); 37 | }); 38 | }; 39 | return app; 40 | } 41 | 42 | // route handlers 43 | const handlers = []; 44 | 45 | // http methods 46 | const METHODS_NAMES = [ 47 | "GET", 48 | "POST", 49 | "PUT", 50 | "DELETE", 51 | "PATCH", 52 | "OPTIONS", 53 | "HEAD'", 54 | "ALL", 55 | ]; 56 | 57 | // dispatch url to route 58 | const dispatch = (request, start = 0) => { 59 | let {method, url, headers, body} = request; 60 | if (debug) { 61 | console.log("=================== Dispatching Request ==============================="); 62 | console.log(JSON.stringify(request, null, 2)); 63 | } 64 | if (/json/i.test(headers["Content-Type"])) { 65 | body = JSON.parse(body); 66 | } 67 | 68 | method = method.toUpperCase(); 69 | const {path, query} = extractURL(url); 70 | let i; 71 | 72 | // path matching 73 | let handler = null; 74 | let longestMatchedPattern = 0; 75 | for (i = start; i < handlers.length; i++) { 76 | if (handlers[i].method === "ALL" || method === handlers[i].method) { 77 | const {pattern} = handlers[i]; 78 | if (patternMatched(pattern, path)) { 79 | if (pattern.split("/").length > longestMatchedPattern) { 80 | handler = handlers[i]; 81 | longestMatchedPattern = pattern.split("/").length; 82 | } 83 | } 84 | } 85 | } 86 | if (handler) { 87 | if (debug) { 88 | console.log(`Pattern: ${handler.pattern} matched`); 89 | } 90 | // dispatch to next handler 91 | const next = () => { 92 | dispatch(method, url, i); 93 | }; 94 | const req = { 95 | method, 96 | url, 97 | path, 98 | query, 99 | params: extractPathParams(handler.pattern, path), 100 | headers, 101 | body, 102 | }; 103 | const res = Response(); 104 | const cb = handler.callback; 105 | const onError = (err) => { 106 | res.status(500).json({ 107 | status: "failed", 108 | message: `Internal Server Error: ${err}`, 109 | }); 110 | }; 111 | if (cb.constructor.name === "AsyncFunction") { 112 | handler.callback(req, res, next).catch(onError); 113 | } else { 114 | try { 115 | handler.callback(req, res, next); 116 | } catch (err) { 117 | onError(err); 118 | } 119 | } 120 | } else { 121 | // no route, return 404 122 | const res = Response(); 123 | res.status(404).json({ 124 | status: "failed", 125 | message: "ERROR: 404 not found", 126 | }); 127 | } 128 | }; 129 | 130 | const app = {}; 131 | 132 | // attach http methods 133 | METHODS_NAMES.forEach((method) => { 134 | app[method.toLowerCase()] = (pattern, callback) => { 135 | // add handler 136 | handlers.push({method, pattern, callback}); 137 | }; 138 | }); 139 | 140 | // chainable route 141 | app.route = (pattern) => { 142 | const chainApp = {}; 143 | METHODS_NAMES.forEach((method) => { 144 | chainApp[method.toLowerCase()] = (callback) => { 145 | // add handler 146 | handlers.push({method, pattern, callback}); 147 | return chainApp; 148 | }; 149 | }); 150 | return chainApp; 151 | }; 152 | 153 | // start service 154 | app.start = () => { 155 | dispatch($request); 156 | }; 157 | 158 | return app; 159 | 160 | /************************************************ 161 | Utility Functions 162 | *************************************************/ 163 | function rawBodySaver(req, res, buf, encoding) { 164 | if (buf && buf.length) { 165 | req.rawBody = buf.toString(encoding || "utf8"); 166 | } 167 | } 168 | 169 | function Response() { 170 | let statusCode = 200; 171 | const {isQX, isLoon, isSurge} = ENV(); 172 | const headers = DEFAULT_HEADERS; 173 | const STATUS_CODE_MAP = { 174 | 200: "HTTP/1.1 200 OK", 175 | 201: "HTTP/1.1 201 Created", 176 | 302: "HTTP/1.1 302 Found", 177 | 307: "HTTP/1.1 307 Temporary Redirect", 178 | 308: "HTTP/1.1 308 Permanent Redirect", 179 | 404: "HTTP/1.1 404 Not Found", 180 | 500: "HTTP/1.1 500 Internal Server Error", 181 | }; 182 | return new (class { 183 | status(code) { 184 | statusCode = code; 185 | return this; 186 | } 187 | 188 | send(body = "") { 189 | const response = { 190 | status: isQX ? STATUS_CODE_MAP[statusCode] : statusCode, 191 | body, 192 | headers, 193 | }; 194 | if (isQX) { 195 | $done(response); 196 | } else if (isLoon || isSurge) { 197 | $done({ 198 | response, 199 | }); 200 | } 201 | } 202 | 203 | end() { 204 | this.send(); 205 | } 206 | 207 | html(data) { 208 | this.set("Content-Type", "text/html;charset=UTF-8"); 209 | this.send(data); 210 | } 211 | 212 | json(data) { 213 | this.set("Content-Type", "application/json;charset=UTF-8"); 214 | this.send(JSON.stringify(data)); 215 | } 216 | 217 | set(key, val) { 218 | headers[key] = val; 219 | return this; 220 | } 221 | })(); 222 | } 223 | 224 | function patternMatched(pattern, path) { 225 | if (pattern instanceof RegExp && pattern.test(path)) { 226 | return true; 227 | } else { 228 | // root pattern, match all 229 | if (pattern === "/") return true; 230 | // normal string pattern 231 | if (pattern.indexOf(":") === -1) { 232 | const spath = path.split("/"); 233 | const spattern = pattern.split("/"); 234 | for (let i = 0; i < spattern.length; i++) { 235 | if (spath[i] !== spattern[i]) { 236 | return false; 237 | } 238 | } 239 | return true; 240 | } 241 | // string pattern with path parameters 242 | else if (extractPathParams(pattern, path)) { 243 | return true; 244 | } 245 | } 246 | return false; 247 | } 248 | 249 | function extractURL(url) { 250 | // extract path 251 | const match = url.match(/https?:\/\/[^\/]+(\/[^?]*)/) || []; 252 | const path = match[1] || "/"; 253 | 254 | // extract query string 255 | const split = url.indexOf("?"); 256 | const query = {}; 257 | if (split !== -1) { 258 | let hashes = url.slice(url.indexOf("?") + 1).split("&"); 259 | for (let i = 0; i < hashes.length; i++) { 260 | hash = hashes[i].split("="); 261 | query[hash[0]] = hash[1]; 262 | } 263 | } 264 | return { 265 | path, 266 | query, 267 | }; 268 | } 269 | 270 | function extractPathParams(pattern, path) { 271 | if (pattern.indexOf(":") === -1) { 272 | return null; 273 | } else { 274 | const params = {}; 275 | for (let i = 0, j = 0; i < pattern.length; i++, j++) { 276 | if (pattern[i] === ":") { 277 | let key = []; 278 | let val = []; 279 | while (pattern[++i] !== "/" && i < pattern.length) { 280 | key.push(pattern[i]); 281 | } 282 | while (path[j] !== "/" && j < path.length) { 283 | val.push(path[j++]); 284 | } 285 | params[key.join("")] = val.join(""); 286 | } else { 287 | if (pattern[i] !== path[j]) { 288 | return null; 289 | } 290 | } 291 | } 292 | return params; 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /api-minified.js: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | /*********************************** API *************************************/ 3 | function ENV(){const t='undefined'!=typeof $task,e='undefined'!=typeof $loon;return{isQX:t,isLoon:e,isSurge:'undefined'!=typeof $httpClient&&!e,isNode:'undefined'!=typeof module&&!!module.exports,isRequest:'undefined'!=typeof $request,isResponse:'undefined'!=typeof $response,isScriptable:'undefined'!=typeof importModule}}function HTTP(t={baseURL:''}){const{isQX:e,isLoon:s,isSurge:o,isNode:i,isScriptable:n}=ENV(),r=/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;const h={};return['GET','POST','PUT','DELETE','HEAD','OPTIONS','PATCH'].forEach(c=>h[c.toLowerCase()]=(h=>(function(h,c){c='string'==typeof c?{url:c}:c;const l=t.baseURL;l&&!r.test(c.url||'')&&(c.url=l?l+c.url:c.url),c&&c.body&&c.headers&&!c.headers['Content-Type']&&(c.headers['Content-Type']='application/x-www-form-urlencoded');const a=(c={...t,...c}).timeout,p={...{onRequest:()=>{},onResponse:t=>t,onTimeout:()=>{}},...c.events};let _,u;if(p.onRequest(h,c),e)_=$task.fetch({method:h,...c});else if(s||o||i)_=new Promise((t,e)=>{(i?require('request'):$httpClient)[h.toLowerCase()](c,(s,o,i)=>{s?e(s):t({statusCode:o.status||o.statusCode,headers:o.headers,body:i})})});else if(n){const t=new Request(c.url);t.method=h,t.headers=c.headers,t.body=c.body,_=new Promise((e,s)=>{t.loadString().then(s=>{e({statusCode:t.response.statusCode,headers:t.response.headers,body:s})}).catch(t=>s(t))})}const d=a?new Promise((t,e)=>{u=setTimeout(()=>(p.onTimeout(),e(`${h} URL: ${c.url} exceeds the timeout ${a} ms`)),a)}):null;return(d?Promise.race([d,_]).then(t=>(clearTimeout(u),t)):_).then(t=>p.onResponse(t))})(c,h))),h}function API(t='untitled',e=!1){const{isQX:s,isLoon:o,isSurge:i,isNode:n,isScriptable:r}=ENV();return new class{constructor(t,e){this.name=t,this.debug=e,this.http=HTTP(),this.env=ENV(),n&&(this.isMute=process.env.isMute||this.isMute,this.isMuteLog=process.env.isMuteLog||this.isMuteLog),this.startTime=(new Date).getTime(),console.log(`\ud83d\udd14${t}, \u5f00\u59cb!`),this.node=(()=>{if(n){return{fs:require('fs')}}return null})(),this.initCache();Promise.prototype.delay=function(t){return this.then(function(e){return((t,e)=>new Promise(function(s){setTimeout(s.bind(null,e),t)}))(t,e)})}}initCache(){if(s&&(this.cache=JSON.parse($prefs.valueForKey(this.name)||'{}')),(o||i)&&(this.cache=JSON.parse($persistentStore.read(this.name)||'{}')),n){let t='root.json';this.node.fs.existsSync(t)||this.node.fs.writeFileSync(t,JSON.stringify({}),{flag:'wx'},t=>console.log(t)),this.root={},t=`${this.name}.json`,this.node.fs.existsSync(t)?this.cache=JSON.parse(this.node.fs.readFileSync(`${this.name}.json`)):(this.node.fs.writeFileSync(t,JSON.stringify({}),{flag:'wx'},t=>console.log(t)),this.cache={})}}persistCache(){const t=JSON.stringify(this.cache,null,2);s&&$prefs.setValueForKey(t,this.name),(o||i)&&$persistentStore.write(t,this.name),n&&(this.node.fs.writeFileSync(`${this.name}.json`,t,{flag:'w'},t=>console.log(t)),this.node.fs.writeFileSync('root.json',JSON.stringify(this.root,null,2),{flag:'w'},t=>console.log(t)))}write(t,e){if(this.log(`SET ${e}`),-1!==e.indexOf('#')){if(e=e.substr(1),i||o)return $persistentStore.write(t,e);if(s)return $prefs.setValueForKey(t,e);n&&(this.root[e]=t)}else this.cache[e]=t;this.persistCache()}read(t){return this.log(`READ ${t}`),-1===t.indexOf('#')?this.cache[t]:(t=t.substr(1),i||o?$persistentStore.read(t):s?$prefs.valueForKey(t):n?this.root[t]:void 0)}delete(t){if(this.log(`DELETE ${t}`),-1!==t.indexOf('#')){if(t=t.substr(1),i||o)return $persistentStore.write(null,t);if(s)return $prefs.removeValueForKey(t);n&&delete this.root[t]}else delete this.cache[t];this.persistCache()}notify(t,e='',r='',h={}){const c=h['open-url'],l=h['media-url'];if(r=r.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,''),!this.isMute){if(s&&$notify(t,e,r,h),i&&$notification.post(t,e,r+`${l?'\n\u591a\u5a92\u4f53:'+l:''}`,{url:c}),o){let s={};c&&(s.openUrl=c),l&&(s.mediaUrl=l),'{}'===this.toStr(s)?$notification.post(t,e,r):$notification.post(t,e,r,s)}n&&new Promise(async s=>{const o=(e?`${e}\n`:'')+r+(c?`\n\u70b9\u51fb\u8df3\u8f6c: ${c}`:'')+(l?'\n\u591a\u5a92\u4f53: '+l:'');await this.sendNotify(t,o,{url:c})})}if(!this.isMuteLog){let s=['','==============\ud83d\udce3\u7cfb\u7edf\u901a\u77e5\ud83d\udce3=============='];s.push(t),e&&s.push(e),r&&s.push(r),c&&s.push(`\u70b9\u51fb\u8df3\u8f6c: ${c}`),l&&s.push(`\u591a\u5a92\u4f53: ${l}`),console.log(s.join('\n'))}}sendNotify(t,e,s={}){return new Promise(async o=>{this.querystring=require('querystring'),this.timeout=this.timeout||'15000',e+=this.author||'\n\n\u4ec5\u4f9b\u7528\u4e8e\u5b66\u4e60 https://ooxx.be/js',this.setParam(),await Promise.all([this.serverNotify(t,e),this.pushPlusNotify(t,e)]),t=t.match(/.*?(?=\s?-)/g)?t.match(/.*?(?=\s?-)/g)[0]:t,await Promise.all([this.BarkNotify(t,e,s),this.tgBotNotify(t,e),this.ddBotNotify(t,e),this.qywxBotNotify(t,e),this.qywxamNotify(t,e),this.iGotNotify(t,e,s),this.gobotNotify(t,e)])})}setParam(){this.SCKEY=process.env.SCKEY||this.SCKEY,this.PUSH_PLUS_TOKEN=process.env.PUSH_PLUS_TOKEN||this.PUSH_PLUS_TOKEN,this.PUSH_PLUS_USER=process.env.PUSH_PLUS_USER||this.PUSH_PLUS_USER,this.BARK_PUSH=process.env.BARK_PUSH||this.BARK_PUSH,this.BARK_SOUND=process.env.BARK_SOUND||this.BARK_SOUND,this.BARK_GROUP=process.env.BARK_GROUP||'AsVow',this.BARK_PUSH&&!this.BARK_PUSH.includes('http')&&(this.BARK_PUSH=`https://api.day.app/${this.BARK_PUSH}`),this.TG_BOT_TOKEN=process.env.TG_BOT_TOKEN||this.TG_BOT_TOKEN,this.TG_USER_ID=process.env.TG_USER_ID||this.TG_USER_ID,this.TG_PROXY_AUTH=process.env.TG_PROXY_AUTH||this.TG_PROXY_AUTH,this.TG_PROXY_HOST=process.env.TG_PROXY_HOST||this.TG_PROXY_HOST,this.TG_PROXY_PORT=process.env.TG_PROXY_PORT||this.TG_PROXY_PORT,this.TG_API_HOST=process.env.TG_API_HOST||'api.telegram.org',this.DD_BOT_TOKEN=process.env.DD_BOT_TOKEN||this.DD_BOT_TOKEN,this.DD_BOT_SECRET=process.env.DD_BOT_SECRET||this.DD_BOT_SECRET,this.QYWX_KEY=process.env.QYWX_KEY||this.QYWX_KEY,this.QYWX_AM=process.env.QYWX_AM||this.QYWX_AM,this.IGOT_PUSH_KEY=process.env.IGOT_PUSH_KEY||this.IGOT_PUSH_KEY,this.GOBOT_URL=process.env.GOBOT_URL||this.GOBOT_URL,this.GOBOT_TOKEN=process.env.GOBOT_TOKEN||this.GOBOT_TOKEN,this.GOBOT_QQ=process.env.GOBOT_QQ||this.GOBOT_QQ}serverNotify(t,e,s=2100){return new Promise(o=>{if(this.SCKEY){e=e.replace(/[\n\r]/g,'\n\n');const i={url:this.SCKEY.includes('SCT')?`https://sctapi.ftqq.com/${this.SCKEY}.send`:`https://sc.ftqq.com/${this.SCKEY}.send`,body:`text=${t}&desp=${e}`,headers:{'Content-Type':'application/x-www-form-urlencoded'},timeout:this.timeout};setTimeout(()=>{this.http.post(i).then(t=>{const e=this.toObj(t.body);0===e.errno||0===e.data.errno?console.log('server\u9171\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\n'):1024===e.errno?console.log(`server\u9171\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5f02\u5e38: ${e.errmsg}\n`):console.log(`server\u9171\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5f02\u5e38\n${this.toStr(e)}`)}).catch(t=>{console.log('server\u9171\u53d1\u9001\u901a\u77e5\u8c03\u7528API\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{o()})},s)}else o()})}pushPlusNotify(t,e){return new Promise(s=>{if(this.PUSH_PLUS_TOKEN){e=e.replace(/[\n\r]/g,'
');const o={token:`${this.PUSH_PLUS_TOKEN}`,title:`${t}`,content:`${e}`,topic:`${this.PUSH_PLUS_USER}`},i={url:'https://www.pushplus.plus/send',body:this.toStr(o),headers:{'Content-Type':' application/json'},timeout:this.timeout};this.http.post(i).then(t=>{const e=this.toObj(t.body);200===e.code?console.log(`push+\u53d1\u9001${this.PUSH_PLUS_USER?'\u4e00\u5bf9\u591a':'\u4e00\u5bf9\u4e00'}\u901a\u77e5\u6d88\u606f\u5b8c\u6210\u3002\n`):console.log(`push+\u53d1\u9001${this.PUSH_PLUS_USER?'\u4e00\u5bf9\u591a':'\u4e00\u5bf9\u4e00'}\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff1a${e.msg}\n`)}).catch(t=>{console.log(`push+\u53d1\u9001${this.PUSH_PLUS_USER?'\u4e00\u5bf9\u591a':'\u4e00\u5bf9\u4e00'}\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff01\uff01\n`),this.error(t)}).finally(()=>{s()})}else s()})}BarkNotify(t,e,s={}){return new Promise(o=>{if(this.BARK_PUSH){const i={url:`${this.BARK_PUSH}/${encodeURIComponent(t)}/${encodeURIComponent(e)}?sound=${this.BARK_SOUND}&group=${this.BARK_GROUP}&${this.querystring.stringify(s)}`,headers:{'Content-Type':'application/x-www-form-urlencoded'},timeout:this.timeout};this.http.get(i).then(t=>{const e=this.toObj(t.body);200===e.code?console.log('Bark APP\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\n'):console.log(`${e.message}\n`)}).catch(t=>{console.log('Bark APP\u53d1\u9001\u901a\u77e5\u8c03\u7528API\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{o()})}else o()})}tgBotNotify(t,e){return new Promise(s=>{if(this.TG_BOT_TOKEN&&this.TG_USER_ID){const o={url:`https://${this.TG_API_HOST}/bot${this.TG_BOT_TOKEN}/sendMessage`,body:`chat_id=${this.TG_USER_ID}&text=${t}\n\n${e}&disable_web_page_preview=true`,headers:{'Content-Type':'application/x-www-form-urlencoded'},timeout:this.timeout};if(this.TG_PROXY_HOST&&this.TG_PROXY_PORT){const t={host:this.TG_PROXY_HOST,port:1*this.TG_PROXY_PORT,proxyAuth:this.TG_PROXY_AUTH};Object.assign(o,{proxy:t})}this.http.post(o).then(t=>{const e=this.toObj(t.body);e.ok?console.log('Telegram\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\u3002\n'):400===e.error_code?console.log('\u8bf7\u4e3b\u52a8\u7ed9bot\u53d1\u9001\u4e00\u6761\u6d88\u606f\u5e76\u68c0\u67e5\u63a5\u6536\u7528\u6237ID\u662f\u5426\u6b63\u786e\u3002\n'):401===e.error_code&&console.log('Telegram bot token \u586b\u5199\u9519\u8bef\u3002\n')}).catch(t=>{console.log('Telegram\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{s()})}else s()})}ddBotNotify(t,e){return new Promise(s=>{const o={url:`https://oapi.dingtalk.com/robot/send?access_token=${this.DD_BOT_TOKEN}`,json:{msgtype:'text',text:{content:` ${t}\n\n${e}`}},headers:{'Content-Type':'application/json'},timeout:this.timeout};if(this.DD_BOT_TOKEN&&this.DD_BOT_SECRET){const t=require('crypto'),e=Date.now(),i=t.createHmac('sha256',this.DD_BOT_SECRET);i.update(`${e}\n${this.DD_BOT_SECRET}`);const n=encodeURIComponent(i.digest('base64'));o.url=`${o.url}×tamp=${e}&sign=${n}`,this.http.post(o).then(t=>{const e=this.toObj(t.body);0===e.errcode?console.log('\u9489\u9489\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\u3002\n'):console.log(`${e.errmsg}\n`)}).catch(t=>{console.log('\u9489\u9489\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{s()})}else this.DD_BOT_TOKEN?this.http.post(o).then(t=>{const e=this.toObj(t.body);0===e.errcode?console.log('\u9489\u9489\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5b8c\u6210\u3002\n'):console.log(`${e.errmsg}\n`)}).catch(t=>{console.log('\u9489\u9489\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{s()}):s()})}qywxBotNotify(t,e){return new Promise(s=>{const o={url:`https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${this.QYWX_KEY}`,json:{msgtype:'text',text:{content:` ${t}\n\n${e}`}},headers:{'Content-Type':'application/json'},timeout:this.timeout};this.QYWX_KEY?this.http.post(o).then(t=>{const e=this.toObj(t.body);0===e.errcode?console.log('\u4f01\u4e1a\u5fae\u4fe1\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\u3002\n'):console.log(`${e.errmsg}\n`)}).catch(t=>{console.log('\u4f01\u4e1a\u5fae\u4fe1\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{s()}):s()})}ChangeUserId(t){if(this.QYWX_AM_AY=this.QYWX_AM.split(','),this.QYWX_AM_AY[2]){const e=this.QYWX_AM_AY[2].split('|');let s='';for(let o=0;o{if(this.QYWX_AM){this.QYWX_AM_AY=this.QYWX_AM.split(',');const o={url:'https://qyapi.weixin.qq.com/cgi-bin/gettoken',json:{corpid:`${this.QYWX_AM_AY[0]}`,corpsecret:`${this.QYWX_AM_AY[1]}`},headers:{'Content-Type':'application/json'},timeout:this.timeout};let i;this.http.post(o).then(s=>{const o=e.replace(/\n/g,'
'),n=this.toObj(s.body).access_token;switch(this.QYWX_AM_AY[4]){case'0':i={msgtype:'textcard',textcard:{title:`${t}`,description:`${e}`,url:'https://ooxx.be/js',btntxt:'\u66f4\u591a'}};break;case'1':i={msgtype:'text',text:{content:`${t}\n\n${e}`}};break;default:i={msgtype:'mpnews',mpnews:{articles:[{title:`${t}`,thumb_media_id:`${this.QYWX_AM_AY[4]}`,author:'\u667a\u80fd\u52a9\u624b',content_source_url:'',content:`${o}`,digest:`${e}`}]}}}this.QYWX_AM_AY[4]||(i={msgtype:'text',text:{content:`${t}\n\n${e}`}}),i={url:`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${n}`,json:{touser:`${this.ChangeUserId(e)}`,agentid:`${this.QYWX_AM_AY[3]}`,safe:'0',...i},headers:{'Content-Type':'application/json'}}}),this.http.post(i).then(t=>{const s=this.toObj(s);0===s.errcode?console.log('\u6210\u5458ID:'+this.ChangeUserId(e)+'\u4f01\u4e1a\u5fae\u4fe1\u5e94\u7528\u6d88\u606f\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\u3002\n'):console.log(`${s.errmsg}\n`)}).catch(t=>{console.log('\u6210\u5458ID:'+this.ChangeUserId(e)+'\u4f01\u4e1a\u5fae\u4fe1\u5e94\u7528\u6d88\u606f\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{s()})}else s()})}iGotNotify(t,e,s={}){return new Promise(o=>{if(this.IGOT_PUSH_KEY){if(this.IGOT_PUSH_KEY_REGX=new RegExp('^[a-zA-Z0-9]{24}$'),!this.IGOT_PUSH_KEY_REGX.test(this.IGOT_PUSH_KEY))return console.log('\u60a8\u6240\u63d0\u4f9b\u7684IGOT_PUSH_KEY\u65e0\u6548\n'),void o();const i={url:`https://push.hellyw.com/${this.IGOT_PUSH_KEY.toLowerCase()}`,body:`title=${t}&content=${e}&${this.querystring.stringify(s)}`,headers:{'Content-Type':'application/x-www-form-urlencoded'},timeout:this.timeout};this.http.post(i).then(t=>{const e=this.toObj(t.body);0===e.ret?console.log('iGot\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\n'):console.log(`iGot\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff1a${e.errMsg}\n`)}).catch(t=>{console.log('iGot\u53d1\u9001\u901a\u77e5\u8c03\u7528API\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{o()})}else o()})}gobotNotify(t,e,s=2100){return new Promise(o=>{if(this.GOBOT_URL){const i={url:`${this.GOBOT_URL}?access_token=${this.GOBOT_TOKEN}&${this.GOBOT_QQ}`,body:`message=${t}\n${e}`,headers:{'Content-Type':'application/x-www-form-urlencoded'},timeout:this.timeout};setTimeout(()=>{this.http.post(i).then(t=>{const e=this.toObj(t.body);0===e.retcode?console.log('go-cqhttp\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\n'):100===e.retcode?console.log(`go-cqhttp\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5f02\u5e38: ${e.errmsg}\n`):console.log(`go-cqhttp\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5f02\u5e38\n${this.toStr(e)}`)}).catch(t=>{console.log('\u53d1\u9001go-cqhttp\u901a\u77e5\u8c03\u7528API\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{o()})},s)}else o()})}log(t){this.debug&&console.log(`[${this.name}] LOG:\n${this.toStr(t)}`)}info(t){console.log(`[${this.name}] INFO:\n${this.toStr(t)}`)}error(t){console.log(`[${this.name}] ERROR:\n${this.toStr(t)}`)}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=((new Date).getTime()-this.startTime)/1e3;console.log(`\ud83d\udd14${this.name}, \u7ed3\u675f! \ud83d\udd5b ${e} \u79d2`),s||o||i?$done(t):n&&'undefined'!=typeof $context&&($context.headers=t.headers,$context.statusCode=t.statusCode,$context.body=t.body)}toObj(t){if('object'==typeof t||t instanceof Object)return t;try{return JSON.parse(t)}catch(e){return t}}toStr(t){if('string'==typeof t||t instanceof String)return t;try{return JSON.stringify(t)}catch(e){return t}}}(t,e)} 4 | /*****************************************************************************/ -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | const $ = API("APP", true); // API("APP") --> 无log输出 2 | // 测试console 3 | $.log("测试输出"); 4 | $.error("这是一条错误信息"); 5 | 6 | // 测试通知 7 | $.error({ 8 | message: "Test ERROR" 9 | }); 10 | $.notify("标题"); 11 | $.notify("跳转测试", "Subtitle", "点击跳转", { 12 | "open-url": "http://www.bing.com", 13 | }); 14 | $.notify("图片测试(QX有效)", "Subtitle", "", { 15 | "media-url": 16 | "https://avatars.githubusercontent.com/u/88471740", 17 | }); 18 | $.notify("HELLO", "", ""); 19 | 20 | // 测试缓存 21 | const key = "测试"; 22 | const data = "数据"; 23 | $.write(data, key); 24 | $.write("Hello", "World"); 25 | $.log(`当前缓存:\n${JSON.stringify($.cache)}`); 26 | if ($.read(key) !== data) { 27 | $.notify("缓存测试炸了!", "", ""); 28 | } else { 29 | $.log("缓存测试通过!"); 30 | } 31 | $.delete(key); 32 | if ($.read(key)) { 33 | $.log("缓存Key未删除!"); 34 | } 35 | 36 | $.write("World", "#Hello"); 37 | if ($.read("#Hello") !== "World") { 38 | $.notify("缓存测试炸了!", "", ""); 39 | } else { 40 | $.log("缓存测试通过!"); 41 | } 42 | 43 | $.delete("#Hello"); 44 | if ($.read("#Hello")) { 45 | $.log("缓存Key未删除!"); 46 | } 47 | 48 | const obj = { 49 | hello: { 50 | world: "HELLO", 51 | }, 52 | }; 53 | 54 | $.write(obj, "obj"); 55 | 56 | // 测试请求 57 | const headers = { 58 | "user-agent": "OpenAPI", 59 | }; 60 | const rawBody = "This is expected to be sent back as part of response body."; 61 | const jsonBody = { 62 | HELLO: "WORLD", 63 | FROM: "OpenAPI", 64 | }; 65 | 66 | function assertEqual(a, b) { 67 | for (let [key, value] of Object.entries(a)) { 68 | if (a[key] !== b[key]) { 69 | return false; 70 | } 71 | } 72 | return true; 73 | } 74 | !(async () => { 75 | await $.http 76 | .get({ 77 | url: "https://postman-echo.com/get?foo1=bar1&foo2=bar2", 78 | headers, 79 | }) 80 | .then((response) => { 81 | const body = JSON.parse(response.body); 82 | if (!assertEqual(headers, body.headers)) { 83 | console.log("ERROR: HTTP GET with header test failed!"); 84 | } else { 85 | console.log("OK: HTTP GET with header test"); 86 | } 87 | }); 88 | 89 | await $.http 90 | .put({ 91 | url: "https://postman-echo.com/put", 92 | body: rawBody, 93 | headers: { 94 | "content-type": "text/plain", 95 | }, 96 | }) 97 | .then((response) => { 98 | const body = JSON.parse(response.body); 99 | if (body.data !== rawBody) { 100 | console.log("ERROR: HTTP PUT with raw body test failed!"); 101 | } else { 102 | console.log("OK: HTTP PUT with raw body test"); 103 | } 104 | }); 105 | 106 | await $.http 107 | .patch({ 108 | url: "https://postman-echo.com/patch", 109 | body: JSON.stringify(jsonBody), 110 | }) 111 | .then((response) => { 112 | const body = JSON.parse(response.body); 113 | if (!assertEqual(body.data, jsonBody)) { 114 | console.log("ERROR: HTTP PATCH with json body test failed!"); 115 | } else { 116 | console.log("OK: HTTP PATCH with json body test"); 117 | } 118 | }); 119 | 120 | // timeout 测试,不要挂代理 121 | await $.http 122 | .get({ 123 | url: "http://www.twitter.com", 124 | timeout: 100, 125 | events: { 126 | onTimeout: () => { 127 | $.error("OHHHHHHHH"); 128 | } 129 | } 130 | }) 131 | .then((response) => { 132 | console.log(response); 133 | }) 134 | .catch((error) => { 135 | console.log(error); 136 | }); 137 | 138 | // 高级用法,自定义HTTP对象,设置默认的baseURL,以及默认的请求选项 139 | const myHttp = HTTP("http://postman-echo.com", { 140 | // 这里可以设置默认的请求options,比如timeout,events等 141 | }); 142 | })().then(() => $.done()); 143 | 144 | // delay 145 | $.wait(1000).then(() => $.log("等待1s")); 146 | 147 | $.done(); 148 | 149 | // prettier-ignore 150 | /*********************************** API *************************************/ 151 | function ENV(){const t='undefined'!=typeof $task,e='undefined'!=typeof $loon;return{isQX:t,isLoon:e,isSurge:'undefined'!=typeof $httpClient&&!e,isNode:'undefined'!=typeof module&&!!module.exports,isRequest:'undefined'!=typeof $request,isResponse:'undefined'!=typeof $response,isScriptable:'undefined'!=typeof importModule}}function HTTP(t={baseURL:''}){const{isQX:e,isLoon:s,isSurge:o,isNode:i,isScriptable:n}=ENV(),r=/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;const h={};return['GET','POST','PUT','DELETE','HEAD','OPTIONS','PATCH'].forEach(c=>h[c.toLowerCase()]=(h=>(function(h,c){c='string'==typeof c?{url:c}:c;const l=t.baseURL;l&&!r.test(c.url||'')&&(c.url=l?l+c.url:c.url),c&&c.body&&c.headers&&!c.headers['Content-Type']&&(c.headers['Content-Type']='application/x-www-form-urlencoded');const a=(c={...t,...c}).timeout,p={...{onRequest:()=>{},onResponse:t=>t,onTimeout:()=>{}},...c.events};let _,u;if(p.onRequest(h,c),e)_=$task.fetch({method:h,...c});else if(s||o||i)_=new Promise((t,e)=>{(i?require('request'):$httpClient)[h.toLowerCase()](c,(s,o,i)=>{s?e(s):t({statusCode:o.status||o.statusCode,headers:o.headers,body:i})})});else if(n){const t=new Request(c.url);t.method=h,t.headers=c.headers,t.body=c.body,_=new Promise((e,s)=>{t.loadString().then(s=>{e({statusCode:t.response.statusCode,headers:t.response.headers,body:s})}).catch(t=>s(t))})}const d=a?new Promise((t,e)=>{u=setTimeout(()=>(p.onTimeout(),e(`${h} URL: ${c.url} exceeds the timeout ${a} ms`)),a)}):null;return(d?Promise.race([d,_]).then(t=>(clearTimeout(u),t)):_).then(t=>p.onResponse(t))})(c,h))),h}function API(t='untitled',e=!1){const{isQX:s,isLoon:o,isSurge:i,isNode:n,isScriptable:r}=ENV();return new class{constructor(t,e){this.name=t,this.debug=e,this.http=HTTP(),this.env=ENV(),n&&(this.isMute=process.env.isMute||this.isMute,this.isMuteLog=process.env.isMuteLog||this.isMuteLog),this.startTime=(new Date).getTime(),console.log(`\ud83d\udd14${t}, \u5f00\u59cb!`),this.node=(()=>{if(n){return{fs:require('fs')}}return null})(),this.initCache();Promise.prototype.delay=function(t){return this.then(function(e){return((t,e)=>new Promise(function(s){setTimeout(s.bind(null,e),t)}))(t,e)})}}initCache(){if(s&&(this.cache=JSON.parse($prefs.valueForKey(this.name)||'{}')),(o||i)&&(this.cache=JSON.parse($persistentStore.read(this.name)||'{}')),n){let t='root.json';this.node.fs.existsSync(t)||this.node.fs.writeFileSync(t,JSON.stringify({}),{flag:'wx'},t=>console.log(t)),this.root={},t=`${this.name}.json`,this.node.fs.existsSync(t)?this.cache=JSON.parse(this.node.fs.readFileSync(`${this.name}.json`)):(this.node.fs.writeFileSync(t,JSON.stringify({}),{flag:'wx'},t=>console.log(t)),this.cache={})}}persistCache(){const t=JSON.stringify(this.cache,null,2);s&&$prefs.setValueForKey(t,this.name),(o||i)&&$persistentStore.write(t,this.name),n&&(this.node.fs.writeFileSync(`${this.name}.json`,t,{flag:'w'},t=>console.log(t)),this.node.fs.writeFileSync('root.json',JSON.stringify(this.root,null,2),{flag:'w'},t=>console.log(t)))}write(t,e){if(this.log(`SET ${e}`),-1!==e.indexOf('#')){if(e=e.substr(1),i||o)return $persistentStore.write(t,e);if(s)return $prefs.setValueForKey(t,e);n&&(this.root[e]=t)}else this.cache[e]=t;this.persistCache()}read(t){return this.log(`READ ${t}`),-1===t.indexOf('#')?this.cache[t]:(t=t.substr(1),i||o?$persistentStore.read(t):s?$prefs.valueForKey(t):n?this.root[t]:void 0)}delete(t){if(this.log(`DELETE ${t}`),-1!==t.indexOf('#')){if(t=t.substr(1),i||o)return $persistentStore.write(null,t);if(s)return $prefs.removeValueForKey(t);n&&delete this.root[t]}else delete this.cache[t];this.persistCache()}notify(t,e='',r='',h={}){const c=h['open-url'],l=h['media-url'];if(r=r.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,''),!this.isMute){if(s&&$notify(t,e,r,h),i&&$notification.post(t,e,r+`${l?'\n\u591a\u5a92\u4f53:'+l:''}`,{url:c}),o){let s={};c&&(s.openUrl=c),l&&(s.mediaUrl=l),'{}'===this.toStr(s)?$notification.post(t,e,r):$notification.post(t,e,r,s)}n&&new Promise(async s=>{const o=(e?`${e}\n`:'')+r+(c?`\n\u70b9\u51fb\u8df3\u8f6c: ${c}`:'')+(l?'\n\u591a\u5a92\u4f53: '+l:'');await this.sendNotify(t,o,{url:c})})}if(!this.isMuteLog){let s=['','==============\ud83d\udce3\u7cfb\u7edf\u901a\u77e5\ud83d\udce3=============='];s.push(t),e&&s.push(e),r&&s.push(r),c&&s.push(`\u70b9\u51fb\u8df3\u8f6c: ${c}`),l&&s.push(`\u591a\u5a92\u4f53: ${l}`),console.log(s.join('\n'))}}sendNotify(t,e,s={}){return new Promise(async o=>{this.querystring=require('querystring'),this.timeout=this.timeout||'15000',e+=this.author||'\n\n\u4ec5\u4f9b\u7528\u4e8e\u5b66\u4e60 https://ooxx.be/js',this.setParam(),await Promise.all([this.serverNotify(t,e),this.pushPlusNotify(t,e)]),t=t.match(/.*?(?=\s?-)/g)?t.match(/.*?(?=\s?-)/g)[0]:t,await Promise.all([this.BarkNotify(t,e,s),this.tgBotNotify(t,e),this.ddBotNotify(t,e),this.qywxBotNotify(t,e),this.qywxamNotify(t,e),this.iGotNotify(t,e,s),this.gobotNotify(t,e)])})}setParam(){this.SCKEY=process.env.SCKEY||this.SCKEY,this.PUSH_PLUS_TOKEN=process.env.PUSH_PLUS_TOKEN||this.PUSH_PLUS_TOKEN,this.PUSH_PLUS_USER=process.env.PUSH_PLUS_USER||this.PUSH_PLUS_USER,this.BARK_PUSH=process.env.BARK_PUSH||this.BARK_PUSH,this.BARK_SOUND=process.env.BARK_SOUND||this.BARK_SOUND,this.BARK_GROUP=process.env.BARK_GROUP||'AsVow',this.BARK_PUSH&&!this.BARK_PUSH.includes('http')&&(this.BARK_PUSH=`https://api.day.app/${this.BARK_PUSH}`),this.TG_BOT_TOKEN=process.env.TG_BOT_TOKEN||this.TG_BOT_TOKEN,this.TG_USER_ID=process.env.TG_USER_ID||this.TG_USER_ID,this.TG_PROXY_AUTH=process.env.TG_PROXY_AUTH||this.TG_PROXY_AUTH,this.TG_PROXY_HOST=process.env.TG_PROXY_HOST||this.TG_PROXY_HOST,this.TG_PROXY_PORT=process.env.TG_PROXY_PORT||this.TG_PROXY_PORT,this.TG_API_HOST=process.env.TG_API_HOST||'api.telegram.org',this.DD_BOT_TOKEN=process.env.DD_BOT_TOKEN||this.DD_BOT_TOKEN,this.DD_BOT_SECRET=process.env.DD_BOT_SECRET||this.DD_BOT_SECRET,this.QYWX_KEY=process.env.QYWX_KEY||this.QYWX_KEY,this.QYWX_AM=process.env.QYWX_AM||this.QYWX_AM,this.IGOT_PUSH_KEY=process.env.IGOT_PUSH_KEY||this.IGOT_PUSH_KEY,this.GOBOT_URL=process.env.GOBOT_URL||this.GOBOT_URL,this.GOBOT_TOKEN=process.env.GOBOT_TOKEN||this.GOBOT_TOKEN,this.GOBOT_QQ=process.env.GOBOT_QQ||this.GOBOT_QQ}serverNotify(t,e,s=2100){return new Promise(o=>{if(this.SCKEY){e=e.replace(/[\n\r]/g,'\n\n');const i={url:this.SCKEY.includes('SCT')?`https://sctapi.ftqq.com/${this.SCKEY}.send`:`https://sc.ftqq.com/${this.SCKEY}.send`,body:`text=${t}&desp=${e}`,headers:{'Content-Type':'application/x-www-form-urlencoded'},timeout:this.timeout};setTimeout(()=>{this.http.post(i).then(t=>{const e=this.toObj(t.body);0===e.errno||0===e.data.errno?console.log('server\u9171\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\n'):1024===e.errno?console.log(`server\u9171\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5f02\u5e38: ${e.errmsg}\n`):console.log(`server\u9171\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5f02\u5e38\n${this.toStr(e)}`)}).catch(t=>{console.log('server\u9171\u53d1\u9001\u901a\u77e5\u8c03\u7528API\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{o()})},s)}else o()})}pushPlusNotify(t,e){return new Promise(s=>{if(this.PUSH_PLUS_TOKEN){e=e.replace(/[\n\r]/g,'
');const o={token:`${this.PUSH_PLUS_TOKEN}`,title:`${t}`,content:`${e}`,topic:`${this.PUSH_PLUS_USER}`},i={url:'https://www.pushplus.plus/send',body:this.toStr(o),headers:{'Content-Type':' application/json'},timeout:this.timeout};this.http.post(i).then(t=>{const e=this.toObj(t.body);200===e.code?console.log(`push+\u53d1\u9001${this.PUSH_PLUS_USER?'\u4e00\u5bf9\u591a':'\u4e00\u5bf9\u4e00'}\u901a\u77e5\u6d88\u606f\u5b8c\u6210\u3002\n`):console.log(`push+\u53d1\u9001${this.PUSH_PLUS_USER?'\u4e00\u5bf9\u591a':'\u4e00\u5bf9\u4e00'}\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff1a${e.msg}\n`)}).catch(t=>{console.log(`push+\u53d1\u9001${this.PUSH_PLUS_USER?'\u4e00\u5bf9\u591a':'\u4e00\u5bf9\u4e00'}\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff01\uff01\n`),this.error(t)}).finally(()=>{s()})}else s()})}BarkNotify(t,e,s={}){return new Promise(o=>{if(this.BARK_PUSH){const i={url:`${this.BARK_PUSH}/${encodeURIComponent(t)}/${encodeURIComponent(e)}?sound=${this.BARK_SOUND}&group=${this.BARK_GROUP}&${this.querystring.stringify(s)}`,headers:{'Content-Type':'application/x-www-form-urlencoded'},timeout:this.timeout};this.http.get(i).then(t=>{const e=this.toObj(t.body);200===e.code?console.log('Bark APP\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\n'):console.log(`${e.message}\n`)}).catch(t=>{console.log('Bark APP\u53d1\u9001\u901a\u77e5\u8c03\u7528API\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{o()})}else o()})}tgBotNotify(t,e){return new Promise(s=>{if(this.TG_BOT_TOKEN&&this.TG_USER_ID){const o={url:`https://${this.TG_API_HOST}/bot${this.TG_BOT_TOKEN}/sendMessage`,body:`chat_id=${this.TG_USER_ID}&text=${t}\n\n${e}&disable_web_page_preview=true`,headers:{'Content-Type':'application/x-www-form-urlencoded'},timeout:this.timeout};if(this.TG_PROXY_HOST&&this.TG_PROXY_PORT){const t={host:this.TG_PROXY_HOST,port:1*this.TG_PROXY_PORT,proxyAuth:this.TG_PROXY_AUTH};Object.assign(o,{proxy:t})}this.http.post(o).then(t=>{const e=this.toObj(t.body);e.ok?console.log('Telegram\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\u3002\n'):400===e.error_code?console.log('\u8bf7\u4e3b\u52a8\u7ed9bot\u53d1\u9001\u4e00\u6761\u6d88\u606f\u5e76\u68c0\u67e5\u63a5\u6536\u7528\u6237ID\u662f\u5426\u6b63\u786e\u3002\n'):401===e.error_code&&console.log('Telegram bot token \u586b\u5199\u9519\u8bef\u3002\n')}).catch(t=>{console.log('Telegram\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{s()})}else s()})}ddBotNotify(t,e){return new Promise(s=>{const o={url:`https://oapi.dingtalk.com/robot/send?access_token=${this.DD_BOT_TOKEN}`,json:{msgtype:'text',text:{content:` ${t}\n\n${e}`}},headers:{'Content-Type':'application/json'},timeout:this.timeout};if(this.DD_BOT_TOKEN&&this.DD_BOT_SECRET){const t=require('crypto'),e=Date.now(),i=t.createHmac('sha256',this.DD_BOT_SECRET);i.update(`${e}\n${this.DD_BOT_SECRET}`);const n=encodeURIComponent(i.digest('base64'));o.url=`${o.url}×tamp=${e}&sign=${n}`,this.http.post(o).then(t=>{const e=this.toObj(t.body);0===e.errcode?console.log('\u9489\u9489\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\u3002\n'):console.log(`${e.errmsg}\n`)}).catch(t=>{console.log('\u9489\u9489\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{s()})}else this.DD_BOT_TOKEN?this.http.post(o).then(t=>{const e=this.toObj(t.body);0===e.errcode?console.log('\u9489\u9489\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5b8c\u6210\u3002\n'):console.log(`${e.errmsg}\n`)}).catch(t=>{console.log('\u9489\u9489\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{s()}):s()})}qywxBotNotify(t,e){return new Promise(s=>{const o={url:`https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${this.QYWX_KEY}`,json:{msgtype:'text',text:{content:` ${t}\n\n${e}`}},headers:{'Content-Type':'application/json'},timeout:this.timeout};this.QYWX_KEY?this.http.post(o).then(t=>{const e=this.toObj(t.body);0===e.errcode?console.log('\u4f01\u4e1a\u5fae\u4fe1\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\u3002\n'):console.log(`${e.errmsg}\n`)}).catch(t=>{console.log('\u4f01\u4e1a\u5fae\u4fe1\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{s()}):s()})}ChangeUserId(t){if(this.QYWX_AM_AY=this.QYWX_AM.split(','),this.QYWX_AM_AY[2]){const e=this.QYWX_AM_AY[2].split('|');let s='';for(let o=0;o{if(this.QYWX_AM){this.QYWX_AM_AY=this.QYWX_AM.split(',');const o={url:'https://qyapi.weixin.qq.com/cgi-bin/gettoken',json:{corpid:`${this.QYWX_AM_AY[0]}`,corpsecret:`${this.QYWX_AM_AY[1]}`},headers:{'Content-Type':'application/json'},timeout:this.timeout};let i;this.http.post(o).then(s=>{const o=e.replace(/\n/g,'
'),n=this.toObj(s.body).access_token;switch(this.QYWX_AM_AY[4]){case'0':i={msgtype:'textcard',textcard:{title:`${t}`,description:`${e}`,url:'https://ooxx.be/js',btntxt:'\u66f4\u591a'}};break;case'1':i={msgtype:'text',text:{content:`${t}\n\n${e}`}};break;default:i={msgtype:'mpnews',mpnews:{articles:[{title:`${t}`,thumb_media_id:`${this.QYWX_AM_AY[4]}`,author:'\u667a\u80fd\u52a9\u624b',content_source_url:'',content:`${o}`,digest:`${e}`}]}}}this.QYWX_AM_AY[4]||(i={msgtype:'text',text:{content:`${t}\n\n${e}`}}),i={url:`https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${n}`,json:{touser:`${this.ChangeUserId(e)}`,agentid:`${this.QYWX_AM_AY[3]}`,safe:'0',...i},headers:{'Content-Type':'application/json'}}}),this.http.post(i).then(t=>{const s=this.toObj(s);0===s.errcode?console.log('\u6210\u5458ID:'+this.ChangeUserId(e)+'\u4f01\u4e1a\u5fae\u4fe1\u5e94\u7528\u6d88\u606f\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\u3002\n'):console.log(`${s.errmsg}\n`)}).catch(t=>{console.log('\u6210\u5458ID:'+this.ChangeUserId(e)+'\u4f01\u4e1a\u5fae\u4fe1\u5e94\u7528\u6d88\u606f\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{s()})}else s()})}iGotNotify(t,e,s={}){return new Promise(o=>{if(this.IGOT_PUSH_KEY){if(this.IGOT_PUSH_KEY_REGX=new RegExp('^[a-zA-Z0-9]{24}$'),!this.IGOT_PUSH_KEY_REGX.test(this.IGOT_PUSH_KEY))return console.log('\u60a8\u6240\u63d0\u4f9b\u7684IGOT_PUSH_KEY\u65e0\u6548\n'),void o();const i={url:`https://push.hellyw.com/${this.IGOT_PUSH_KEY.toLowerCase()}`,body:`title=${t}&content=${e}&${this.querystring.stringify(s)}`,headers:{'Content-Type':'application/x-www-form-urlencoded'},timeout:this.timeout};this.http.post(i).then(t=>{const e=this.toObj(t.body);0===e.ret?console.log('iGot\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\n'):console.log(`iGot\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5931\u8d25\uff1a${e.errMsg}\n`)}).catch(t=>{console.log('iGot\u53d1\u9001\u901a\u77e5\u8c03\u7528API\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{o()})}else o()})}gobotNotify(t,e,s=2100){return new Promise(o=>{if(this.GOBOT_URL){const i={url:`${this.GOBOT_URL}?access_token=${this.GOBOT_TOKEN}&${this.GOBOT_QQ}`,body:`message=${t}\n${e}`,headers:{'Content-Type':'application/x-www-form-urlencoded'},timeout:this.timeout};setTimeout(()=>{this.http.post(i).then(t=>{const e=this.toObj(t.body);0===e.retcode?console.log('go-cqhttp\u53d1\u9001\u901a\u77e5\u6d88\u606f\u6210\u529f\ud83c\udf89\n'):100===e.retcode?console.log(`go-cqhttp\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5f02\u5e38: ${e.errmsg}\n`):console.log(`go-cqhttp\u53d1\u9001\u901a\u77e5\u6d88\u606f\u5f02\u5e38\n${this.toStr(e)}`)}).catch(t=>{console.log('\u53d1\u9001go-cqhttp\u901a\u77e5\u8c03\u7528API\u5931\u8d25\uff01\uff01\n'),this.error(t)}).finally(()=>{o()})},s)}else o()})}log(t){this.debug&&console.log(`[${this.name}] LOG:\n${this.toStr(t)}`)}info(t){console.log(`[${this.name}] INFO:\n${this.toStr(t)}`)}error(t){console.log(`[${this.name}] ERROR:\n${this.toStr(t)}`)}wait(t){return new Promise(e=>setTimeout(e,t))}done(t={}){const e=((new Date).getTime()-this.startTime)/1e3;console.log(`\ud83d\udd14${this.name}, \u7ed3\u675f! \ud83d\udd5b ${e} \u79d2`),s||o||i?$done(t):n&&'undefined'!=typeof $context&&($context.headers=t.headers,$context.statusCode=t.statusCode,$context.body=t.body)}toObj(t){if('object'==typeof t||t instanceof Object)return t;try{return JSON.parse(t)}catch(e){return t}}toStr(t){if('string'==typeof t||t instanceof String)return t;try{return JSON.stringify(t)}catch(e){return t}}}(t,e)} 152 | /*****************************************************************************/ 153 | -------------------------------------------------------------------------------- /API.js: -------------------------------------------------------------------------------- 1 | /** 2 | * OpenAPI 3 | * @author: AsVow 4 | * @Modified form: Peng-YM 5 | * https://github.com/asvow/OpenAPI/blob/main/README.md 6 | */ 7 | function ENV() { 8 | const isQX = typeof $task !== 'undefined'; 9 | const isLoon = typeof $loon !== 'undefined'; 10 | const isSurge = typeof $httpClient !== 'undefined' && !isLoon; 11 | const isNode = typeof module !== 'undefined' && !!module.exports; 12 | const isRequest = typeof $request !== 'undefined'; 13 | const isResponse = typeof $response !== 'undefined'; 14 | const isScriptable = typeof importModule !== 'undefined'; 15 | return { 16 | isQX, 17 | isLoon, 18 | isSurge, 19 | isNode, 20 | isRequest, 21 | isResponse, 22 | isScriptable 23 | }; 24 | } 25 | 26 | function HTTP(defaultOptions = { 27 | baseURL: '' 28 | }) { 29 | const { 30 | isQX, 31 | isLoon, 32 | isSurge, 33 | isNode, 34 | isScriptable, 35 | } = ENV(); 36 | const methods = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH']; 37 | const URL_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/ 38 | 39 | function send(method, options) { 40 | options = typeof options === 'string' ? { 41 | url: options 42 | } : options; 43 | const baseURL = defaultOptions.baseURL; 44 | if (baseURL && !URL_REGEX.test(options.url || '')) { 45 | options.url = baseURL ? baseURL + options.url : options.url; 46 | } 47 | if (options && options.body && options.headers && !options.headers['Content-Type']) { 48 | options.headers['Content-Type'] = 'application/x-www-form-urlencoded' 49 | } 50 | options = { 51 | ...defaultOptions, 52 | ...options 53 | }; 54 | const timeout = options.timeout; 55 | const events = { 56 | ...{ 57 | onRequest: () => {}, 58 | onResponse: (resp) => resp, 59 | onTimeout: () => {}, 60 | }, 61 | ...options.events, 62 | }; 63 | 64 | events.onRequest(method, options); 65 | 66 | let worker; 67 | if (isQX) { 68 | worker = $task.fetch({ 69 | method, 70 | ...options 71 | }); 72 | } else if (isLoon || isSurge || isNode) { 73 | worker = new Promise((resolve, reject) => { 74 | const request = isNode ? require('request') : $httpClient; 75 | request[method.toLowerCase()](options, (err, response, body) => { 76 | if (err) reject(err); 77 | else 78 | resolve({ 79 | statusCode: response.status || response.statusCode, 80 | headers: response.headers, 81 | body, 82 | }); 83 | }); 84 | }); 85 | } else if (isScriptable) { 86 | const request = new Request(options.url); 87 | request.method = method; 88 | request.headers = options.headers; 89 | request.body = options.body; 90 | worker = new Promise((resolve, reject) => { 91 | request 92 | .loadString() 93 | .then((body) => { 94 | resolve({ 95 | statusCode: request.response.statusCode, 96 | headers: request.response.headers, 97 | body, 98 | }); 99 | }) 100 | .catch((err) => reject(err)); 101 | }); 102 | } 103 | 104 | let timeoutid; 105 | const timer = timeout ? 106 | new Promise((_, reject) => { 107 | timeoutid = setTimeout(() => { 108 | events.onTimeout(); 109 | return reject( 110 | `${method} URL: ${options.url} exceeds the timeout ${timeout} ms` 111 | ); 112 | }, timeout); 113 | }) : 114 | null; 115 | 116 | return (timer ? 117 | Promise.race([timer, worker]).then((res) => { 118 | clearTimeout(timeoutid); 119 | return res; 120 | }) : 121 | worker 122 | ).then((resp) => events.onResponse(resp)); 123 | } 124 | 125 | const http = {}; 126 | methods.forEach( 127 | (method) => 128 | (http[method.toLowerCase()] = (options) => send(method, options)) 129 | ); 130 | return http; 131 | } 132 | 133 | function API(name = 'untitled', debug = false) { 134 | const { 135 | isQX, 136 | isLoon, 137 | isSurge, 138 | isNode, 139 | isScriptable 140 | } = ENV(); 141 | return new(class { 142 | constructor(name, debug) { 143 | this.name = name; 144 | this.debug = debug; 145 | 146 | this.http = HTTP(); 147 | this.env = ENV(); 148 | 149 | isNode && (this.isMute = process.env.isMute || this.isMute, this.isMuteLog = process.env.isMuteLog || this.isMuteLog); 150 | this.startTime = new Date().getTime(); 151 | console.log(`🔔${name}, 开始!`); 152 | 153 | this.node = (() => { 154 | if (isNode) { 155 | const fs = require('fs'); 156 | return { 157 | fs, 158 | }; 159 | } else { 160 | return null; 161 | } 162 | })(); 163 | 164 | this.initCache(); 165 | 166 | const delay = (t, v) => 167 | new Promise(function (resolve) { 168 | setTimeout(resolve.bind(null, v), t); 169 | }); 170 | 171 | Promise.prototype.delay = function (t) { 172 | return this.then(function (v) { 173 | return delay(t, v); 174 | }); 175 | }; 176 | } 177 | 178 | // persistence 179 | // initialize cache 180 | initCache() { 181 | if (isQX) this.cache = JSON.parse($prefs.valueForKey(this.name) || '{}'); 182 | if (isLoon || isSurge) 183 | this.cache = JSON.parse($persistentStore.read(this.name) || '{}'); 184 | 185 | if (isNode) { 186 | // create a json for root cache 187 | let fpath = 'root.json'; 188 | if (!this.node.fs.existsSync(fpath)) { 189 | this.node.fs.writeFileSync( 190 | fpath, 191 | JSON.stringify({}), { 192 | flag: 'wx' 193 | }, 194 | (err) => console.log(err) 195 | ); 196 | } 197 | this.root = {}; 198 | 199 | // create a json file with the given name if not exists 200 | fpath = `${this.name}.json`; 201 | if (!this.node.fs.existsSync(fpath)) { 202 | this.node.fs.writeFileSync( 203 | fpath, 204 | JSON.stringify({}), { 205 | flag: 'wx' 206 | }, 207 | (err) => console.log(err) 208 | ); 209 | this.cache = {}; 210 | } else { 211 | this.cache = JSON.parse( 212 | this.node.fs.readFileSync(`${this.name}.json`) 213 | ); 214 | } 215 | } 216 | } 217 | 218 | // store cache 219 | persistCache() { 220 | const data = JSON.stringify(this.cache, null, 2); 221 | if (isQX) $prefs.setValueForKey(data, this.name); 222 | if (isLoon || isSurge) $persistentStore.write(data, this.name); 223 | if (isNode) { 224 | this.node.fs.writeFileSync( 225 | `${this.name}.json`, 226 | data, { 227 | flag: 'w' 228 | }, 229 | (err) => console.log(err) 230 | ); 231 | this.node.fs.writeFileSync( 232 | 'root.json', 233 | JSON.stringify(this.root, null, 2), { 234 | flag: 'w' 235 | }, 236 | (err) => console.log(err) 237 | ); 238 | } 239 | } 240 | 241 | write(data, key) { 242 | this.log(`SET ${key}`); 243 | if (key.indexOf('#') !== -1) { 244 | key = key.substr(1); 245 | if (isSurge || isLoon) { 246 | return $persistentStore.write(data, key); 247 | } 248 | if (isQX) { 249 | return $prefs.setValueForKey(data, key); 250 | } 251 | if (isNode) { 252 | this.root[key] = data; 253 | } 254 | } else { 255 | this.cache[key] = data; 256 | } 257 | this.persistCache(); 258 | } 259 | 260 | read(key) { 261 | this.log(`READ ${key}`); 262 | if (key.indexOf('#') !== -1) { 263 | key = key.substr(1); 264 | if (isSurge || isLoon) { 265 | return $persistentStore.read(key); 266 | } 267 | if (isQX) { 268 | return $prefs.valueForKey(key); 269 | } 270 | if (isNode) { 271 | return this.root[key]; 272 | } 273 | } else { 274 | return this.cache[key]; 275 | } 276 | } 277 | 278 | delete(key) { 279 | this.log(`DELETE ${key}`); 280 | if (key.indexOf('#') !== -1) { 281 | key = key.substr(1); 282 | if (isSurge || isLoon) { 283 | return $persistentStore.write(null, key); 284 | } 285 | if (isQX) { 286 | return $prefs.removeValueForKey(key); 287 | } 288 | if (isNode) { 289 | delete this.root[key]; 290 | } 291 | } else { 292 | delete this.cache[key]; 293 | } 294 | this.persistCache(); 295 | } 296 | 297 | // notification 298 | notify(title, subtitle = '', content = '', options = {}) { 299 | const openURL = options['open-url']; 300 | const mediaURL = options['media-url']; 301 | content = content.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,''); 302 | 303 | if (!this.isMute) { 304 | if (isQX) $notify(title, subtitle, content, options); 305 | if (isSurge) { 306 | $notification.post( 307 | title, 308 | subtitle, 309 | content + `${mediaURL ? '\n多媒体:' + mediaURL : ''}`, { 310 | url: openURL, 311 | } 312 | ); 313 | } 314 | if (isLoon) { 315 | let opts = {}; 316 | if (openURL) opts['openUrl'] = openURL; 317 | if (mediaURL) opts['mediaUrl'] = mediaURL; 318 | if (this.toStr(opts) === '{}') { 319 | $notification.post(title, subtitle, content); 320 | } else { 321 | $notification.post(title, subtitle, content, opts); 322 | } 323 | } 324 | if (isNode) { 325 | new Promise(async(resolve) => { 326 | const content_ = (subtitle ? `${subtitle}\n` : '') + 327 | content + 328 | (openURL ? `\n点击跳转: ${openURL}` : '') + 329 | (mediaURL ? '\n多媒体: ' + mediaURL : ''); 330 | await this.sendNotify(title, content_, {url: openURL}); 331 | }); 332 | } 333 | } 334 | if (!this.isMuteLog) { 335 | let logs = ['', '==============📣系统通知📣==============']; 336 | logs.push(title); 337 | subtitle ? logs.push(subtitle) : ''; 338 | content ? logs.push(content) : ''; 339 | openURL ? logs.push(`点击跳转: ${openURL}`) : ''; 340 | mediaURL ? logs.push(`多媒体: ${mediaURL}`) : ''; 341 | console.log(logs.join('\n')); 342 | } 343 | } 344 | 345 | /** 346 | * sendNotify 推送通知功能 347 | * @param text 通知头 348 | * @param desp 通知体 349 | * @param params 某些推送通知方式点击弹窗可跳转, 例:{ url: 'https://abc.com' } 350 | * @returns {Promise} 351 | */ 352 | sendNotify(text, desp, params = {}) { 353 | return new Promise(async(resolve) => { 354 | //提供9种通知 355 | this.querystring = require('querystring'); 356 | this.timeout = this.timeout || '15000'; //超时时间(单位毫秒) 357 | desp += this.author || '\n\n仅供用于学习 https://ooxx.be/js'; //增加作者信息 358 | this.setParam(); 359 | await Promise.all([ 360 | this.serverNotify(text, desp), //微信server酱 361 | this.pushPlusNotify(text, desp), //pushplus(推送加) 362 | ]); 363 | //由于上述两种微信通知需点击进去才能查看到详情, 故text(标题内容)携带了通知概要, 方便区分消息来源 364 | text = text.match(/.*?(?=\s?-)/g) ? text.match(/.*?(?=\s?-)/g)[0] : text; 365 | await Promise.all([ 366 | this.BarkNotify(text, desp, params), //iOS Bark APP 367 | this.tgBotNotify(text, desp), //Telegram 机器人 368 | this.ddBotNotify(text, desp), //钉钉机器人 369 | this.qywxBotNotify(text, desp), //企业微信机器人 370 | this.qywxamNotify(text, desp), //企业微信应用消息推送 371 | this.iGotNotify(text, desp, params), //iGot 372 | this.gobotNotify(text, desp), //go-cqhttp 373 | ]); 374 | }); 375 | } 376 | 377 | setParam() { 378 | // 云端环境变量的判断与接收 379 | // 微信server酱 380 | this.SCKEY = process.env.SCKEY || this.SCKEY; 381 | // pushplus(推送加) 382 | this.PUSH_PLUS_TOKEN = process.env.PUSH_PLUS_TOKEN || this.PUSH_PLUS_TOKEN; 383 | this.PUSH_PLUS_USER = process.env.PUSH_PLUS_USER || this.PUSH_PLUS_USER; 384 | // iOS Bark APP 385 | this.BARK_PUSH = process.env.BARK_PUSH || this.BARK_PUSH; 386 | this.BARK_SOUND = process.env.BARK_SOUND || this.BARK_SOUND; 387 | this.BARK_GROUP = process.env.BARK_GROUP || 'AsVow'; 388 | if (this.BARK_PUSH && !this.BARK_PUSH.includes('http')) { 389 | this.BARK_PUSH = `https://api.day.app/${this.BARK_PUSH}`; 390 | } 391 | // Telegram 机器人 392 | this.TG_BOT_TOKEN = process.env.TG_BOT_TOKEN || this.TG_BOT_TOKEN; 393 | this.TG_USER_ID = process.env.TG_USER_ID || this.TG_USER_ID; 394 | this.TG_PROXY_AUTH = process.env.TG_PROXY_AUTH || this.TG_PROXY_AUTH; 395 | this.TG_PROXY_HOST = process.env.TG_PROXY_HOST || this.TG_PROXY_HOST; 396 | this.TG_PROXY_PORT = process.env.TG_PROXY_PORT || this.TG_PROXY_PORT; 397 | this.TG_API_HOST = process.env.TG_API_HOST || 'api.telegram.org'; 398 | // 钉钉机器人 399 | this.DD_BOT_TOKEN = process.env.DD_BOT_TOKEN || this.DD_BOT_TOKEN; 400 | this.DD_BOT_SECRET = process.env.DD_BOT_SECRET || this.DD_BOT_SECRET; 401 | // 企业微信机器人 402 | this.QYWX_KEY = process.env.QYWX_KEY || this.QYWX_KEY; 403 | // 企业微信应用消息推送 404 | this.QYWX_AM = process.env.QYWX_AM || this.QYWX_AM; 405 | // iGot 406 | this.IGOT_PUSH_KEY = process.env.IGOT_PUSH_KEY || this.IGOT_PUSH_KEY; 407 | // go-cqhttp 408 | this.GOBOT_URL = process.env.GOBOT_URL || this.GOBOT_URL; 409 | this.GOBOT_TOKEN = process.env.GOBOT_TOKEN || this.GOBOT_TOKEN; 410 | this.GOBOT_QQ = process.env.GOBOT_QQ || this.GOBOT_QQ; 411 | } 412 | 413 | serverNotify(text, desp, time = 2100) { 414 | return new Promise((resolve) => { 415 | if (this.SCKEY) { 416 | //微信server酱推送通知一个\n不会换行,需要两个\n才能换行,故做此替换 417 | desp = desp.replace(/[\n\r]/g, '\n\n'); 418 | const options = { 419 | url: this.SCKEY.includes('SCT') ? `https://sctapi.ftqq.com/${this.SCKEY}.send` : `https://sc.ftqq.com/${this.SCKEY}.send`, 420 | body: `text=${text}&desp=${desp}`, 421 | headers: { 422 | 'Content-Type': 'application/x-www-form-urlencoded', 423 | }, 424 | timeout: this.timeout 425 | }; 426 | setTimeout(() => { 427 | this.http.post(options) 428 | .then((resp) => { 429 | const data = this.toObj(resp.body); 430 | //server酱和Server酱·Turbo版的返回json格式不太一样 431 | if (data.errno === 0 || data.data.errno === 0) { 432 | console.log('server酱发送通知消息成功🎉\n'); 433 | } else if (data.errno === 1024) { 434 | // 一分钟内发送相同的内容会触发 435 | console.log(`server酱发送通知消息异常: ${data.errmsg}\n`); 436 | } else { 437 | console.log(`server酱发送通知消息异常\n${this.toStr(data)}`); 438 | } 439 | }) 440 | .catch((err) => { 441 | console.log('server酱发送通知调用API失败!!\n'); 442 | this.error(err); 443 | }) 444 | .finally(() => { 445 | resolve(); 446 | }); 447 | }, time); 448 | } else { 449 | //console.log('\n\n您未提供server酱的SCKEY,取消微信推送消息通知🚫\n'); 450 | resolve(); 451 | } 452 | }); 453 | } 454 | 455 | pushPlusNotify(text, desp) { 456 | return new Promise((resolve) => { 457 | if (this.PUSH_PLUS_TOKEN) { 458 | desp = desp.replace(/[\n\r]/g, '
'); // 默认为html, 不支持plaintext 459 | const body = { 460 | token: `${this.PUSH_PLUS_TOKEN}`, 461 | title: `${text}`, 462 | content: `${desp}`, 463 | topic: `${this.PUSH_PLUS_USER}`, 464 | }; 465 | const options = { 466 | url: `https://www.pushplus.plus/send`, 467 | body: this.toStr(body), 468 | headers: { 469 | 'Content-Type': ' application/json', 470 | }, 471 | timeout: this.timeout 472 | }; 473 | this.http.post(options) 474 | .then((resp) => { 475 | const data = this.toObj(resp.body); 476 | if (data.code === 200) { 477 | console.log(`push+发送${this.PUSH_PLUS_USER ? '一对多' : '一对一'}通知消息完成。\n`); 478 | } else { 479 | console.log(`push+发送${this.PUSH_PLUS_USER ? '一对多' : '一对一'}通知消息失败:${data.msg}\n`); 480 | } 481 | }) 482 | .catch((err) => { 483 | console.log(`push+发送${this.PUSH_PLUS_USER ? '一对多' : '一对一'}通知消息失败!!\n`); 484 | this.error(err); 485 | }) 486 | .finally(() => { 487 | resolve(); 488 | }); 489 | } else { 490 | //console.log('您未提供push+推送所需的PUSH_PLUS_TOKEN,取消push+推送消息通知🚫\n'); 491 | resolve(); 492 | } 493 | }); 494 | } 495 | 496 | BarkNotify(text, desp, params = {}) { 497 | return new Promise((resolve) => { 498 | if (this.BARK_PUSH) { 499 | const options = { 500 | url: `${this.BARK_PUSH}/${encodeURIComponent(text)}/${encodeURIComponent( 501 | desp 502 | )}?sound=${this.BARK_SOUND}&group=${this.BARK_GROUP}&${this.querystring.stringify(params)}`, 503 | headers: { 504 | 'Content-Type': 'application/x-www-form-urlencoded', 505 | }, 506 | timeout: this.timeout 507 | }; 508 | this.http.get(options) 509 | .then((resp) => { 510 | const data = this.toObj(resp.body); 511 | if (data.code === 200) { 512 | console.log('Bark APP发送通知消息成功🎉\n'); 513 | } else { 514 | console.log(`${data.message}\n`); 515 | } 516 | }) 517 | .catch((err) => { 518 | console.log('Bark APP发送通知调用API失败!!\n'); 519 | this.error(err); 520 | }) 521 | .finally(() => { 522 | resolve(); 523 | }); 524 | } else { 525 | //console.log('您未提供Bark的APP推送BARK_PUSH,取消Bark推送消息通知🚫\n'); 526 | resolve(); 527 | } 528 | }); 529 | } 530 | 531 | tgBotNotify(text, desp) { 532 | return new Promise((resolve) => { 533 | if (this.TG_BOT_TOKEN && this.TG_USER_ID) { 534 | const options = { 535 | url: `https://${this.TG_API_HOST}/bot${this.TG_BOT_TOKEN}/sendMessage`, 536 | body: `chat_id=${this.TG_USER_ID}&text=${text}\n\n${desp}&disable_web_page_preview=true`, 537 | headers: { 538 | 'Content-Type': 'application/x-www-form-urlencoded', 539 | }, 540 | timeout: this.timeout 541 | }; 542 | if (this.TG_PROXY_HOST && this.TG_PROXY_PORT) { 543 | const proxy = { 544 | host: this.TG_PROXY_HOST, 545 | port: this.TG_PROXY_PORT * 1, 546 | proxyAuth: this.TG_PROXY_AUTH, 547 | }; 548 | Object.assign(options, { proxy }); 549 | } 550 | this.http.post(options) 551 | .then((resp) => { 552 | const data = this.toObj(resp.body); 553 | if (data.ok) { 554 | console.log('Telegram发送通知消息成功🎉。\n'); 555 | } else if (data.error_code === 400) { 556 | console.log('请主动给bot发送一条消息并检查接收用户ID是否正确。\n'); 557 | } else if (data.error_code === 401) { 558 | console.log('Telegram bot token 填写错误。\n'); 559 | } 560 | }) 561 | .catch((err) => { 562 | console.log('Telegram发送通知消息失败!!\n'); 563 | this.error(err); 564 | }) 565 | .finally(() => { 566 | resolve(); 567 | }); 568 | } else { 569 | //console.log('您未提供telegram机器人推送所需的TG_BOT_TOKEN和TG_USER_ID,取消telegram推送消息通知🚫\n'); 570 | resolve(); 571 | } 572 | }); 573 | } 574 | 575 | ddBotNotify(text, desp) { 576 | return new Promise((resolve) => { 577 | const options = { 578 | url: `https://oapi.dingtalk.com/robot/send?access_token=${this.DD_BOT_TOKEN}`, 579 | json: { 580 | msgtype: 'text', 581 | text: { 582 | content: ` ${text}\n\n${desp}`, 583 | }, 584 | }, 585 | headers: { 586 | 'Content-Type': 'application/json', 587 | }, 588 | timeout: this.timeout 589 | }; 590 | if (this.DD_BOT_TOKEN && this.DD_BOT_SECRET) { 591 | const crypto = require('crypto'); 592 | const dateNow = Date.now(); 593 | const hmac = crypto.createHmac('sha256', this.DD_BOT_SECRET); 594 | hmac.update(`${dateNow}\n${this.DD_BOT_SECRET}`); 595 | const result = encodeURIComponent(hmac.digest('base64')); 596 | options.url = `${options.url}×tamp=${dateNow}&sign=${result}`; 597 | this.http.post(options) 598 | .then((resp) => { 599 | const data = this.toObj(resp.body); 600 | if (data.errcode === 0) { 601 | console.log('钉钉发送通知消息成功🎉。\n'); 602 | } else { 603 | console.log(`${data.errmsg}\n`); 604 | } 605 | }) 606 | .catch((err) => { 607 | console.log('钉钉发送通知消息失败!!\n'); 608 | this.error(err); 609 | }) 610 | .finally(() => { 611 | resolve(); 612 | }); 613 | } else if (this.DD_BOT_TOKEN) { 614 | this.http.post(options) 615 | .then((resp) => { 616 | const data = this.toObj(resp.body); 617 | if (data.errcode === 0) { 618 | console.log('钉钉发送通知消息完成。\n'); 619 | } else { 620 | console.log(`${data.errmsg}\n`); 621 | } 622 | }) 623 | .catch((err) => { 624 | console.log('钉钉发送通知消息失败!!\n'); 625 | this.error(err); 626 | }) 627 | .finally(() => { 628 | resolve(); 629 | }); 630 | } else { 631 | //console.log('您未提供钉钉机器人推送所需的DD_BOT_TOKEN或者DD_BOT_SECRET,取消钉钉推送消息通知🚫\n'); 632 | resolve(); 633 | } 634 | }); 635 | } 636 | 637 | qywxBotNotify(text, desp) { 638 | return new Promise((resolve) => { 639 | const options = { 640 | url: `https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=${this.QYWX_KEY}`, 641 | json: { 642 | msgtype: 'text', 643 | text: { 644 | content: ` ${text}\n\n${desp}`, 645 | }, 646 | }, 647 | headers: { 648 | 'Content-Type': 'application/json', 649 | }, 650 | timeout: this.timeout 651 | }; 652 | if (this.QYWX_KEY) { 653 | this.http.post(options) 654 | .then((resp) => { 655 | const data = this.toObj(resp.body); 656 | if (data.errcode === 0) { 657 | console.log('企业微信发送通知消息成功🎉。\n'); 658 | } else { 659 | console.log(`${data.errmsg}\n`); 660 | } 661 | }) 662 | .catch((err) => { 663 | console.log('企业微信发送通知消息失败!!\n'); 664 | this.error(err); 665 | }) 666 | .finally(() => { 667 | resolve(); 668 | }); 669 | } else { 670 | //console.log('您未提供企业微信机器人推送所需的QYWX_KEY,取消企业微信推送消息通知🚫\n'); 671 | resolve(); 672 | } 673 | }); 674 | } 675 | 676 | ChangeUserId(desp) { 677 | this.QYWX_AM_AY = this.QYWX_AM.split(','); 678 | if (this.QYWX_AM_AY[2]) { 679 | const userIdTmp = this.QYWX_AM_AY[2].split('|'); 680 | let userId = ''; 681 | for (let i = 0; i < userIdTmp.length; i++) { 682 | const count = '账号' + (i + 1); 683 | const count2 = '签到号 ' + (i + 1); 684 | if (desp.match(count2)) { 685 | userId = userIdTmp[i]; 686 | } 687 | } 688 | if (!userId) userId = this.QYWX_AM_AY[2]; 689 | return userId; 690 | } else { 691 | return '@all'; 692 | } 693 | } 694 | 695 | qywxamNotify(text, desp) { 696 | return new Promise((resolve) => { 697 | if (this.QYWX_AM) { 698 | this.QYWX_AM_AY = this.QYWX_AM.split(','); 699 | const options_accesstoken = { 700 | url: `https://qyapi.weixin.qq.com/cgi-bin/gettoken`, 701 | json: { 702 | corpid: `${this.QYWX_AM_AY[0]}`, 703 | corpsecret: `${this.QYWX_AM_AY[1]}`, 704 | }, 705 | headers: { 706 | 'Content-Type': 'application/json', 707 | }, 708 | timeout: this.timeout 709 | }; 710 | let options; 711 | this.http.post(options_accesstoken) 712 | .then((resp) => { 713 | const html = desp.replace(/\n/g, '
'); 714 | const json = this.toObj(resp.body); 715 | const accesstoken = json.access_token; 716 | 717 | switch (this.QYWX_AM_AY[4]) { 718 | case '0': 719 | options = { 720 | msgtype: 'textcard', 721 | textcard: { 722 | title: `${text}`, 723 | description: `${desp}`, 724 | url: 'https://ooxx.be/js', 725 | btntxt: '更多', 726 | }, 727 | }; 728 | break; 729 | 730 | case '1': 731 | options = { 732 | msgtype: 'text', 733 | text: { 734 | content: `${text}\n\n${desp}`, 735 | }, 736 | }; 737 | break; 738 | 739 | default: 740 | options = { 741 | msgtype: 'mpnews', 742 | mpnews: { 743 | articles: [ 744 | { 745 | title: `${text}`, 746 | thumb_media_id: `${this.QYWX_AM_AY[4]}`, 747 | author: `智能助手`, 748 | content_source_url: ``, 749 | content: `${html}`, 750 | digest: `${desp}`, 751 | }, 752 | ], 753 | }, 754 | }; 755 | } 756 | if (!this.QYWX_AM_AY[4]) { 757 | //如不提供第四个参数,则默认进行文本消息类型推送 758 | options = { 759 | msgtype: 'text', 760 | text: { 761 | content: `${text}\n\n${desp}`, 762 | }, 763 | }; 764 | } 765 | options = { 766 | url: `https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=${accesstoken}`, 767 | json: { 768 | touser: `${this.ChangeUserId(desp)}`, 769 | agentid: `${this.QYWX_AM_AY[3]}`, 770 | safe: '0', 771 | ...options, 772 | }, 773 | headers: { 774 | 'Content-Type': 'application/json', 775 | }, 776 | }; 777 | }) 778 | 779 | this.http.post(options) 780 | .then((resp) => { 781 | const data = this.toObj(data); 782 | if (data.errcode === 0) { 783 | console.log('成员ID:' + this.ChangeUserId(desp) + '企业微信应用消息发送通知消息成功🎉。\n'); 784 | } else { 785 | console.log(`${data.errmsg}\n`); 786 | } 787 | }) 788 | .catch((err) => { 789 | console.log('成员ID:' + this.ChangeUserId(desp) + '企业微信应用消息发送通知消息失败!!\n'); 790 | this.error(err); 791 | }) 792 | .finally(() => { 793 | resolve(); 794 | }); 795 | } else { 796 | //console.log('您未提供企业微信应用消息推送所需的QYWX_AM,取消企业微信应用消息推送消息通知🚫\n'); 797 | resolve(); 798 | } 799 | }); 800 | } 801 | 802 | iGotNotify(text, desp, params = {}) { 803 | return new Promise((resolve) => { 804 | if (this.IGOT_PUSH_KEY) { 805 | // 校验传入的IGOT_PUSH_KEY是否有效 806 | this.IGOT_PUSH_KEY_REGX = new RegExp('^[a-zA-Z0-9]{24}$'); 807 | if (!this.IGOT_PUSH_KEY_REGX.test(this.IGOT_PUSH_KEY)) { 808 | console.log('您所提供的IGOT_PUSH_KEY无效\n'); 809 | resolve(); 810 | return; 811 | } 812 | const options = { 813 | url: `https://push.hellyw.com/${this.IGOT_PUSH_KEY.toLowerCase()}`, 814 | body: `title=${text}&content=${desp}&${this.querystring.stringify(params)}`, 815 | headers: { 816 | 'Content-Type': 'application/x-www-form-urlencoded', 817 | }, 818 | timeout: this.timeout 819 | }; 820 | this.http.post(options) 821 | .then((resp) => { 822 | const data = this.toObj(resp.body); 823 | if (data.ret === 0) { 824 | console.log('iGot发送通知消息成功🎉\n'); 825 | } else { 826 | console.log(`iGot发送通知消息失败:${data.errMsg}\n`); 827 | } 828 | }) 829 | .catch((err) => { 830 | console.log('iGot发送通知调用API失败!!\n'); 831 | this.error(err); 832 | }) 833 | .finally(() => { 834 | resolve(); 835 | }); 836 | } else { 837 | //console.log('您未提供iGot的推送IGOT_PUSH_KEY,取消iGot推送消息通知🚫\n'); 838 | resolve(); 839 | } 840 | }); 841 | } 842 | 843 | gobotNotify(text, desp, time = 2100) { 844 | return new Promise((resolve) => { 845 | if (this.GOBOT_URL) { 846 | const options = { 847 | url: `${this.GOBOT_URL}?access_token=${this.GOBOT_TOKEN}&${this.GOBOT_QQ}`, 848 | body: `message=${text}\n${desp}`, 849 | headers: { 850 | 'Content-Type': 'application/x-www-form-urlencoded', 851 | }, 852 | timeout: this.timeout 853 | }; 854 | setTimeout(() => { 855 | this.http.post(options) 856 | .then((resp) => { 857 | const data = this.toObj(resp.body); 858 | if (data.retcode === 0) { 859 | console.log('go-cqhttp发送通知消息成功🎉\n'); 860 | } else if (data.retcode === 100) { 861 | console.log(`go-cqhttp发送通知消息异常: ${data.errmsg}\n`); 862 | } else { 863 | console.log(`go-cqhttp发送通知消息异常\n${this.toStr(data)}`); 864 | } 865 | }) 866 | .catch((err) => { 867 | console.log('发送go-cqhttp通知调用API失败!!\n'); 868 | this.error(err); 869 | }) 870 | .finally(() => { 871 | resolve(); 872 | }); 873 | }, time); 874 | } else { 875 | //console.log('您未提供Gobot的GOBOT_URL、GOBOT_TOKEN、GOBOT_QQ,取消go-cqhttp推送消息通知🚫\n'); 876 | resolve(); 877 | } 878 | }); 879 | } 880 | 881 | // other helper functions 882 | log(msg) { 883 | if (this.debug) console.log(`[${this.name}] LOG:\n${this.toStr(msg)}`); 884 | } 885 | 886 | info(msg) { 887 | console.log(`[${this.name}] INFO:\n${this.toStr(msg)}`); 888 | } 889 | 890 | error(msg) { 891 | console.log(`[${this.name}] ERROR:\n${this.toStr(msg)}`); 892 | } 893 | 894 | wait(millisec) { 895 | return new Promise((resolve) => setTimeout(resolve, millisec)); 896 | } 897 | 898 | done(value = {}) { 899 | const endTime = new Date().getTime(); 900 | const costTime = (endTime - this.startTime) / 1000; 901 | console.log(`🔔${this.name}, 结束! 🕛 ${costTime} 秒`); 902 | if (isQX || isLoon || isSurge) { 903 | $done(value); 904 | } else if (isNode) { 905 | if (typeof $context !== 'undefined') { 906 | $context.headers = value.headers; 907 | $context.statusCode = value.statusCode; 908 | $context.body = value.body; 909 | } 910 | } 911 | } 912 | 913 | toObj(obj_or_str) { 914 | if (typeof obj_or_str === 'object' || obj_or_str instanceof Object) 915 | return obj_or_str; 916 | else 917 | try { 918 | return JSON.parse(obj_or_str) 919 | } catch (err) { 920 | return obj_or_str; 921 | } 922 | } 923 | 924 | toStr(obj_or_str) { 925 | if (typeof obj_or_str === 'string' || obj_or_str instanceof String) 926 | return obj_or_str; 927 | else 928 | try { 929 | return JSON.stringify(obj_or_str) 930 | } catch (err) { 931 | return obj_or_str; 932 | } 933 | } 934 | })(name, debug); 935 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------