mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-11 05:58:59 +08:00
Implemented Subscription Collection API
This commit is contained in:
parent
9641596832
commit
12508188ab
231
parser.js
231
parser.js
@ -2,13 +2,16 @@ const $ = API("sub-store");
|
|||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const SUBS_KEY = "subs";
|
const SUBS_KEY = "subs";
|
||||||
|
const COLLECTIONS_KEY = "collections";
|
||||||
|
|
||||||
// SOME INITIALIZATIONS
|
// SOME INITIALIZATIONS
|
||||||
if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY);
|
if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY);
|
||||||
|
if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY);
|
||||||
|
|
||||||
// BACKEND API
|
// BACKEND API
|
||||||
const $app = express();
|
const $app = express();
|
||||||
|
|
||||||
|
// subscriptions
|
||||||
$app.get("/v1/download/:name", downloadSub)
|
$app.get("/v1/download/:name", downloadSub)
|
||||||
|
|
||||||
$app.route("/v1/sub/:name")
|
$app.route("/v1/sub/:name")
|
||||||
@ -21,6 +24,17 @@ $app.route("/v1/sub")
|
|||||||
.post(newSub)
|
.post(newSub)
|
||||||
.delete(deleteAllSubs);
|
.delete(deleteAllSubs);
|
||||||
|
|
||||||
|
// collections
|
||||||
|
$app.get("/v1/download/collection/:name", downloadCollection);
|
||||||
|
$app.route("/v1/collection/:name")
|
||||||
|
.get(getCollection)
|
||||||
|
.patch(updateCollection)
|
||||||
|
.delete(deleteCollection);
|
||||||
|
$app.route("/v1/collection")
|
||||||
|
.get(getAllCollections)
|
||||||
|
.post(newCollection)
|
||||||
|
.delete(deleteAllCollections);
|
||||||
|
|
||||||
$app.all("/", (req, res) => {
|
$app.all("/", (req, res) => {
|
||||||
res.send("Hello from Sub-Store! Made with ❤️ by Peng-YM.")
|
res.send("Hello from Sub-Store! Made with ❤️ by Peng-YM.")
|
||||||
});
|
});
|
||||||
@ -68,58 +82,67 @@ async function downloadSub(req, res) {
|
|||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
if (allSubs[name]) {
|
if (allSubs[name]) {
|
||||||
const sub = allSubs[name];
|
const sub = allSubs[name];
|
||||||
// download from url
|
try {
|
||||||
const raw = await $.http.get(sub.url).then(resp => resp.body).catch(err => {
|
const output = await parseSub(sub, platform);
|
||||||
|
res.send(output);
|
||||||
|
} catch (err) {
|
||||||
$.notify('[Sub-Store]', '❌ 无法获取订阅!', `错误信息:${err}`)
|
$.notify('[Sub-Store]', '❌ 无法获取订阅!', `错误信息:${err}`)
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
status: "failed",
|
status: "failed",
|
||||||
message: err
|
message: err
|
||||||
});
|
});
|
||||||
});
|
|
||||||
const $parser = ProxyParser(platform);
|
|
||||||
let proxies = $parser.parse(raw);
|
|
||||||
|
|
||||||
// filters
|
|
||||||
const $filter = ProxyFilter();
|
|
||||||
// create filters from sub conf
|
|
||||||
const userFilters = [];
|
|
||||||
for (const item of sub.filters || []) {
|
|
||||||
const filter = AVAILABLE_FILTERS[item.type];
|
|
||||||
if (filter) {
|
|
||||||
userFilters.push(filter(...(item.args || [])));
|
|
||||||
console.log(`Filter "${item.type}" added. Arguments: ${item.args || "None"}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$filter.addFilters(...userFilters);
|
|
||||||
|
|
||||||
// operators
|
|
||||||
const $operator = ProxyOperator();
|
|
||||||
const userOperators = [];
|
|
||||||
for (const item of sub.operators || []) {
|
|
||||||
const operator = AVAILABLE_OPERATORS[item.type];
|
|
||||||
if (operator) {
|
|
||||||
userOperators.push(operator(...(item.args || [])));
|
|
||||||
console.log(`Operator "${item.type}" added. Arguments: ${item.args || "None"}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$operator.addOperators(...userOperators);
|
|
||||||
|
|
||||||
// process filters and operators
|
|
||||||
console.log("\nApplying filters...");
|
|
||||||
proxies = $filter.process(proxies);
|
|
||||||
console.log("\nApplying operators...");
|
|
||||||
proxies = $operator.process(proxies);
|
|
||||||
|
|
||||||
// convert to target platform and output
|
|
||||||
res.send($parser.produce(proxies));
|
|
||||||
} else {
|
} else {
|
||||||
res.status(404).json({
|
res.status(404).json({
|
||||||
status: "failed",
|
status: "failed",
|
||||||
message: `订阅${name}不存在!`
|
message: `订阅${name}不存在!`
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function parseSub(sub, platform) {
|
||||||
|
if (!sub) throw new Error("Subscription is undefined!")
|
||||||
|
// download from url
|
||||||
|
const raw = await $.http.get(sub.url).then(resp => resp.body).catch(err => {
|
||||||
|
throw new Error(err);
|
||||||
|
});
|
||||||
|
const $parser = ProxyParser(platform);
|
||||||
|
let proxies = $parser.parse(raw);
|
||||||
|
|
||||||
|
// filters
|
||||||
|
const $filter = ProxyFilter();
|
||||||
|
// create filters from sub conf
|
||||||
|
const userFilters = [];
|
||||||
|
for (const item of sub.filters || []) {
|
||||||
|
const filter = AVAILABLE_FILTERS[item.type];
|
||||||
|
if (filter) {
|
||||||
|
userFilters.push(filter(...(item.args || [])));
|
||||||
|
console.log(`Filter "${item.type}" added. Arguments: ${item.args || "None"}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$filter.addFilters(...userFilters);
|
||||||
|
|
||||||
|
// operators
|
||||||
|
const $operator = ProxyOperator();
|
||||||
|
const userOperators = [];
|
||||||
|
for (const item of sub.operators || []) {
|
||||||
|
const operator = AVAILABLE_OPERATORS[item.type];
|
||||||
|
if (operator) {
|
||||||
|
userOperators.push(operator(...(item.args || [])));
|
||||||
|
console.log(`Operator "${item.type}" added. Arguments: ${item.args || "None"}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$operator.addOperators(...userOperators);
|
||||||
|
|
||||||
|
// process filters and operators
|
||||||
|
console.log("\nApplying filters...");
|
||||||
|
proxies = $filter.process(proxies);
|
||||||
|
console.log("\nApplying operators...");
|
||||||
|
proxies = $operator.process(proxies);
|
||||||
|
return $parser.produce(proxies)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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];
|
||||||
@ -130,14 +153,15 @@ async function getSub(req, res) {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
res.status(404).json({
|
res.status(404).json({
|
||||||
status: "failed"
|
status: "failed",
|
||||||
|
message: `未找到订阅:${name}!`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function newSub(req, res) {
|
async function newSub(req, res) {
|
||||||
const sub = req.body;
|
const sub = req.body;
|
||||||
const allSubs = $.read('subs');
|
const allSubs = $.read(SUBS_KEY);
|
||||||
if (allSubs[sub.name]) {
|
if (allSubs[sub.name]) {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
status: "failed",
|
status: "failed",
|
||||||
@ -147,7 +171,7 @@ async function newSub(req, res) {
|
|||||||
// validate name
|
// validate name
|
||||||
if (/^[\w-_]*$/.test(sub.name)) {
|
if (/^[\w-_]*$/.test(sub.name)) {
|
||||||
allSubs[sub.name] = sub;
|
allSubs[sub.name] = sub;
|
||||||
$.write(allSubs, 'subs');
|
$.write(allSubs, SUBS_KEY);
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
status: "success",
|
status: "success",
|
||||||
data: sub
|
data: sub
|
||||||
@ -163,14 +187,14 @@ async function newSub(req, res) {
|
|||||||
async function updateSub(req, res) {
|
async function updateSub(req, res) {
|
||||||
const {name} = req.params;
|
const {name} = req.params;
|
||||||
let sub = req.body;
|
let sub = req.body;
|
||||||
const allSubs = $.read('subs');
|
const allSubs = $.read(SUBS_KEY);
|
||||||
if (allSubs[name]) {
|
if (allSubs[name]) {
|
||||||
const newSub = {
|
const newSub = {
|
||||||
...allSubs[name],
|
...allSubs[name],
|
||||||
...sub
|
...sub
|
||||||
};
|
};
|
||||||
allSubs[name] = newSub;
|
allSubs[name] = newSub;
|
||||||
$.write(allSubs, 'subs');
|
$.write(allSubs, SUBS_KEY);
|
||||||
res.json({
|
res.json({
|
||||||
status: "success",
|
status: "success",
|
||||||
data: newSub
|
data: newSub
|
||||||
@ -187,7 +211,7 @@ async function deleteSub(req, res) {
|
|||||||
const {name} = req.params;
|
const {name} = req.params;
|
||||||
let allSubs = $.read(SUBS_KEY);
|
let allSubs = $.read(SUBS_KEY);
|
||||||
delete allSubs[name];
|
delete allSubs[name];
|
||||||
$.write(allSubs, "subs");
|
$.write(allSubs, SUBS_KEY);
|
||||||
res.json({
|
res.json({
|
||||||
status: "success"
|
status: "success"
|
||||||
});
|
});
|
||||||
@ -202,7 +226,122 @@ async function getAllSubs(req, res) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteAllSubs(req, res) {
|
async function deleteAllSubs(req, res) {
|
||||||
$.write({}, "subs");
|
$.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({
|
res.json({
|
||||||
status: "success"
|
status: "success"
|
||||||
});
|
});
|
||||||
|
@ -1 +1 @@
|
|||||||
{"subs":{"Nexitally":{"name":"Nexitally","url":"http://127.0.0.1:8080/nex.list","filters":[{"type":"Region Filter","args":["HK","JP","TW"]},{"type":"Discard Keyword Filter","args":["Premium"]}],"operators":[{"type":"Flag Operator","args":[1]},{"type":"Keyword Sort Operator","args":["Hong Kong","Taiwan","Japan"]}]},"SSR":{"name":"SSR","url":"http://127.0.0.1:8080/SSR.list","filters":[{"type":"Region Filter","args":["HK"]},{"type":"Keyword Filter","args":["IEPL"]}],"operators":[{"type":"Flag Operator","args":[1]},{"type":"Sort Operator"}]},"AAEX":{"name":"AAEX","url":"http://127.0.0.1:8080/AAEX.list"}}}
|
{"subs":{"Nexitally":{"name":"Nexitally","url":"http://127.0.0.1:8080/nex.list","filters":[{"type":"Region Filter","args":["HK","JP","TW"]},{"type":"Discard Keyword Filter","args":["Premium"]}],"operators":[{"type":"Flag Operator","args":[1]},{"type":"Keyword Sort Operator","args":["Hong Kong","Taiwan","Japan"]}]},"SSR":{"name":"SSR","url":"http://127.0.0.1:8080/SSR.list","filters":[{"type":"Region Filter","args":["HK"]},{"type":"Keyword Filter","args":["IEPL"]}],"operators":[{"type":"Flag Operator","args":[1]},{"type":"Sort Operator"}]},"AAEX":{"name":"AAEX","url":"http://127.0.0.1:8080/AAEX.list"}},"collections":{"Surge":{"name":"Surge","subscriptions":["Nexitally","AAEX"]}}}
|
Loading…
x
Reference in New Issue
Block a user