From d7ac5d4a1d4d1d3db3104f1e7349dd00d620895a Mon Sep 17 00:00:00 2001 From: Peng-YM <1048217874pengym@gmail.com> Date: Wed, 19 Aug 2020 16:18:48 +0800 Subject: [PATCH] Re-organized project structure --- .gitignore | 4 +- collection.json => backend/collection.json | 0 backend/package-lock.json | 684 +++++ backend/package.json | 7 +- sub.json => backend/sub.json | 0 sub-store.js | 2420 ----------------- {sub-store-web => web}/.gitignore | 0 {sub-store-web => web}/README.md | 0 {sub-store-web => web}/babel.config.js | 0 {sub-store-web => web}/package-lock.json | 0 {sub-store-web => web}/package.json | 0 {sub-store-web => web}/public/favicon.ico | Bin {sub-store-web => web}/public/index.html | 0 {sub-store-web => web}/src/App.vue | 0 {sub-store-web => web}/src/assets/logo.png | Bin .../src/components/HelloWorld.vue | 0 {sub-store-web => web}/src/main.js | 0 17 files changed, 692 insertions(+), 2423 deletions(-) rename collection.json => backend/collection.json (100%) create mode 100644 backend/package-lock.json rename sub.json => backend/sub.json (100%) delete mode 100644 sub-store.js rename {sub-store-web => web}/.gitignore (100%) rename {sub-store-web => web}/README.md (100%) rename {sub-store-web => web}/babel.config.js (100%) rename {sub-store-web => web}/package-lock.json (100%) rename {sub-store-web => web}/package.json (100%) rename {sub-store-web => web}/public/favicon.ico (100%) rename {sub-store-web => web}/public/index.html (100%) rename {sub-store-web => web}/src/App.vue (100%) rename {sub-store-web => web}/src/assets/logo.png (100%) rename {sub-store-web => web}/src/components/HelloWorld.vue (100%) rename {sub-store-web => web}/src/main.js (100%) diff --git a/.gitignore b/.gitignore index 39695f6..5f3ef18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # json config -sub-store.json -root.json +backend/sub-store.json +backend/root.json # Logs logs diff --git a/collection.json b/backend/collection.json similarity index 100% rename from collection.json rename to backend/collection.json diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 0000000..cf8a275 --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,684 @@ +{ + "name": "sub-store-backend", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "ajv": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", + "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + } + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + } + } +} diff --git a/backend/package.json b/backend/package.json index c578068..73363f2 100644 --- a/backend/package.json +++ b/backend/package.json @@ -7,5 +7,10 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "author": "", - "license": "GPL" + "license": "GPL", + "dependencies": { + "body-parser": "^1.19.0", + "express": "^4.17.1", + "request": "^2.88.2" + } } diff --git a/sub.json b/backend/sub.json similarity index 100% rename from sub.json rename to backend/sub.json diff --git a/sub-store.js b/sub-store.js deleted file mode 100644 index cbd9691..0000000 --- a/sub-store.js +++ /dev/null @@ -1,2420 +0,0 @@ -/** - * Sub-Store v0.1 (Backend only) - * @Author: Peng-YM - * @Description: - * 适用于QX,Loon,Surge的订阅管理工具。 - * - 功能 - * 1. 订阅转换,支持SS, SSR, V2RayN, QX, Loon, Surge格式的互相转换。 - * 2. 节点过滤,重命名,排序等。 - * 3. 订阅拆分,组合。 - */ - -const $ = API("sub-store"); - -// Constants -const SUBS_KEY = "subs"; -const COLLECTIONS_KEY = "collections"; - -// SOME INITIALIZATIONS -if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY); -if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY); - -// BACKEND API -const $app = express(); - -// subscriptions -$app.get("/download/:name", downloadSub) - -$app.route("/sub/:name") - .get(getSub) - .patch(updateSub) - .delete(deleteSub); - -$app.route("/sub") - .get(getAllSubs) - .post(newSub) - .delete(deleteAllSubs); - -// collections -$app.get("/download/collection/:name", downloadCollection); -$app.route("/collection/:name") - .get(getCollection) - .patch(updateCollection) - .delete(deleteCollection); -$app.route("/collection") - .get(getAllCollections) - .post(newCollection) - .delete(deleteAllCollections); - -$app.all("/", (req, res) => { - res.send("Hello from Sub-Store! Made with ❤️ by Peng-YM.") -}); - -$app.start(); - -// SOME CONSTANTS -const FALL_BACK_TARGET = "Raw"; -const DEFAULT_SUPPORTED_PLATFORMS = { - QX: true, - Loon: true, - Surge: true, - Raw: true -} -const AVAILABLE_FILTERS = { - "Keyword Filter": KeywordFilter, - "Discard Keyword Filter": DiscardKeywordFilter, - "Useless Filter": UselessFilter, - "Region Filter": RegionFilter, - "Regex Filter": RegexFilter, - "Discard Regex Filter": DiscardRegexFilter, - "Type Filter": TypeFilter, - "Script Filter": ScriptFilter -} - -const AVAILABLE_OPERATORS = { - "Set Property Operator": SetPropertyOperator, - "Flag Operator": FlagOperator, - "Sort Operator": SortOperator, - "Keyword Sort Operator": KeywordSortOperator, - "Keyword Rename Operator": KeywordRenameOperator, - "Keyword Delete Operator": KeywordDeleteOperator, - "Regex Rename Operator": RegexRenameOperator, - "Regex Delete Operator": RegexDeleteOperator, - "Script Operator": ScriptOperator -} - -/**************************** API -- Subscriptions ***************************************/ -// download subscription, for APP only -async function downloadSub(req, res) { - const {name} = req.params; - const platform = getPlatformFromHeaders(req.headers); - const allSubs = $.read(SUBS_KEY); - if (allSubs[name]) { - const sub = allSubs[name]; - try { - const output = await parseSub(sub, platform); - res.send(output); - } catch (err) { - $.notify('[Sub-Store]', '❌ 无法获取订阅!', `错误信息:${err}`) - res.status(500).json({ - status: "failed", - message: err - }); - } - } else { - res.status(404).json({ - status: "failed", - message: `订阅${name}不存在!` - }); - } -} - -async function parseSub(sub, platform) { - // download from url - const raw = await $.http.get(sub.url).then(resp => resp.body).catch(err => { - throw new Error(err); - }); - console.log("======================================================================="); - console.log(`Processing subscription: ${sub.name}, target platform ==> ${platform}.`); - const $parser = ProxyParser(platform); - let proxies = $parser.parse(raw); - - // filters - const $filter = ProxyFilter(); - // operators - const $operator = ProxyOperator(); - - for (const item of sub.process || []) { - if (item.type.indexOf("Script") !== -1) { - if (item.args && item.args[0].indexOf("http") !== -1) { - // if this is remote script - item.args[0] = await $.http.get(item.args[0]).then(resp => resp.body).catch(err => { - throw new Error(`Error when downloading remote script: ${item.args[0]}.\n Reason: ${err}`); - }); - } - } - if (item.type.indexOf("Filter") !== -1) { - const filter = AVAILABLE_FILTERS[item.type]; - if (filter) { - $filter.addFilters(filter(...(item.args || []))); - proxies = $filter.process(proxies); - console.log(`Applying filter "${item.type}" with arguments:\n >>> ${item.args || "None"}`); - } - } else if (item.type.indexOf("Operator") !== -1) { - const operator = AVAILABLE_OPERATORS[item.type]; - if (operator) { - $operator.addOperators(operator(...(item.args || []))); - proxies = $operator.process(proxies); - console.log(`Applying operator "${item.type}" with arguments: \n >>> ${item.args || "None"}`); - } - } - } - return $parser.produce(proxies); -} - -// Subscriptions -async function getSub(req, res) { - const {name} = req.params; - const sub = $.read(SUBS_KEY)[name]; - if (sub) { - res.json({ - status: "success", - data: sub - }); - } else { - res.status(404).json({ - status: "failed", - message: `未找到订阅:${name}!` - }); - } -} - -async function newSub(req, res) { - const sub = req.body; - const allSubs = $.read(SUBS_KEY); - if (allSubs[sub.name]) { - res.status(500).json({ - status: "failed", - message: `订阅${sub.name}已存在!` - }); - } - // validate name - if (/^[\w-_]*$/.test(sub.name)) { - allSubs[sub.name] = sub; - $.write(allSubs, SUBS_KEY); - res.status(201).json({ - status: "success", - data: sub - }); - } else { - res.status(500).json({ - status: "failed", - message: `订阅名称 ${sub.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。` - }) - } -} - -async function updateSub(req, res) { - const {name} = req.params; - let sub = req.body; - const allSubs = $.read(SUBS_KEY); - if (allSubs[name]) { - const newSub = { - ...allSubs[name], - ...sub - }; - allSubs[name] = newSub; - $.write(allSubs, SUBS_KEY); - res.json({ - status: "success", - data: newSub - }) - } else { - res.status(500).json({ - status: "failed", - message: `订阅${name}不存在,无法更新!` - }); - } -} - -async function deleteSub(req, res) { - const {name} = req.params; - let allSubs = $.read(SUBS_KEY); - delete allSubs[name]; - $.write(allSubs, SUBS_KEY); - res.json({ - status: "success" - }); -} - -async function getAllSubs(req, res) { - const allSubs = $.read(SUBS_KEY); - res.json({ - status: "success", - data: Object.keys(allSubs) - }); -} - -async function deleteAllSubs(req, res) { - $.write({}, SUBS_KEY); - res.json({ - status: "success" - }); -} - -// Collections -async function downloadCollection(req, res) { - const {name} = req.params; - const collection = $.read(COLLECTIONS_KEY)[name]; - const platform = getPlatformFromHeaders(req.headers); - if (collection) { - const subs = collection.subscriptions || []; - const output = await Promise.all(subs.map(async id => { - const sub = $.read(SUBS_KEY)[id]; - try { - return parseSub(sub, platform); - } catch (err) { - console.log(`ERROR when process subscription: ${id}`); - return ""; - } - })); - res.send(output.join("\n")); - } else { - $.notify('[Sub-Store]', `❌ 未找到订阅集:${name}!`) - res.status(404).json({ - status: "failed", - message: `❌ 未找到订阅集:${name}!` - }); - } -} - -async function getCollection(req, res) { - const {name} = req.params; - const collection = $.read(COLLECTIONS_KEY)[name]; - if (collection) { - res.json({ - status: "success", - data: collection - }); - } else { - res.status(404).json({ - status: "failed", - message: `未找到订阅集:${name}!` - }); - } -} - -async function newCollection(req, res) { - const collection = req.body; - const allCol = $.read(COLLECTIONS_KEY); - if (allCol[collection.name]) { - res.status(500).json({ - status: "failed", - message: `订阅集${collection.name}已存在!` - }); - } - // validate name - if (/^[\w-_]*$/.test(collection.name)) { - allCol[collection.name] = collection; - $.write(allCol, COLLECTIONS_KEY); - res.status(201).json({ - status: "success", - data: collection - }); - } else { - res.status(500).json({ - status: "failed", - message: `订阅集名称 ${collection.name} 中含有非法字符!名称中只能包含英文字母、数字、下划线、横杠。` - }) - } -} - -async function updateCollection(req, res) { - const {name} = req.params; - let collection = req.body; - const allCol = $.read(COLLECTIONS_KEY); - if (allCol[name]) { - const newCol = { - ...allCol[name], - ...collection - }; - allCol[name] = newCol; - $.write(allCol, COLLECTIONS_KEY); - res.json({ - status: "success", - data: newCol - }) - } else { - res.status(500).json({ - status: "failed", - message: `订阅集${name}不存在,无法更新!` - }); - } -} - -async function deleteCollection(req, res) { - const {name} = req.params; - let allCol = $.read(COLLECTIONS_KEY); - delete allCol[name]; - $.write(allCol, COLLECTIONS_KEY); - res.json({ - status: "success" - }); -} - -async function getAllCollections(req, res) { - const allCols = $.read(COLLECTIONS_KEY); - res.json({ - status: "success", - data: Object.keys(allCols) - }); -} - -async function deleteAllCollections(req, res) { - $.write({}, COLLECTIONS_KEY); - res.json({ - status: "success" - }); -} - -/**************************** Proxy Handlers ***************************************/ -function ProxyParser(targetPlatform) { - // parser collections - const parsers = []; - const producers = []; - - function addParsers(...args) { - args.forEach(a => parsers.push(a())); - } - - function addProducers(...args) { - args.forEach(a => producers.push(a())) - } - - function parse(raw) { - raw = preprocessing(raw); - const lines = raw.split("\n"); - const result = []; - // convert to json format - for (let line of lines) { - line = line.trim(); - if (line.length === 0) continue; // skip empty line - if (line.startsWith("#")) continue; // skip comments - let matched = false; - for (const p of parsers) { - const {patternTest, func} = p; - - // some lines with weird format may produce errors! - let patternMatched; - try { - patternMatched = patternTest(line); - } catch (err) { - patternMatched = false; - } - - if (patternMatched) { - matched = true; - // run parser safely. - try { - const proxy = func(line); - if (!proxy) { - // failed to parse this line - console.log(`ERROR: parser return nothing for \n${line}\n`); - break; - } - // skip unsupported proxies - // if proxy.supported is undefined, assume that all platforms are supported. - if (typeof proxy.supported === 'undefined' || proxy.supported[targetPlatform]) { - delete proxy.supported; - result.push(proxy); - break; - } - } catch (err) { - console.log(`ERROR: Failed to parse line: \n ${line}\n Reason: ${err}`); - } - } - } - if (!matched) { - console.log(`ERROR: Failed to find a rule to parse line: \n${line}\n`); - } - } - if (result.length === 0) { - throw new Error(`ERROR: Input does not contains any valid node for platform ${targetPlatform}`) - } - return result; - } - - function produce(proxies) { - for (const p of producers) { - if (p.targetPlatform === targetPlatform) { - return proxies.map(proxy => { - try { - return p.output(proxy) - } catch (err) { - console.log(`ERROR: cannot produce proxy: ${JSON.stringify(proxy)}\nReason: ${err}`); - return ""; - } - }).join("\n"); - } - } - throw new Error(`Cannot find any producer for target platform: ${targetPlatform}`); - } - - // preprocess raw input - function preprocessing(raw) { - let output; - if (raw.indexOf("DOCTYPE html") !== -1) { - // HTML format, maybe a wrong URL! - throw new Error("Invalid format HTML!"); - } - // check if content is based64 encoded - const Base64 = new Base64Code(); - const keys = ["dm1lc3M", "c3NyOi8v", "dHJvamFu", "c3M6Ly", "c3NkOi8v"]; - if (keys.some(k => raw.indexOf(k) !== -1)) { - output = Base64.safeDecode(raw); - } else { - output = raw; - } - output = output.split("\n"); - for (let i = 0; i < output.length; i++) { - output[i] = output[i].trim(); // trim lines - } - return output.join("\n"); - } - - // Parsers - addParsers( - // URI format parsers - URI_SS, URI_SSR, URI_VMess, URI_Trojan, - // Quantumult X platform - QX_SS, QX_SSR, QX_VMess, QX_Trojan, QX_Http, - // Loon platform - Loon_SS, Loon_SSR, Loon_VMess, Loon_Trojan, Loon_Http, - // Surge platform - Surge_SS, Surge_VMess, Surge_Trojan, Surge_Http - ); - - // Producers - addProducers( - QX_Producer, Loon_Producer, Surge_Producer, Raw_Producer - ); - - return { - parse, produce - }; -} - -function ProxyFilter() { - const filters = []; - - function addFilters(...args) { - args.forEach(a => filters.push(a)); - } - - // select proxies - function process(proxies) { - let selected = FULL(proxies.length, true); - for (const filter of filters) { - try { - selected = AND(selected, filter.func(proxies)); - } catch (err) { - console.log(`Cannot apply filter ${filter.name}\n Reason: ${err}`); - } - } - return proxies.filter((_, i) => selected[i]) - } - - return { - process, addFilters - } -} - -function ProxyOperator() { - const operators = []; - - function addOperators(...args) { - args.forEach(a => operators.push(a)); - } - - // run all operators - function process(proxies) { - let output = clone(proxies); - for (const op of operators) { - try { - const output_ = op.func(output); - if (output_) output = output_; - } catch (err) { - // print log and skip this operator - console.log(`ERROR: cannot apply operator ${op.name}! Reason: ${err}`); - } - } - return output; - } - - return {addOperators, process} -} - -/**************************** URI Format ***************************************/ -// Parse SS URI format (only supports new SIP002, legacy format is depreciated). -// reference: https://shadowsocks.org/en/spec/SIP002-URI-Scheme.html -function URI_SS() { - const patternTest = (line) => { - return /^ss:\/\//.test(line); - } - const Base64 = new Base64Code(); - const supported = clone(DEFAULT_SUPPORTED_PLATFORMS); - const func = (line) => { - // parse url - let content = line.split("ss://")[1]; - - const proxy = { - name: decodeURIComponent(line.split("#")[1]), - type: "ss", - supported - } - content = content.split("#")[0]; // strip proxy name - - // handle IPV4 and IPV6 - const serverAndPort = content.match(/@([^\/]*)\//)[1]; - const portIdx = serverAndPort.lastIndexOf(":"); - proxy.server = serverAndPort.substring(0, portIdx); - proxy.port = serverAndPort.substring(portIdx + 1); - - const userInfo = Base64.safeDecode(content.split("@")[0]).split(":"); - proxy.cipher = userInfo[0]; - proxy.password = userInfo[1]; - - // handle obfs - const idx = content.indexOf("?plugin="); - if (idx !== -1) { - const pluginInfo = ("plugin=" + decodeURIComponent(content.split("?plugin=")[1])).split(";"); - const params = {}; - for (const item of pluginInfo) { - const [key, val] = item.split("="); - if (key) params[key] = val || true; // some options like "tls" will not have value - } - switch (params.plugin) { - case 'simple-obfs': - proxy.plugin = 'obfs' - proxy['plugin-opts'] = { - mode: params.obfs, - host: params['obfs-host'] - } - break - case 'v2ray-plugin': - proxy.supported = { - ...DEFAULT_SUPPORTED_PLATFORMS, - Loon: false, - Surge: false - } - proxy.obfs = 'v2ray-plugin' - proxy['plugin-opts'] = { - mode: "websocket", - host: params['obfs-host'], - path: params.path || "" - } - break - default: - throw new Error(`Unsupported plugin option: ${params.plugin}`) - } - } - return proxy; - } - return {patternTest, func}; -} - -// Parse URI SSR format, such as ssr://xxx -function URI_SSR() { - const patternTest = (line) => { - return /^ssr:\/\//.test(line); - } - const Base64 = new Base64Code(); - const supported = { - ...DEFAULT_SUPPORTED_PLATFORMS, - Surge: false - } - - const func = (line) => { - line = Base64.safeDecode(line.split("ssr://")[1]); - - // handle IPV6 & IPV4 format - let splitIdx = line.indexOf(':origin'); - if (splitIdx === -1) { - splitIdx = line.indexOf(":auth_"); - } - const serverAndPort = line.substring(0, splitIdx); - const server = serverAndPort.substring(0, serverAndPort.lastIndexOf(":")); - const port = serverAndPort.substring(serverAndPort.lastIndexOf(":") + 1); - - let params = line.substring(splitIdx + 1).split("/?")[0].split(":"); - let proxy = { - type: "ssr", - server, - port, - protocol: params[0], - cipher: params[1], - obfs: params[2], - password: Base64.safeDecode(params[3]), - supported - } - // get other params - params = {}; - line = line.split("/?")[1].split("&"); - if (line.length > 1) { - for (const item of line) { - const [key, val] = item.split("="); - params[key] = val; - } - } - proxy = { - ...proxy, - name: Base64.safeDecode(params.remarks), - "protocol-param": Base64.safeDecode(params.protoparam).replace(/\s/g, "") || "", - "obfs-param": Base64.safeDecode(params.obfsparam).replace(/\s/g, "") || "" - } - return proxy; - } - - return {patternTest, func}; -} - -// V2rayN URI VMess format -// reference: https://github.com/2dust/v2rayN/wiki/%E5%88%86%E4%BA%AB%E9%93%BE%E6%8E%A5%E6%A0%BC%E5%BC%8F%E8%AF%B4%E6%98%8E(ver-2) -function URI_VMess() { - const patternTest = (line) => { - return /^vmess:\/\//.test(line); - } - const Base64 = new Base64Code(); - const supported = clone(DEFAULT_SUPPORTED_PLATFORMS); - const func = (line) => { - line = line.split("vmess://")[1]; - const params = JSON.parse(Base64.safeDecode(line)); - const proxy = { - name: params.ps, - type: "vmess", - server: params.add, - port: params.port, - cipher: "auto", // V2rayN has no default cipher! use aes-128-gcm as default. - uuid: params.id, - alterId: params.aid || 0, - tls: JSON.parse(params.tls || "false"), - supported - } - // handle obfs - if (params.net === 'ws') { - proxy.network = 'ws'; - proxy['ws-path'] = params.path; - proxy['ws-headers'] = { - Host: params.host || params.add - } - } - return proxy - } - return {patternTest, func}; -} - -// Trojan URI format -function URI_Trojan() { - const patternTest = (line) => { - return /^trojan:\/\//.test(line); - } - const supported = clone(DEFAULT_SUPPORTED_PLATFORMS); - const func = (line) => { - // trojan forces to use 443 port - if (line.indexOf(":443") === -1) { - throw new Error("Trojan port should always be 443!"); - } - line = line.split("trojan://")[1]; - const server = line.split("@")[1].split(":443")[0]; - - return { - name: `[Trojan] ${server}`, // trojan uri has no server tag! - type: "trojan", - server, - port: 443, - password: line.split("@")[0], - supported - } - } - return {patternTest, func}; -} - -/**************************** Quantumult X ***************************************/ -function QX_SS() { - const patternTest = (line) => { - return /^shadowsocks\s*=/.test(line.split(",")[0].trim()) && line.indexOf("ssr-protocol") === -1; - }; - const func = (line) => { - const params = getQXParams(line); - const proxy = { - name: params.tag, - type: "ss", - server: params.server, - port: params.port, - cipher: params.method, - password: params.password, - udp: JSON.parse(params["udp-relay"] || "false"), - tfo: JSON.parse(params["fast-open"] || "false"), - supported: clone(DEFAULT_SUPPORTED_PLATFORMS) - }; - // handle obfs options - if (params.obfs) { - proxy["plugin-opts"] = { - host: params['obfs-host'] || proxy.server - }; - switch (params.obfs) { - case "http": - case "tls": - proxy.plugin = "obfs"; - proxy["plugin-opts"].mode = params.obfs; - break; - case "ws": - case "wss": - proxy["plugin-opts"] = { - ...proxy["plugin-opts"], - mode: "websocket", - path: params['obfs-uri'], - tls: params.obfs === 'wss' - } - proxy.plugin = "v2ray-plugin" - // Surge and Loon lack support for v2ray-plugin obfs - proxy.supported.Surge = false - proxy.supported.Loon = false - break; - } - } - return proxy; - }; - return {patternTest, func}; -} - -function QX_SSR() { - const patternTest = (line) => { - return /^shadowsocks\s*=/.test(line.split(",")[0].trim()) && line.indexOf("ssr-protocol") !== -1; - }; - const supported = { - ...DEFAULT_SUPPORTED_PLATFORMS, - Surge: false - } - const func = (line) => { - const params = getQXParams(line); - const proxy = { - name: params.tag, - type: "ssr", - server: params.server, - port: params.port, - cipher: params.method, - password: params.password, - protocol: params["ssr-protocol"], - obfs: "plain", // default obfs - "protocol-param": params['ssr-protocol-param'], - udp: JSON.parse(params["udp-relay"] || "false"), - tfo: JSON.parse(params["fast-open"] || "false"), - supported - } - // handle obfs options - if (params.obfs) { - proxy.obfs = params.obfs; - proxy['obfs-param'] = params['obfs-host'] - } - return proxy; - } - return {patternTest, func}; -} - -function QX_VMess() { - const patternTest = (line) => { - return /^vmess\s*=/.test(line.split(",")[0].trim()); - }; - const func = (line) => { - const params = getQXParams(line) - const proxy = { - type: "vmess", - name: params.tag, - server: params.server, - port: params.port, - cipher: params.method || 'none', - uuid: params.password, - alterId: 0, - tls: params.obfs === 'over-tls' || params.obfs === 'wss', - udp: JSON.parse(params["udp-relay"] || "false"), - tfo: JSON.parse(params["fast-open"] || "false"), - } - if (proxy.tls) { - proxy.sni = params['obfs-host'] || params.server; - proxy.scert = !JSON.parse(params['tls-verification'] || 'true'); - } - // handle ws headers - if (params.obfs === 'ws' || params.obfs === 'wss') { - proxy.network = 'ws'; - proxy['ws-path'] = params['obfs-uri']; - proxy['ws-headers'] = { - Host: params['obfs-host'] || params.server // if no host provided, use the same as server - } - } - return proxy; - } - - return {patternTest, func}; -} - -function QX_Trojan() { - const patternTest = (line) => { - return /^trojan\s*=/.test(line.split(",")[0].trim()); - }; - const func = (line) => { - const params = getQXParams(line); - const proxy = { - type: "trojan", - name: params.tag, - server: params.server, - port: params.port, - password: params.password, - sni: params['tls-host'] || params.server, - udp: JSON.parse(params["udp-relay"] || "false"), - tfo: JSON.parse(params["fast-open"] || "false"), - } - proxy.scert = !JSON.parse(params['tls-verification'] || 'true'); - return proxy; - } - return {patternTest, func} -} - -function QX_Http() { - const patternTest = (line) => { - return /^http\s*=/.test(line.split(",")[0].trim()); - }; - const func = (line) => { - const params = getQXParams(line); - const proxy = { - type: "http", - name: params.tag, - server: params.server, - port: params.port, - username: params.username, - password: params.password, - tls: JSON.parse(params['over-tls'] || "false"), - udp: JSON.parse(params["udp-relay"] || "false"), - tfo: JSON.parse(params["fast-open"] || "false"), - } - if (proxy.tls) { - proxy.sni = params['tls-host'] || proxy.server; - proxy.scert = !JSON.parse(params['tls-verification'] || 'true'); - } - return proxy; - } - - return {patternTest, func}; -} - -function getQXParams(line) { - const groups = line.split(","); - const params = {}; - const protocols = ["shadowsocks", "vmess", "http", "trojan"]; - groups.forEach((g) => { - const [key, value] = g.split("="); - if (protocols.indexOf(key) !== -1) { - params.type = key; - const conf = value.split(":"); - params.server = conf[0]; - params.port = conf[1]; - } else { - params[key.trim()] = value.trim(); - } - }); - return params; -} - -/**************************** Loon ***************************************/ -function Loon_SS() { - const patternTest = (line) => { - return line.split(",")[0].split("=")[1].trim().toLowerCase() === 'shadowsocks'; - } - const func = (line) => { - const params = line.split("=")[1].split(","); - const proxy = { - name: line.split("=")[0].trim(), - type: "ss", - server: params[1], - port: params[2], - cipher: params[3], - password: params[4].replace(/"/g, "") - } - // handle obfs - if (params.length > 5) { - proxy.plugin = 'obfs'; - proxy['plugin-opts'] = { - mode: proxy.obfs, - host: params[6] - } - } - return proxy; - } - return {patternTest, func}; -} - -function Loon_SSR() { - const patternTest = (line) => { - return line.split(",")[0].split("=")[1].trim().toLowerCase() === 'shadowsocksr'; - } - const func = (line) => { - const params = line.split("=")[1].split(","); - const supported = clone(DEFAULT_SUPPORTED_PLATFORMS); - supported.Surge = false; - return { - name: line.split("=")[0].trim(), - type: "ssr", - server: params[1], - port: params[2], - cipher: params[3], - password: params[4].replace(/"/g, ""), - protocol: params[5], - "protocol-param": params[6].match(/{(.*)}/)[1], - supported, - obfs: params[7], - 'obfs-param': params[8].match(/{(.*)}/)[1] - } - } - return {patternTest, func}; -} - -function Loon_VMess() { - const patternTest = (line) => { - // distinguish between surge vmess - return /^.*=\s*vmess/i.test(line.split(",")[0]) && line.indexOf("username") === -1; - } - const func = (line) => { - let params = line.split("=")[1].split(","); - const proxy = { - name: line.split("=")[0].trim(), - type: "vmess", - server: params[1], - port: params[2], - cipher: params[3] || 'none', - uuid: params[4].replace(/"/g, ""), - alterId: 0, - } - // get transport options - params = params.splice(5); - for (const item of params) { - const [key, val] = item.split(":"); - params[key] = val; - } - proxy.tls = JSON.parse(params['over-tls'] || 'false'); - if (proxy.tls) { - proxy.sni = params['tls-name'] || proxy.server; - proxy.scert = JSON.parse(params['skip-cert-verify'] || 'false'); - } - switch (params.transport) { - case "tcp": - break; - case "ws": - proxy.network = params.transport - proxy['ws-path'] = params.path - proxy['ws-headers'] = { - Host: params.host - } - } - if (proxy.tls) { - proxy.scert = JSON.parse(params['skip-cert-verify'] || 'false') - } - return proxy; - } - return {patternTest, func}; -} - -function Loon_Trojan() { - const patternTest = (line) => { - return /^.*=\s*trojan/i.test(line.split(",")[0]) && line.indexOf("password") === -1; - } - - const func = (line) => { - const params = line.split("=")[1].split(","); - const proxy = { - name: line.split("=")[0].trim(), - type: "trojan", - server: params[1], - port: params[2], - password: params[3].replace(/"/g, ""), - sni: params[1], // default sni is the server itself - scert: JSON.parse(params['skip-cert-verify'] || 'false') - } - // trojan sni - if (params.length > 4) { - const [key, val] = params[4].split(":"); - if (key === 'tls-name') proxy.sni = val; - else throw new Error(`ERROR: unknown option ${key} for line: \n${line}`); - } - return proxy; - } - - return {patternTest, func} -} - -function Loon_Http() { - const patternTest = (line) => { - return /^.*=\s*http/i.test(line.split(",")[0]) - && line.split(",").length === 5 - && line.indexOf("username") === -1 - && line.indexOf("password") === -1 - } - - const func = (line) => { - const params = line.split("=")[1].split(","); - const proxy = { - name: line.split("=")[0].trim(), - type: "http", - server: params[1], - port: params[2], - tls: params[2] === "443", // port 443 is considered as https type - username: (params[3] || "").replace(/"/g, ""), - password: (params[4] || "").replace(/"/g, "") - } - if (proxy.tls) { - proxy.sni = params['tls-name'] || proxy.server; - proxy.scert = JSON.parse(params['skip-cert-verify'] || 'false'); - } - - return proxy; - } - return {patternTest, func} -} - -/**************************** Surge ***************************************/ -function Surge_SS() { - const patternTest = (line) => { - return /^.*=\s*ss/.test(line.split(",")[0]); - } - const func = (line) => { - const params = getSurgeParams(line); - const proxy = { - name: params.name, - type: "ss", - server: params.server, - port: params.port, - cipher: params['encrypt-method'], - password: params.password, - tfo: JSON.parse(params.tfo || "false"), - udp: JSON.parse(params['udp-relay'] || "false"), - } - // handle obfs - if (params.obfs) { - proxy.plugin = 'obfs'; - proxy['plugin-opts'] = { - mode: params.obfs, - host: params['obfs-host'] - } - } - return proxy; - } - return {patternTest, func} -} - -function Surge_VMess() { - const patternTest = (line) => { - return /^.*=\s*vmess/.test(line.split(",")[0]) && line.indexOf("username") !== -1; - } - const func = (line) => { - const params = getSurgeParams(line); - const proxy = { - name: params.name, - type: "vmess", - server: params.server, - port: params.port, - uuid: params.username, - alterId: 0, // surge does not have this field - cipher: "none", // surge does not have this field - tls: JSON.parse(params.tls || "false"), - tfo: JSON.parse(params.tfo || "false"), - } - if (proxy.tls) { - proxy.scert = JSON.parse(params['skip-cert-verify'] || "false"); - proxy.sni = params['sni'] || params.server; - } - // use websocket - if (JSON.parse(params.ws || "false")) { - proxy.network = 'ws'; - proxy['ws-path'] = params['ws-path']; - proxy['ws-headers'] = { - Host: params.sni - } - } - return proxy; - } - return {patternTest, func}; -} - -function Surge_Trojan() { - const patternTest = (line) => { - return /^.*=\s*trojan/.test(line.split(",")[0]) && line.indexOf("sni") !== -1; - } - const func = (line) => { - const params = getSurgeParams(line); - return { - name: params.name, - type: "trojan", - server: params.server, - port: params.port, - password: params.password, - sni: params.sni || params.server, - tfo: JSON.parse(params.tfo || "false"), - scert: JSON.parse(params['skip-cert-verify'] || "false"), - } - } - - return {patternTest, func}; -} - -function Surge_Http() { - const patternTest = (line) => { - return /^.*=\s*http/.test(line.split(",")[0]) && !Loon_Http().patternTest(line) - } - const func = (line) => { - const params = getSurgeParams(line); - const proxy = { - name: params.name, - type: "http", - server: params.server, - port: params.port, - tls: JSON.parse(params.tls || "false"), - tfo: JSON.parse(params.tfo || "false"), - } - if (proxy.tls) { - proxy.scert = JSON.parse(params['skip-cert-verify'] || "false"); - proxy.sni = params.sni || params.server; - } - if (params.username !== 'none') proxy.username = params.username; - if (params.password !== 'none') proxy.password = params.password; - return proxy; - } - return {patternTest, func} -} - -function getSurgeParams(line) { - const params = {}; - params.name = line.split("=")[0].trim(); - const segments = line.split(","); - params.server = segments[1].trim(); - params.port = segments[2].trim(); - for (let i = 3; i < segments.length; i++) { - const item = segments[i] - if (item.indexOf("=") !== -1) { - const [key, value] = item.split("="); - params[key.trim()] = value.trim(); - } - } - return params; -} - -/**************************** Output Functions ***************************************/ -function QX_Producer() { - const targetPlatform = "QX"; - const output = (proxy) => { - let obfs_opts; - let tls_opts; - switch (proxy.type) { - case 'ss': - obfs_opts = ""; - if (proxy.plugin === 'obfs') { - obfs_opts = `,obfs=${proxy['plugin-opts'].mode},obfs-host=${proxy['plugin-opts'].host}`; - } - if (proxy.plugin === 'v2ray-plugin') { - const {tls, host, path} = proxy['plugin-opts']; - obfs_opts = `,obfs=${tls ? 'wss' : 'ws'},obfs-host=${host}${path ? ',obfs-uri=' + path : ""}`; - } - return `shadowsocks = ${proxy.server}:${proxy.port}, method=${proxy.cipher}, password=${proxy.password}${obfs_opts}${proxy.tfo ? ", fast-open=true" : ", fast-open=false"}${proxy.udp ? ", udp-relay=true" : ", udp-relay=false"}, tag=${proxy.name}` - case 'ssr': - return `shadowsocks=${proxy.server}:${proxy.port},method=${proxy.cipher},password=${proxy.password},ssr-protocol=${proxy.protocol}${proxy['protocol-param'] ? ",ssr-protocol-param=" + proxy['protocol-param'] : ""}${proxy.obfs ? ",obfs=" + proxy.obfs : ""}${proxy['obfs-param'] ? ",obfs-host=" + proxy['obfs-param'] : ""}${proxy.tfo ? ",fast-open=true" : ",fast-open=false"}${proxy.udp ? ",udp-relay=true" : ",udp-relay=false"},tag=${proxy.name}` - case 'vmess': - obfs_opts = ""; - if (proxy.network === 'ws') { - // websocket - if (proxy.tls) { - // ws-tls - obfs_opts = `,obfs=wss,obfs-host=${proxy.sni}${proxy['ws-path'] ? ",obfs-uri=" + proxy['ws-path'] : ""},tls-verification=${proxy.scert ? "false" : "true"}`; - } else { - // ws - obfs_opts = `,obfs=ws,obfs-host=${proxy['ws-headers'].Host}${proxy['ws-path'] ? ",obfs-uri=" + proxy['ws-path'] : ""}`; - } - } else { - // tcp - if (proxy.tls) { - obfs_opts = `,obfs=over-tls,obfs-host=${proxy.sni},tls-verification=${proxy.scert ? "false" : "true"}`; - } - } - return `vmess=${proxy.server}:${proxy.port},method=${proxy.cipher},password=${proxy.uuid}${obfs_opts}${proxy.tfo ? ",fast-open=true" : ",fast-open=false"}${proxy.udp ? ",udp-relay=true" : ",udp-relay=false"},tag=${proxy.name}` - case 'trojan': - return `trojan=${proxy.server}:${proxy.port},password=${proxy.password},tls-host=${proxy.sni},tls-verification=${proxy.scert ? "false" : "true"}${proxy.tfo ? ",fast-open=true" : ",fast-open=false"}${proxy.udp ? ",udp-relay=true" : ",udp-relay=false"},tag=${proxy.name}` - case 'http': - tls_opts = ""; - if (proxy.tls) { - tls_opts = `,over-tls=true,tls-verification=${proxy.scert ? "false" : "true"},tls-host=${proxy.sni}`; - } - return `http=${proxy.server}:${proxy.port},username=${proxy.username},password=${proxy.password}${tls_opts}${proxy.tfo ? ",fast-open=true" : ",fast-open=false"},tag=${proxy.name}`; - } - throw new Error(`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`); - } - return {targetPlatform, output}; -} - -function Loon_Producer() { - const targetPlatform = "Loon"; - const output = (proxy) => { - let obfs_opts, tls_opts; - switch (proxy.type) { - case "ss": - obfs_opts = ",,"; - if (proxy.plugin === 'obfs') { - const {mode, host} = proxy['plugin-opts']; - obfs_opts = `,${mode},${host}` - } - return `${proxy.name}=shadowsocks,${proxy.server},${proxy.port},${proxy.cipher},${proxy.password}${obfs_opts}`; - case "ssr": - return `${proxy.name}=shadowsocksr,${proxy.server},${proxy.port},${proxy.cipher},${proxy.password},${proxy.protocol},{${proxy['protocol-param']}},${proxy.obfs},{${proxy['obfs-param']}}` - case "vmess": - obfs_opts = ""; - if (proxy.network === 'ws') { - const host = proxy['ws-headers'].Host; - obfs_opts = `,transport:ws,host:${host},path:${proxy['ws-path']}`; - } else { - obfs_opts = `,transport:tcp`; - } - if (proxy.tls) { - obfs_opts += `,tls-name=${proxy.sni},skip-cert-verify:${proxy.scert}`; - } - return `${proxy.name}=vmess,${proxy.server},${proxy.port},${proxy.cipher},over-tls:${proxy.tls}${obfs_opts}`; - case "trojan": - return `${proxy.name}=trojan,${proxy.server},${proxy.port},${proxy.password},tls-name:${proxy.sni},skip-cert-verify:${proxy.scert}`; - case "http": - tls_opts = ""; - const base = `${proxy.name}=${proxy.tls ? 'http' : 'https'},${proxy.server},${proxy.port},${proxy.username || ""},${proxy.password || ""}`; - if (proxy.tls) { - // https - tls_opts = `,skip-cert-verify:${proxy.scert},tls-name:${proxy.sni}`; - return base + tls_opts; - } else return base; - } - throw new Error(`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`); - } - return {targetPlatform, output} -} - -function Surge_Producer() { - const targetPlatform = "Surge"; - const output = (proxy) => { - let obfs_opts, tls_opts; - switch (proxy.type) { - case 'ss': - obfs_opts = ""; - if (proxy.plugin === "obfs") { - obfs_opts = `,obfs=${proxy['plugin-opts'].mode},obfs-host=${proxy['plugin-opts'].host}` - } else { - throw new Error(`Platform ${targetPlatform} does not support obfs option: ${proxy.obfs}`); - } - return `${proxy.name}=ss,${proxy.server},${proxy.port},encrypt-method=${proxy.cipher},password=${proxy.password}${obfs_opts},tfo=${proxy.tfo || 'false'},udp-relay=${proxy.udp || 'false'}`; - case 'vmess': - tls_opts = ""; - let config = `${proxy.name}=vmess,${proxy.server},${proxy.port},username=${proxy.uuid},tls=${proxy.tls},tfo=${proxy.tfo || "false"}`; - if (proxy.network === 'ws') { - const path = proxy['ws-path']; - const host = proxy['ws-headers'].Host; - config += `,ws=true${path ? ',ws-path=' + path : ""}${host ? ',ws-headers=HOST:' + host : ""}`; - } - if (proxy.tls) { - config += `,skip-cert-verify=${proxy.scert},sni=${proxy.sni}`; - } - return config; - case 'trojan': - return `${proxy.name}=trojan,${proxy.server},${proxy.port},password=${proxy.password},sni=${proxy.sni},tfo=${proxy.tfo || 'false'}`; - case 'http': - tls_opts = ",tls=false"; - if (proxy.tls) { - tls_opts = `,tls=true,skip-cert-verify=${proxy.scert},sni=${proxy.sni}`; - } - return `${proxy.name}=http,${proxy.server},${proxy.port}${proxy.username ? ",username=" + proxy.username : ""}${proxy.password ? ",password=" + proxy.password : ""}${tls_opts},tfo=${proxy.tfo || 'false'}`; - } - throw new Error(`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`); - } - return {targetPlatform, output}; -} - -function Raw_Producer() { - const targetPlatform = "Raw"; - const output = (proxy) => { - return JSON.stringify(proxy); - } - return {targetPlatform, output}; -} - -/**************************** Operators ***************************************/ -// force to set some properties (e.g., scert, udp, tfo, etc.) -function SetPropertyOperator(key, val) { - return { - name: "Set Property Operator", - func: proxies => { - return proxies.map(p => { - p[key] = val; - return p; - }) - } - } -} - -// add or remove flag for proxies -function FlagOperator(type) { - return { - name: "Flag Operator", - func: proxies => { - return proxies.map(proxy => { - switch (type) { - case 0: - // no flag - proxy.name = removeFlag(proxy.name); - break - case 1: - // get flag - const newFlag = getFlag(proxy.name); - // remove old flag - proxy.name = removeFlag(proxy.name); - proxy.name = newFlag + " " + proxy.name; - proxy.name = proxy.name.replace(/🇹🇼/g, "🇨🇳"); - break; - default: - throw new Error("Unknown flag type: " + type); - } - return proxy; - }) - } - } -} - -// sort proxies according to their names -function SortOperator(order = 'asc') { - return { - name: "Sort Operator", - func: proxies => { - switch (order) { - case "asc": - case 'desc': - return proxies.sort((a, b) => { - let res = (a.name > b.name) ? 1 : -1; - res *= order === 'desc' ? -1 : 1; - return res - }) - case 'random': - return shuffle(proxies); - default: - throw new Error("Unknown sort option: " + order); - } - } - } -} - -// sort by keywords -function KeywordSortOperator(...keywords) { - return { - name: "Keyword Sort Operator", - func: proxies => proxies.sort((a, b) => { - const oA = getKeywordOrder(keywords, a.name); - const oB = getKeywordOrder(keywords, b.name); - if (oA && !oB) return -1; - if (oB && !oA) return 1; - if (oA && oB) return oA < oB ? -1 : 1; - if ((!oA && !oB) || (oA && oB && oA === oB)) return a.name < b.name ? -1 : 1; // fallback to normal sort - }) - } -} - -function getKeywordOrder(keywords, str) { - let order = null; - for (let i = 0; i < keywords.length; i++) { - if (str.indexOf(keywords[i]) !== -1) { - order = i + 1; // plus 1 is important! 0 will be treated as false!!! - break; - } - } - return order; -} - -// rename by keywords -// keywords: [{old: "old", now: "now"}] -function KeywordRenameOperator(...keywords) { - return { - name: "Keyword Rename Operator", - func: proxies => { - return proxies.map(proxy => { - for (const {old, now} of keywords) { - proxy.name = proxy.name.replace(old, now); - } - return proxy; - }) - } - } -} - -// rename by regex -// keywords: [{expr: "string format regex", now: "now"}] -function RegexRenameOperator(...regex) { - return { - name: "Regex Rename Operator", - func: proxies => { - return proxies.map(proxy => { - for (const {expr, now} of regex) { - proxy.name = proxy.name.replace(new RegExp(expr, "g"), now); - } - return proxy; - }) - } - } -} - -// delete keywords operator -// keywords: ['a', 'b', 'c'] -function KeywordDeleteOperator(...keywords) { - const keywords_ = keywords.map(k => { - return { - old: k, - now: "" - } - }) - return { - name: "Keyword Delete Operator", - func: KeywordRenameOperator(keywords_).func - } -} - -// delete regex operator -// regex: ['a', 'b', 'c'] -function RegexDeleteOperator(...regex) { - const regex_ = regex.map(r => { - return { - expr: r, - now: "" - } - }); - return { - name: "Regex Delete Operator", - func: RegexRenameOperator(regex_).func - } -} - -// use base64 encoded script to rename -/** Example script - function func(proxies) { - // do something - return proxies; - } - - WARNING: - 1. This function name should be `func`! - 2. Always declare variable before using it! - */ -function ScriptOperator(script) { - return { - name: "Script Operator", - func: (proxies) => { - ;(function () { - eval(script); - return func(proxies); - })(); - } - } -} - -/**************************** Filters ***************************************/ -// filter by keywords -function KeywordFilter(...keywords) { - return { - name: "Keyword Filter", - func: (proxies) => { - return proxies.map(proxy => keywords.some(k => proxy.name.indexOf(k) !== -1)); - } - } -} - -function DiscardKeywordFilter(...keywords) { - return { - name: "Discard Keyword Filter", - func: proxies => { - const filter = KeywordFilter(keywords).func; - return NOT(filter(proxies)); - } - } -} - -// filter useless proxies -function UselessFilter() { - const KEYWORDS = ["流量", "时间", "应急", "过期", "Bandwidth", "expire"]; - return { - name: "Useless Filter", - func: DiscardKeywordFilter(KEYWORDS).func - } -} - -// filter by regions -function RegionFilter(...regions) { - const REGION_MAP = { - "HK": "🇭🇰", - "TW": "🇹🇼", - "US": "🇺🇸", - "SG": "🇸🇬", - "JP": "🇯🇵", - "UK": "🇬🇧", - "KR": "🇰🇷" - }; - return { - name: "Region Filter", - func: (proxies) => { - // this would be high memory usage - return proxies.map(proxy => { - const flag = getFlag(proxy.name); - return regions.some(r => REGION_MAP[r] === flag); - }) - } - } -} - -// filter by regex -function RegexFilter(...regex) { - return { - name: "Regex Filter", - func: (proxies) => { - return proxies.map(proxy => regex.some(r => r.test(proxy.name))); - } - } -} - -function DiscardRegexFilter(...regex) { - return { - name: "Discard Regex Filter", - func: proxies => { - const filter = RegexFilter(regex).func; - return NOT(filter(proxies)); - } - } -} - -// filter by proxy types -function TypeFilter(...types) { - return { - name: "Type Filter", - func: (proxies) => { - return proxies.map(proxy => types.some(t => proxy.type === t)); - } - } -} - -// use base64 encoded script to filter proxies -/** Script Example - function func(proxies) { - const selected = FULL(proxies.length, true); - // do something - return selected; - } - WARNING: - 1. This function name should be `func`! - 2. Always declare variable before using it! - */ -function ScriptFilter(script) { - return { - name: "Script Filter", - func: (proxies) => { - !(function () { - eval(script); - return filter(proxies); - })(); - } - } -} - -/******************************** Utility Functions *********************************************/ -// get proxy flag according to its name -function getFlag(name) { - // flags from @KOP-XIAO: https://github.com/KOP-XIAO/QuantumultX/blob/master/Scripts/resource-parser.js - const flags = { - "🏳️‍🌈": ["流量", "时间", "应急", "过期", "Bandwidth", "expire"], - "🇦🇨": ["AC"], - "🇦🇹": ["奥地利", "维也纳"], - "🇦🇺": ["AU", "Australia", "Sydney", "澳大利亚", "澳洲", "墨尔本", "悉尼"], - "🇧🇪": ["BE", "比利时"], - "🇧🇬": ["保加利亚", "Bulgaria"], - "🇧🇷": ["BR", "Brazil", "巴西", "圣保罗"], - "🇨🇦": ["Canada", "Waterloo", "加拿大", "蒙特利尔", "温哥华", "楓葉", "枫叶", "滑铁卢", "多伦多"], - "🇨🇭": ["瑞士", "苏黎世", "Switzerland"], - "🇩🇪": ["DE", "German", "GERMAN", "德国", "德國", "法兰克福"], - "🇩🇰": ["丹麦"], - "🇪🇸": ["ES", "西班牙", "Spain"], - "🇪🇺": ["EU", "欧盟", "欧罗巴"], - "🇫🇮": ["Finland", "芬兰", "赫尔辛基"], - "🇫🇷": ["FR", "France", "法国", "法國", "巴黎"], - "🇬🇧": ["UK", "GB", "England", "United Kingdom", "英国", "伦敦", "英"], - "🇲🇴": ["MO", "Macao", "澳门", "CTM"], - "🇭🇺": ["匈牙利", "Hungary"], - "🇭🇰": ["HK", "Hongkong", "Hong Kong", "香港", "深港", "沪港", "呼港", "HKT", "HKBN", "HGC", "WTT", "CMI", "穗港", "京港", "港"], - "🇮🇩": ["Indonesia", "印尼", "印度尼西亚", "雅加达"], - "🇮🇪": ["Ireland", "爱尔兰", "都柏林"], - "🇮🇳": ["India", "印度", "孟买", "Mumbai"], - "🇰🇵": ["KP", "朝鲜"], - "🇰🇷": ["KR", "Korea", "KOR", "韩国", "首尔", "韩", "韓"], - "🇱🇻": ["Latvia", "Latvija", "拉脱维亚"], - "🇲🇽️": ["MEX", "MX", "墨西哥"], - "🇲🇾": ["MY", "Malaysia", "马来西亚", "吉隆坡"], - "🇳🇱": ["NL", "Netherlands", "荷兰", "荷蘭", "尼德蘭", "阿姆斯特丹"], - "🇵🇭": ["PH", "Philippines", "菲律宾"], - "🇷🇴": ["RO", "罗马尼亚"], - "🇷🇺": ["RU", "Russia", "俄罗斯", "俄羅斯", "伯力", "莫斯科", "圣彼得堡", "西伯利亚", "新西伯利亚", "京俄", "杭俄"], - "🇸🇦": ["沙特", "迪拜"], - "🇸🇪": ["SE", "Sweden"], - "🇸🇬": ["SG", "Singapore", "新加坡", "狮城", "沪新", "京新", "泉新", "穗新", "深新", "杭新", "广新"], - "🇹🇭": ["TH", "Thailand", "泰国", "泰國", "曼谷"], - "🇹🇷": ["TR", "Turkey", "土耳其", "伊斯坦布尔"], - "🇹🇼": ["TW", "Taiwan", "台湾", "台北", "台中", "新北", "彰化", "CHT", "台", "HINET"], - "🇺🇸": ["US", "USA", "America", "United States", "美国", "美", "京美", "波特兰", "达拉斯", "俄勒冈", "凤凰城", "费利蒙", "硅谷", "矽谷", "拉斯维加斯", "洛杉矶", "圣何塞", "圣克拉拉", "西雅图", "芝加哥", "沪美", "哥伦布", "纽约"], - "🇻🇳": ["VN", "越南", "胡志明市"], - "🇮🇹": ["Italy", "IT", "Nachash", "意大利", "米兰", "義大利"], - "🇿🇦": ["South Africa", "南非"], - "🇦🇪": ["United Arab Emirates", "阿联酋"], - "🇯🇵": ["JP", "Japan", "日", "日本", "东京", "大阪", "埼玉", "沪日", "穗日", "川日", "中日", "泉日", "杭日", "深日", "辽日", "广日"], - "🇦🇷": ["AR", "阿根廷"], - "🇳🇴": ["Norway", "挪威", "NO"], - "🇨🇳": ["CN", "China", "回国", "中国", "江苏", "北京", "上海", "广州", "深圳", "杭州", "徐州", "青岛", "宁波", "镇江", "back"] - }; - for (let k of Object.keys(flags)) { - if (flags[k].some((item => name.indexOf(item) !== -1))) { - return k; - } - } - // no flag found - const oldFlag = (name.match(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/) || [])[0]; - return oldFlag || "🏴‍☠️"; -} - -// remove flag -function removeFlag(str) { - return str.replace(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/g, "").trim(); -} - -// clone an object -function clone(obj) { - return JSON.parse(JSON.stringify(obj)) -} - -// shuffle array -function shuffle(array) { - let currentIndex = array.length, temporaryValue, randomIndex; - - // While there remain elements to shuffle... - while (0 !== currentIndex) { - - // Pick a remaining element... - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex -= 1; - - // And swap it with the current element. - temporaryValue = array[currentIndex]; - array[currentIndex] = array[randomIndex]; - array[randomIndex] = temporaryValue; - } - - return array; -} - -// some logical functions for proxy filters -function AND(...args) { - return args.reduce((a, b) => a.map((c, i) => b[i] && c)); -} - -function OR(...args) { - return args.reduce((a, b) => a.map((c, i) => b[i] || c)) -} - -function NOT(array) { - return array.map(c => !c); -} - -function FULL(length, bool) { - return [...Array(length).keys()].map(() => bool); -} - -// UUID -// source: https://stackoverflow.com/questions/105034/how-to-create-guid-uuid -function UUID() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); -} - -// get platform form UA -function getPlatformFromHeaders(headers) { - const keys = Object.keys(headers); - let UA = ""; - for (let k of keys) { - if (k.match(/USER-AGENT/i)) { - UA = headers[k]; - break; - } - } - if (UA.indexOf("Quantumult%20X") !== -1) { - return "QX"; - } else if (UA.indexOf("Surge") !== -1) { - return "Surge"; - } else if (UA.indexOf("Decar") !== -1) { - return "Loon"; - } else { - // browser - return FALL_BACK_TARGET; - } -} - -/*********************************** OpenAPI *************************************/ -// OpenAPI -// prettier-ignore -function ENV() { - const isQX = typeof $task != "undefined"; - const isLoon = typeof $loon != "undefined"; - const isSurge = typeof $httpClient != "undefined" && !this.isLoon; - const isJSBox = typeof require == "function" && typeof $jsbox != "undefined"; - const isNode = typeof require == "function" && !isJSBox; - const isRequest = typeof $request !== "undefined"; - return {isQX, isLoon, isSurge, isNode, isJSBox, isRequest}; -} - -function HTTP(baseURL, defaultOptions = {}) { - const {isQX, isLoon, isSurge} = ENV(); - const methods = ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"]; - - function send(method, options) { - options = typeof options === "string" ? {url: options} : options; - options.url = baseURL ? baseURL + options.url : options.url; - options = {...defaultOptions, ...options}; - const timeout = options.timeout; - const events = { - ...{ - onRequest: () => { - }, - onResponse: (resp) => resp, - onTimeout: () => { - }, - }, - ...options.events, - }; - - events.onRequest(method, options); - - let worker; - if (isQX) { - worker = $task.fetch({method, ...options}); - } else { - worker = new Promise((resolve, reject) => { - const request = isSurge || isLoon ? $httpClient : require("request"); - request[method.toLowerCase()](options, (err, response, body) => { - if (err) reject(err); - else - resolve({ - statusCode: response.status || response.statusCode, - headers: response.headers, - body, - }); - }); - }); - } - - let timeoutid; - const timer = timeout - ? new Promise((_, reject) => { - timeoutid = setTimeout(() => { - events.onTimeout(); - return reject( - `${method} URL: ${options.url} exceeds the timeout ${timeout} ms` - ); - }, timeout); - }) - : null; - - return (timer - ? Promise.race([timer, worker]).then((res) => { - clearTimeout(timeoutid); - return res; - }) - : worker - ) - .then((resp) => events.onResponse(resp)) - } - - const http = {}; - methods.forEach( - (method) => - (http[method.toLowerCase()] = (options) => send(method, options)) - ); - return http; -} - -function API(name = "untitled", debug = false) { - const {isQX, isLoon, isSurge, isNode, isJSBox} = ENV(); - return new (class { - constructor(name, debug) { - this.name = name; - this.debug = debug; - - this.http = HTTP(); - this.env = ENV(); - - this.node = (() => { - if (isNode) { - const fs = require("fs"); - - return { - fs, - }; - } else { - return null; - } - })(); - this.initCache(); - - const delay = (t, v) => - new Promise(function (resolve) { - setTimeout(resolve.bind(null, v), t); - }); - - Promise.prototype.delay = function (t) { - return this.then(function (v) { - return delay(t, v); - }); - }; - } - - // persistance - - // initialize cache - initCache() { - if (isQX) this.cache = JSON.parse($prefs.valueForKey(this.name) || "{}"); - if (isLoon || isSurge) - this.cache = JSON.parse($persistentStore.read(this.name) || "{}"); - - if (isNode) { - // create a json for root cache - let fpath = "root.json"; - if (!this.node.fs.existsSync(fpath)) { - this.node.fs.writeFileSync( - fpath, - JSON.stringify({}), - {flag: "wx"}, - (err) => console.log(err) - ); - } - this.root = {}; - - // create a json file with the given name if not exists - fpath = `${this.name}.json`; - if (!this.node.fs.existsSync(fpath)) { - this.node.fs.writeFileSync( - fpath, - JSON.stringify({}), - {flag: "wx"}, - (err) => console.log(err) - ); - this.cache = {}; - } else { - this.cache = JSON.parse( - this.node.fs.readFileSync(`${this.name}.json`) - ); - } - } - } - - // store cache - persistCache() { - const data = JSON.stringify(this.cache); - if (isQX) $prefs.setValueForKey(data, this.name); - if (isLoon || isSurge) $persistentStore.write(data, this.name); - if (isNode) { - this.node.fs.writeFileSync( - `${this.name}.json`, - data, - {flag: "w"}, - (err) => console.log(err) - ); - this.node.fs.writeFileSync( - "root.json", - JSON.stringify(this.root), - {flag: "w"}, - (err) => console.log(err) - ); - } - } - - write(data, key) { - this.log(`SET ${key}`); - if (key.indexOf("#") !== -1) { - key = key.substr(1); - if (isSurge & isLoon) { - $persistentStore.write(data, key); - } - if (isQX) { - $prefs.setValueForKey(data, key); - } - if (isNode) { - this.root[key] = data; - } - } else { - this.cache[key] = data; - } - this.persistCache(); - } - - read(key) { - this.log(`READ ${key}`); - if (key.indexOf("#") !== -1) { - key = key.substr(1); - if (isSurge & isLoon) { - return $persistentStore.read(key); - } - if (isQX) { - return $prefs.valueForKey(key); - } - if (isNode) { - return this.root[key]; - } - } else { - return this.cache[key]; - } - } - - delete(key) { - this.log(`DELETE ${key}`); - if (key.indexOf("#") !== -1) { - key = key.substr(1); - if (isSurge & isLoon) { - $persistentStore.write(null, key); - } - if (isQX) { - $prefs.removeValueForKey(key); - } - if (isNode) { - delete this.root[key]; - } - } else { - delete this.cache[key]; - } - this.persistCache(); - } - - // notification - notify(title, subtitle = "", content = "", options = {}) { - const openURL = options["open-url"]; - const mediaURL = options["media-url"]; - - const content_ = - content + - (openURL ? `\n点击跳转: ${openURL}` : "") + - (mediaURL ? `\n多媒体: ${mediaURL}` : ""); - - if (isQX) $notify(title, subtitle, content, options); - if (isSurge) $notification.post(title, subtitle, content_); - if (isLoon) $notification.post(title, subtitle, content, openURL); - if (isNode) { - if (isJSBox) { - const push = require("push"); - push.schedule({ - title: title, - body: (subtitle ? subtitle + "\n" : "") + content_, - }); - } else { - console.log(`${title}\n${subtitle}\n${content_}\n\n`); - } - } - } - - // other helper functions - log(msg) { - if (this.debug) console.log(msg); - } - - info(msg) { - console.log(msg); - } - - error(msg) { - console.log("ERROR: " + msg); - } - - wait(millisec) { - return new Promise((resolve) => setTimeout(resolve, millisec)); - } - - done(value = {}) { - if (isQX || isLoon || isSurge) { - $done(value); - } else if (isNode && !isJSBox) { - if (typeof $context !== "undefined") { - $context.headers = value.headers; - $context.statusCode = value.statusCode; - $context.body = value.body; - } - } - } - })(name, debug); -} - -/*********************************** Mini Express *************************************/ -function express(port = 3000) { - const {isNode} = ENV(); - - // node support - if (isNode) { - const express_ = require("express"); - const bodyParser = require("body-parser"); - const app = express_(); - app.use(bodyParser.json({verify: rawBodySaver})); - app.use(bodyParser.urlencoded({verify: rawBodySaver, extended: true})); - app.use(bodyParser.raw({verify: rawBodySaver, type: '*/*'})); - - // adapter - app.start = () => { - app.listen(port, () => { - console.log(`Express started on port: ${port}`); - }) - } - return app; - } - - // route handlers - const handlers = []; - - // http methods - const METHODS_NAMES = [ - "GET", - "POST", - "PUT", - "DELETE", - "PATCH", - "OPTIONS", - "HEAD'", - "ALL", - ]; - - // dispatch url to route - const dispatch = (request, start = 0) => { - let {method, url, headers, body} = request; - method = method.toUpperCase(); - const {path, query} = extractURL(url); - let handler = null; - let i; - - for (i = start; i < handlers.length; i++) { - if (handlers[i].method === "ALL" || method === handlers[i].method) { - const {pattern} = handlers[i]; - if (patternMatched(pattern, path)) { - handler = handlers[i]; - break; - } - } - } - if (handler) { - // dispatch to next handler - const next = () => { - dispatch(method, url, i); - }; - const req = { - method, url, path, query, - params: extractPathParams(handler.pattern, path), - headers, body - }; - const res = Response(); - handler.callback(req, res, next).catch(err => { - res.status(500).json({ - status: "failed", - message: err - }); - }); - } else { - // no route, return 404 - const res = Response(); - res.status("404").json({ - status: "failed", - message: "ERROR: 404 not found" - }); - } - }; - - const app = {}; - - // attach http methods - METHODS_NAMES.forEach((method) => { - app[method.toLowerCase()] = (pattern, callback) => { - // add handler - handlers.push({method, pattern, callback}); - }; - }); - - // chainable route - app.route = (pattern) => { - const chainApp = {}; - METHODS_NAMES.forEach((method) => { - chainApp[method.toLowerCase()] = (callback) => { - // add handler - handlers.push({method, pattern, callback}); - return chainApp; - }; - }); - return chainApp; - }; - - // start service - app.start = () => { - dispatch($request); - }; - - return app; - - /************************************************ - Utility Functions - *************************************************/ - function rawBodySaver(req, res, buf, encoding) { - if (buf && buf.length) { - req.rawBody = buf.toString(encoding || 'utf8'); - } - } - - function Response() { - let statusCode = "200"; - const {isQX, isLoon, isSurge} = ENV(); - const headers = { - "Content-Type": "text/plain;charset=UTF-8", - }; - return new (class { - status(code) { - statusCode = code; - return this; - } - - send(body = "") { - const response = { - status: statusCode, - body, - headers, - }; - if (isQX) { - $done(...response); - } else if (isLoon || isSurge) { - $done({ - response, - }); - } - } - - end() { - this.send(); - } - - html(data) { - this.set("Content-Type", "text/html;charset=UTF-8"); - this.send(data); - } - - json(data) { - this.set("Content-Type", "application/json;charset=UTF-8"); - this.send(JSON.stringify(data)); - } - - set(key, val) { - headers[key] = val; - return this; - } - })(); - } - - function patternMatched(pattern, path) { - if (pattern instanceof RegExp && pattern.test(path)) { - return true; - } else { - // root pattern, match all - if (pattern === "/") return true; - // normal string pattern - if (pattern.indexOf(":") === -1) { - const spath = path.split("/"); - const spattern = pattern.split("/"); - for (let i = 0; i < spattern.length; i++) { - if (spath[i] !== spattern[i]) { - return false; - } - } - return true; - } - // string pattern with path parameters - else if (extractPathParams(pattern, path)) { - return true; - } - } - return false; - } - - function extractURL(url) { - // extract path - const match = url.match(/https?:\/\/[^\/]+(\/[^?]*)/) || []; - const path = match[1] || "/"; - - // extract query string - const split = url.indexOf("?"); - const query = {}; - if (split !== -1) { - let hashes = url.slice(url.indexOf("?") + 1).split("&"); - for (let i = 0; i < hashes.length; i++) { - hash = hashes[i].split("="); - query[hash[0]] = hash[1]; - } - } - return { - path, - query, - }; - } - - function extractPathParams(pattern, path) { - if (pattern.indexOf(":") === -1) { - return null; - } else { - const params = {}; - for (let i = 0, j = 0; i < pattern.length; i++, j++) { - if (pattern[i] === ":") { - let key = []; - let val = []; - while (pattern[++i] !== "/" && i < pattern.length) { - key.push(pattern[i]); - } - while (path[j] !== "/" && j < path.length) { - val.push(path[j++]); - } - params[key.join("")] = val.join(""); - } else { - if (pattern[i] !== path[j]) { - return null; - } - } - } - return params; - } - } -} - -/******************************** Base 64 *********************************************/ -// Base64 Coding Library -// https://github.com/dankogai/js-base64#readme -// Under BSD License -function Base64Code() { - // constants - const b64chars - = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; - const b64tab = function (bin) { - const t = {}; - let i = 0; - const l = bin.length; - for (; i < l; i++) t[bin.charAt(i)] = i; - return t; - }(b64chars); - const fromCharCode = String.fromCharCode; - // encoder stuff - const cb_utob = function (c) { - let cc; - if (c.length < 2) { - cc = c.charCodeAt(0); - return cc < 0x80 ? c - : cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6)) - + fromCharCode(0x80 | (cc & 0x3f))) - : (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f)) - + fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) - + fromCharCode(0x80 | (cc & 0x3f))); - } else { - cc = 0x10000 - + (c.charCodeAt(0) - 0xD800) * 0x400 - + (c.charCodeAt(1) - 0xDC00); - return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07)) - + fromCharCode(0x80 | ((cc >>> 12) & 0x3f)) - + fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) - + fromCharCode(0x80 | (cc & 0x3f))); - } - }; - const re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g; - const utob = function (u) { - return u.replace(re_utob, cb_utob); - }; - const cb_encode = function (ccc) { - const padlen = [0, 2, 1][ccc.length % 3], - ord = ccc.charCodeAt(0) << 16 - | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8) - | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)), - chars = [ - b64chars.charAt(ord >>> 18), - b64chars.charAt((ord >>> 12) & 63), - padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63), - padlen >= 1 ? '=' : b64chars.charAt(ord & 63) - ]; - return chars.join(''); - }; - const btoa = function (b) { - return b.replace(/[\s\S]{1,3}/g, cb_encode); - }; - this.encode = function (u) { - const isUint8Array = Object.prototype.toString.call(u) === '[object Uint8Array]'; - return isUint8Array ? u.toString('base64') - : btoa(utob(String(u))); - } - const uriencode = function (u, urisafe) { - return !urisafe - ? _encode(u) - : _encode(String(u)).replace(/[+\/]/g, function (m0) { - return m0 === '+' ? '-' : '_'; - }).replace(/=/g, ''); - }; - const encodeURI = function (u) { - return uriencode(u, true) - }; - // decoder stuff - const re_btou = /[\xC0-\xDF][\x80-\xBF]|[\xE0-\xEF][\x80-\xBF]{2}|[\xF0-\xF7][\x80-\xBF]{3}/g; - const cb_btou = function (cccc) { - switch (cccc.length) { - case 4: - const cp = ((0x07 & cccc.charCodeAt(0)) << 18) - | ((0x3f & cccc.charCodeAt(1)) << 12) - | ((0x3f & cccc.charCodeAt(2)) << 6) - | (0x3f & cccc.charCodeAt(3)), - offset = cp - 0x10000; - return (fromCharCode((offset >>> 10) + 0xD800) - + fromCharCode((offset & 0x3FF) + 0xDC00)); - case 3: - return fromCharCode( - ((0x0f & cccc.charCodeAt(0)) << 12) - | ((0x3f & cccc.charCodeAt(1)) << 6) - | (0x3f & cccc.charCodeAt(2)) - ); - default: - return fromCharCode( - ((0x1f & cccc.charCodeAt(0)) << 6) - | (0x3f & cccc.charCodeAt(1)) - ); - } - }; - const btou = function (b) { - return b.replace(re_btou, cb_btou); - }; - const cb_decode = function (cccc) { - const len = cccc.length, - padlen = len % 4, - n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0) - | (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0) - | (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0) - | (len > 3 ? b64tab[cccc.charAt(3)] : 0), - chars = [ - fromCharCode(n >>> 16), - fromCharCode((n >>> 8) & 0xff), - fromCharCode(n & 0xff) - ]; - chars.length -= [0, 0, 2, 1][padlen]; - return chars.join(''); - }; - const _atob = function (a) { - return a.replace(/\S{1,4}/g, cb_decode); - }; - const atob = function (a) { - return _atob(String(a).replace(/[^A-Za-z0-9\+\/]/g, '')); - }; - const _decode = function (u) { - return btou(_atob(u)) - }; - this.decode = function (a) { - return _decode( - String(a).replace(/[-_]/g, function (m0) { - return m0 === '-' ? '+' : '/' - }) - .replace(/[^A-Za-z0-9\+\/]/g, '') - ).replace(/>/g, ">").replace(/</g, "<"); - }; - this.safeEncode = function (a) { - return this.encode(a.replace(/\+/g, "-").replace(/\//g, "_")); - } - this.safeDecode = function (a) { - return this.decode(a.replace(/-/g, "+").replace(/_/g, "/")); - } -} \ No newline at end of file diff --git a/sub-store-web/.gitignore b/web/.gitignore similarity index 100% rename from sub-store-web/.gitignore rename to web/.gitignore diff --git a/sub-store-web/README.md b/web/README.md similarity index 100% rename from sub-store-web/README.md rename to web/README.md diff --git a/sub-store-web/babel.config.js b/web/babel.config.js similarity index 100% rename from sub-store-web/babel.config.js rename to web/babel.config.js diff --git a/sub-store-web/package-lock.json b/web/package-lock.json similarity index 100% rename from sub-store-web/package-lock.json rename to web/package-lock.json diff --git a/sub-store-web/package.json b/web/package.json similarity index 100% rename from sub-store-web/package.json rename to web/package.json diff --git a/sub-store-web/public/favicon.ico b/web/public/favicon.ico similarity index 100% rename from sub-store-web/public/favicon.ico rename to web/public/favicon.ico diff --git a/sub-store-web/public/index.html b/web/public/index.html similarity index 100% rename from sub-store-web/public/index.html rename to web/public/index.html diff --git a/sub-store-web/src/App.vue b/web/src/App.vue similarity index 100% rename from sub-store-web/src/App.vue rename to web/src/App.vue diff --git a/sub-store-web/src/assets/logo.png b/web/src/assets/logo.png similarity index 100% rename from sub-store-web/src/assets/logo.png rename to web/src/assets/logo.png diff --git a/sub-store-web/src/components/HelloWorld.vue b/web/src/components/HelloWorld.vue similarity index 100% rename from sub-store-web/src/components/HelloWorld.vue rename to web/src/components/HelloWorld.vue diff --git a/sub-store-web/src/main.js b/web/src/main.js similarity index 100% rename from sub-store-web/src/main.js rename to web/src/main.js