// ==UserScript==
// @name Gooboo存档同步(WebDAV)
// @version 1.2
// @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://dav.jianguoyun.com/dav'; //示例用的是坚果云的免费webdav
const defaultUsername = 'Username';
const defaultPassword = 'password';
const defaultBackupCount = 5;
const domain = window.location.hostname;
const gamesavesDirectory = 'Gamesaves'
const webdavDirectory = domain + '_saves'; // WebDAV 上的目录,模式使用当前域名_saves
const defaultShowFloatingPanel = true;
const defaultKeysToBackup = 'goobooSavefile'; //备份的key名称
let url = GM_getValue('url', defaultUrl);
if (url.endsWith('/')) {
url = url.slice(0, -1);
}
let username = GM_getValue('username', defaultUsername);
let password = GM_getValue('password', defaultPassword);
let backupCount = GM_getValue('backupCount', defaultBackupCount);
let showFloatingPanel = GM_getValue('showFloatingPanel', defaultShowFloatingPanel);
showFloatingPanel = (typeof showFloatingPanel === 'string') ? (showFloatingPanel === 'true') : showFloatingPanel;
let keysToBackup = GM_getValue('keysToBackup', defaultKeysToBackup);
let webdavInitialized = false;
class UI {
constructor() {
this.createSettingsPage();
this.addStyles();
}
addStyles() {
GM_addStyle(`
.gooboo-ui-container {
font-family: 'Arial', sans-serif;
color: #333;
}
.gooboo-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
display: flex;
justify-content: center;
align-items: center;
}
.gooboo-modal-content {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
width: 70%;
max-width: 350px;
}
.gooboo-modal-message {
margin-bottom: 15px;
font-size: 16px;
line-height: 1.5;
text-align: center;
}
.gooboo-modal-button-container {
display: flex;
justify-content: center;
gap: 10px;
}
.gooboo-modal-button {
padding: 10px 20px;
background-color: #007bff; /* 蓝色主色调 */
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s ease;
}
.gooboo-modal-button:hover {
background-color: #0056b3; /* 鼠标悬停时的颜色 */
}
.gooboo-modal-button.cancel {
background-color: #6c757d
}
.gooboo-modal-button.cancel:hover {
background-color: #5a6268;
}
.gooboo-settings-page {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1001;
display: none;
justify-content: center;
align-items: center;
}
.gooboo-settings-content {
background-color: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
width: 80%;
max-width: 600px;
}
.gooboo-settings-label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
.gooboo-settings-input {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
font-size: 14px;
}
.gooboo-settings-button-container {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
.gooboo-settings-button {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.3s ease;
}
.gooboo-settings-button:hover {
background-color: #0056b3;
}
.gooboo-settings-button.cancel {
background-color: #6c757d;
}
.gooboo-settings-button.cancel:hover {
background-color: #5a6268;
}
.gooboo-switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
.gooboo-switch input {
opacity: 0;
width: 0;
height: 0;
}
.gooboo-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
}
.gooboo-slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
}
input:checked + .gooboo-slider {
background-color: #2196F3;
}
input:focus + .gooboo-slider {
box-shadow: 0 0 1px #2196F3;
}
input:checked + .gooboo-slider:before {
transform: translateX(26px);
}
/* Rounded sliders */
.gooboo-slider.round {
border-radius: 34px;
}
.gooboo-slider.round:before {
border-radius: 50%;
}
.gooboo-floating-panel {
position: fixed;
left: 0;
top: 50%;
transform: translateY(-50%);
z-index: 999;
display: flex;
align-items: center;
}
.gooboo-floating-arrow {
width: 30px;
height: 30px;
color: #007bff;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0 5px 5px 0;
font-size: 1.2em;
}
.gooboo-floating-menu {
display: none;
flex-direction: column;
background-color: rgba(255, 255, 255, 0.3);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
border-radius: 5px;
padding: 10px;
backdrop-filter: blur(5px);
}
.gooboo-floating-menu button {
padding: 8px 15px;
margin: 5px 0;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
display: flex;
align-items: center;
}
.gooboo-floating-menu button:hover {
background-color: #076ddc;
}
.gooboo-floating-menu.active {
display: flex;
}
@media (max-width: 600px) {
.gooboo-settings-content {
width: 95%;
}
}
.gooboo-modal-content select {
width: 100%;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
body {
min-height: 100vh;
}
`);
}
createSettingsPage() {
this.settingsPage = document.createElement('div');
this.settingsPage.className = 'gooboo-settings-page';
this.settingsContent = document.createElement('div');
this.settingsContent.className = 'gooboo-settings-content';
this.urlLabel = document.createElement('label');
this.urlLabel.className = 'gooboo-settings-label';
this.urlLabel.textContent = 'WebDAV URL:';
this.urlInput = document.createElement('input');
this.urlInput.className = 'gooboo-settings-input';
this.urlInput.type = 'text';
this.urlInput.value = url;
this.usernameLabel = document.createElement('label');
this.usernameLabel.className = 'gooboo-settings-label';
this.usernameLabel.textContent = 'WebDAV 用户名:';
this.usernameInput = document.createElement('input');
this.usernameInput.className = 'gooboo-settings-input';
this.usernameInput.type = 'text';
this.usernameInput.value = username;
this.passwordLabel = document.createElement('label');
this.passwordLabel.className = 'gooboo-settings-label';
this.passwordLabel.textContent = 'WebDAV 密码:';
this.passwordInput = document.createElement('input');
this.passwordInput.className = 'gooboo-settings-input';
this.passwordInput.type = 'password';
this.passwordInput.value = password;
this.backupCountLabel = document.createElement('label');
this.backupCountLabel.className = 'gooboo-settings-label';
this.backupCountLabel.textContent = '备份数量:';
this.backupCountInput = document.createElement('input');
this.backupCountInput.className = 'gooboo-settings-input';
this.backupCountInput.type = 'number';
this.backupCountInput.value = backupCount;
this.keysToBackupLabel = document.createElement('label');
this.keysToBackupLabel.className = 'gooboo-settings-label';
this.keysToBackupLabel.textContent = '要备份的 Key 名 (空格分隔):';
this.keysToBackupInput = document.createElement('input');
this.keysToBackupInput.className = 'gooboo-settings-input';
this.keysToBackupInput.type = 'text';
this.keysToBackupInput.value = keysToBackup;
// 显示悬浮面板 开关
this.showFloatingPanelLabel = document.createElement('label');
this.showFloatingPanelLabel.className = 'gooboo-settings-label';
this.showFloatingPanelLabel.textContent = '显示悬浮面板:';
this.showFloatingPanelSwitch = document.createElement('label');
this.showFloatingPanelSwitch.className = 'gooboo-switch';
this.showFloatingPanelInput = document.createElement('input');
this.showFloatingPanelInput.type = 'checkbox';
this.showFloatingPanelInput.checked = showFloatingPanel;
this.showFloatingPanelSlider = document.createElement('span');
this.showFloatingPanelSlider.className = 'gooboo-slider round';
this.showFloatingPanelSwitch.appendChild(this.showFloatingPanelInput);
this.showFloatingPanelSwitch.appendChild(this.showFloatingPanelSlider);
this.saveButton = document.createElement('button');
this.saveButton.className = 'gooboo-settings-button';
this.saveButton.textContent = '保存';
this.saveButton.addEventListener('click', () => this.saveSettings());
this.cancelButton = document.createElement('button');
this.cancelButton.className = 'gooboo-settings-button cancel';
this.cancelButton.textContent = '取消';
this.cancelButton.addEventListener('click', () => this.closeSettingsPage());
this.settingsContent.appendChild(this.urlLabel);
this.settingsContent.appendChild(this.urlInput);
this.settingsContent.appendChild(this.usernameLabel);
this.settingsContent.appendChild(this.usernameInput);
this.settingsContent.appendChild(this.passwordLabel);
this.settingsContent.appendChild(this.passwordInput);
this.settingsContent.appendChild(this.backupCountLabel);
this.settingsContent.appendChild(this.backupCountInput);
this.settingsContent.appendChild(this.keysToBackupLabel);
this.settingsContent.appendChild(this.keysToBackupInput);
this.settingsContent.appendChild(this.showFloatingPanelLabel);
this.settingsContent.appendChild(this.showFloatingPanelSwitch);
const buttonContainer = document.createElement('div');
buttonContainer.style.display = 'flex';
buttonContainer.style.justifyContent = 'center';
buttonContainer.style.marginTop = '10px';
buttonContainer.appendChild(this.saveButton);
buttonContainer.appendChild(this.cancelButton);
this.settingsContent.appendChild(buttonContainer);
this.settingsPage.appendChild(this.settingsContent);
document.body.appendChild(this.settingsPage);
}
saveSettings() {
url = this.urlInput.value;
if (url.endsWith('/')) {
url = url.slice(0, -1);
}
username = this.usernameInput.value;
password = this.passwordInput.value;
backupCount = parseInt(this.backupCountInput.value, 10);
showFloatingPanel = this.showFloatingPanelInput.checked;
keysToBackup = this.keysToBackupInput.value;
GM_setValue('url', url);
GM_setValue('username', username);
GM_setValue('password', password);
GM_setValue('backupCount', backupCount);
GM_setValue('keysToBackup', keysToBackup);
GM_setValue('showFloatingPanel', showFloatingPanel);
webdavClient.url = url;
webdavClient.username = username;
webdavClient.password = password;
this.closeSettingsPage();
this.showAlert('设置已保存');
webdavInitialized = false;
initializeWebDAV();
this.reloadFloatingElements();
}
reloadFloatingElements() {
if (floatingPanelContainer) {
floatingPanelContainer.remove();
floatingPanelContainer = null;
}
if (showFloatingPanel) {
createFloatingPanel();
}
}
closeSettingsPage() {
this.settingsPage.style.display = 'none';
}
showSettingsPage() {
this.settingsPage.style.display = 'flex';
}
showAlert(message) {
alert(message);
}
showModal(message, content, onConfirm, onCancel, showCancel = false) {
const modal = document.createElement('div');
modal.className = 'gooboo-modal';
modal.style.display = 'flex';
const modalContent = document.createElement('div');
modalContent.className = 'gooboo-modal-content';
const messageElement = document.createElement('p');
messageElement.className = 'gooboo-modal-message';
messageElement.textContent = message;
modalContent.appendChild(messageElement);
if (content) {
modalContent.appendChild(content);
}
const buttonContainer = document.createElement('div');
buttonContainer.className = 'gooboo-modal-button-container';
const confirmButton = document.createElement('button');
confirmButton.className = 'gooboo-modal-button';
confirmButton.textContent = '确定';
confirmButton.addEventListener('click', () => {
modal.style.display = 'none';
if (onConfirm) {
onConfirm();
}
modal.remove();
});
buttonContainer.appendChild(confirmButton);
if (showCancel) {
const cancelButton = document.createElement('button');
cancelButton.className = 'gooboo-modal-button cancel';
cancelButton.textContent = '取消';
cancelButton.addEventListener('click', () => {
modal.style.display = 'none';
if (onCancel) {
onCancel();
}
modal.remove();
});
buttonContainer.appendChild(cancelButton);
}
modalContent.appendChild(buttonContainer);
modal.appendChild(modalContent);
document.body.appendChild(modal);
}
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;
}
}
const ui = new UI();
class WebDAVClient {
constructor(url, username, password, webdavDirectory, gamesavesDirectory) {
this.url = url;
this.username = username;
this.password = password;
this.webdavDirectory = webdavDirectory;
this.gamesavesDirectory = gamesavesDirectory;
this.directoriesEnsured = false;
}
request({method, path='', headers, data}) {
return new Promise((resolve, reject) => {
const fullPath = this.url + '/' + this.gamesavesDirectory + '/' + this.webdavDirectory + '/' + path;
GM_xmlhttpRequest({
method: method,
url: fullPath,
headers: {
authorization: 'Basic ' + btoa(`${this.username}:${this.password}`),
...headers
},
data: data,
onload: response => {
if (response.status >= 200 && response.status < 300) {
resolve(response);
} else {
console.error(`WebDAV 请求失败!状态码: ${response.status} ${response.statusText},URL: ${fullPath}`);
reject({
status: response.status,
message: `WebDAV 请求失败!状态码: ${response.status} ${response.statusText},URL: ${fullPath}`
});
}
},
onerror: (error) => {
console.error("GM_xmlhttpRequest error:", error);
reject({
status: 0,
message: "GM_xmlhttpRequest error:" + error
});
}
});
});
}
async checkDirectoryExists(directory) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'PROPFIND',
url: this.url + '/' + directory,
headers: {
authorization: 'Basic ' + btoa(`${this.username}:${this.password}`),
'Depth': 0
},
onload: function(response) {
if (response.status === 207) {
resolve(true); // 目录存在
} else if (response.status === 404) {
resolve(false); // 目录不存在
} else {
console.error(`PROPFIND 失败,状态码: ${response.status}`);
reject({
status: response.status,
message: `PROPFIND 失败,状态码: ${response.status}`
});
}
},
onerror: function(error) {
console.error('PROPFIND 请求失败:', error);
reject({
status: 0,
message: 'PROPFIND 请求失败:' + error
});
}
});
});
}
createDirectory(directory) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'MKCOL',
url: this.url + '/' + directory,
headers: {
authorization: 'Basic ' + btoa(`${this.username}:${this.password}`)
},
onload: function(response) {
if (response.status === 201) {
console.log(`目录 ${directory} 创建成功`);
resolve();
} else {
console.error(`创建目录 ${directory} 失败,状态码: ${response.status}`);
reject({
status: response.status,
message: `创建目录 ${directory} 失败,状态码: ${response.status}`
});
}
},
onerror: function(error) {
console.error(`创建目录 ${directory} 请求失败:`, error);
reject({
status: 0,
message: `创建目录 ${directory} 请求失败:` + error
});
}
});
});
}
async ensureDirectoriesExist() {
if (this.directoriesEnsured) return;
try {
const gamesavesExists = await this.checkDirectoryExists(this.gamesavesDirectory);
if (!gamesavesExists) {
await this.createDirectory(this.gamesavesDirectory);
}
const webdavDirFullPath = this.gamesavesDirectory + '/' + this.webdavDirectory;
const webdavDirectoryExists = await this.checkDirectoryExists(webdavDirFullPath);
if (!webdavDirectoryExists) {
await this.createDirectory(webdavDirFullPath);
}
this.directoriesEnsured = true;
} catch (error) {
console.error("创建目录结构出错:", error);
throw error;
}
}
async getFileList() {
try {
const res = await this.request({
method: 'PROPFIND',
headers: {depth: 1}
});
let files = [];
let path = res.responseText.match(/(?<=<d:href>).*?(?=<\/d:href>)/gi);
if (path) {
path.forEach(p => {
const prefix = '/dav/' + this.gamesavesDirectory + '/' + this.webdavDirectory + "/";
if (p.startsWith(prefix)) {
const filename = p.substring(prefix.length);
if (filename && filename.endsWith('.json')) {
files.push(filename);
}
}
});
}
files.sort().reverse();
return files;
} catch (error) {
console.error("Error getting file list:", error);
return [];
}
}
deleteFile(filename) {
return this.request({
method: 'DELETE',
path: filename
}).then(() => {
console.log(`Deleted file: ${filename}`);
}).catch(error => {
console.error(`Error deleting file: ${filename}`, error);
throw error;
});
}
async uploadFile(filename, data) {
const jsonData = JSON.stringify(data);
try {
await this.request({
method: 'PUT',
path: filename,
data: jsonData,
headers: {
'Content-Type': 'application/json'
}
});
} catch (uploadError) {
console.error(`上传文件 ${filename} 失败:`, uploadError);
throw uploadError;
}
}
downloadFile(filename) {
return this.request({
method: 'GET',
path: filename
}).then(res => {
try {
return JSON.parse(res.responseText);
} catch (error) {
console.error("JSON 解析失败:", error);
throw error;
}
}).catch(error => {
console.error("Error downloading config:", error);
throw error;
});
}
handleWebDAVError(error, filename) {
let message;
if (error.status === 401 || error.status === 403) {
message = `操作失败: 账户密码错误或无权限。请检查 WebDAV 用户名和密码是否正确。`;
} else if (error.status === 0) {
message = `操作失败: 无法连接到 WebDAV 服务器。请检查网络连接或 WebDAV URL 是否正确(当前: ${this.url})。`;
} else {
message = `操作失败: 服务器返回错误 (状态码: ${error.status})。请检查 WebDAV 服务状态或稍后重试。`;
}
if (filename) {
message += `\n涉及文件: ${filename}`;
}
ui.showModal(message);
}
}
const webdavClient = new WebDAVClient(url, username, password, webdavDirectory, gamesavesDirectory);
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;
}
async function uploadSave() {
if (!webdavInitialized) {
initializeWebDAV();
}
try {
const currentDate = getCurrentDate();
const filename = "gamesave_" + currentDate + ".json";
const saveData = {};
const keysToBackupList = keysToBackup.split(' ').filter(key => key.trim() !== '');
await new Promise(resolve => {
setTimeout(() => {
keysToBackupList.forEach(key => {
const value = localStorage.getItem(key);
if (value !== null) {
saveData[key] = value;
}
});
resolve();
}, 0);
});
const data = JSON.stringify(saveData);
await webdavClient.uploadFile(filename, data);
console.log(`游戏存档文件 [${filename}] 上传成功。`);
ui.showModal("指定存档上传完成!", null, async () => {
await cleanupOldBackups();
});
} catch (error) {
console.error("上传游戏存档文件出错:", error);
ui.showModal(`上传游戏存档文件出错: ${error.message || '未知错误'}。`);
}
}
async function getMatchingDates() {
const files = await webdavClient.getFileList();
const dateRegex = /gamesave_(\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2})\.json/;
const matchingDates = files.map(file => {
const match = file.match(dateRegex);
return match ? match[1] : null;
}).filter(date => date !== null);
return matchingDates;
}
async function downloadLatestConfig() {
if (!webdavInitialized) {
initializeWebDAV();
}
try {
const matchingDates = await getMatchingDates();
if (matchingDates.length === 0) {
ui.showModal("没有找到匹配的存档日期!");
return;
}
const latestDate = matchingDates.sort((a, b) => {
const dateA = new Date(a.replace(/_/g, '-'));
const dateB = new Date(b.replace(/_/g, '-'));
return dateB - dateA;
})[0];
if (!latestDate) {
ui.showModal("没有找到最新的存档日期!");
return;
}
const filename = "gamesave_" + latestDate + ".json";
try {
const configData = await webdavClient.downloadFile(filename);
await setLocalStorageFromJSON(configData);
ui.showModal(`${latestDate} 存档下载成功!`, null, () => {
window.location.reload();
});
} catch (error) {
console.warn(`下载配置文件 [${filename}] 出错:`, error);
webdavClient.handleWebDAVError(error, filename);
}
} catch (error) {
console.error("下载最新配置文件出错:", error);
ui.showModal("下载最新配置文件出错! 请检查控制台。");
}
}
async function downloadSpecificConfig() {
if (!webdavInitialized) {
initializeWebDAV();
}
const matchingDates = await getMatchingDates();
if (matchingDates.length === 0) {
ui.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);
});
ui.showModal("选择要下载的存档日期:", select, async () => {
const selectedDate = select.value;
const filename = "gamesave_" + selectedDate + ".json";
try {
const configData = await webdavClient.downloadFile(filename);
await setLocalStorageFromJSON(configData);
ui.showModal(`${selectedDate} 存档下载成功!`, null, () => {
window.location.reload();
});
} catch (error) {
console.warn(`下载配置文件 [${filename}] 出错:`, error);
webdavClient.handleWebDAVError(error, filename);
}
}, () => {}, true);
}
async function cleanupOldBackups() {
try {
const files = await webdavClient.getFileList();
const dateRegex = /gamesave_(\d{4}_\d{2}_\d{2}_\d{2}_\d{2}_\d{2})\.json/;
const saveFiles = files.filter(file => dateRegex.test(file));
saveFiles.sort().reverse();
const filesToDelete = saveFiles.slice(backupCount);
console.log(`需要删除的旧存档数量: ${filesToDelete.length}`);
console.log(filesToDelete);
for (const filename of filesToDelete) {
try {
await webdavClient.deleteFile(filename);
} catch (error) {
console.warn(`删除文件 [${filename}] 出错:`, error);
webdavClient.handleWebDAVError(error, filename);
}
}
if (filesToDelete.length > 0) {
} else {
console.log(`没有需要删除的旧存档。`);
}
} catch (error) {
console.error("清理旧存档出错:", error);
ui.showModal("清理旧存档出错! 请检查控制台。");
}
}
async function setLocalStorageFromJSON(configData) {
try {
for (const key in configData) {
if (configData.hasOwnProperty(key)) {
localStorage.setItem(key, configData[key]);
}
}
} catch (error) {
console.error("设置 localStorage 失败:", error);
throw error;
}
}
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 initializeWebDAV() {
if (webdavInitialized) {
console.log("WebDAV 已经初始化过,跳过。");
return;
}
webdavClient.ensureDirectoriesExist().then(() => {
console.log("WebDAV 目录初始化完成");
function initialize() {
console.log("WebDAV 初始化完成。");
unsafeWindow.WebDAV = {
downloadLatest: downloadLatestConfig,
list: webdavClient.getFileList,
upload: uploadSave,
downloadSpecific: downloadSpecificConfig
};
}
initialize();
webdavInitialized = true; // 设置标志为已初始化
}).catch(error => {
ui.showModal("WebDAV 目录初始化失败: " + error.message);
});
}
let floatingPanelContainer;
function createFloatingPanel() {
floatingPanelContainer = document.createElement('div');
floatingPanelContainer.className = 'gooboo-floating-panel';
const arrowButton = document.createElement('button');
arrowButton.className = 'gooboo-floating-arrow';
arrowButton.innerHTML = '▶';
const menuContainer = document.createElement('div');
menuContainer.className = 'gooboo-floating-menu';
const uploadButton = document.createElement('button');
uploadButton.textContent = '上传';
uploadButton.addEventListener('click', uploadSave);
menuContainer.appendChild(uploadButton);
const latestButton = document.createElement('button');
latestButton.textContent = '最新';
latestButton.addEventListener('click', downloadLatestConfig);
menuContainer.appendChild(latestButton);
const listButton = document.createElement('button');
listButton.textContent = '列表';
listButton.addEventListener('click', downloadSpecificConfig);
menuContainer.appendChild(listButton);
const settingsButton = document.createElement('button');
settingsButton.textContent = '设置';
settingsButton.addEventListener('click', () => {
ui.showSettingsPage();
});
menuContainer.appendChild(settingsButton);
floatingPanelContainer.appendChild(menuContainer);
floatingPanelContainer.appendChild(arrowButton);
document.body.appendChild(floatingPanelContainer);
arrowButton.addEventListener('click', toggleMenu);
arrowButton.addEventListener('touchstart', (e) => {
e.preventDefault(); // 防止触摸事件触发点击
toggleMenu();
}, { passive: false });
function toggleMenu() {
const isActive = menuContainer.classList.toggle('active');
if (isActive) {
document.addEventListener('click', closeMenuOnOutside);
document.addEventListener('touchstart', closeMenuOnOutside);
} else {
document.removeEventListener('click', closeMenuOnOutside);
document.removeEventListener('touchstart', closeMenuOnOutside);
}
}
function closeMenuOnOutside(event) {
if (!floatingPanelContainer.contains(event.target)) {
menuContainer.classList.remove('active');
document.removeEventListener('click', closeMenuOnOutside);
document.removeEventListener('touchstart', closeMenuOnOutside);
}
}
}
window.addEventListener('load', () => {
if (showFloatingPanel) {
createFloatingPanel();
}
});
GM_registerMenuCommand("Gooboo 设置", () => {
ui.showSettingsPage();
});
GM_registerMenuCommand("上传所有存档", uploadSave);
GM_registerMenuCommand("下载最新配置", downloadLatestConfig);
GM_registerMenuCommand("下载指定配置", downloadSpecificConfig);
})();