// ==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) => `
${item.size}
`).join(''); dialog.innerHTML = `

选择下载格式

${formatListHTML}
`; 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 = `

WebDAV设置

目录路径应以 / 结尾,留空则为根目录
`; 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(); } })();