mirror of
https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
synced 2025-07-25 01:24:26 +08:00
多项改进
1. 现在可以编辑远程配置了。 2. 远程配置增加自动同步的选项,配合Cron脚本可以定期同步并上传配置到gist。
This commit is contained in:
parent
85ee743988
commit
a0691bedaf
@ -116,6 +116,9 @@ function service() {
|
||||
$app.get("/api/utils/env", getEnv); // get runtime environment
|
||||
$app.get("/api/utils/backup", gistBackup); // gist backup actions
|
||||
|
||||
// cron triggered functions
|
||||
$app.get("/api/cron/sync-artifacts", cronSyncArtifacts); // sync all artifacts
|
||||
|
||||
// Redirect sub.store to vercel webpage
|
||||
$app.get("/", async (req, res) => {
|
||||
// 302 redirect
|
||||
@ -600,7 +603,7 @@ function service() {
|
||||
}
|
||||
|
||||
function updateArtifact(req, res) {
|
||||
const allArtifacts = $.read(SETTINGS_KEY);
|
||||
const allArtifacts = $.read(ARTIFACTS_KEY);
|
||||
const oldName = req.params.name;
|
||||
const artifact = allArtifacts[oldName];
|
||||
if (artifact) {
|
||||
@ -632,9 +635,61 @@ function service() {
|
||||
}
|
||||
}
|
||||
|
||||
async function cronSyncArtifacts(req, res) {
|
||||
$.info("开始同步所有远程配置...");
|
||||
const allArtifacts = $.read(ARTIFACTS_KEY);
|
||||
let success = [], failed = [];
|
||||
for (const artifact of Object.values(allArtifacts)) {
|
||||
if (artifact.sync) {
|
||||
$.info(`正在同步云配置:${artifact.name}...`);
|
||||
try {
|
||||
let item;
|
||||
switch (artifact.type) {
|
||||
case 'subscription':
|
||||
item = $.read(SUBS_KEY)[artifact.source];
|
||||
break;
|
||||
case 'collection':
|
||||
item = $.read(COLLECTIONS_KEY)[artifact.source];
|
||||
break;
|
||||
case 'rule':
|
||||
item = $.read(RULES_KEY)[artifact.source];
|
||||
break;
|
||||
}
|
||||
const output = await produceArtifact({
|
||||
type: artifact.type,
|
||||
item,
|
||||
platform: artifact.platform
|
||||
});
|
||||
const resp = await syncArtifact({
|
||||
filename: artifact.name,
|
||||
content: output
|
||||
});
|
||||
artifact.updated = new Date().getTime();
|
||||
const body = JSON.parse(resp.body);
|
||||
artifact.url = body.files[artifact.name].raw_url.replace(/\/raw\/[^\/]*\/(.*)/, "/raw/$1");
|
||||
$.write(allArtifacts, ARTIFACTS_KEY);
|
||||
$.info(`✅ 成功同步云配置:${artifact.name}`);
|
||||
success.push(artifact);
|
||||
} catch (err) {
|
||||
$.error(`云配置: ${artifact.name} 同步失败!原因:${err}`);
|
||||
$.notify(
|
||||
`🌍 『 𝑺𝒖𝒃-𝑺𝒕𝒐𝒓𝒆 』 同步订阅失败`,
|
||||
`❌ 无法同步订阅:${artifact.name}!`,
|
||||
`🤔 原因:${err}`
|
||||
);
|
||||
failed.push(artifact);
|
||||
}
|
||||
}
|
||||
}
|
||||
res.json({
|
||||
success,
|
||||
failed
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteArtifact(req, res) {
|
||||
const name = req.params.name;
|
||||
$.info(`正在删除Artifact:${name}`);
|
||||
$.info(`正在删除远程配置:${name}`);
|
||||
const allArtifacts = $.read(ARTIFACTS_KEY);
|
||||
try {
|
||||
const artifact = allArtifacts[name];
|
||||
|
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
6
scripts/cron-sync-artifacts.js
Normal file
6
scripts/cron-sync-artifacts.js
Normal file
@ -0,0 +1,6 @@
|
||||
const $ = API();
|
||||
$.http.get('https://sub.store/api/cron/sync-artifacts');
|
||||
$.done();
|
||||
|
||||
// prettier ignore
|
||||
function ENV(){const e="undefined"!=typeof $task,t="undefined"!=typeof $loon,s="undefined"!=typeof $httpClient&&!t,i="function"==typeof require&&"undefined"!=typeof $jsbox;return{isQX:e,isLoon:t,isSurge:s,isNode:"function"==typeof require&&!i,isJSBox:i,isRequest:"undefined"!=typeof $request,isScriptable:"undefined"!=typeof importModule}}function HTTP(e={baseURL:""}){const{isQX:t,isLoon:s,isSurge:i,isScriptable:n,isNode:o}=ENV(),r=/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&\/\/=]*)/;const u={};return["GET","POST","PUT","DELETE","HEAD","OPTIONS","PATCH"].forEach(l=>u[l.toLowerCase()]=(u=>(function(u,l){l="string"==typeof l?{url:l}:l;const h=e.baseURL;h&&!r.test(l.url||"")&&(l.url=h?h+l.url:l.url);const a=(l={...e,...l}).timeout,c={onRequest:()=>{},onResponse:e=>e,onTimeout:()=>{},...l.events};let f,d;if(c.onRequest(u,l),t)f=$task.fetch({method:u,...l});else if(s||i||o)f=new Promise((e,t)=>{(o?require("request"):$httpClient)[u.toLowerCase()](l,(s,i,n)=>{s?t(s):e({statusCode:i.status||i.statusCode,headers:i.headers,body:n})})});else if(n){const e=new Request(l.url);e.method=u,e.headers=l.headers,e.body=l.body,f=new Promise((t,s)=>{e.loadString().then(s=>{t({statusCode:e.response.statusCode,headers:e.response.headers,body:s})}).catch(e=>s(e))})}const p=a?new Promise((e,t)=>{d=setTimeout(()=>(c.onTimeout(),t(`${u} URL: ${l.url} exceeds the timeout ${a} ms`)),a)}):null;return(p?Promise.race([p,f]).then(e=>(clearTimeout(d),e)):f).then(e=>c.onResponse(e))})(l,u))),u}function API(e="untitled",t=!1){const{isQX:s,isLoon:i,isSurge:n,isNode:o,isJSBox:r,isScriptable:u}=ENV();return new class{constructor(e,t){this.name=e,this.debug=t,this.http=HTTP(),this.env=ENV(),this.node=(()=>{if(o){return{fs:require("fs")}}return null})(),this.initCache();Promise.prototype.delay=function(e){return this.then(function(t){return((e,t)=>new Promise(function(s){setTimeout(s.bind(null,t),e)}))(e,t)})}}initCache(){if(s&&(this.cache=JSON.parse($prefs.valueForKey(this.name)||"{}")),(i||n)&&(this.cache=JSON.parse($persistentStore.read(this.name)||"{}")),o){let e="root.json";this.node.fs.existsSync(e)||this.node.fs.writeFileSync(e,JSON.stringify({}),{flag:"wx"},e=>console.log(e)),this.root={},e=`${this.name}.json`,this.node.fs.existsSync(e)?this.cache=JSON.parse(this.node.fs.readFileSync(`${this.name}.json`)):(this.node.fs.writeFileSync(e,JSON.stringify({}),{flag:"wx"},e=>console.log(e)),this.cache={})}}persistCache(){const e=JSON.stringify(this.cache,null,2);s&&$prefs.setValueForKey(e,this.name),(i||n)&&$persistentStore.write(e,this.name),o&&(this.node.fs.writeFileSync(`${this.name}.json`,e,{flag:"w"},e=>console.log(e)),this.node.fs.writeFileSync("root.json",JSON.stringify(this.root,null,2),{flag:"w"},e=>console.log(e)))}write(e,t){if(this.log(`SET ${t}`),-1!==t.indexOf("#")){if(t=t.substr(1),n||i)return $persistentStore.write(e,t);if(s)return $prefs.setValueForKey(e,t);o&&(this.root[t]=e)}else this.cache[t]=e;this.persistCache()}read(e){return this.log(`READ ${e}`),-1===e.indexOf("#")?this.cache[e]:(e=e.substr(1),n||i?$persistentStore.read(e):s?$prefs.valueForKey(e):o?this.root[e]:void 0)}delete(e){if(this.log(`DELETE ${e}`),-1!==e.indexOf("#")){if(e=e.substr(1),n||i)return $persistentStore.write(null,e);if(s)return $prefs.removeValueForKey(e);o&&delete this.root[e]}else delete this.cache[e];this.persistCache()}notify(e,t="",l="",h={}){const a=h["open-url"],c=h["media-url"];if(s&&$notify(e,t,l,h),n&&$notification.post(e,t,l+`${c?"\n多媒体:"+c:""}`,{url:a}),i){let s={};a&&(s.openUrl=a),c&&(s.mediaUrl=c),"{}"===JSON.stringify(s)?$notification.post(e,t,l):$notification.post(e,t,l,s)}if(o||u){const s=l+(a?`\n点击跳转: ${a}`:"")+(c?`\n多媒体: ${c}`:"");if(r){require("push").schedule({title:e,body:(t?t+"\n":"")+s})}else console.log(`${e}\n${t}\n${s}\n\n`)}}log(e){this.debug&&console.log(`[${this.name}] LOG: ${this.stringify(e)}`)}info(e){console.log(`[${this.name}] INFO: ${this.stringify(e)}`)}error(e){console.log(`[${this.name}] ERROR: ${this.stringify(e)}`)}wait(e){return new Promise(t=>setTimeout(t,e))}done(e={}){s||i||n?$done(e):o&&!r&&"undefined"!=typeof $context&&($context.headers=e.headers,$context.statusCode=e.statusCode,$context.body=e.body)}stringify(e){if("string"==typeof e||e instanceof String)return e;try{return JSON.stringify(e,null,2)}catch(e){return"[object Object]"}}}(e,t)}
|
@ -8,34 +8,35 @@
|
||||
<v-btn icon @click="openGist()">
|
||||
<v-icon>visibility</v-icon>
|
||||
</v-btn>
|
||||
<v-dialog max-width="400px" v-model="addArtifactDialog">
|
||||
<v-dialog v-model="showArtifactDialog" max-width="400px">
|
||||
<template #activator="{on}">
|
||||
<v-btn icon v-on="on">
|
||||
<v-btn v-on="on" icon>
|
||||
<v-icon color="primary">mdi-plus-circle</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card class="pl-4 pr-4 pb-4 pt-4">
|
||||
<v-subheader>
|
||||
<v-icon left>mdi-plus-circle</v-icon>
|
||||
<h3>添加同步配置</h3>
|
||||
<v-icon left>{{ editing ? 'edit_off' : 'mdi-plus-circle' }}</v-icon>
|
||||
<h3>{{ editing ? '修改' : '添加' }}同步配置</h3>
|
||||
</v-subheader>
|
||||
<v-divider></v-divider>
|
||||
<v-form class="pt-4 pl-4 pr-4 pb-0" v-model="formValid">
|
||||
<v-form v-model="formValid" class="pt-4 pl-4 pr-4 pb-0">
|
||||
<v-text-field
|
||||
v-model="newArtifact.name"
|
||||
v-model="currentArtifact.name"
|
||||
:disabled="editing"
|
||||
:rules="validations.nameRules"
|
||||
clear-icon="clear"
|
||||
clearable
|
||||
label="配置名称"
|
||||
placeholder="填入生成配置名称,名称需唯一,如Clash.yaml。"
|
||||
:rules="validations.nameRules"
|
||||
clearable
|
||||
clear-icon="clear"
|
||||
/>
|
||||
<v-menu offset-y>
|
||||
<template v-slot:activator="{on}">
|
||||
<v-text-field
|
||||
label="类型"
|
||||
v-on="on"
|
||||
:rules="validations.required"
|
||||
:value="getType(newArtifact.type)"
|
||||
:value="getType(currentArtifact.type)"
|
||||
label="类型"
|
||||
/>
|
||||
</template>
|
||||
<v-list dense>
|
||||
@ -56,22 +57,22 @@
|
||||
<v-menu offset-y>
|
||||
<template v-slot:activator="{on}">
|
||||
<v-text-field
|
||||
v-model="newArtifact.source"
|
||||
label="来源"
|
||||
:rules="validations.required"
|
||||
:placeholder="`填入${getType(newArtifact.type) || '来源'}的名称。`"
|
||||
v-model="currentArtifact.source"
|
||||
v-on="on"
|
||||
:placeholder="`填入${getType(currentArtifact.type) || '来源'}的名称。`"
|
||||
:rules="validations.required"
|
||||
label="来源"
|
||||
/>
|
||||
</template>
|
||||
<v-list dense>
|
||||
<v-list-item
|
||||
v-for="(sub, idx) in getSources(newArtifact.type)"
|
||||
@click="newArtifact.source = sub.name"
|
||||
v-for="(sub, idx) in getSources(currentArtifact.type)"
|
||||
:key="idx"
|
||||
@click="currentArtifact.source = sub.name"
|
||||
>
|
||||
<v-list-item-avatar>
|
||||
<v-icon v-if="!sub.icon" color="teal darken-1">mdi-cloud</v-icon>
|
||||
<v-img :src="sub.icon" v-else :class="getIconClass(sub.icon)"/>
|
||||
<v-img v-else :class="getIconClass(sub.icon)" :src="sub.icon"/>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-title>{{ sub.name }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
@ -81,20 +82,20 @@
|
||||
<v-menu offset-y>
|
||||
<template v-slot:activator="{on}">
|
||||
<v-text-field
|
||||
label="目标"
|
||||
v-on="on"
|
||||
:rules="validations.required"
|
||||
:value="newArtifact.platform"
|
||||
:value="currentArtifact.platform"
|
||||
label="目标"
|
||||
/>
|
||||
</template>
|
||||
<v-list dense>
|
||||
<v-list-item
|
||||
v-for="platform in ['Surge', 'Loon', 'QX', 'Clash']"
|
||||
:key="platform"
|
||||
@click="newArtifact.platform = platform"
|
||||
@click="currentArtifact.platform = platform"
|
||||
>
|
||||
<v-list-item-avatar>
|
||||
<v-img :src="getIcon(platform)" :class="getIconClass('#invert')"></v-img>
|
||||
<v-img :class="getIconClass('#invert')" :src="getIcon(platform)"></v-img>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-title>{{ platform }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
@ -104,10 +105,10 @@
|
||||
<v-divider></v-divider>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="primary" text small :disabled="!formValid" @click="createArtifact()">
|
||||
<v-btn :disabled="!formValid" color="primary" small text @click="doneEditArtifact()">
|
||||
确认
|
||||
</v-btn>
|
||||
<v-btn text small @click="clear()">
|
||||
<v-btn small text @click="clear()">
|
||||
取消
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
@ -116,9 +117,9 @@
|
||||
</v-card-title>
|
||||
|
||||
<template v-for="(artifact, idx) in artifacts">
|
||||
<v-list-item three-line dense :key="artifact.name">
|
||||
<v-list-item :key="artifact.name" dense three-line>
|
||||
<v-list-item-avatar>
|
||||
<v-img :src="getIcon(artifact.platform)" :class="getIconClass('#invert')"/>
|
||||
<v-img :class="getIconClass('#invert')" :src="getIcon(artifact.platform)"/>
|
||||
</v-list-item-avatar>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title>
|
||||
@ -137,27 +138,39 @@
|
||||
<v-list-item-subtitle>更新于:{{ getUpdatedTime(artifact.updated) }}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-menu bottom left>
|
||||
<template #activator="{ on }">
|
||||
<v-btn icon v-on="on">
|
||||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-btn icon @click="toggleSync(artifact)">
|
||||
<v-icon :color="artifact.sync ? undefined: 'red'">{{ artifact.sync ? "alarm" : "alarm_off" }}</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list dense>
|
||||
<v-list-item @click="copy(artifact.url)" v-if="artifact.url">
|
||||
<v-list-item-title>复制</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="preview(artifact.name)">
|
||||
<v-list-item-title>预览</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="sync(artifact.name)">
|
||||
<v-list-item-title>同步</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="deleteArtifact(idx, artifact.name)">
|
||||
<v-list-item-title>删除</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-menu bottom left>
|
||||
<template #activator="{ on }">
|
||||
<v-btn v-on="on" icon>
|
||||
<v-icon>mdi-dots-vertical</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list dense>
|
||||
<v-list-item v-if="artifact.url" @click="copy(artifact.url)">
|
||||
<v-list-item-title>复制</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="editArtifact(artifact)">
|
||||
<v-list-item-title>编辑</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="preview(artifact.name)">
|
||||
<v-list-item-title>预览</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="sync(artifact.name)">
|
||||
<v-list-item-title>同步</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="deleteArtifact(idx, artifact.name)">
|
||||
<v-list-item-title>删除</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-list-item-action>
|
||||
</v-list-item>
|
||||
</template>
|
||||
@ -174,13 +187,14 @@ export default {
|
||||
name: "Cloud",
|
||||
data() {
|
||||
return {
|
||||
addArtifactDialog: false,
|
||||
newArtifact: {
|
||||
showArtifactDialog: false,
|
||||
currentArtifact: {
|
||||
name: "",
|
||||
type: "subscription",
|
||||
source: "",
|
||||
platform: "",
|
||||
},
|
||||
editing: null,
|
||||
formValid: false,
|
||||
validations: {
|
||||
nameRules: [
|
||||
@ -230,13 +244,35 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
async createArtifact() {
|
||||
async doneEditArtifact() {
|
||||
console.log(JSON.stringify(this.currentArtifact, null, 2));
|
||||
try {
|
||||
await axios.post("/artifacts", this.newArtifact);
|
||||
if (this.editing) {
|
||||
await axios.patch(`/artifact/${this.currentArtifact.name}`, this.currentArtifact);
|
||||
} else {
|
||||
await axios.post("/artifacts", this.currentArtifact);
|
||||
}
|
||||
await this.$store.dispatch("FETCH_ARTIFACTS");
|
||||
this.clear();
|
||||
} catch (err) {
|
||||
this.$store.commit("SET_ERROR_MESSAGE", `创建配置失败!${err}`);
|
||||
this.$store.commit("SET_ERROR_MESSAGE", `${this.editing ? "更新" : "创建"}配置失败!${err}`);
|
||||
}
|
||||
},
|
||||
|
||||
async editArtifact(artifact) {
|
||||
this.editing = true;
|
||||
Object.assign(this.currentArtifact, artifact);
|
||||
this.showArtifactDialog = true;
|
||||
},
|
||||
|
||||
async toggleSync(artifact) {
|
||||
artifact.sync = !artifact.sync;
|
||||
try {
|
||||
await axios.patch(`/artifact/${artifact.name}`, artifact);
|
||||
await this.$store.dispatch("FETCH_ARTIFACTS");
|
||||
this.$store.commit("SET_SUCCESS_MESSAGE", `已${artifact.sync ? '启用' : '禁用'}自动同步配置${artifact.name}`);
|
||||
} catch (err) {
|
||||
this.$store.commit("SET_ERROR_MESSAGE", `更改同步配置失败!${err}`);
|
||||
}
|
||||
},
|
||||
|
||||
@ -250,13 +286,14 @@ export default {
|
||||
},
|
||||
|
||||
clear() {
|
||||
this.newArtifact = {
|
||||
this.currentArtifact = {
|
||||
name: "",
|
||||
type: "subscription",
|
||||
source: "",
|
||||
platform: ""
|
||||
}
|
||||
this.addArtifactDialog = false;
|
||||
this.showArtifactDialog = false;
|
||||
this.editing = false;
|
||||
},
|
||||
|
||||
copy(url) {
|
||||
@ -282,8 +319,8 @@ export default {
|
||||
},
|
||||
|
||||
setArtifactType(type) {
|
||||
this.newArtifact.type = type;
|
||||
this.newArtifact.source = "";
|
||||
this.currentArtifact.type = type;
|
||||
this.currentArtifact.source = "";
|
||||
},
|
||||
|
||||
getSources(type) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user