// ==UserScript==
// @name Gooboo存档同步(WebDAV)
// @version 1.1
// @description 游戏存档WebDAV 上传和下载,适用于使用 localStorage 存储存档的网页游戏。
// @author zding
// @match *://*/gooboo/
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @run-at document-start
// @license MIT
// @namespace https://greasyfork.org/users/762887
// ==/UserScript==
(function() {
'use strict';
// 默认值
const defaultUrl = 'https://bora.teracloud.jp/dav'; //示例用的是infini-cloud.net的免费webdav,可以注册验证后,在个人页面的Refferal Code填写邀请码GR52W获得额外的5g
const defaultUsername = 'Username';
const defaultPassword = 'password';
const defaultBackupCount = 5;
const defaultShowFloatingTopButton = true;
const defaultShowFloatingMenu = true;
const defaultBlacklistedKeyPatterns = [/Hm_lvt/i, /_ga/i, /_gid/i, /HMACCOUNT/i, /Hm_lpvt/i];
// 定义需要屏蔽的 localStorage key 关键词,使用正则表达式,忽略大小写(屏蔽这个的原因是因为有些手机浏览器会有如百度联盟的影响项,而电脑没有,会导致两者同步内容不一致。)
const domain = window.location.hostname;
const webdavDirectory = domain + '_saves'; // WebDAV 上的目录,模式使用当前域名_saves
// 从 GM_getValue 中读取设置,如果不存在则使用默认值
let url = GM_getValue('url', defaultUrl);
let username = GM_getValue('username', defaultUsername);
let password = GM_getValue('password', defaultPassword);
let backupCount = GM_getValue('backupCount', defaultBackupCount);
let showFloatingTopButton = GM_getValue('showFloatingTopButton', defaultShowFloatingTopButton);
showFloatingTopButton = (typeof showFloatingTopButton === 'string') ? (showFloatingTopButton === 'true') : showFloatingTopButton;
let showFloatingMenu = GM_getValue('showFloatingMenu', defaultShowFloatingMenu);
showFloatingMenu = (typeof showFloatingMenu === 'string') ? (showFloatingMenu === 'true') : showFloatingMenu;
let blacklistedKeyPatterns = GM_getValue('blacklistedKeyPatterns', defaultBlacklistedKeyPatterns.map(regex => regex.source));
blacklistedKeyPatterns = blacklistedKeyPatterns.map(source => new RegExp(source, 'i'));
GM_addStyle(`
.custom-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.2);
backdrop-filter: blur(5px);
z-index: 1000;
animation: fadeIn 0.3s ease-in-out;
}
.custom-modal-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(255, 255, 255, 0.8);
padding: 20px;
border-radius: 12px;
text-align: center;
width: 70%;
max-width: 450px;
}
.custom-modal-button {
background-color: #66b3ff;
border: none;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 6px 3px;
cursor: pointer;
border-radius: 8px;
transition: background-color 0.3s ease;
}
.custom-modal-button:hover {
background-color: #3385ff;
}
.custom-select {
width: 100%;
padding: 10px;
margin: 6px 0;
border: 1px solid #ccc;
border-radius: 6px;
box-sizing: border-box;
font-size: 14px;
}
.custom-select:focus {
outline: none;
border-color: #66b3ff;
box-shadow: 0 0 5px rgba(102, 179, 255, 0.5);
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
/* 顶部按钮栏样式 */
#topButtonContainer {
position: fixed;
top: -1000px;
left: 0;
width: 100%;
background: linear-gradient(to right, rgba(255, 255, 255, 0) 30%, rgba(255, 255, 255, 0.8) 50%, rgba(255, 255, 255, 0) 70%);
padding: 10px;
text-align: center;
z-index: 9999;
transition: top 0.3s ease;
border: none;
display: flex;
justify-content: center;
align-items: center;
}
#topButtonContainer.show {
top: 0;
}
#topButtonContainer button {
padding: 6px 10px;
border: none;
background-color: #66b3ff;
color: white;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.3s ease;
margin: 0 3px;
font-size: 17px;
}
#topButtonContainer button:hover {
background-color: #3385ff;
}
@media (max-width: 768px) {
#topButtonContainer {
padding: 5px;
}
#topButtonContainer button {
padding: 4px 8px;
margin: 0 2px;
font-size: 12px;
}
}
/* 向下箭头样式 */
#arrowIndicator {
position: fixed;
top: 0;
left: 50%;
transform: translateX(-50%);
font-size: 20px;
color: rgba(0, 0, 0, 0.3);
z-index: 10001;
cursor: pointer;
height: 30px;
line-height: 30px;
}
#arrowIndicator.hidden {
opacity: 0;
}
/* 悬浮按钮样式 */
#floatingButtonContainer {
position: fixed;
left: 10px;
bottom: 10px;
z-index: 5000;
display: flex;
flex-direction: column;
align-items: flex-start;
transform: scale(0.8);
}
#floatingButtonContainer button {
padding: 6px 10px;
border: none;
background-color: #66b3ff;
color: white;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.3s ease;
margin: 3px 0;
width: auto;
display: flex;
align-items: center;
font-size: 14px;
}
#floatingButtonContainer button:hover {
background-color: #3385ff;
}
#floatingButtonContainer button img {
width: 16px;
height: 16px;
margin-right: 4px;
}
`);
function isValidLocalStorageKey(key) {
if (!key) {
return false;
}
const trimmedKey = key.trim();
if (trimmedKey === "") {
return false;
}
for (const pattern of blacklistedKeyPatterns) {
if (pattern.test(key)) {
return false;
}
}
return true;
}
// 弹窗
function createModal(message, content, onConfirm, onCancel, showCancel) {
const modal = document.createElement('div');
modal.className = 'custom-modal';
const modalContent = document.createElement('div');
modalContent.className = 'custom-modal-content';
const messageElement = document.createElement('p');
messageElement.textContent = message;
modalContent.appendChild(messageElement);
if (content) {
modalContent.appendChild(content);
}
const confirmButton = document.createElement('button');
confirmButton.className = 'custom-modal-button';
confirmButton.textContent = '确定';
confirmButton.addEventListener('click', () => {
modal.style.display = 'none';
if (onConfirm) {
onConfirm();
}
});
modalContent.appendChild(confirmButton);
if (showCancel) {
const cancelButton = document.createElement('button');
cancelButton.className = 'custom-modal-button';
cancelButton.textContent = '取消';
cancelButton.addEventListener('click', () => {
modal.style.display = 'none';
if (onCancel) {
onCancel();
}
});
modalContent.appendChild(cancelButton);
}
modal.appendChild(modalContent);
document.body.appendChild(modal);
return modal;
}
// 显示弹窗
function showModal(message, content, onConfirm, onCancel, showCancel = false) {
const modal = createModal(message, content, onConfirm, onCancel, showCancel);
modal.style.display = 'block';
}
// 请求构造
function request({method, path='', headers, data}) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: method,
url: url + '/' + webdavDirectory + '/' + path,
headers: {
authorization: 'Basic ' + btoa(`${username}:${password}`),
...headers
},
data: data,
onload: response => {
if (response.status >= 200 && response.status < 300) {
resolve(response);
} else {
console.error(`WebDAV 请求失败!状态码: ${response.status} ${response.statusText}`);
reject(response);
}
},
onerror: (error) => {
console.error("GM_xmlhttpRequest error:", error);
reject(error);
}
});
});
}
// 获取文件列表
async function getFileList() {
try {
const res = await request({
method: 'PROPFIND',
headers: {depth: 1}
});
let files = [];
let path = res.responseText.match(/(?<=<d:href>).*?(?=<\/d:href>)/gi);
if (path) {
path.forEach(p => {
const filename = p.split('/').pop();
if (filename && filename.endsWith('.txt')) {
files.push(filename);
}
});
}
files.sort().reverse();
return files;
} catch (error) {
console.error("Error getting file list:", error);
return [];
}
}
// 删除文件
function deleteFile(filename) {
return request({
method: 'DELETE',
path: filename
}).then(() => {
console.log(`Deleted file: ${filename}`);
}).catch(error => {
console.error(`Error deleting file: ${filename}`, error);
throw error;
});
}
// 清理旧存档
async function cleanupOldBackups() {
try {
for (let i = 0; i < localStorage.length; i++) {
const localStorageKey = localStorage.key(i);
const filenamePrefix = localStorageKey + "_";
const files = await getFileList();
const relevantFiles = files.filter(file => file.startsWith(filenamePrefix));
relevantFiles.sort().reverse();
const filesToDelete = relevantFiles.slice(backupCount);
console.log(`需要删除的旧存档数量 (${localStorageKey}): ${filesToDelete.length}`);
for (const filename of filesToDelete) {
await deleteFile(filename);
}
if (filesToDelete.length > 0) {
// showModal(`成功删除 ${filesToDelete.length} 个旧存档。`);
} else {
console.log(`没有需要删除的旧存档 (${localStorageKey})。`);
}
}
} catch (error) {
console.error("清理旧存档出错:", error);
showModal("清理旧存档出错! 请检查控制台。");
}
}
// 下载配置
function downloadConfig(localStorageKey, filename) {
return request({
method: 'GET',
path: filename
}).then(res => {
const configData = res.responseText;
localStorage.setItem(localStorageKey, configData);
return configData;
}).catch(error => {
console.error("Error downloading config:", error);
throw error;
});
}
// 导入所有配置
async function downloadLatestConfig() {
try {
const matchingDates = await getMatchingDates();
if (matchingDates.length === 0) {
showModal("没有找到匹配的存档日期!");
return;
}
const latestDate = matchingDates.sort((a, b) => {
const dateA = new Date(a.replace(/_/g, '-')); // 将 _ 替换为 -,以便 Date 对象可以正确解析
const dateB = new Date(b.replace(/_/g, '-'));
return dateB - dateA;
})[0];
if (!latestDate) {
showModal("没有找到最新的存档日期!");
return;
}
const failedDownloads = [];
const downloadedKeys = [];
for (let i = 0; i < localStorage.length; i++) {
const localStorageKey = localStorage.key(i);
if (!localStorageKey) {
continue;
}
if (isValidLocalStorageKey(localStorageKey)) {
const filename = localStorageKey + "_" + latestDate + ".txt";
try {
const res = await request({
method: 'GET',
path: filename
});
const configData = res.responseText;
localStorage.setItem(localStorageKey, configData);
console.log(`配置文件 [${filename}] 下载成功并保存到 localStorageKey [${localStorageKey}]!`);
downloadedKeys.push(localStorageKey);
} catch (error) {
console.warn(`下载配置文件 [${filename}] 出错 (localStorageKey: ${localStorageKey}):`, error);
failedDownloads.push(filename);
}
}
}
if (failedDownloads.length > 0) {
showModal(`以下配置文件下载失败: ${failedDownloads.join(', ')}。请检查控制台。`, null, () => {
window.location.reload();
});
} else if (downloadedKeys.length > 0) {
showModal(`${latestDate} 存档下载成功!`, null, () => {
window.location.reload();
});
} else {
showModal("没有找到可下载的存档!", null, () => {
window.location.reload();
});
}
} catch (error) {
console.error("下载最新配置文件出错:", error);
showModal("下载最新配置文件出错! 请检查控制台。");
}
}
function getCurrentDate() {
const now = new Date();
const utc = now.getTime() + (now.getTimezoneOffset() * 60000);
const chinaTime = new Date(utc + (3600000 * 8));
const year = chinaTime.getFullYear();
const month = (chinaTime.getMonth() + 1).toString().padStart(2, '0');
const day = chinaTime.getDate().toString().padStart(2, '0');
const hours = chinaTime.getHours().toString().padStart(2, '0');
const minutes = chinaTime.getMinutes().toString().padStart(2, '0');
const seconds = chinaTime.getSeconds().toString().padStart(2, '0');
return `${year}_${month}_${day}_${hours}_${minutes}_${seconds}`;
}
// 创建目录
function createDirectory() {
return request({
method: 'PROPFIND',
headers: {depth: 0}
}).catch(error => {
if (error && error.status === 404) {
console.log("目录不存在,尝试创建它。");
return request({
method: 'MKCOL'
}).then(() => {
console.log(`创建目录: ${webdavDirectory}`);
}).catch(createError => {
console.error(`创建目录出错: ${webdavDirectory}`, createError);
throw createError;
});
} else {
console.error("PROPFIND 请求失败:", error);
throw error;
}
});
}
// 上传存档
async function uploadSave() {
try {
try {
await createDirectory();
} catch (createError) {
console.error("创建目录失败:", createError);
if (createError.status === 401 || createError.status === 403) {
showModal("创建目录失败: 账户密码错误或无权限。请检查你的 WebDAV 账户和密码。");
} else if (createError.status === 0) {
showModal("创建目录失败: 无法连接到 WebDAV 服务器。请检查网络连接或服务器地址。");
} else {
showModal(`创建目录失败: 服务器错误 (状态码 ${createError.status})。请检查服务器状态。`);
}
return;
}
const currentDate = getCurrentDate();
for (let i = 0; i < localStorage.length; i++) {
const localStorageKey = localStorage.key(i);
if (isValidLocalStorageKey(localStorageKey)) {
const filename = localStorageKey + "_" + currentDate + ".txt";
const data = localStorage.getItem(localStorageKey);
if (!data) {
console.warn(`localStorageKey [${localStorageKey}] 没有数据!`);
continue;
}
try {
await request({
method: 'PUT',
path: filename,
data: data,
headers: {
'Content-Type': 'text/plain'
}
});
console.log(`游戏存档文件 [${filename}] 上传成功 (localStorageKey: ${localStorageKey})。`);
} catch (uploadError) {
console.error(`上传文件 ${filename} 失败:`, uploadError);
if (uploadError.status === 401 || uploadError.status === 403) {
showModal(`上传文件 ${filename} 失败: 账户密码错误或无权限。请检查你的 WebDAV 账户和密码。`);
return;
} else if (uploadError.status === 0) {
showModal(`上传文件 ${filename} 失败: 无法连接到 WebDAV 服务器。请检查网络连接或服务器地址。`);
return;
} else {
showModal(`上传文件 ${filename} 失败: 服务器错误 (状态码 ${uploadError.status})。请检查服务器状态。`);
return;
}
}
}
}
showModal("所有存档上传完成!", null, async () => {
await cleanupOldBackups();
});
} catch (error) {
console.error("上传游戏存档文件出错:", error);
showModal("上传游戏存档文件出错! 请检查控制台。");
}
}
// 获取所有匹配的日期
async function getMatchingDates() {
const localStorageKeys = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (isValidLocalStorageKey(key)) {
localStorageKeys.push(key);
}
}
const files = await getFileList();
console.log("getFileList() 返回的文件列表:", files);
const dateToKeys = {};
files.forEach(file => {
const parts = file.split('_');
if (parts.length > 6) {
const key = parts.slice(0, parts.length - 6).join('_');
let date = parts.slice(parts.length - 6).slice(0, 6).join('_');
if (date.endsWith(".txt")) {
date = date.slice(0, -4);
}
if (!dateToKeys[date]) {
dateToKeys[date] = new Set();
}
dateToKeys[date].add(key);
}
});
console.log("dateToKeys:", dateToKeys);
console.log("localStorageKeys:", localStorageKeys);
const matchingDates = Object.entries(dateToKeys)
.filter(([date, keys]) => {
const allKeysPresent = localStorageKeys.every(localStorageKey => keys.has(localStorageKey));
return allKeysPresent;
})
.map(([date, ]) => date);
return matchingDates;
}
// 下载指定日期的配置
async function downloadSpecificConfig() {
const matchingDates = await getMatchingDates();
if (matchingDates.length === 0) {
showModal("没有找到匹配的存档日期!");
return;
}
const select = document.createElement('select');
select.className = 'custom-select';
matchingDates.forEach(date => {
const option = document.createElement('option');
option.value = date;
option.textContent = date;
select.appendChild(option);
});
showModal("选择要下载的存档日期:", select, async () => {
const selectedDate = select.value;
const failedDownloads = [];
const downloadedKeys = [];
try {
for (let i = 0; i < localStorage.length; i++) {
const localStorageKey = localStorage.key(i);
if (isValidLocalStorageKey(localStorageKey)) {
if (!localStorageKey) {
continue;
}
const filename = localStorageKey + "_" + selectedDate + ".txt";
try {
const res = await request({
method: 'GET',
path: filename
});
const configData = res.responseText;
localStorage.setItem(localStorageKey, configData);
console.log(`配置文件 [${filename}] 下载成功并保存到 localStorageKey [${localStorageKey}]!`);
downloadedKeys.push(localStorageKey);
} catch (error) {
console.warn(`下载配置文件 [${filename}] 出错 (localStorageKey: ${localStorageKey}):`, error);
failedDownloads.push(filename);
}
}
}
if (failedDownloads.length > 0) {
showModal(`以下配置文件下载失败: ${failedDownloads.join(', ')}。请检查控制台。`, null, () => {
window.location.reload();
});
} else if (downloadedKeys.length > 0) {
showModal(`${selectedDate} 存档下载成功!`, null, () => {
window.location.reload();
});
} else {
showModal("没有找到可下载的存档!", null, () => {
window.location.reload();
});
}
} catch (error) {
console.error("下载指定日期的配置文件出错:", error);
showModal("下载指定日期的配置文件出错! 请检查控制台。");
}
}, () => {}, true);
}
// 初始化
function initialize() {
console.log("WebDAV 初始化完成。");
unsafeWindow.webdavDemo = {
downloadLatest: downloadLatestConfig,
list: getFileList,
upload: uploadSave,
downloadSpecific: downloadSpecificConfig
};
}
initialize();
let topButtonContainer;
let arrowIndicator;
function createTopButtonContainer() {
topButtonContainer = document.createElement('div');
topButtonContainer.id = 'topButtonContainer';
const downloadLatestButton = document.createElement('button');
downloadLatestButton.textContent = '下载最新配置';
downloadLatestButton.addEventListener('click', downloadLatestConfig);
topButtonContainer.appendChild(downloadLatestButton);
const uploadButton = document.createElement('button');
uploadButton.textContent = '上传所有存档';
uploadButton.addEventListener('click', uploadSave);
topButtonContainer.appendChild(uploadButton);
const downloadSpecificButton = document.createElement('button');
downloadSpecificButton.textContent = '下载指定配置';
downloadSpecificButton.addEventListener('click', downloadSpecificConfig);
topButtonContainer.appendChild(downloadSpecificButton);
document.body.insertBefore(topButtonContainer, document.body.firstChild);
topButtonContainer.addEventListener('mouseenter', () => {
topButtonContainer.classList.add('show');
arrowIndicator.classList.add('hidden'); // 鼠标进入按钮栏时隐藏箭头
});
topButtonContainer.addEventListener('mouseleave', () => {
topButtonContainer.classList.remove('show');
arrowIndicator.classList.remove('hidden');
});
}
function createArrowIndicator() {
arrowIndicator = document.createElement('div');
arrowIndicator.id = 'arrowIndicator';
arrowIndicator.textContent = '▼';
document.body.insertBefore(arrowIndicator, document.body.firstChild);
arrowIndicator.addEventListener('mouseenter', () => {
topButtonContainer.classList.add('show');
arrowIndicator.classList.add('hidden');
});
arrowIndicator.addEventListener('mouseleave', () => {
topButtonContainer.classList.remove('show');
arrowIndicator.classList.remove('hidden');
});
arrowIndicator.addEventListener('touchstart', (event) => {
event.preventDefault();
topButtonContainer.classList.toggle('show');
arrowIndicator.classList.toggle('hidden');
if (topButtonContainer.classList.contains('show')) {
document.addEventListener('touchstart', hideOnOutsideClick);
}
});
}
function hideOnOutsideClick(event) {
if (!topButtonContainer.contains(event.target) && event.target !== arrowIndicator) {
topButtonContainer.classList.remove('show');
arrowIndicator.classList.remove('hidden');
document.removeEventListener('touchstart', hideOnOutsideClick);
}
}
let floatingButtonContainer;
function createFloatingButton() {
floatingButtonContainer = document.createElement('div');
floatingButtonContainer.id = 'floatingButtonContainer';
// 上传按钮
const uploadButton = document.createElement('button');
uploadButton.addEventListener('click', uploadSave);
uploadButton.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width: 20px; height: 20px; margin-right: 5px;">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75V16.5m-13.5-9L12 3m0 0l4.5 4.5M12 3v13.5" />
</svg>
<span>上传</span>
`;
floatingButtonContainer.appendChild(uploadButton);
// 下载最新配置按钮
const downloadLatestButton = document.createElement('button');
downloadLatestButton.addEventListener('click', downloadLatestConfig);
downloadLatestButton.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width: 20px; height: 20px; margin-right: 5px;">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
</svg>
<span>最新</span>
`;
floatingButtonContainer.appendChild(downloadLatestButton);
// 下载指定配置按钮
const downloadSpecificButton = document.createElement('button');
downloadSpecificButton.addEventListener('click', downloadSpecificConfig);
downloadSpecificButton.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" style="width: 20px; height: 20px; margin-right: 5px;">
<path stroke-linecap="round" stroke-linejoin="round" d="M7.5 7.5h-.75A2.25 2.25 0 0 0 4.5 9.75v7.5a2.25 2.25 0 0 0 2.25 2.25h7.5a2.25 2.25 0 0 0 2.25-2.25v-7.5a2.25 2.25 0 0 0-2.25-2.25h-.75m-6 3.75 3 3m0 0 3-3m-3 3V1.5m6 9h.75a2.25 2.25 0 0 1 2.25 2.25v7.5a2.25 2.25 0 0 1-2.25 2.25h-7.5a2.25 2.25 0 0 1-2.25-2.25v-.75" />
</svg>
<span>指定</span>
`;
floatingButtonContainer.appendChild(downloadSpecificButton);
document.body.insertBefore(floatingButtonContainer, document.body.firstChild);
}
// 保存设置函数
function promptAndSave(key, message, defaultValue, type = 'text') {
let promptMessage = message + ' (默认为: ' + defaultValue + ')';
let value = prompt(promptMessage, GM_getValue(key, defaultValue));
if (value !== null) {
if (type === 'number') {
value = parseInt(value, 10);
if (isNaN(value)) {
alert('无效的数字,请重新设置。');
return;
}
} else if (type === 'boolean') {
value = (value.toLowerCase() === 'true');
} else if (type === 'regex') {
try {
new RegExp(value); // 验证正则表达式
} catch (e) {
alert('无效的正则表达式,请重新设置。');
return;
}
}
GM_setValue(key, value);
// 根据key更新变量的值
if (key === 'url') {
url = value;
} else if (key === 'username') {
username = value;
} else if (key === 'password') {
password = value;
} else if (key === 'backupCount') {
backupCount = value;
} else if (key === 'showFloatingTopButton') {
showFloatingTopButton = value;
} else if (key === 'showFloatingMenu') {
showFloatingMenu = value;
} else if (key === 'blacklistedKeyPatterns') {
try {
blacklistedKeyPatterns = value.split(',').map(s => s.trim()).map(source => new RegExp(source, 'i'));
GM_setValue(key, value.split(',').map(s => s.trim())); // 保存为字符串数组
} catch (e) {
alert("无效的正则表达式列表,请使用逗号分隔。");
return;
}
}
alert(message + ' 已保存: ' + value);
}
}
window.addEventListener('load', () => {
if (showFloatingTopButton) {
createTopButtonContainer();
createArrowIndicator();
}
if (showFloatingMenu) {
createFloatingButton();
}
});
GM_registerMenuCommand("上传所有存档", uploadSave);
GM_registerMenuCommand("下载最新配置", downloadLatestConfig);
GM_registerMenuCommand("下载指定配置", downloadSpecificConfig);
GM_registerMenuCommand("设置 WebDAV URL", () => promptAndSave('url', '请输入 WebDAV URL', defaultUrl));
GM_registerMenuCommand("设置 WebDAV 用户名", () => promptAndSave('username', '请输入 WebDAV 用户名', defaultUsername));
GM_registerMenuCommand("设置 WebDAV 密码", () => promptAndSave('password', '请输入 WebDAV 密码', defaultPassword));
GM_registerMenuCommand("设置 备份数量", () => promptAndSave('backupCount', '请输入 备份数量', defaultBackupCount, 'number'));
GM_registerMenuCommand("设置 显示顶部悬浮按钮", () => promptAndSave('showFloatingTopButton', '是否显示顶部悬浮按钮 (true/false)', defaultShowFloatingTopButton, 'boolean'));
GM_registerMenuCommand("设置 显示悬浮菜单", () => promptAndSave('showFloatingMenu', '是否显示悬浮菜单 (true/false)', defaultShowFloatingMenu, 'boolean'));
GM_registerMenuCommand("设置 屏蔽的 Key 关键词 (逗号分隔)", () => {
const defaultPatternsString = defaultBlacklistedKeyPatterns.map(regex => regex.source).join(', ');
promptAndSave('blacklistedKeyPatterns', '请输入需要屏蔽的 localStorage Key 关键词 (使用逗号分隔, 忽略大小写)', defaultPatternsString, 'regex');
});
})();