mirror of
				https://git.mirrors.martin98.com/https://github.com/sub-store-org/Sub-Store.git
				synced 2025-10-31 16:11:05 +08:00 
			
		
		
		
	支持配置Gist同步
This commit is contained in:
		
							parent
							
								
									d30ece21ae
								
							
						
					
					
						commit
						3b57b895e2
					
				| @ -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 = [], | ||||
|  | ||||
| @ -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); | ||||
|       }); | ||||
|  | ||||
| @ -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> | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Peng-YM
						Peng-YM