// ==UserScript==
// @name Zlib2WebDAV上传助手 发布版本
// @namespace http://tampermonkey.net/
// @version 1.4
// @description 在Z-lib网站的图书详情页 上传图书至WebDAV 可从脚本设置修改WebDAV设置 支持多格式选择
// @author StreamL
// @match https://zlib.by/*
// @match https://zh.zlib.li/*
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant GM_notification
// @grant GM_registerMenuCommand
// ==/UserScript==
(function() {
'use strict';
// 默认配置
const defaultConfig = {
webdav: {
endpoint: ' ',
id: ' ',
password: ' ',
directory: ''
}
};
// 获取配置
function getConfig() {
const saved = GM_getValue('webdav_config');
return saved ? JSON.parse(saved) : defaultConfig;
}
// 保存配置
function saveConfig(config) {
GM_setValue('webdav_config', JSON.stringify(config));
}
// 创建浮动按钮
function createFloatingButton() {
const buttonContainer = document.createElement('div');
buttonContainer.innerHTML = `
WebDAV
`;
document.body.appendChild(buttonContainer);
const btn = document.getElementById('webdav-btn');
// 添加悬停效果
btn.addEventListener('mouseenter', () => {
btn.style.background = '#0056b3';
btn.style.transform = 'translateY(-50%) scale(1.05)';
btn.style.boxShadow = '0 6px 16px rgba(0,123,255,0.4)';
});
btn.addEventListener('mouseleave', () => {
btn.style.background = '#007bff';
btn.style.transform = 'translateY(-50%) scale(1)';
btn.style.boxShadow = '0 4px 12px rgba(0,123,255,0.3)';
});
btn.addEventListener('click', handleButtonClick);
}
// 获取所有可下载的资源
function getAllDownloadLinks() {
const formats = [];
// 方法1:获取主下载按钮(第一个格式)
const mainBtn = document.querySelector('.book-actions-buttons .btn-group a.addDownloadedBook');
if (mainBtn) {
const extElement = mainBtn.querySelector('.book-property__extension');
const sizeText = mainBtn.textContent.trim();
const sizeMatch = sizeText.match(/[\d.]+\s*(KB|MB|GB)/i);
formats.push({
url: mainBtn.href,
format: extElement ? extElement.textContent.trim() : 'unknown',
size: sizeMatch ? sizeMatch[0] : 'unknown'
});
}
// 方法2:获取下拉菜单中的所有格式
const dropdownLinks = document.querySelectorAll('.book-actions-buttons .dropdown-menu li a.addDownloadedBook');
dropdownLinks.forEach(link => {
const extElement = link.querySelector('.book-property__extension');
const sizeElement = link.querySelector('.book-property__size');
const format = extElement ? extElement.textContent.trim() : 'unknown';
const size = sizeElement ? sizeElement.textContent.trim() : 'unknown';
const url = link.href;
// 检查是否已存在(避免重复)
if (!formats.some(f => f.url === url)) {
formats.push({ url, format, size });
}
});
return formats;
}
// 处理按钮点击
function handleButtonClick() {
const availableFormats = getAllDownloadLinks();
if (!availableFormats || availableFormats.length === 0) {
alert('未找到可下载的资源');
return;
}
// 检查是否存在下拉按钮但只找到一个格式
const dropdownBtn = document.querySelector('.book-actions-buttons .btn-group .dropdown-toggle');
if (dropdownBtn && availableFormats.length === 1) {
showExpandHintDialog(availableFormats);
return;
}
// 显示格式选择对话框
showFormatSelectionDialog(availableFormats);
}
// 显示展开提示对话框
function showExpandHintDialog(formats) {
const dialog = document.createElement('div');
dialog.innerHTML = `
⚠️
检测到可扩展的格式列表
当前只检测到 1 个格式,但页面存在展开按钮。
如果需要查看更多格式,请先点击下载按钮旁边的 ┇ (三点图标) 展开所有可用格式。
如果确认只需要当前格式,可以直接继续下载。
`;
document.body.appendChild(dialog);
const cancelBtn = document.getElementById('hint-cancel-btn');
const continueBtn = document.getElementById('hint-continue-btn');
cancelBtn.addEventListener('mouseenter', () => {
cancelBtn.style.borderColor = '#bbb';
cancelBtn.style.color = '#333';
});
cancelBtn.addEventListener('mouseleave', () => {
cancelBtn.style.borderColor = '#ddd';
cancelBtn.style.color = '#666';
});
continueBtn.addEventListener('mouseenter', () => {
continueBtn.style.background = '#0056b3';
});
continueBtn.addEventListener('mouseleave', () => {
continueBtn.style.background = '#007bff';
});
cancelBtn.addEventListener('click', () => {
document.body.removeChild(dialog);
});
continueBtn.addEventListener('click', () => {
document.body.removeChild(dialog);
// 继续显示格式选择对话框(即使只有一个格式)
showFormatSelectionDialog(formats);
});
// 允许点击遮罩层关闭
dialog.querySelector('div:last-child').addEventListener('click', () => {
document.body.removeChild(dialog);
});
}
// 显示格式选择对话框
function showFormatSelectionDialog(formats) {
const dialog = document.createElement('div');
// 生成格式列表HTML
const formatListHTML = formats.map((item, index) => `
`).join('');
dialog.innerHTML = `
`;
document.body.appendChild(dialog);
// 添加格式项点击效果
const formatItems = dialog.querySelectorAll('.format-item');
formatItems.forEach(item => {
item.addEventListener('mouseenter', () => {
item.style.borderColor = '#007bff';
item.style.background = '#f8f9fa';
});
item.addEventListener('mouseleave', () => {
const radio = item.querySelector('input[type="radio"]');
if (!radio.checked) {
item.style.borderColor = '#e1e5e9';
item.style.background = 'white';
}
});
item.addEventListener('click', () => {
const radio = item.querySelector('input[type="radio"]');
radio.checked = true;
// 更新所有项的样式
formatItems.forEach(otherItem => {
const otherRadio = otherItem.querySelector('input[type="radio"]');
if (otherRadio.checked) {
otherItem.style.borderColor = '#007bff';
otherItem.style.background = '#e7f3ff';
} else {
otherItem.style.borderColor = '#e1e5e9';
otherItem.style.background = 'white';
}
});
});
});
// 默认选中第一个
if (formats.length > 0) {
const firstRadio = dialog.querySelector('input[type="radio"]');
const firstItem = dialog.querySelector('.format-item');
firstRadio.checked = true;
firstItem.style.borderColor = '#007bff';
firstItem.style.background = '#e7f3ff';
}
// 按钮事件
const cancelBtn = document.getElementById('cancel-format-btn');
const downloadBtn = document.getElementById('download-format-btn');
cancelBtn.addEventListener('mouseenter', () => {
cancelBtn.style.borderColor = '#bbb';
cancelBtn.style.color = '#333';
});
cancelBtn.addEventListener('mouseleave', () => {
cancelBtn.style.borderColor = '#ddd';
cancelBtn.style.color = '#666';
});
downloadBtn.addEventListener('mouseenter', () => {
downloadBtn.style.background = '#0056b3';
});
downloadBtn.addEventListener('mouseleave', () => {
downloadBtn.style.background = '#007bff';
});
cancelBtn.addEventListener('click', () => {
document.body.removeChild(dialog);
});
downloadBtn.addEventListener('click', () => {
const selectedRadio = dialog.querySelector('input[name="format-select"]:checked');
if (!selectedRadio) {
showNotification('请选择一个格式', 'error');
return;
}
const selectedIndex = parseInt(selectedRadio.value);
const selectedFormat = formats[selectedIndex];
document.body.removeChild(dialog);
downloadAndUpload(selectedFormat.url, selectedFormat.format);
});
}
// 下载并上传文件
function downloadAndUpload(downloadUrl, formatType) {
// 设置按钮为等待状态
setButtonLoading(true);
// 显示进度提示
showNotification(`正在下载 ${formatType.toUpperCase()} 格式...`);
// 发送请求获取文件
GM_xmlhttpRequest({
method: 'GET',
url: downloadUrl,
responseType: 'blob',
onload: function(response) {
setButtonLoading(false);
handleDownloadResponse(response, formatType);
},
onerror: function(error) {
setButtonLoading(false);
showNotification('下载失败: ' + error.statusText, 'error');
}
});
}
// 处理下载响应
function handleDownloadResponse(response, formatType = '') {
if (response.status === 200) {
// 尝试从响应头获取文件名
const contentDisposition = response.responseHeaders.match(/filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/);
let filename = '';
if (contentDisposition) {
filename = contentDisposition[1].replace(/['"]/g, '');
// 解码可能的URL编码
try {
filename = decodeURIComponent(filename);
} catch(e) {
// 如果解码失败,保持原始文件名
}
}
// 如果无法从响应头获取文件名,尝试从页面获取书名
if (!filename) {
const titleElement = document.querySelector('h1[itemprop="name"]');
const bookTitle = titleElement ? titleElement.textContent.trim() : 'book';
filename = `${bookTitle}.${formatType}`;
}
// 检查文件扩展名
const validExtensions = ['txt', 'epub', 'mobi', 'azw3', 'pdf', 'fb2', 'rtf', 'djvu', 'doc', 'docx'];
const fileExt = filename.split('.').pop().toLowerCase();
if (!validExtensions.includes(fileExt)) {
showNotification('无法获取有效的电子书文件', 'error');
return;
}
// 显示确认对话框
showConfirmDialog(filename, response.response);
} else {
showNotification('无法获取文件内容', 'error');
}
}
// 显示确认对话框
function showConfirmDialog(filename, fileBlob) {
const dialog = document.createElement('div');
dialog.innerHTML = `
确认上传
文件名: ${filename}
`;
document.body.appendChild(dialog);
// 添加按钮悬停效果
const cancelBtn = document.getElementById('cancel-btn');
const confirmBtn = document.getElementById('confirm-btn');
cancelBtn.addEventListener('mouseenter', () => {
cancelBtn.style.borderColor = '#bbb';
cancelBtn.style.color = '#333';
});
cancelBtn.addEventListener('mouseleave', () => {
cancelBtn.style.borderColor = '#ddd';
cancelBtn.style.color = '#666';
});
confirmBtn.addEventListener('mouseenter', () => {
confirmBtn.style.background = '#0056b3';
});
confirmBtn.addEventListener('mouseleave', () => {
confirmBtn.style.background = '#007bff';
});
// 绑定事件
cancelBtn.addEventListener('click', () => {
document.body.removeChild(dialog);
});
confirmBtn.addEventListener('click', () => {
document.body.removeChild(dialog);
uploadToWebDAV(filename, fileBlob);
});
}
// 上传到WebDAV
function uploadToWebDAV(filename, fileBlob) {
const config = getConfig();
const webdav = config.webdav;
const uploadUrl = webdav.endpoint + webdav.directory + filename;
// 设置按钮为等待状态
setButtonLoading(true);
showNotification('正在上传文件...');
GM_xmlhttpRequest({
method: 'PUT',
url: uploadUrl,
data: fileBlob,
headers: {
'Authorization': 'Basic ' + btoa(webdav.id + ':' + webdav.password)
},
onload: function(response) {
setButtonLoading(false);
if (response.status >= 200 && response.status < 300) {
showNotification('文件已成功上传到WebDAV', 'success');
} else {
showNotification('上传失败: ' + response.statusText, 'error');
}
},
onerror: function(error) {
setButtonLoading(false);
showNotification('上传错误: ' + error.statusText, 'error');
}
});
}
// 显示通知
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
const bgColor = type === 'success' ? '#28a745' : type === 'error' ? '#dc3545' : '#007bff';
notification.innerHTML = `
${message}
`;
document.body.appendChild(notification);
// 3秒后自动移除
setTimeout(() => {
if (notification.parentNode) {
document.body.removeChild(notification);
}
}, 3000);
}
// 显示设置对话框
function showSettingsDialog() {
const config = getConfig();
const dialog = document.createElement('div');
dialog.innerHTML = `
`;
document.body.appendChild(dialog);
// 添加输入框焦点效果
const inputs = dialog.querySelectorAll('input, textarea');
inputs.forEach(input => {
input.addEventListener('focus', () => {
input.style.borderColor = '#007bff';
input.style.boxShadow = '0 0 0 3px rgba(0,123,255,0.1)';
});
input.addEventListener('blur', () => {
input.style.borderColor = '#e1e5e9';
input.style.boxShadow = 'none';
});
});
// 绑定事件
document.getElementById('cancel-settings-btn').addEventListener('click', () => {
document.body.removeChild(dialog);
});
document.getElementById('save-settings-btn').addEventListener('click', () => {
saveSettings(dialog);
});
document.getElementById('reset-settings-btn').addEventListener('click', () => {
if (confirm('确定要重置所有设置到默认值吗?')) {
resetSettings(dialog);
}
});
}
// 保存设置
function saveSettings(dialog) {
const endpoint = document.getElementById('endpoint-input').value.trim();
const username = document.getElementById('username-input').value.trim();
const password = document.getElementById('password-input').value;
let directory = document.getElementById('directory-input').value.trim();
// 确保目录路径以 / 结尾(如果不为空)
if (directory && !directory.endsWith('/')) {
directory += '/';
}
if (!endpoint || !username || !password) {
showNotification('请填写完整的WebDAV配置', 'error');
return;
}
const newConfig = {
webdav: {
endpoint: endpoint,
id: username,
password: password,
directory: directory
}
};
saveConfig(newConfig);
showNotification('设置已保存', 'success');
document.body.removeChild(dialog);
}
// 重置设置
function resetSettings(dialog) {
document.getElementById('endpoint-input').value = defaultConfig.webdav.endpoint;
document.getElementById('username-input').value = defaultConfig.webdav.id;
document.getElementById('password-input').value = defaultConfig.webdav.password;
document.getElementById('directory-input').value = defaultConfig.webdav.directory;
showNotification('设置已重置为默认值', 'info');
}
// 测试WebDAV连接
function testWebDAVConnection() {
const endpoint = document.getElementById('endpoint-input').value.trim();
const username = document.getElementById('username-input').value.trim();
const password = document.getElementById('password-input').value;
if (!endpoint || !username || !password) {
showNotification('请先填写完整的WebDAV配置', 'error');
return;
}
showNotification('正在测试连接...', 'info');
GM_xmlhttpRequest({
method: 'PROPFIND',
url: endpoint,
headers: {
'Authorization': 'Basic ' + btoa(username + ':' + password),
'Depth': '0'
},
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
showNotification('连接测试成功!', 'success');
} else {
showNotification(`连接失败: ${response.status} ${response.statusText}`, 'error');
}
},
onerror: function(error) {
showNotification('连接测试失败: ' + error.statusText, 'error');
}
});
}
// 设置按钮加载状态
function setButtonLoading(isLoading) {
const btn = document.getElementById('webdav-btn');
if (!btn) return;
if (isLoading) {
btn.innerHTML = '处理中...';
btn.style.background = '#6c757d';
btn.style.cursor = 'not-allowed';
btn.style.opacity = '0.7';
btn.disabled = true;
// 移除悬停效果
btn.onmouseenter = null;
btn.onmouseleave = null;
} else {
btn.innerHTML = 'WebDAV';
btn.style.background = '#007bff';
btn.style.cursor = 'pointer';
btn.style.opacity = '1';
btn.disabled = false;
// 重新添加悬停效果
btn.addEventListener('mouseenter', () => {
if (!btn.disabled) {
btn.style.background = '#0056b3';
btn.style.transform = 'translateY(-50%) scale(1.05)';
btn.style.boxShadow = '0 6px 16px rgba(0,123,255,0.4)';
}
});
btn.addEventListener('mouseleave', () => {
if (!btn.disabled) {
btn.style.background = '#007bff';
btn.style.transform = 'translateY(-50%) scale(1)';
btn.style.boxShadow = '0 4px 12px rgba(0,123,255,0.3)';
}
});
}
}
// 初始化
function init() {
createFloatingButton();
// 注册脚本菜单命令
GM_registerMenuCommand('WebDAV配置', showSettingsDialog);
}
// 页面加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();