支持配置Gist同步

This commit is contained in:
Peng-YM 2020-09-24 14:59:14 +08:00
parent d30ece21ae
commit 3b57b895e2
3 changed files with 207 additions and 17 deletions

View File

@ -17,6 +17,7 @@ $.http = HTTP({
},
});
// Constants
const SETTINGS_KEY = "settings";
const SUBS_KEY = "subs";
const COLLECTIONS_KEY = "collections";
const AVAILABLE_FILTERS = {
@ -43,6 +44,7 @@ const AVAILABLE_OPERATORS = {
// SOME INITIALIZATIONS
if (!$.read(SUBS_KEY)) $.write({}, SUBS_KEY);
if (!$.read(COLLECTIONS_KEY)) $.write({}, COLLECTIONS_KEY);
if (!$.read(SETTINGS_KEY)) $.write({}, SETTINGS_KEY);
// BACKEND API
$.info("Initializing Express...");
@ -56,7 +58,6 @@ $app.get("/api/IP_API/:server", IP_API);
// subscriptions
$app.route("/api/sub/:name").get(getSub).patch(updateSub).delete(deleteSub);
$app.route("/api/sub").get(getAllSubs).post(newSub).delete(deleteAllSubs);
// refresh
@ -75,6 +76,14 @@ $app
.post(newCollection)
.delete(deleteAllCollections);
// settings
$app.route("/api/settings")
.get(getSettings)
.patch(updateSettings);
// backup
$app.get("/api/backup", gistBackup);
$app.all("/", async (req, res) => {
res.send("Hello from Sub-Store! Made with ❤️ by Peng-YM.");
});
@ -93,13 +102,7 @@ async function IP_API(req, res) {
async function downloadResource(url) {
let raw = await $.http
.get(url)
.then((resp) => resp.body)
.catch((err) => {
res.status(500).json({
status: "failed",
message: `Cannot refresh remote resource: ${url}\n Reason: ${err}`,
});
});
.then((resp) => resp.body);
// trim Clash config to save memory
const start = raw.indexOf("proxies:");
if (start !== -1) {
@ -109,6 +112,61 @@ async function downloadResource(url) {
return raw;
}
async function gistBackup(req, res) {
const {action} = req.query;
// read token
const { gistToken } = $.read(SETTINGS_KEY);
if (!gistToken) {
res.status(500).json({
status: "failed",
message: "未找到Gist备份Token!"
});
} else {
const gist = new Gist("Auto Generated Sub-Store Backup", gistToken);
try{
let content;
switch (action) {
case "upload":
content = $.read("#sub-store");
await gist.upload(JSON.stringify(content));
break;
case "download":
content = await gist.download();
// restore settings
$.write(content,"#sub-store");
break;
}
res.json({
status: "success",
});
} catch (err) {
res.status(500).json({
status: "failed",
message: `${action === "upload" ? "上传" : "下载"}备份失败!${err}`
});
}
}
}
// settings
async function getSettings(req, res) {
const settings = $.read(SETTINGS_KEY);
res.json(settings);
}
async function updateSettings(req, res) {
const data = req.body;
const settings = $.read(SETTINGS_KEY);
$.write({
...settings,
...data
}, SETTINGS_KEY);
res.json({
status: "success"
});
}
/**************************** API -- Subscriptions ***************************************/
// refresh resource
async function refreshResource(req, res) {
@ -2580,7 +2638,10 @@ function API(name = "untitled", debug = false) {
this.log(`SET ${key}`);
if (key.indexOf("#") !== -1) {
key = key.substr(1);
if (isSurge & isLoon) {
if (key === name) {
this.cache = JSON.parse(data);
}
if (isSurge || isLoon) {
$persistentStore.write(data, key);
}
if (isQX) {
@ -2599,7 +2660,8 @@ function API(name = "untitled", debug = false) {
this.log(`READ ${key}`);
if (key.indexOf("#") !== -1) {
key = key.substr(1);
if (isSurge & isLoon) {
if (key === name) return this.cache;
if (isSurge || isLoon) {
return $persistentStore.read(key);
}
if (isQX) {
@ -2617,7 +2679,7 @@ function API(name = "untitled", debug = false) {
this.log(`DELETE ${key}`);
if (key.indexOf("#") !== -1) {
key = key.substr(1);
if (isSurge & isLoon) {
if (isSurge || isLoon) {
$persistentStore.write(null, key);
}
if (isQX) {
@ -2962,6 +3024,80 @@ function express(port = 3000) {
}
}
function Gist(backupKey, token) {
const FILE_NAME = "Sub-Store";
const http = HTTP({
baseURL: "https://api.github.com",
headers: {
Authorization: `token ${token}`,
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.141 Safari/537.36",
},
events: {
onResponse: (resp) => {
if (String(resp.statusCode).startsWith("4")) {
return Promise.reject(`ERROR: ${JSON.parse(resp.body).message}`);
} else {
return resp;
}
},
},
});
async function locate() {
return http.get("/gists").then((response) => {
const gists = JSON.parse(response.body);
for (let g of gists) {
if (g.description === backupKey) {
return g.id;
}
}
return -1;
});
}
this.upload = async function(content) {
const id = await locate();
const files = {
[FILE_NAME]: { content }
};
if (id === -1) {
// create a new gist for backup
return http.post({
url: "/gists",
body: JSON.stringify({
description: backupKey,
public: false,
files
})
});
} else {
// update an existing gist
return http.patch({
url: `/gists/${id}`,
body: JSON.stringify({ files })
});
}
};
this.download = async function() {
const id = await locate();
if (id === -1) {
return Promise.reject("未找到Gist备份");
} else {
try {
const { files } = await http
.get(`/gists/${id}`)
.then(resp => JSON.parse(resp.body));
const url = files[FILE_NAME].raw_url;
return await HTTP().get(url).then(resp => resp.body);
} catch (err) {
return Promise.reject(err);
}
}
};
}
/******************************** Base 64 *********************************************/
// Base64 Coding Library
// https://github.com/dankogai/js-base64#readme
@ -3121,7 +3257,6 @@ Author: Diogo Costa
This program is released under the MIT License
*/
var YAML = (function () {
var errors = [],
reference_blocks = [],

View File

@ -125,10 +125,12 @@ export default {
async fetch() {
await axios.get(this.url).then(resp => {
let {data} = resp;
if (data instanceof String && data.indexOf("\n") !== -1)
if ((typeof data === 'string' || data instanceof String) && data.indexOf("\n") !== -1){
this.proxies = data.split("\n").map(p => JSON.parse(p));
else
}
else {
this.proxies = [data];
}
}).catch(err => {
this.$store.commit("SET_ERROR_MESSAGE", err);
});

View File

@ -1,11 +1,64 @@
<template>
<v-container>
</v-container>
<v-card
class="mb-4 ml-4 mr-4 mt-4"
>
<v-card-title>
设置
<v-spacer></v-spacer>
<v-icon small>settings</v-icon>
</v-card-title>
<v-card-text>
<v-text-field
label="GitHub Token"
hint="填入GitHub Token"
:value="settings.gistToken"
clearable clear-icon="clear"
/>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn label @click="sync('upload')">上传</v-btn>
<v-btn label @click="sync('download')">下载</v-btn>
</v-card-actions>
<v-divider/>
</v-card>
</template>
<script>
import {axios} from "@/utils";
export default {
name: "User"
data() {
return {
settings: {
gistToken: ""
}
}
},
created() {
axios.get(`/settings`).then(resp => {
this.settings = resp.data;
});
},
methods: {
save() {
axios.patch(`/settings`, this.settings);
},
sync(action) {
if (!this.settings.gistToken) {
this.$store.commit("SET_ERROR_MESSAGE", "未设置GitHub Token");
return;
}
this.save();
axios.get(`/backup?action=${action}`).then(() => {
this.$store.commit("SET_SUCCESS_MESSAGE", `${action === 'upload' ? "备份" : "还原"}成功!`);
}).catch(err => {
this.$store.commit("SET_ERROR_MESSAGE", `备份失败!${err}`);
});
},
}
}
</script>