mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-08-14 02:05:54 +08:00
组合订阅现在允许添加所有节点操作
This commit is contained in:
parent
f886bc11e9
commit
ff4790f12e
@ -25,7 +25,7 @@ function startService() {
|
|||||||
___/ // /_/ // /_/ //_____/___/ // /_ / /_/ // / / __/
|
___/ // /_/ // /_/ //_____/___/ // /_ / /_/ // / / __/
|
||||||
/____/ \__,_//_.___/ /____/ \__/ \____//_/ \___/
|
/____/ \__,_//_.___/ /____/ \__/ \____//_/ \___/
|
||||||
*/
|
*/
|
||||||
});
|
});
|
||||||
console.log(welcome);
|
console.log(welcome);
|
||||||
const $app = express();
|
const $app = express();
|
||||||
// Constants
|
// Constants
|
||||||
@ -90,22 +90,36 @@ function startService() {
|
|||||||
$app.get("/api/utils/env", getEnv); // get runtime environment
|
$app.get("/api/utils/env", getEnv); // get runtime environment
|
||||||
$app.get("/api/utils/backup", gistBackup); // gist backup actions
|
$app.get("/api/utils/backup", gistBackup); // gist backup actions
|
||||||
|
|
||||||
|
// Redirect sub.store to vercel webpage
|
||||||
|
$app.get("/", async (req, res) => {
|
||||||
|
// 302 redirect
|
||||||
|
res.set("location", "https://sub-store.vercel.app/").status(302).end();
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle preflight request for QX
|
||||||
|
if (ENV().isQX) {
|
||||||
|
$app.options("/", async (req, res) => {
|
||||||
|
res.status(200).end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
$app.start();
|
$app.start();
|
||||||
|
|
||||||
// subscriptions API
|
// subscriptions API
|
||||||
async function downloadSubscription(req, res) {
|
async function downloadSubscription(req, res) {
|
||||||
const {name} = req.params;
|
const {name} = req.params;
|
||||||
const {cache} = req.query || false;
|
const {cache} = req.query;
|
||||||
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);
|
||||||
const sub = allSubs[name];
|
const sub = allSubs[name];
|
||||||
if (sub) {
|
if (sub) {
|
||||||
try {
|
try {
|
||||||
const raw = await getResource(sub.url, cache);
|
const useCache = typeof cache === 'undefined' ? (platform === 'JSON' || platform === 'URI') : cache;
|
||||||
|
const raw = await getResource(sub.url, useCache);
|
||||||
// parse proxies
|
// parse proxies
|
||||||
let proxies = ProxyUtils.parse(raw);
|
let proxies = ProxyUtils.parse(raw);
|
||||||
// apply processors
|
// apply processors
|
||||||
proxies = await ProxyUtils.process(proxies, sub.process);
|
proxies = await ProxyUtils.process(proxies, sub.process || []);
|
||||||
// produce
|
// produce
|
||||||
const output = ProxyUtils.produce(proxies, platform);
|
const output = ProxyUtils.produce(proxies, platform);
|
||||||
if (platform === 'JSON') {
|
if (platform === 'JSON') {
|
||||||
@ -138,6 +152,7 @@ function startService() {
|
|||||||
function createSubscription(req, res) {
|
function createSubscription(req, res) {
|
||||||
const sub = req.body;
|
const sub = req.body;
|
||||||
const allSubs = $.read(SUBS_KEY);
|
const allSubs = $.read(SUBS_KEY);
|
||||||
|
$.info(`正在创建订阅: ${sub.name}`);
|
||||||
if (allSubs[sub.name]) {
|
if (allSubs[sub.name]) {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
status: "failed",
|
status: "failed",
|
||||||
@ -185,6 +200,7 @@ function startService() {
|
|||||||
...allSubs[name],
|
...allSubs[name],
|
||||||
...sub,
|
...sub,
|
||||||
};
|
};
|
||||||
|
$.info(`正在更新订阅: ${name}`);
|
||||||
// allow users to update the subscription name
|
// allow users to update the subscription name
|
||||||
if (name !== sub.name) {
|
if (name !== sub.name) {
|
||||||
// we need to find out all collections refer to this name
|
// we need to find out all collections refer to this name
|
||||||
@ -216,6 +232,7 @@ function startService() {
|
|||||||
|
|
||||||
function deleteSubscription(req, res) {
|
function deleteSubscription(req, res) {
|
||||||
const {name} = req.params;
|
const {name} = req.params;
|
||||||
|
$.info(`删除订阅:${name}...`);
|
||||||
// delete from subscriptions
|
// delete from subscriptions
|
||||||
let allSubs = $.read(SUBS_KEY);
|
let allSubs = $.read(SUBS_KEY);
|
||||||
delete allSubs[name];
|
delete allSubs[name];
|
||||||
@ -260,9 +277,12 @@ function startService() {
|
|||||||
const sub = allSubs[subs[i]];
|
const sub = allSubs[subs[i]];
|
||||||
$.info(`正在处理子订阅:${sub.name},进度--${100 * (i + 1 / subs.length).toFixed(1)}% `);
|
$.info(`正在处理子订阅:${sub.name},进度--${100 * (i + 1 / subs.length).toFixed(1)}% `);
|
||||||
try {
|
try {
|
||||||
const raw = await getResource(sub.url, cache);
|
const useCache = typeof cache === 'undefined' ? (platform === 'JSON' || platform === 'URI') : cache;
|
||||||
|
const raw = await getResource(sub.url, useCache);
|
||||||
// parse proxies
|
// parse proxies
|
||||||
proxies = proxies.concat(ProxyUtils.parse(raw));
|
proxies = proxies.concat(ProxyUtils.parse(raw));
|
||||||
|
// apply processors
|
||||||
|
proxies = await ProxyUtils.process(proxies, sub.process || []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.error(`处理组合订阅中的子订阅: ${sub.name}时出现错误:${err}! 该订阅已被跳过。`);
|
$.error(`处理组合订阅中的子订阅: ${sub.name}时出现错误:${err}! 该订阅已被跳过。`);
|
||||||
}
|
}
|
||||||
@ -312,6 +332,7 @@ function startService() {
|
|||||||
|
|
||||||
function createCollection(req, res) {
|
function createCollection(req, res) {
|
||||||
const collection = req.body;
|
const collection = req.body;
|
||||||
|
$.info(`正在创建组合订阅:${collection.name}`);
|
||||||
const allCol = $.read(COLLECTIONS_KEY);
|
const allCol = $.read(COLLECTIONS_KEY);
|
||||||
if (allCol[collection.name]) {
|
if (allCol[collection.name]) {
|
||||||
res.status(500).json({
|
res.status(500).json({
|
||||||
@ -360,6 +381,7 @@ function startService() {
|
|||||||
...allCol[name],
|
...allCol[name],
|
||||||
...collection,
|
...collection,
|
||||||
};
|
};
|
||||||
|
$.info(`正在更新组合订阅:${name}...`);
|
||||||
// allow users to update collection name
|
// allow users to update collection name
|
||||||
delete allCol[name];
|
delete allCol[name];
|
||||||
allCol[collection.name || name] = newCol;
|
allCol[collection.name || name] = newCol;
|
||||||
@ -378,6 +400,7 @@ function startService() {
|
|||||||
|
|
||||||
function deleteCollection(req, res) {
|
function deleteCollection(req, res) {
|
||||||
const {name} = req.params;
|
const {name} = req.params;
|
||||||
|
$.info(`正在删除组合订阅:${name}`);
|
||||||
let allCol = $.read(COLLECTIONS_KEY);
|
let allCol = $.read(COLLECTIONS_KEY);
|
||||||
delete allCol[name];
|
delete allCol[name];
|
||||||
$.write(allCol, COLLECTIONS_KEY);
|
$.write(allCol, COLLECTIONS_KEY);
|
||||||
@ -423,6 +446,7 @@ function startService() {
|
|||||||
|
|
||||||
async function refreshCache(req, res) {
|
async function refreshCache(req, res) {
|
||||||
const {url} = req.body;
|
const {url} = req.body;
|
||||||
|
$.info(`Refreshing cache for URL: ${url}`);
|
||||||
try {
|
try {
|
||||||
const raw = await getResource(url, false);
|
const raw = await getResource(url, false);
|
||||||
$.write(raw, `#${Base64.safeEncode(url)}`);
|
$.write(raw, `#${Base64.safeEncode(url)}`);
|
||||||
@ -531,6 +555,9 @@ function startService() {
|
|||||||
return resource;
|
return resource;
|
||||||
}
|
}
|
||||||
const {body} = await $http.get(url);
|
const {body} = await $http.get(url);
|
||||||
|
if (body.replace(/\s/g, "").length === 0) {
|
||||||
|
throw new Error("订阅内容为空!");
|
||||||
|
}
|
||||||
$.write(body, key);
|
$.write(body, key);
|
||||||
$.write(new Date().getTime(), timeKey);
|
$.write(new Date().getTime(), timeKey);
|
||||||
return body;
|
return body;
|
||||||
@ -666,7 +693,7 @@ var ProxyUtils = (function () {
|
|||||||
return producer.produce(proxy);
|
return producer.produce(proxy);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
$.error(
|
$.error(
|
||||||
`ERROR: cannot produce proxy: ${JSON.stringify(
|
`Cannot produce proxy: ${JSON.stringify(
|
||||||
proxy, null, 2
|
proxy, null, 2
|
||||||
)}\nReason: ${err}`
|
)}\nReason: ${err}`
|
||||||
);
|
);
|
||||||
@ -1385,7 +1412,7 @@ var PROXY_PARSERS = (function () {
|
|||||||
if (params.length > 4) {
|
if (params.length > 4) {
|
||||||
const [key, val] = params[4].split(":");
|
const [key, val] = params[4].split(":");
|
||||||
if (key === "tls-name") proxy.sni = val;
|
if (key === "tls-name") proxy.sni = val;
|
||||||
else throw new Error(`ERROR: unknown option ${key} for line: \n${line}`);
|
else throw new Error(`Unknown option ${key} for line: \n${line}`);
|
||||||
}
|
}
|
||||||
return proxy;
|
return proxy;
|
||||||
};
|
};
|
||||||
@ -2122,7 +2149,7 @@ var PROXY_PROCESSORS = (function () {
|
|||||||
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(`Cannot apply operator ${op.name}! Reason: ${err}`);
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@ -2413,9 +2440,8 @@ var PROXY_PRODUCERS = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function URI_Producer() {
|
function URI_Producer() {
|
||||||
const targetPlatform = "URI";
|
const type = "SINGLE";
|
||||||
const Base64 = new Base64Code();
|
const produce = (proxy) => {
|
||||||
const output = (proxy) => {
|
|
||||||
let result = "";
|
let result = "";
|
||||||
switch (proxy.type) {
|
switch (proxy.type) {
|
||||||
case "ss":
|
case "ss":
|
||||||
@ -2489,6 +2515,7 @@ var PROXY_PRODUCERS = (function () {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
return {type, produce};
|
||||||
}
|
}
|
||||||
|
|
||||||
function JSON_Producer() {
|
function JSON_Producer() {
|
||||||
|
4
backend/sub-store.min.js
vendored
4
backend/sub-store.min.js
vendored
File diff suppressed because one or more lines are too long
@ -16,7 +16,7 @@
|
|||||||
<v-icon left x-small>mdi-flash</v-icon>
|
<v-icon left x-small>mdi-flash</v-icon>
|
||||||
TFO
|
TFO
|
||||||
</v-chip>
|
</v-chip>
|
||||||
<v-chip x-small v-if="proxy.scert" color="error" outlined>
|
<v-chip x-small v-if="proxy['skip-cert-verify']" color="error" outlined>
|
||||||
<v-icon left x-small>error</v-icon>
|
<v-icon left x-small>error</v-icon>
|
||||||
SCERT
|
SCERT
|
||||||
</v-chip>
|
</v-chip>
|
||||||
@ -117,20 +117,18 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
refresh() {
|
refresh() {
|
||||||
axios.post(`/refresh`, {url: this.sub}).then(() => {
|
axios.post(`/utils/refresh`, {url: this.sub}).then(() => {
|
||||||
this.fetch();
|
this.fetch();
|
||||||
|
}).catch(err => {
|
||||||
|
this.$store.commit("SET_ERROR_MESSAGE", err.response.data.message);
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetch() {
|
async fetch() {
|
||||||
await axios.get(this.url).then(resp => {
|
await axios.get(this.url).then(resp => {
|
||||||
let {data} = resp;
|
let {data} = resp;
|
||||||
if ((typeof data === 'string' || data instanceof String) && data.indexOf("\n") !== -1){
|
// eslint-disable-next-line no-debugger
|
||||||
this.proxies = data.split("\n").map(p => JSON.parse(p));
|
this.proxies = data;
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.proxies = [data];
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
this.$store.commit("SET_ERROR_MESSAGE", err);
|
this.$store.commit("SET_ERROR_MESSAGE", err);
|
||||||
});
|
});
|
||||||
@ -150,7 +148,7 @@ export default {
|
|||||||
|
|
||||||
async showInfo(idx) {
|
async showInfo(idx) {
|
||||||
const {server, name} = this.proxies[idx];
|
const {server, name} = this.proxies[idx];
|
||||||
const res = await axios.get(`/IP_API/${encodeURIComponent(server)}`).then(resp => resp.data);
|
const res = await axios.get(`/utils/IP_API/${encodeURIComponent(server)}`).then(resp => resp.data);
|
||||||
this.info.name = name;
|
this.info.name = name;
|
||||||
this.info.isp = `ISP:${res.isp}`;
|
this.info.isp = `ISP:${res.isp}`;
|
||||||
this.info.region = `地区:${flags.get(res.countryCode)} ${res.regionName} ${res.city}`;
|
this.info.region = `地区:${flags.get(res.countryCode)} ${res.regionName} ${res.city}`;
|
||||||
|
@ -6,7 +6,6 @@ import Subscription from "@/views/Subscription";
|
|||||||
import Dashboard from "@/views/Dashboard";
|
import Dashboard from "@/views/Dashboard";
|
||||||
import User from "@/views/User";
|
import User from "@/views/User";
|
||||||
import SubEditor from "@/views/SubEditor";
|
import SubEditor from "@/views/SubEditor";
|
||||||
import CollectionEditor from "@/views/CollectionEditor";
|
|
||||||
|
|
||||||
Vue.use(Router);
|
Vue.use(Router);
|
||||||
|
|
||||||
@ -40,7 +39,8 @@ const router = new Router({
|
|||||||
{
|
{
|
||||||
path: "/collection-edit/:name",
|
path: "/collection-edit/:name",
|
||||||
name: "collection-edit",
|
name: "collection-edit",
|
||||||
component: CollectionEditor,
|
component: SubEditor,
|
||||||
|
props: {isCollection: true},
|
||||||
meta: {title: "订阅编辑"}
|
meta: {title: "订阅编辑"}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -45,21 +45,21 @@ const store = new Vuex.Store({
|
|||||||
actions: {
|
actions: {
|
||||||
// fetch subscriptions
|
// fetch subscriptions
|
||||||
async FETCH_SUBSCRIPTIONS({state}) {
|
async FETCH_SUBSCRIPTIONS({state}) {
|
||||||
return axios.get("/sub").then(resp => {
|
return axios.get("/subs").then(resp => {
|
||||||
const {data} = resp.data;
|
const {data} = resp.data;
|
||||||
state.subscriptions = data;
|
state.subscriptions = data;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// fetch collections
|
// fetch collections
|
||||||
async FETCH_COLLECTIONS({state}) {
|
async FETCH_COLLECTIONS({state}) {
|
||||||
return axios.get("/collection").then(resp => {
|
return axios.get("/collections").then(resp => {
|
||||||
const {data} = resp.data;
|
const {data} = resp.data;
|
||||||
state.collections = data;
|
state.collections = data;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// fetch env
|
// fetch env
|
||||||
async FETCH_ENV({state}) {
|
async FETCH_ENV({state}) {
|
||||||
return axios.get("/env").then(resp => {
|
return axios.get("/utils/env").then(resp => {
|
||||||
state.env = resp.data;
|
state.env = resp.data;
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@ -72,7 +72,7 @@ const store = new Vuex.Store({
|
|||||||
},
|
},
|
||||||
// new subscription
|
// new subscription
|
||||||
async NEW_SUBSCRIPTION({dispatch}, sub) {
|
async NEW_SUBSCRIPTION({dispatch}, sub) {
|
||||||
return axios.post(`/sub`, sub).then(() => {
|
return axios.post(`/subs`, sub).then(() => {
|
||||||
dispatch("FETCH_SUBSCRIPTIONS");
|
dispatch("FETCH_SUBSCRIPTIONS");
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -91,7 +91,7 @@ const store = new Vuex.Store({
|
|||||||
},
|
},
|
||||||
// new collection
|
// new collection
|
||||||
async NEW_COLLECTION({dispatch}, collection) {
|
async NEW_COLLECTION({dispatch}, collection) {
|
||||||
return axios.post(`/collection`, collection).then(() => {
|
return axios.post(`/collections`, collection).then(() => {
|
||||||
dispatch("FETCH_COLLECTIONS");
|
dispatch("FETCH_COLLECTIONS");
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-container>
|
<v-container>
|
||||||
<v-card class="mb-4">
|
<v-card class="mb-4">
|
||||||
<v-subheader>基本信息</v-subheader>
|
<v-subheader>订阅配置</v-subheader>
|
||||||
<v-form class="pl-4 pr-4 pb-0" v-model="formState.basicValid">
|
<v-form class="pl-4 pr-4 pb-0" v-model="formState.basicValid">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="options.name"
|
v-model="options.name"
|
||||||
@ -13,7 +13,9 @@
|
|||||||
clearable
|
clearable
|
||||||
clear-icon="clear"
|
clear-icon="clear"
|
||||||
/>
|
/>
|
||||||
|
<!--For Subscription-->
|
||||||
<v-textarea
|
<v-textarea
|
||||||
|
v-if="!isCollection"
|
||||||
v-model="options.url"
|
v-model="options.url"
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
rows="2"
|
rows="2"
|
||||||
@ -24,6 +26,27 @@
|
|||||||
clearable
|
clearable
|
||||||
clear-icon="clear"
|
clear-icon="clear"
|
||||||
/>
|
/>
|
||||||
|
<!--For Collection-->
|
||||||
|
<v-list
|
||||||
|
v-if="isCollection"
|
||||||
|
dense
|
||||||
|
>
|
||||||
|
<v-subheader class="pl-0">包含的订阅</v-subheader>
|
||||||
|
<v-list-item v-for="sub in availableSubs" :key="sub.name">
|
||||||
|
<v-list-item-avatar dark>
|
||||||
|
<v-icon>mdi-cloud</v-icon>
|
||||||
|
</v-list-item-avatar>
|
||||||
|
<v-list-item-content>
|
||||||
|
{{ sub.name }}
|
||||||
|
</v-list-item-content>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-checkbox
|
||||||
|
:value="sub.name"
|
||||||
|
v-model="selected"
|
||||||
|
class="pr-1"
|
||||||
|
/>
|
||||||
|
</v-list-item>
|
||||||
|
</v-list>
|
||||||
</v-form>
|
</v-form>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
@ -46,7 +69,7 @@
|
|||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-textarea
|
<v-textarea
|
||||||
v-model="importedSub"
|
v-model="imported"
|
||||||
solo
|
solo
|
||||||
label="粘贴配置以导入"
|
label="粘贴配置以导入"
|
||||||
rows="5"
|
rows="5"
|
||||||
@ -56,7 +79,7 @@
|
|||||||
/>
|
/>
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn text color="primary" @click="importSub">确认</v-btn>
|
<v-btn text color="primary" @click="importConf">确认</v-btn>
|
||||||
<v-btn text @click="showShareDialog = false">取消</v-btn>
|
<v-btn text @click="showShareDialog = false">取消</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
@ -105,7 +128,7 @@
|
|||||||
</v-radio-group>
|
</v-radio-group>
|
||||||
|
|
||||||
<v-radio-group
|
<v-radio-group
|
||||||
v-model="options.scert"
|
v-model="options['skip-cert-verify']"
|
||||||
dense
|
dense
|
||||||
class="mt-0 mb-0"
|
class="mt-0 mb-0"
|
||||||
>
|
>
|
||||||
@ -258,6 +281,14 @@ const AVAILABLE_PROCESSORS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
props: {
|
||||||
|
isCollection: {
|
||||||
|
type: Boolean,
|
||||||
|
default() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
components: {
|
components: {
|
||||||
FlagOperator,
|
FlagOperator,
|
||||||
KeywordFilter,
|
KeywordFilter,
|
||||||
@ -277,7 +308,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
selectedProcess: null,
|
selectedProcess: null,
|
||||||
showShareDialog: false,
|
showShareDialog: false,
|
||||||
importedSub: "",
|
imported: "",
|
||||||
dialog: false,
|
dialog: false,
|
||||||
validations: {
|
validations: {
|
||||||
nameRules: [
|
nameRules: [
|
||||||
@ -300,21 +331,37 @@ export default {
|
|||||||
url: "",
|
url: "",
|
||||||
useless: "KEEP",
|
useless: "KEEP",
|
||||||
udp: "DEFAULT",
|
udp: "DEFAULT",
|
||||||
scert: "DEFAULT",
|
"skip-cert-verify": "DEFAULT",
|
||||||
tfo: "DEFAULT",
|
tfo: "DEFAULT",
|
||||||
},
|
},
|
||||||
process: [],
|
process: [],
|
||||||
|
selected: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
const name = this.$route.params.name;
|
const name = this.$route.params.name;
|
||||||
const sub = (typeof name === 'undefined' || name === 'UNTITLED') ? {} : this.$store.state.subscriptions[name];
|
let source;
|
||||||
this.$store.commit("SET_NAV_TITLE", sub.name ? `订阅编辑 -- ${sub.name}` : "新建订阅");
|
if (this.isCollection) {
|
||||||
const {options, process} = loadSubscription(this.options, sub);
|
source = (typeof name === 'undefined' || name === 'UNTITLED') ? {} : this.$store.state.collections[name];
|
||||||
|
this.$store.commit("SET_NAV_TITLE", source.name ? `组合订阅编辑 -- ${source.name}` : "新建组合订阅");
|
||||||
|
this.selected = source.subscriptions || [];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
source = (typeof name === 'undefined' || name === 'UNTITLED') ? {} : this.$store.state.subscriptions[name];
|
||||||
|
this.$store.commit("SET_NAV_TITLE", source.name ? `订阅编辑 -- ${source.name}` : "新建订阅");
|
||||||
|
}
|
||||||
|
this.name = source.name;
|
||||||
|
const {options, process} = loadProcess(this.options, source);
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.process = process;
|
this.process = process;
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
|
availableSubs() {
|
||||||
|
return this.$store.state.subscriptions;
|
||||||
|
},
|
||||||
|
|
||||||
availableProcessors() {
|
availableProcessors() {
|
||||||
return AVAILABLE_PROCESSORS;
|
return AVAILABLE_PROCESSORS;
|
||||||
},
|
},
|
||||||
@ -327,45 +374,101 @@ export default {
|
|||||||
id: p.id
|
id: p.id
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
config() {
|
||||||
|
const output = {
|
||||||
|
name: this.options.name,
|
||||||
|
process: []
|
||||||
|
};
|
||||||
|
if (this.isCollection) {
|
||||||
|
output.subscriptions = this.selected;
|
||||||
|
} else {
|
||||||
|
output.url = this.options.url;
|
||||||
|
}
|
||||||
|
// useless filter
|
||||||
|
if (this.options.useless === 'REMOVE') {
|
||||||
|
output.process.push({
|
||||||
|
type: "Useless Filter"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// udp, tfo, scert
|
||||||
|
for (const opt of ['udp', 'tfo', 'skip-cert-verify']) {
|
||||||
|
if (this.options[opt] !== 'DEFAULT') {
|
||||||
|
output.process.push({
|
||||||
|
type: "Set Property Operator",
|
||||||
|
args: {key: opt, value: this.options[opt] === 'FORCE_OPEN'}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const p of this.process) {
|
||||||
|
output.process.push(p);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
save() {
|
save() {
|
||||||
|
if (this.isCollection) {
|
||||||
|
if (this.options.name && this.selected) {
|
||||||
|
if (this.$route.params.name === 'UNTITLED') {
|
||||||
|
this.$store.dispatch("NEW_COLLECTION", this.config).then(() => {
|
||||||
|
showInfo(`成功创建组合订阅:${this.name}!`)
|
||||||
|
}).catch(() => {
|
||||||
|
showError(`发生错误,无法创建组合订阅!`)
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.$store.dispatch("UPDATE_COLLECTION", {
|
||||||
|
name: this.$route.params.name,
|
||||||
|
collection: this.config
|
||||||
|
}).then(() => {
|
||||||
|
showInfo(`成功保存组合订阅:${this.name}!`)
|
||||||
|
}).catch(() => {
|
||||||
|
showError(`发生错误,无法保存组合订阅!`)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Saving subscription...");
|
||||||
if (this.options.name && this.options.url) {
|
if (this.options.name && this.options.url) {
|
||||||
const sub = buildSubscription(this.options, this.process);
|
|
||||||
if (this.$route.params.name !== "UNTITLED") {
|
if (this.$route.params.name !== "UNTITLED") {
|
||||||
this.$store.dispatch("UPDATE_SUBSCRIPTION", {
|
this.$store.dispatch("UPDATE_SUBSCRIPTION", {
|
||||||
name: this.$route.params.name,
|
name: this.$route.params.name,
|
||||||
sub
|
sub: this.config
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
showInfo(`成功保存订阅:${this.options.name}!`);
|
showInfo(`成功保存订阅:${this.options.name}!`);
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
showError(`发生错误,无法保存订阅!`);
|
showError(`发生错误,无法保存订阅!`);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.$store.dispatch("NEW_SUBSCRIPTION", sub).then(() => {
|
this.$store.dispatch("NEW_SUBSCRIPTION", this.config).then(() => {
|
||||||
showInfo(`成功创建订阅:${this.options.name}!`);
|
showInfo(`成功创建订阅:${this.options.name}!`);
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
showError(`发生错误,无法创建订阅!`);
|
showError(`发生错误,无法创建订阅!`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
share() {
|
share() {
|
||||||
let sub = buildSubscription(this.options, this.process);
|
let config = this.config;
|
||||||
sub.name = "「订阅名称」";
|
config.name = "「订阅名称」";
|
||||||
sub.url = "「订阅链接」";
|
if (this.isCollection) {
|
||||||
sub = JSON.stringify(sub);
|
config.subscriptions = [];
|
||||||
this.$clipboard(sub);
|
} else {
|
||||||
|
config.url = "「订阅链接」";
|
||||||
|
}
|
||||||
|
config = JSON.stringify(config);
|
||||||
|
this.$clipboard(config);
|
||||||
this.$store.commit("SET_SUCCESS_MESSAGE", "导出成功,订阅已复制到剪贴板!");
|
this.$store.commit("SET_SUCCESS_MESSAGE", "导出成功,订阅已复制到剪贴板!");
|
||||||
this.showShareDialog = false;
|
this.showShareDialog = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
importSub() {
|
importConf() {
|
||||||
if (this.importedSub) {
|
if (this.imported) {
|
||||||
const sub = JSON.parse(this.importedSub);
|
const sub = JSON.parse(this.imported);
|
||||||
const {options, process} = loadSubscription(this.options, sub);
|
const {options, process} = loadProcess(this.options, sub);
|
||||||
delete options.name;
|
delete options.name;
|
||||||
delete options.url;
|
delete options.url;
|
||||||
|
|
||||||
@ -433,16 +536,20 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadSubscription(options, sub) {
|
function loadProcess(options, source, isCollection = false) {
|
||||||
options = {
|
options = {
|
||||||
...options,
|
...options,
|
||||||
name: sub.name,
|
name: source.name,
|
||||||
url: sub.url,
|
};
|
||||||
|
if (isCollection) {
|
||||||
|
options.subscriptions = source.subscriptions;
|
||||||
|
} else {
|
||||||
|
options.url = source.url;
|
||||||
}
|
}
|
||||||
let process = []
|
let process = []
|
||||||
|
|
||||||
// flag
|
// flag
|
||||||
for (const p of (sub.process || [])) {
|
for (const p of (source.process || [])) {
|
||||||
switch (p.type) {
|
switch (p.type) {
|
||||||
case 'Useless Filter':
|
case 'Useless Filter':
|
||||||
options.useless = "REMOVE";
|
options.useless = "REMOVE";
|
||||||
@ -458,33 +565,6 @@ function loadSubscription(options, sub) {
|
|||||||
return {options, process};
|
return {options, process};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildSubscription(options, process) {
|
|
||||||
const sub = {
|
|
||||||
name: options.name,
|
|
||||||
url: options.url,
|
|
||||||
process: []
|
|
||||||
};
|
|
||||||
// useless filter
|
|
||||||
if (options.useless === 'REMOVE') {
|
|
||||||
sub.process.push({
|
|
||||||
type: "Useless Filter"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// udp, tfo, scert
|
|
||||||
for (const opt of ['udp', 'tfo', 'scert']) {
|
|
||||||
if (options[opt] !== 'DEFAULT') {
|
|
||||||
sub.process.push({
|
|
||||||
type: "Set Property Operator",
|
|
||||||
args: {key: opt, value: options[opt] === 'FORCE_OPEN'}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const p of process) {
|
|
||||||
sub.process.push(p);
|
|
||||||
}
|
|
||||||
return sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
function uuidv4() {
|
function uuidv4() {
|
||||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
@ -52,7 +52,7 @@ export default {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.save();
|
this.save();
|
||||||
axios.get(`/backup?action=${action}`).then(resp => {
|
axios.get(`/utils/backup?action=${action}`).then(resp => {
|
||||||
if (resp.data.status === 'success') {
|
if (resp.data.status === 'success') {
|
||||||
this.$store.commit("SET_SUCCESS_MESSAGE", `${action === 'upload' ? "备份" : "还原"}成功!`);
|
this.$store.commit("SET_SUCCESS_MESSAGE", `${action === 'upload' ? "备份" : "还原"}成功!`);
|
||||||
this.updateStore(this.$store);
|
this.updateStore(this.$store);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user