mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-09-12 19:53:14 +08:00
bug fixed
This commit is contained in:
parent
8fc5e58276
commit
2d6aa1ef1a
4
backend/.idea/workspace.xml
generated
4
backend/.idea/workspace.xml
generated
@ -22,8 +22,9 @@
|
|||||||
<list default="true" id="8b97a098-48b2-4e64-a9ef-522fe2d30b52" name="Default Changelist" comment="">
|
<list default="true" id="8b97a098-48b2-4e64-a9ef-522fe2d30b52" name="Default Changelist" comment="">
|
||||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/sub-store.js" beforeDir="false" afterPath="$PROJECT_DIR$/sub-store.js" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/sub-store.js" beforeDir="false" afterPath="$PROJECT_DIR$/sub-store.js" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../web/src/components/SortOperator.vue" beforeDir="false" afterPath="$PROJECT_DIR$/../web/src/components/SortOperator.vue" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/../web/src/components/ProxyList.vue" beforeDir="false" afterPath="$PROJECT_DIR$/../web/src/components/ProxyList.vue" afterDir="false" />
|
||||||
<change beforePath="$PROJECT_DIR$/../web/src/config.js" beforeDir="false" afterPath="$PROJECT_DIR$/../web/src/config.js" afterDir="false" />
|
<change beforePath="$PROJECT_DIR$/../web/src/config.js" beforeDir="false" afterPath="$PROJECT_DIR$/../web/src/config.js" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/../web/src/views/Subscription.vue" beforeDir="false" afterPath="$PROJECT_DIR$/../web/src/views/Subscription.vue" afterDir="false" />
|
||||||
</list>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
@ -93,6 +94,7 @@
|
|||||||
<workItem from="1598900239005" duration="4742000" />
|
<workItem from="1598900239005" duration="4742000" />
|
||||||
<workItem from="1598931009084" duration="2069000" />
|
<workItem from="1598931009084" duration="2069000" />
|
||||||
<workItem from="1598946261983" duration="463000" />
|
<workItem from="1598946261983" duration="463000" />
|
||||||
|
<workItem from="1598948545209" duration="3969000" />
|
||||||
</task>
|
</task>
|
||||||
<servers />
|
<servers />
|
||||||
</component>
|
</component>
|
||||||
|
@ -9,8 +9,9 @@
|
|||||||
* 3. 订阅拆分,组合。
|
* 3. 订阅拆分,组合。
|
||||||
*/
|
*/
|
||||||
const $ = API("sub-store", true);
|
const $ = API("sub-store", true);
|
||||||
const $code = new Base64Code();
|
const $app = express();
|
||||||
$.http = HTTP("", {
|
|
||||||
|
$.http = HTTP({
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": "Quantumult",
|
"User-Agent": "Quantumult",
|
||||||
},
|
},
|
||||||
@ -18,6 +19,26 @@ $.http = HTTP("", {
|
|||||||
// Constants
|
// Constants
|
||||||
const SUBS_KEY = "subs";
|
const SUBS_KEY = "subs";
|
||||||
const COLLECTIONS_KEY = "collections";
|
const COLLECTIONS_KEY = "collections";
|
||||||
|
const AVAILABLE_FILTERS = {
|
||||||
|
"Keyword Filter": KeywordFilter,
|
||||||
|
"Useless Filter": UselessFilter,
|
||||||
|
"Region Filter": RegionFilter,
|
||||||
|
"Regex Filter": RegexFilter,
|
||||||
|
"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,
|
||||||
|
};
|
||||||
|
|
||||||
// SOME INITIALIZATIONS
|
// SOME INITIALIZATIONS
|
||||||
if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY);
|
if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY);
|
||||||
@ -25,20 +46,19 @@ if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY);
|
|||||||
|
|
||||||
// BACKEND API
|
// BACKEND API
|
||||||
$.log("Initializing Express...");
|
$.log("Initializing Express...");
|
||||||
const $app = express();
|
|
||||||
|
|
||||||
// download
|
// download
|
||||||
$app.get("/download/collection/:name", downloadCollection);
|
$app.get("/download/collection/:name", downloadCollection);
|
||||||
$app.get("/download/:name", downloadSub);
|
$app.get("/download/:name", downloadSub);
|
||||||
|
|
||||||
// refresh
|
|
||||||
$app.post("/api/refresh", refreshResource);
|
|
||||||
|
|
||||||
// subscriptions
|
// subscriptions
|
||||||
$app.route("/api/sub/:name").get(getSub).patch(updateSub).delete(deleteSub);
|
$app.route("/api/sub/:name").get(getSub).patch(updateSub).delete(deleteSub);
|
||||||
|
|
||||||
$app.route("/api/sub").get(getAllSubs).post(newSub).delete(deleteAllSubs);
|
$app.route("/api/sub").get(getAllSubs).post(newSub).delete(deleteAllSubs);
|
||||||
|
|
||||||
|
// refresh
|
||||||
|
$app.post("/api/refresh", refreshResource);
|
||||||
|
|
||||||
// collections
|
// collections
|
||||||
$app
|
$app
|
||||||
.route("/api/collection/:name")
|
.route("/api/collection/:name")
|
||||||
@ -56,42 +76,14 @@ $app.all("/", async (req, res) => {
|
|||||||
res.send("Hello from Sub-Store! Made with ❤️ by Peng-YM.");
|
res.send("Hello from Sub-Store! Made with ❤️ by Peng-YM.");
|
||||||
});
|
});
|
||||||
|
|
||||||
$app.start();
|
|
||||||
$.log("Express initialized");
|
$.log("Express initialized");
|
||||||
|
$app.start();
|
||||||
// SOME CONSTANTS
|
|
||||||
var DEFAULT_SUPPORTED_PLATFORMS = {
|
|
||||||
QX: true,
|
|
||||||
Loon: true,
|
|
||||||
Surge: true,
|
|
||||||
Raw: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
var AVAILABLE_FILTERS = {
|
|
||||||
"Keyword Filter": KeywordFilter,
|
|
||||||
"Useless Filter": UselessFilter,
|
|
||||||
"Region Filter": RegionFilter,
|
|
||||||
"Regex Filter": RegexFilter,
|
|
||||||
"Type Filter": TypeFilter,
|
|
||||||
"Script Filter": ScriptFilter,
|
|
||||||
};
|
|
||||||
|
|
||||||
var 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 ***************************************/
|
/**************************** API -- Subscriptions ***************************************/
|
||||||
// refresh resource
|
// refresh resource
|
||||||
async function refreshResource(req, res) {
|
async function refreshResource(req, res) {
|
||||||
const { url } = req.body;
|
const Base64 = new Base64Code();
|
||||||
|
const {url} = req.body;
|
||||||
const raw = await $.http
|
const raw = await $.http
|
||||||
.get(url)
|
.get(url)
|
||||||
.then((resp) => resp.body)
|
.then((resp) => resp.body)
|
||||||
@ -101,7 +93,7 @@ async function refreshResource(req, res) {
|
|||||||
message: `Cannot refresh remote resource: ${url}\n Reason: ${err}`,
|
message: `Cannot refresh remote resource: ${url}\n Reason: ${err}`,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
$.write(raw, `#${$code.safeEncode(url)}`);
|
$.write(raw, `#${Base64.safeEncode(url)}`);
|
||||||
res.json({
|
res.json({
|
||||||
status: "success",
|
status: "success",
|
||||||
});
|
});
|
||||||
@ -109,7 +101,7 @@ async function refreshResource(req, res) {
|
|||||||
|
|
||||||
// download subscription, for APP only
|
// download subscription, for APP only
|
||||||
async function downloadSub(req, res) {
|
async function downloadSub(req, res) {
|
||||||
const { name } = req.params;
|
const {name} = req.params;
|
||||||
const platform = req.query.target || getPlatformFromHeaders(req.headers);
|
const platform = req.query.target || getPlatformFromHeaders(req.headers);
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
if (allSubs[name]) {
|
if (allSubs[name]) {
|
||||||
@ -133,7 +125,7 @@ async function downloadSub(req, res) {
|
|||||||
|
|
||||||
async function parseSub(sub, platform) {
|
async function parseSub(sub, platform) {
|
||||||
let raw;
|
let raw;
|
||||||
const key = $code.safeEncode(sub.url);
|
const key = new Base64Code().safeEncode(sub.url);
|
||||||
|
|
||||||
if (platform === "Raw") {
|
if (platform === "Raw") {
|
||||||
const cache = $.read(`#${key}`);
|
const cache = $.read(`#${key}`);
|
||||||
@ -221,7 +213,7 @@ async function parseSub(sub, platform) {
|
|||||||
if (item.type.indexOf("Filter") !== -1) {
|
if (item.type.indexOf("Filter") !== -1) {
|
||||||
const filter = AVAILABLE_FILTERS[item.type];
|
const filter = AVAILABLE_FILTERS[item.type];
|
||||||
if (filter) {
|
if (filter) {
|
||||||
$filter.addFilters(filter(item.args));
|
$filter.setFilter(filter(item.args));
|
||||||
proxies = $filter.process(proxies);
|
proxies = $filter.process(proxies);
|
||||||
$.log(
|
$.log(
|
||||||
`Applying filter "${item.type}" with arguments:\n >>> ${
|
`Applying filter "${item.type}" with arguments:\n >>> ${
|
||||||
@ -232,7 +224,7 @@ async function parseSub(sub, platform) {
|
|||||||
} else if (item.type.indexOf("Operator") !== -1) {
|
} else if (item.type.indexOf("Operator") !== -1) {
|
||||||
const operator = AVAILABLE_OPERATORS[item.type];
|
const operator = AVAILABLE_OPERATORS[item.type];
|
||||||
if (operator) {
|
if (operator) {
|
||||||
$operator.addOperators(operator(item.args));
|
$operator.setOperator(operator(item.args));
|
||||||
proxies = $operator.process(proxies);
|
proxies = $operator.process(proxies);
|
||||||
$.log(
|
$.log(
|
||||||
`Applying operator "${item.type}" with arguments: \n >>> ${
|
`Applying operator "${item.type}" with arguments: \n >>> ${
|
||||||
@ -294,7 +286,7 @@ function getFlowHeaders(headers, proxies) {
|
|||||||
|
|
||||||
// Subscriptions
|
// Subscriptions
|
||||||
async function getSub(req, res) {
|
async function getSub(req, res) {
|
||||||
const { name } = req.params;
|
const {name} = req.params;
|
||||||
const sub = $.read(SUBS_KEY)[name];
|
const sub = $.read(SUBS_KEY)[name];
|
||||||
if (sub) {
|
if (sub) {
|
||||||
res.json({
|
res.json({
|
||||||
@ -335,7 +327,8 @@ async function newSub(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function updateSub(req, res) {
|
async function updateSub(req, res) {
|
||||||
const { name } = req.params;
|
const {name} = req.params;
|
||||||
|
$.log(`Updating subscription: ${name}`);
|
||||||
let sub = req.body;
|
let sub = req.body;
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
if (allSubs[name]) {
|
if (allSubs[name]) {
|
||||||
@ -373,7 +366,7 @@ async function updateSub(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteSub(req, res) {
|
async function deleteSub(req, res) {
|
||||||
const { name } = req.params;
|
const {name} = req.params;
|
||||||
// delete from subscriptions
|
// delete from subscriptions
|
||||||
let allSubs = $.read(SUBS_KEY);
|
let allSubs = $.read(SUBS_KEY);
|
||||||
delete allSubs[name];
|
delete allSubs[name];
|
||||||
@ -408,7 +401,7 @@ async function deleteAllSubs(req, res) {
|
|||||||
|
|
||||||
// Collections
|
// Collections
|
||||||
async function downloadCollection(req, res) {
|
async function downloadCollection(req, res) {
|
||||||
const { name } = req.params;
|
const {name} = req.params;
|
||||||
const collection = $.read(COLLECTIONS_KEY)[name];
|
const collection = $.read(COLLECTIONS_KEY)[name];
|
||||||
const platform = getPlatformFromHeaders(req.headers);
|
const platform = getPlatformFromHeaders(req.headers);
|
||||||
if (collection) {
|
if (collection) {
|
||||||
@ -435,7 +428,7 @@ async function downloadCollection(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getCollection(req, res) {
|
async function getCollection(req, res) {
|
||||||
const { name } = req.params;
|
const {name} = req.params;
|
||||||
const collection = $.read(COLLECTIONS_KEY)[name];
|
const collection = $.read(COLLECTIONS_KEY)[name];
|
||||||
if (collection) {
|
if (collection) {
|
||||||
res.json({
|
res.json({
|
||||||
@ -476,7 +469,7 @@ async function newCollection(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function updateCollection(req, res) {
|
async function updateCollection(req, res) {
|
||||||
const { name } = req.params;
|
const {name} = req.params;
|
||||||
let collection = req.body;
|
let collection = req.body;
|
||||||
const allCol = $.read(COLLECTIONS_KEY);
|
const allCol = $.read(COLLECTIONS_KEY);
|
||||||
if (allCol[name]) {
|
if (allCol[name]) {
|
||||||
@ -501,7 +494,7 @@ async function updateCollection(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteCollection(req, res) {
|
async function deleteCollection(req, res) {
|
||||||
const { name } = req.params;
|
const {name} = req.params;
|
||||||
let allCol = $.read(COLLECTIONS_KEY);
|
let allCol = $.read(COLLECTIONS_KEY);
|
||||||
delete allCol[name];
|
delete allCol[name];
|
||||||
$.write(allCol, COLLECTIONS_KEY);
|
$.write(allCol, COLLECTIONS_KEY);
|
||||||
@ -551,7 +544,7 @@ function ProxyParser(targetPlatform) {
|
|||||||
if (line.startsWith("#")) continue; // skip comments
|
if (line.startsWith("#")) continue; // skip comments
|
||||||
let matched = false;
|
let matched = false;
|
||||||
for (const p of parsers) {
|
for (const p of parsers) {
|
||||||
const { patternTest, func } = p;
|
const {patternTest, func} = p;
|
||||||
|
|
||||||
// some lines with weird format may produce errors!
|
// some lines with weird format may produce errors!
|
||||||
let patternMatched;
|
let patternMatched;
|
||||||
@ -666,54 +659,52 @@ function ProxyParser(targetPlatform) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ProxyFilter() {
|
function ProxyFilter() {
|
||||||
const filters = [];
|
let filter;
|
||||||
|
|
||||||
function addFilters(...args) {
|
function setFilter(arg) {
|
||||||
args.forEach((a) => filters.push(a));
|
filter = arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// select proxies
|
// select proxies
|
||||||
function process(proxies) {
|
function process(proxies) {
|
||||||
let selected = FULL(proxies.length, true);
|
let selected = FULL(proxies.length, true);
|
||||||
for (const filter of filters) {
|
|
||||||
try {
|
try {
|
||||||
selected = AND(selected, filter.func(proxies));
|
selected = AND(selected, filter.func(proxies));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`Cannot apply filter ${filter.name}\n Reason: ${err}`);
|
console.log(`Cannot apply filter ${filter.name}\n Reason: ${err}`);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return proxies.filter((_, i) => selected[i]);
|
return proxies.filter((_, i) => selected[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
process,
|
process,
|
||||||
addFilters,
|
setFilter,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function ProxyOperator() {
|
function ProxyOperator() {
|
||||||
const operators = [];
|
let operator;
|
||||||
|
|
||||||
function addOperators(...args) {
|
function setOperator(arg) {
|
||||||
args.forEach((a) => operators.push(a));
|
operator = arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
// run all operators
|
// run all operators
|
||||||
function process(proxies) {
|
function process(proxies) {
|
||||||
let output = objClone(proxies);
|
let output = objClone(proxies);
|
||||||
for (const op of operators) {
|
|
||||||
try {
|
try {
|
||||||
const output_ = op.func(output);
|
const output_ = operator.func(output);
|
||||||
if (output_) output = output_;
|
if (output_) output = output_;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// print log and skip this operator
|
// print log and skip this operator
|
||||||
console.log(`ERROR: cannot apply operator ${op.name}! Reason: ${err}`);
|
console.log(`ERROR: cannot apply operator ${op.name}! Reason: ${err}`);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
||||||
return { addOperators, process };
|
return {setOperator, process};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************** URI Format ***************************************/
|
/**************************** URI Format ***************************************/
|
||||||
@ -790,7 +781,7 @@ function URI_SS() {
|
|||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse URI SSR format, such as ssr://xxx
|
// Parse URI SSR format, such as ssr://xxx
|
||||||
@ -852,7 +843,7 @@ function URI_SSR() {
|
|||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
// V2rayN URI VMess format
|
// V2rayN URI VMess format
|
||||||
@ -934,7 +925,7 @@ function URI_VMess() {
|
|||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Trojan URI format
|
// Trojan URI format
|
||||||
@ -965,7 +956,7 @@ function URI_Trojan() {
|
|||||||
supported,
|
supported,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************** Clash ***************************************/
|
/**************************** Clash ***************************************/
|
||||||
@ -974,7 +965,7 @@ function Clash_All() {
|
|||||||
return line.indexOf("{") !== -1;
|
return line.indexOf("{") !== -1;
|
||||||
};
|
};
|
||||||
const func = (line) => JSON.parse(line);
|
const func = (line) => JSON.parse(line);
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************** Quantumult X ***************************************/
|
/**************************** Quantumult X ***************************************/
|
||||||
@ -1032,7 +1023,7 @@ function QX_SS() {
|
|||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
function QX_SSR() {
|
function QX_SSR() {
|
||||||
@ -1071,7 +1062,7 @@ function QX_SSR() {
|
|||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
function QX_VMess() {
|
function QX_VMess() {
|
||||||
@ -1107,7 +1098,7 @@ function QX_VMess() {
|
|||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
function QX_Trojan() {
|
function QX_Trojan() {
|
||||||
@ -1129,7 +1120,7 @@ function QX_Trojan() {
|
|||||||
proxy.scert = !JSON.parse(params["tls-verification"] || "true");
|
proxy.scert = !JSON.parse(params["tls-verification"] || "true");
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
function QX_Http() {
|
function QX_Http() {
|
||||||
@ -1156,7 +1147,7 @@ function QX_Http() {
|
|||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getQXParams(line) {
|
function getQXParams(line) {
|
||||||
@ -1204,7 +1195,7 @@ function Loon_SS() {
|
|||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Loon_SSR() {
|
function Loon_SSR() {
|
||||||
@ -1235,7 +1226,7 @@ function Loon_SSR() {
|
|||||||
"obfs-param": params[8].match(/{(.*)}/)[1],
|
"obfs-param": params[8].match(/{(.*)}/)[1],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Loon_VMess() {
|
function Loon_VMess() {
|
||||||
@ -1283,7 +1274,7 @@ function Loon_VMess() {
|
|||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Loon_Trojan() {
|
function Loon_Trojan() {
|
||||||
@ -1314,7 +1305,7 @@ function Loon_Trojan() {
|
|||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
|
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Loon_Http() {
|
function Loon_Http() {
|
||||||
@ -1345,7 +1336,7 @@ function Loon_Http() {
|
|||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************** Surge ***************************************/
|
/**************************** Surge ***************************************/
|
||||||
@ -1375,7 +1366,7 @@ function Surge_SS() {
|
|||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Surge_VMess() {
|
function Surge_VMess() {
|
||||||
@ -1411,7 +1402,7 @@ function Surge_VMess() {
|
|||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Surge_Trojan() {
|
function Surge_Trojan() {
|
||||||
@ -1434,7 +1425,7 @@ function Surge_Trojan() {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Surge_Http() {
|
function Surge_Http() {
|
||||||
@ -1461,7 +1452,7 @@ function Surge_Http() {
|
|||||||
if (params.password !== "none") proxy.password = params.password;
|
if (params.password !== "none") proxy.password = params.password;
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
return { patternTest, func };
|
return {patternTest, func};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSurgeParams(line) {
|
function getSurgeParams(line) {
|
||||||
@ -1493,7 +1484,7 @@ function QX_Producer() {
|
|||||||
obfs_opts = `,obfs=${proxy["plugin-opts"].mode},obfs-host=${proxy["plugin-opts"].host}`;
|
obfs_opts = `,obfs=${proxy["plugin-opts"].mode},obfs-host=${proxy["plugin-opts"].host}`;
|
||||||
}
|
}
|
||||||
if (proxy.plugin === "v2ray-plugin") {
|
if (proxy.plugin === "v2ray-plugin") {
|
||||||
const { tls, host, path } = proxy["plugin-opts"];
|
const {tls, host, path} = proxy["plugin-opts"];
|
||||||
obfs_opts = `,obfs=${tls ? "wss" : "ws"},obfs-host=${host}${
|
obfs_opts = `,obfs=${tls ? "wss" : "ws"},obfs-host=${host}${
|
||||||
path ? ",obfs-uri=" + path : ""
|
path ? ",obfs-uri=" + path : ""
|
||||||
}`;
|
}`;
|
||||||
@ -1572,7 +1563,7 @@ function QX_Producer() {
|
|||||||
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`
|
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
return { targetPlatform, output };
|
return {targetPlatform, output};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Loon_Producer() {
|
function Loon_Producer() {
|
||||||
@ -1584,7 +1575,7 @@ function Loon_Producer() {
|
|||||||
obfs_opts = ",,";
|
obfs_opts = ",,";
|
||||||
if (proxy.plugin) {
|
if (proxy.plugin) {
|
||||||
if (proxy.plugin === "obfs") {
|
if (proxy.plugin === "obfs") {
|
||||||
const { mode, host } = proxy["plugin-opts"];
|
const {mode, host} = proxy["plugin-opts"];
|
||||||
obfs_opts = `,${mode},${host}`;
|
obfs_opts = `,${mode},${host}`;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -1629,7 +1620,7 @@ function Loon_Producer() {
|
|||||||
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`
|
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
return { targetPlatform, output };
|
return {targetPlatform, output};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Surge_Producer() {
|
function Surge_Producer() {
|
||||||
@ -1688,7 +1679,7 @@ function Surge_Producer() {
|
|||||||
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`
|
`Platform ${targetPlatform} does not support proxy type: ${proxy.type}`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
return { targetPlatform, output };
|
return {targetPlatform, output};
|
||||||
}
|
}
|
||||||
|
|
||||||
function Raw_Producer() {
|
function Raw_Producer() {
|
||||||
@ -1696,7 +1687,7 @@ function Raw_Producer() {
|
|||||||
const output = (proxy) => {
|
const output = (proxy) => {
|
||||||
return JSON.stringify(proxy);
|
return JSON.stringify(proxy);
|
||||||
};
|
};
|
||||||
return { targetPlatform, output };
|
return {targetPlatform, output};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**************************** Operators ***************************************/
|
/**************************** Operators ***************************************/
|
||||||
@ -1719,10 +1710,10 @@ function FlagOperator(add = true) {
|
|||||||
name: "Flag Operator",
|
name: "Flag Operator",
|
||||||
func: (proxies) => {
|
func: (proxies) => {
|
||||||
return proxies.map((proxy) => {
|
return proxies.map((proxy) => {
|
||||||
if (!add)
|
if (!add) {
|
||||||
// no flag
|
// no flag
|
||||||
proxy.name = removeFlag(proxy.name);
|
proxy.name = removeFlag(proxy.name);
|
||||||
else {
|
} else {
|
||||||
// get flag
|
// get flag
|
||||||
const newFlag = getFlag(proxy.name);
|
const newFlag = getFlag(proxy.name);
|
||||||
// remove old flag
|
// remove old flag
|
||||||
@ -1793,7 +1784,7 @@ function KeywordRenameOperator(keywords) {
|
|||||||
name: "Keyword Rename Operator",
|
name: "Keyword Rename Operator",
|
||||||
func: (proxies) => {
|
func: (proxies) => {
|
||||||
return proxies.map((proxy) => {
|
return proxies.map((proxy) => {
|
||||||
for (const { old, now } of keywords) {
|
for (const {old, now} of keywords) {
|
||||||
proxy.name = proxy.name.replace(old, now).trim();
|
proxy.name = proxy.name.replace(old, now).trim();
|
||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
@ -1809,7 +1800,7 @@ function RegexRenameOperator(regex) {
|
|||||||
name: "Regex Rename Operator",
|
name: "Regex Rename Operator",
|
||||||
func: (proxies) => {
|
func: (proxies) => {
|
||||||
return proxies.map((proxy) => {
|
return proxies.map((proxy) => {
|
||||||
for (const { expr, now } of regex) {
|
for (const {expr, now} of regex) {
|
||||||
proxy.name = proxy.name.replace(new RegExp(expr, "g"), now).trim();
|
proxy.name = proxy.name.replace(new RegExp(expr, "g"), now).trim();
|
||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
@ -1882,7 +1873,7 @@ function ScriptOperator(script) {
|
|||||||
|
|
||||||
/**************************** Filters ***************************************/
|
/**************************** Filters ***************************************/
|
||||||
// filter by keywords
|
// filter by keywords
|
||||||
function KeywordFilter({ keywords = [], keep = true }) {
|
function KeywordFilter({keywords = [], keep = true}) {
|
||||||
return {
|
return {
|
||||||
name: "Keyword Filter",
|
name: "Keyword Filter",
|
||||||
func: (proxies) => {
|
func: (proxies) => {
|
||||||
@ -1937,7 +1928,7 @@ function RegionFilter(regions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// filter by regex
|
// filter by regex
|
||||||
function RegexFilter({ regex = [], keep = true }) {
|
function RegexFilter({regex = [], keep = true}) {
|
||||||
return {
|
return {
|
||||||
name: "Regex Filter",
|
name: "Regex Filter",
|
||||||
func: (proxies) => {
|
func: (proxies) => {
|
||||||
@ -2184,7 +2175,7 @@ function removeFlag(str) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// objClone an object
|
// objClone an object
|
||||||
function objobjClone(obj) {
|
function objClone(obj) {
|
||||||
return JSON.parse(JSON.stringify(obj));
|
return JSON.parse(JSON.stringify(obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2271,20 +2262,22 @@ function ENV() {
|
|||||||
return {isQX, isLoon, isSurge, isNode, isJSBox, isRequest};
|
return {isQX, isLoon, isSurge, isNode, isJSBox, isRequest};
|
||||||
}
|
}
|
||||||
|
|
||||||
function HTTP(baseURL, defaultOptions = {}) {
|
function HTTP(defaultOptions = {}) {
|
||||||
const { isQX, isLoon, isSurge } = ENV();
|
const {isQX, isLoon, isSurge} = ENV();
|
||||||
const methods = ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"];
|
const methods = ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS", "PATCH"];
|
||||||
|
|
||||||
function send(method, options) {
|
function send(method, options) {
|
||||||
options = typeof options === "string" ? { url: options } : options;
|
options = options.hasOwnProperty("url") ? options : {url: options};
|
||||||
options.url = baseURL ? baseURL + options.url : options.url;
|
options.url = defaultOptions.baseURL ? defaultOptions.baseURL + options.url : options.url;
|
||||||
options = { ...defaultOptions, ...options };
|
options = {...defaultOptions, ...options};
|
||||||
const timeout = options.timeout;
|
const timeout = options.timeout;
|
||||||
const events = {
|
const events = {
|
||||||
...{
|
...{
|
||||||
onRequest: () => {},
|
onRequest: () => {
|
||||||
|
},
|
||||||
onResponse: (resp) => resp,
|
onResponse: (resp) => resp,
|
||||||
onTimeout: () => {},
|
onTimeout: () => {
|
||||||
|
},
|
||||||
},
|
},
|
||||||
...options.events,
|
...options.events,
|
||||||
};
|
};
|
||||||
@ -2293,7 +2286,7 @@ function HTTP(baseURL, defaultOptions = {}) {
|
|||||||
|
|
||||||
let worker;
|
let worker;
|
||||||
if (isQX) {
|
if (isQX) {
|
||||||
worker = $task.fetch({ method, ...options });
|
worker = $task.fetch({method, ...options});
|
||||||
} else {
|
} else {
|
||||||
worker = new Promise((resolve, reject) => {
|
worker = new Promise((resolve, reject) => {
|
||||||
const request = isSurge || isLoon ? $httpClient : require("request");
|
const request = isSurge || isLoon ? $httpClient : require("request");
|
||||||
@ -2339,7 +2332,7 @@ function HTTP(baseURL, defaultOptions = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function API(name = "untitled", debug = false) {
|
function API(name = "untitled", debug = false) {
|
||||||
const { isQX, isLoon, isSurge, isNode, isJSBox } = ENV();
|
const {isQX, isLoon, isSurge, isNode, isJSBox} = ENV();
|
||||||
return new (class {
|
return new (class {
|
||||||
constructor(name, debug) {
|
constructor(name, debug) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -2388,7 +2381,7 @@ function API(name = "untitled", debug = false) {
|
|||||||
this.node.fs.writeFileSync(
|
this.node.fs.writeFileSync(
|
||||||
fpath,
|
fpath,
|
||||||
JSON.stringify({}),
|
JSON.stringify({}),
|
||||||
{ flag: "wx" },
|
{flag: "wx"},
|
||||||
(err) => console.log(err)
|
(err) => console.log(err)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2400,7 +2393,7 @@ function API(name = "untitled", debug = false) {
|
|||||||
this.node.fs.writeFileSync(
|
this.node.fs.writeFileSync(
|
||||||
fpath,
|
fpath,
|
||||||
JSON.stringify({}),
|
JSON.stringify({}),
|
||||||
{ flag: "wx" },
|
{flag: "wx"},
|
||||||
(err) => console.log(err)
|
(err) => console.log(err)
|
||||||
);
|
);
|
||||||
this.cache = {};
|
this.cache = {};
|
||||||
@ -2421,13 +2414,13 @@ function API(name = "untitled", debug = false) {
|
|||||||
this.node.fs.writeFileSync(
|
this.node.fs.writeFileSync(
|
||||||
`${this.name}.json`,
|
`${this.name}.json`,
|
||||||
data,
|
data,
|
||||||
{ flag: "w" },
|
{flag: "w"},
|
||||||
(err) => console.log(err)
|
(err) => console.log(err)
|
||||||
);
|
);
|
||||||
this.node.fs.writeFileSync(
|
this.node.fs.writeFileSync(
|
||||||
"root.json",
|
"root.json",
|
||||||
JSON.stringify(this.root),
|
JSON.stringify(this.root),
|
||||||
{ flag: "w" },
|
{flag: "w"},
|
||||||
(err) => console.log(err)
|
(err) => console.log(err)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2532,7 +2525,7 @@ function API(name = "untitled", debug = false) {
|
|||||||
|
|
||||||
/*********************************** Mini Express *************************************/
|
/*********************************** Mini Express *************************************/
|
||||||
function express(port = 3000) {
|
function express(port = 3000) {
|
||||||
const { isNode } = ENV();
|
const {isNode} = ENV();
|
||||||
const DEFAULT_HEADERS = {
|
const DEFAULT_HEADERS = {
|
||||||
"Content-Type": "text/plain;charset=UTF-8",
|
"Content-Type": "text/plain;charset=UTF-8",
|
||||||
"Access-Control-Allow-Origin": "*",
|
"Access-Control-Allow-Origin": "*",
|
||||||
@ -2546,9 +2539,9 @@ function express(port = 3000) {
|
|||||||
const express_ = require("express");
|
const express_ = require("express");
|
||||||
const bodyParser = require("body-parser");
|
const bodyParser = require("body-parser");
|
||||||
const app = express_();
|
const app = express_();
|
||||||
app.use(bodyParser.json({ verify: rawBodySaver }));
|
app.use(bodyParser.json({verify: rawBodySaver}));
|
||||||
app.use(bodyParser.urlencoded({ verify: rawBodySaver, extended: true }));
|
app.use(bodyParser.urlencoded({verify: rawBodySaver, extended: true}));
|
||||||
app.use(bodyParser.raw({ verify: rawBodySaver, type: "*/*" }));
|
app.use(bodyParser.raw({verify: rawBodySaver, type: "*/*"}));
|
||||||
app.use((req, res, next) => {
|
app.use((req, res, next) => {
|
||||||
res.set(DEFAULT_HEADERS);
|
res.set(DEFAULT_HEADERS);
|
||||||
next();
|
next();
|
||||||
@ -2580,19 +2573,21 @@ function express(port = 3000) {
|
|||||||
|
|
||||||
// dispatch url to route
|
// dispatch url to route
|
||||||
const dispatch = (request, start = 0) => {
|
const dispatch = (request, start = 0) => {
|
||||||
let { method, url, headers, body } = request;
|
let {method, url, headers, body} = request;
|
||||||
|
|
||||||
|
console.log("DISPATCHING: " + method + ": " + url);
|
||||||
if (/json/i.test(headers["Content-Type"])) {
|
if (/json/i.test(headers["Content-Type"])) {
|
||||||
body = JSON.parse(body);
|
body = JSON.parse(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
method = method.toUpperCase();
|
method = method.toUpperCase();
|
||||||
const { path, query } = extractURL(url);
|
const {path, query} = extractURL(url);
|
||||||
let handler = null;
|
let handler = null;
|
||||||
let i;
|
let i;
|
||||||
|
|
||||||
for (i = start; i < handlers.length; i++) {
|
for (i = start; i < handlers.length; i++) {
|
||||||
if (handlers[i].method === "ALL" || method === handlers[i].method) {
|
if (handlers[i].method === "ALL" || method === handlers[i].method) {
|
||||||
const { pattern } = handlers[i];
|
const {pattern} = handlers[i];
|
||||||
if (patternMatched(pattern, path)) {
|
if (patternMatched(pattern, path)) {
|
||||||
handler = handlers[i];
|
handler = handlers[i];
|
||||||
break;
|
break;
|
||||||
@ -2614,9 +2609,6 @@ function express(port = 3000) {
|
|||||||
body,
|
body,
|
||||||
};
|
};
|
||||||
const res = Response();
|
const res = Response();
|
||||||
if (typeof handler.callback === "undefined") {
|
|
||||||
$.notify("FUCK");
|
|
||||||
}
|
|
||||||
handler.callback(req, res, next).catch((err) => {
|
handler.callback(req, res, next).catch((err) => {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
status: "failed",
|
status: "failed",
|
||||||
@ -2639,7 +2631,7 @@ function express(port = 3000) {
|
|||||||
METHODS_NAMES.forEach((method) => {
|
METHODS_NAMES.forEach((method) => {
|
||||||
app[method.toLowerCase()] = (pattern, callback) => {
|
app[method.toLowerCase()] = (pattern, callback) => {
|
||||||
// add handler
|
// add handler
|
||||||
handlers.push({ method, pattern, callback });
|
handlers.push({method, pattern, callback});
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2649,7 +2641,7 @@ function express(port = 3000) {
|
|||||||
METHODS_NAMES.forEach((method) => {
|
METHODS_NAMES.forEach((method) => {
|
||||||
chainApp[method.toLowerCase()] = (callback) => {
|
chainApp[method.toLowerCase()] = (callback) => {
|
||||||
// add handler
|
// add handler
|
||||||
handlers.push({ method, pattern, callback });
|
handlers.push({method, pattern, callback});
|
||||||
return chainApp;
|
return chainApp;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -2658,6 +2650,7 @@ function express(port = 3000) {
|
|||||||
|
|
||||||
// start service
|
// start service
|
||||||
app.start = () => {
|
app.start = () => {
|
||||||
|
console.log(`STARTING TO ROUTE: ${JSON.stringify($request)}`);
|
||||||
dispatch($request);
|
dispatch($request);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2674,7 +2667,7 @@ function express(port = 3000) {
|
|||||||
|
|
||||||
function Response() {
|
function Response() {
|
||||||
let statusCode = 200;
|
let statusCode = 200;
|
||||||
const { isQX, isLoon, isSurge } = ENV();
|
const {isQX, isLoon, isSurge} = ENV();
|
||||||
const headers = DEFAULT_HEADERS;
|
const headers = DEFAULT_HEADERS;
|
||||||
const STATUS_CODE_MAP = {
|
const STATUS_CODE_MAP = {
|
||||||
200: "HTTP/1.1 200 OK",
|
200: "HTTP/1.1 200 OK",
|
||||||
|
@ -36,7 +36,7 @@ import {axios} from "@/utils";
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ProxyList",
|
name: "ProxyList",
|
||||||
props: ['url'],
|
props: ['url', 'sub'],
|
||||||
data: function (){
|
data: function (){
|
||||||
return {
|
return {
|
||||||
proxies: []
|
proxies: []
|
||||||
@ -44,7 +44,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
refresh() {
|
refresh() {
|
||||||
axios.post(`/refresh`, {url: this.url}).then(() => {
|
axios.post(`/refresh`, {url: this.sub}).then(() => {
|
||||||
this.fetch();
|
this.fetch();
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
const DEBUG = process.env.NODE_ENV === "development";
|
// const DEBUG = process.env.NODE_ENV === "development";
|
||||||
|
const DEBUG = false;
|
||||||
export const BACKEND_BASE = DEBUG ? `http://192.168.1.134:3000` : `https://sub.store`;
|
export const BACKEND_BASE = DEBUG ? `http://192.168.1.134:3000` : `https://sub.store`;
|
@ -148,7 +148,7 @@
|
|||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-text class="pl-0 pr-0">
|
<v-card-text class="pl-0 pr-0">
|
||||||
<proxy-list :url="url" ref="proxyList" :key="url"></proxy-list>
|
<proxy-list :url="url" :sub="sub" ref="proxyList" :key="url"></proxy-list>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
@ -166,6 +166,7 @@ export default {
|
|||||||
opened: false,
|
opened: false,
|
||||||
showProxyList: false,
|
showProxyList: false,
|
||||||
url: "",
|
url: "",
|
||||||
|
sub: [],
|
||||||
editMenu: [
|
editMenu: [
|
||||||
{
|
{
|
||||||
title: "复制",
|
title: "复制",
|
||||||
@ -227,7 +228,8 @@ export default {
|
|||||||
},
|
},
|
||||||
preview(item, type = 'sub') {
|
preview(item, type = 'sub') {
|
||||||
if (type === 'sub') {
|
if (type === 'sub') {
|
||||||
this.url = `${BACKEND_BASE}/download/${item.name}`
|
this.url = `${BACKEND_BASE}/download/${item.name}`;
|
||||||
|
this.sub = [item.url];
|
||||||
} else {
|
} else {
|
||||||
this.url = `${BACKEND_BASE}/download/collection/${item.name}`
|
this.url = `${BACKEND_BASE}/download/collection/${item.name}`
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user