diff --git a/NGA_BBS_plugins/AutoSyncData/AutoSyncData.user.js b/NGA_BBS_plugins/AutoSyncData/AutoSyncData.user.js new file mode 100644 index 0000000..4e4c9e7 --- /dev/null +++ b/NGA_BBS_plugins/AutoSyncData/AutoSyncData.user.js @@ -0,0 +1,465 @@ +// ==UserScript== +// @name NGA优化摸鱼体验插件-自动同步数据 +// @namespace https://github.com/lifegpc/userscript/tree/master/NGA_BBS_plugins/AutoSyncData +// @version 1.0.0 +// @author lifegpc +// @description 通过WebDAV自动同步数据 +// @license MIT +// @match *://bbs.nga.cn/* +// @match *://ngabbs.com/* +// @match *://nga.178.com/* +// @match *://g.nga.cn/* +// @require https://cdn.staticfile.net/blueimp-md5/2.19.0/js/md5.min.js +// @grant unsafeWindow +// @grant GM_xmlhttpRequest +// @run-at document-start +// @inject-into content +// ==/UserScript== + +(function (registerPlugin) { + 'use strict'; + const AutoSyncData = { + name: 'AutoSyncData', + title: '自动同步数据', + desc: '通过WebDAV自动同步数据', + settings: [{ + key: 'url', + title: 'WebDAV地址', + default: '' + }, { + key: 'username', + title: 'WebDAV账号', + default: '' + }, { + key: 'password', + title: 'WebDAV密码', + default: '' + }, { + key: 'backupKeywordsList', + title: '备份关键词列表', + default: true + }, { + key: 'keywordsListFileName', + title: '关键词列表文件名', + default: 'nga_bbs_script_data_keywords_list.json' + }, { + key: 'backupBanList', + title: '备份黑名单列表', + default: true + }, { + key: 'banListFileName', + title: '黑名单列表文件名', + default: 'nga_bbs_script_data_ban_list.json' + }, { + key: 'backupMarkList', + title: '备份标记名单列表', + default: true + }, { + key: 'markListFileName', + title: '标记名单列表文件名', + default: 'nga_bbs_script_data_mark_list.json' + }, { + key: 'metaFileName', + title: '元数据文件名', + default: 'nga_bbs_script_autosync_meta.json' + }, { + key: 'checkInterval', + title: '检查间隔(单位:分)', + default: 5 + }], + buttons: [{ + title: '检查连接', + action: 'testConnections' + }, { + title: '备份', + action: 'backup' + }, { + title: '还原', + action: 'restore' + }], + lastCheckInfo: {}, + beforeSaveSettingFunc(settings) { + if (settings['checkInterval'] < 0) { + return '检查间隔不能小于0。' + } + if (settings['keywordsListFileName'] == '') { + return '关键词列表文件名不能为空。' + } + if (settings['banListFileName'] == '') { + return '黑名单列表文件名不能为空。' + } + if (settings['markListFileName']) { + return '标记名单列表文件名不能为空。' + } + if (settings['metaFileName']) { + return '元数据文件名不能为空。' + } + }, + initFunc() { + if (typeof this.pluginSettings['backupKeywordsList'] === 'string') { + this.pluginSettings['backupKeywordsList'] = true + } + if (typeof this.pluginSettings['keywordsListFileName'] !== 'string' || this.pluginSettings['keywordsListFileName'] === '') { + this.pluginSettings['keywordsListFileName'] = 'nga_bbs_script_data_keywords_list.json' + } + if (typeof this.pluginSettings['backupBanList'] === 'string') { + this.pluginSettings['backupBanList'] = true + } + if (typeof this.pluginSettings['banListFileName'] !== 'string' || this.pluginSettings['banListFileName'] === '') { + this.pluginSettings['banListFileName'] = 'nga_bbs_script_data_ban_list.json' + } + if (typeof this.pluginSettings['backupMarkList'] === 'string') { + this.pluginSettings['backupMarkList'] = true + } + if (typeof this.pluginSettings['markListFileName'] !== 'string' || this.pluginSettings['markListFileName'] === '') { + this.pluginSettings['markListFileName'] = 'nga_bbs_script_data_mark_list.json' + } + if (typeof this.pluginSettings['metaFileName'] !== 'string' || this.pluginSettings['metaFileName'] === '') { + this.pluginSettings['metaFileName'] = 'nga_bbs_script_autosync_meta.json' + } + if (typeof this.pluginSettings['checkInterval'] !== 'number' || this.pluginSettings['checkInterval'] < 0) { + this.pluginSettings['checkInterval'] = 5 + } + try { + this.lastCheckInfo = JSON.parse(this.mainScript.getValue("lifegpc__AutoSyncData_lastCheckInfo") ?? "{}"); + } catch (e) { + this.printLog('读取上次检查信息失败', 'err'); + console.error(e); + } + }, + postProcFunc() { + if (this.pluginInputs['url'].val()) { + const _this = this; + const handler = async () => { + const lastCheckTime = _this.lastCheckInfo['lastCheckTime']; + let lastCheckInfoChanged = false; + try { + if (typeof lastCheckTime !== 'number' || lastCheckTime + _this.pluginSettings['checkInterval'] * 60 * 1000 < Date.now()) { + const re = await _this.request({ + method: 'GET', + path: _this.pluginSettings['metaFileName'] + }); + if (re == null) { + await _this.backup(true); + return; + } + const latestMeta = JSON.parse(re.responseText); + const localMeta = _this.lastCheckInfo['meta'] || {}; + if (latestMeta.backupBanList) { + if (latestMeta.banListMd5 !== localMeta.banListMd5) { + const filename = latestMeta.banListFileName; + const banListStr = (await _this.request({ + method: 'GET', + path: filename + })).responseText; + const banList = JSON.parse(banListStr); + _this.mainScript.getModule('MarkAndBan').banList = banList; + _this.mainScript.setValue("hld__NGA_ban_list", banListStr); + _this.mainScript.popNotification('黑名单列表已更新'); + lastCheckInfoChanged = true; + } + } + if (latestMeta.backupKeywordsList) { + if (latestMeta.keywordsListMd5 !== localMeta.keywordsListMd5) { + const filename = latestMeta.keywordsListFileName; + const keywordsListStr = (await _this.request({ + method: 'GET', + path: filename + })).responseText; + const keywordsList = JSON.parse(keywordsListStr); + _this.mainScript.getModule('KeywordsBlock').keywordsList = keywordsList; + _this.mainScript.setValue("hld__NGA_keywords_list", keywordsListStr); + _this.mainScript.popNotification('关键词列表已更新'); + lastCheckInfoChanged = true; + } + } + if (latestMeta.backupMarkList) { + if (latestMeta.markListMd5 !== localMeta.markListMd5) { + const filename = latestMeta.markListFileName; + const markListStr = (await _this.request({ + method: 'GET', + path: filename + })).responseText; + const markList = JSON.parse(markListStr); + _this.mainScript.getModule('MarkAndBan').markList = markList; + _this.mainScript.setValue("hld__NGA_mark_list", markListStr); + _this.mainScript.popNotification('标记名单列表已更新'); + lastCheckInfoChanged = true; + } + } + _this.lastCheckInfo['meta'] = latestMeta; + lastCheckInfoChanged = true; + _this.lastCheckInfo['lastCheckTime'] = Date.now(); + } + let metaChanged = false; + if (_this.pluginSettings['backupBanList']) { + const banList = _this.mainScript.getModule('MarkAndBan').banList; + const banListStr = JSON.stringify(banList); + const filename = _this.pluginSettings['banListFileName']; + const md5Sum = md5(banListStr); + const meta = _this.lastCheckInfo['meta'] || {}; + if (md5Sum !== meta['banListMd5']) { + await _this.request({ + method: 'PUT', + path: filename, + data: banListStr + }) + meta['banListMd5'] = md5Sum; + meta['banListModifiedTime'] = Date.now(); + lastCheckInfoChanged = true; + metaChanged = true; + _this.mainScript.popNotification('已备份黑名单列表') + } + } + if (_this.pluginSettings['backupKeywordsList']) { + const keywordsList = _this.mainScript.getModule('KeywordsBlock').keywordsList; + const keywordsListStr = JSON.stringify(keywordsList); + const filename = _this.pluginSettings['keywordsListFileName']; + const md5Sum = md5(keywordsListStr); + const meta = _this.lastCheckInfo['meta'] || {}; + if (md5Sum !== meta['keywordsListMd5']) { + await _this.request({ + method: 'PUT', + path: filename, + data: keywordsListStr + }) + meta['keywordsListMd5'] = md5Sum; + meta['keywordsListModifiedTime'] = Date.now(); + lastCheckInfoChanged = true; + metaChanged = true; + _this.mainScript.popNotification('已备份关键词列表') + } + } + if (_this.pluginSettings['backupMarkList']) { + const markList = _this.mainScript.getModule('MarkAndBan').markList; + const markListStr = JSON.stringify(markList); + const filename = _this.pluginSettings['markListFileName']; + const md5Sum = md5(markListStr); + const meta = _this.lastCheckInfo['meta'] || {}; + if (md5Sum !== meta['markListMd5']) { + await _this.request({ + method: 'PUT', + path: filename, + data: markListStr + }) + meta['markListMd5'] = md5Sum; + meta['markListModifiedTime'] = Date.now(); + lastCheckInfoChanged = true; + metaChanged = true; + _this.mainScript.popNotification('已备份标记名单列表') + } + } + if (metaChanged) { + await _this.request({ + method: 'PUT', + path: _this.pluginSettings['metaFileName'], + data: JSON.stringify(_this.lastCheckInfo['meta']) + }); + _this.mainScript.popNotification('数据已同步'); + } + } catch (e) { + _this.mainScript.printLog('检查连接失败'); + console.log(e); + } finally { + try { + if (lastCheckInfoChanged) { + _this.mainScript.setValue("lifegpc__AutoSyncData_lastCheckInfo", JSON.stringify(_this.lastCheckInfo)); + } + } catch (e) { + _this.mainScript.printLog('保存检查信息失败'); + console.error(e); + } + setTimeout(handler, 1000); + } + } + this.timeout = setTimeout(handler, 1000); + } + }, + // 请求构造 + request({ method, path = '', headers, ...config }) { + // 获取输入框的当前的值 + let url = this.pluginInputs['url'].val().trim() + url[url.length - 1] !== '/' && (url += '/') + const username = this.pluginInputs['username'].val().trim() + const password = this.pluginInputs['password'].val().trim() + const methodDict = { + PROPFIND: 207, + GET: 200, + PUT: 201, + DELETE: 204 + } + this.buttons.forEach(button => button.$el.attr('disabled', true)) + return new Promise((resolve, reject) => { + GM_xmlhttpRequest({ + method, + url: url + path, + headers: { + authorization: 'Basic ' + btoa(`${username}:${password}`), + 'Cache-control': 'no-cache', + ...headers + }, + ...config, + onload: response => { + this.buttons.forEach(button => button.$el.removeAttr('disabled')) + if (response.status === methodDict[method]) { + resolve(response) + } else { + if (method == 'GET' && response.status == 404) { + resolve(null); + return; + } + this.mainScript.popMsg(`WebDAV请求失败! 状态码: ${response.status} ${response.statusText}`, 'err') + } + } + }) + }) + }, + // 获取文件列表 + getFileList() { + return new Promise((resolve, reject) => { + this.request({ + method: 'PROPFIND', + headers: { depth: 1 } + }) + .then(res => { + let files = [] + let path = res.responseText.match(/(?<=).*?(?=<\/d:href>)/gi) + path.forEach(p => { + const filename = p.split('/').pop(); + files.push(filename); + }) + resolve(files) + }) + }) + }, + // 测试连通性 + async testConnections() { + await this.getFileList() + this.mainScript.popMsg('连接成功!同步配置看起来没问题') + }, + // 备份配置 + async backup(saveMeta = false) { + const meta = { + backupBanList: this.pluginSettings['backupBanList'], + backupKeywordsList: this.pluginSettings['backupKeywordsList'], + backupMarkList: this.pluginSettings['backupMarkList'], + banListFileName: this.pluginSettings['banListFileName'], + banListMd5: '', + keywordsListFileName: this.pluginSettings['keywordsListFileName'], + keywordsListMd5: '', + markListFileName: this.pluginSettings['markListFileName'], + markListMd5: '', + backupTime: Date.now(), + }; + const bmeta = this.lastCheckInfo['meta'] || {}; + if (meta.backupBanList) { + const banList = this.mainScript.getModule('MarkAndBan').banList; + const banListStr = JSON.stringify(banList); + const filename = meta.banListFileName; + meta.banListMd5 = md5(banListStr); + meta.banListModifiedTime = bmeta['banListModifiedTime'] || Date.now(); + await this.request({ + method: 'PUT', + path: filename, + data: banListStr + }) + } + if (meta.backupKeywordsList) { + const keywordsList = this.mainScript.getModule('KeywordsBlock').keywordsList; + const keywordsListStr = JSON.stringify(keywordsList); + const filename = meta.keywordsListFileName; + meta.keywordsListMd5 = md5(keywordsListStr); + meta.keywordsListModifiedTime = bmeta['keywordsListModifiedTime'] || Date.now(); + await this.request({ + method: 'PUT', + path: filename, + data: keywordsListStr + }) + } + if (meta.backupMarkList) { + const markList = this.mainScript.getModule('MarkAndBan').markList; + const markListStr = JSON.stringify(markList); + const filename = meta.markListFileName; + meta.markListMd5 = md5(markListStr); + meta.markListModifiedTime = bmeta['markListModifiedTime'] || Date.now(); + await this.request({ + method: 'PUT', + path: filename, + data: markListStr + }) + } + const metaStr = JSON.stringify(meta); + const filename = this.pluginSettings['metaFileName']; + await this.request({ + method: 'PUT', + path: filename, + data: metaStr + }) + this.mainScript.popMsg(`备份成功`) + if (saveMeta) { + this.lastCheckInfo['meta'] = meta; + this.lastCheckInfo['lastCheckTime'] = Date.now(); + this.mainScript.setValue("lifegpc__AutoSyncData_lastCheckInfo", JSON.stringify(this.lastCheckInfo)); + } + }, + async restore() { + const filename = this.pluginSettings['metaFileName']; + const meta = JSON.parse((await this.request({ + method: 'GET', + path: filename + })).responseText); + if (meta.backupBanList) { + const banList = this.mainScript.getModule('MarkAndBan').banList; + const banListStr = JSON.stringify(banList); + if (meta.banListMd5 !== md5(banListStr)) { + const filename = meta.banListFileName; + const banListStr = (await this.request({ + method: 'GET', + path: filename + })).responseText; + const banList = JSON.parse(banListStr); + this.mainScript.getModule('MarkAndBan').banList = banList; + this.mainScript.setValue("hld__NGA_ban_list", banListStr); + this.mainScript.popNotification('黑名单列表已还原'); + } + } + if (meta.backupKeywordsList) { + const keywordsList = this.mainScript.getModule('KeywordsBlock').keywordsList; + const keywordsListStr = JSON.stringify(keywordsList); + if (meta.keywordsListMd5 !== md5(keywordsListStr)) { + const filename = meta.keywordsListFileName; + const keywordsListStr = (await this.request({ + method: 'GET', + path: filename + })).responseText; + const keywordsList = JSON.parse(keywordsListStr); + this.mainScript.getModule('KeywordsBlock').keywordsList = keywordsList; + this.mainScript.setValue("hld__NGA_keywords_list", keywordsListStr); + this.mainScript.popNotification('关键词列表已还原'); + } + } + if (meta.backupMarkList) { + const markList = this.mainScript.getModule('MarkAndBan').markList; + const markListStr = JSON.stringify(markList); + if (meta.markListMd5 !== md5(markListStr)) { + const filename = meta.markListFileName; + const markListStr = (await this.request({ + method: 'GET', + path: filename + })).responseText; + const markList = JSON.parse(markListStr); + this.mainScript.getModule('MarkAndBan').markList = markList; + this.mainScript.setValue("hld__NGA_mark_list", markListStr); + this.mainScript.popNotification('标记名单列表已还原'); + } + } + this.mainScript.popMsg(`还原成功`) + } + } + registerPlugin(AutoSyncData) + +})(function (plugin) { + plugin.meta = GM_info.script + unsafeWindow.ngaScriptPlugins = unsafeWindow.ngaScriptPlugins || [] + unsafeWindow.ngaScriptPlugins.push(plugin) +});