Greasy Fork is available in English.
夸克懒得点.. 修复误判,精准屏蔽,自动记录日志,支持 WebDAV 同步,支持检测重复链接
// ==UserScript==
// @name 夸克懒得点 (WebDAV同步+防重复版)
// @namespace http://greasyfork.icu/users/158417
// @version 0.36
// @description 夸克懒得点.. 修复误判,精准屏蔽,自动记录日志,支持 WebDAV 同步,支持检测重复链接
// @author JIEMO
// @match *://pan.quark.cn/*
// @icon https://pan.quark.cn/favicon.ico
// @license GPL-3.0 License
// @run-at document-end
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_setClipboard
// @grant GM_xmlhttpRequest
// @connect *
// ==/UserScript==
(function() {
'use strict';
// ================= 配置区域 =================
const STORAGE_KEY = "blocked_users_v2"; // 屏蔽列表键名
const LOG_KEY = "auto_save_logs"; // 日志存储键名
const WEBDAV_CONF_KEY = "webdav_config"; // WebDAV配置
const MAX_LOGS = 300; // 最大保留日志条数
const CLOUD_FILE_NAME = "quark_script_data.json"; // 云端文件名
const DEFAULT_BLOCKED = [];
// ===========================================
// ============================================================
// 1. 基础工具 & WebDAV 模块
// ============================================================
function getBlockedList() { return GM_getValue(STORAGE_KEY, DEFAULT_BLOCKED); }
function setBlockedList(list) { GM_setValue(STORAGE_KEY, list); }
function getLogs() { return GM_getValue(LOG_KEY, []); }
function setLogs(list) { GM_setValue(LOG_KEY, list); }
function formatTime(date) {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
const h = String(date.getHours()).padStart(2, '0');
const min = String(date.getMinutes()).padStart(2, '0');
const s = String(date.getSeconds()).padStart(2, '0');
return `${y}-${m}-${d} ${h}:${min}:${s}`;
}
function computeStringHash(str) {
if (!str) return "null";
let hash = 0;
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash |= 0;
}
return "u" + Math.abs(hash);
}
// --- WebDAV 核心逻辑 ---
const WebDAV = {
getConfig: () => GM_getValue(WEBDAV_CONF_KEY, { url: "", user: "", pass: "" }),
setConfig: (conf) => GM_setValue(WEBDAV_CONF_KEY, conf),
pull: function(callback) {
const conf = this.getConfig();
if (!conf.url) { if(callback) callback(); return; }
console.log("[夸克懒得点] 正在从云端拉取数据...");
const fileUrl = conf.url.endsWith('/') ? conf.url + CLOUD_FILE_NAME : conf.url + '/' + CLOUD_FILE_NAME;
GM_xmlhttpRequest({
method: "GET",
url: fileUrl,
user: conf.user,
password: conf.pass,
headers: { "Cache-Control": "no-cache" }, // 防止缓存
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
try {
const cloudData = JSON.parse(response.responseText);
WebDAV.mergeData(cloudData);
console.log("[夸克懒得点] ✅ 云端同步成功");
} catch (e) {
console.error("[夸克懒得点] 解析云端数据失败", e);
}
} else if (response.status === 404) {
console.log("[夸克懒得点] 云端文件不存在,将在下次保存时创建");
} else {
console.error(`[夸克懒得点] 拉取失败: ${response.status} ${response.statusText}`);
}
if(callback) callback();
},
onerror: function(err) {
console.error("[夸克懒得点] 网络请求错误 (Pull)", err);
if(callback) callback();
}
});
},
push: function() {
const conf = this.getConfig();
if (!conf.url) return;
const data = {
blocked: getBlockedList(),
logs: getLogs(),
updated: new Date().getTime()
};
const fileUrl = conf.url.endsWith('/') ? conf.url + CLOUD_FILE_NAME : conf.url + '/' + CLOUD_FILE_NAME;
console.log("[夸克懒得点] 正在上传数据到云端...", fileUrl);
GM_xmlhttpRequest({
method: "PUT",
url: fileUrl,
user: conf.user,
password: conf.pass,
data: JSON.stringify(data),
headers: {
"Content-Type": "application/json;charset=UTF-8" // 明确告诉服务器这是JSON
},
onload: function(response) {
if (response.status >= 200 && response.status < 300) {
console.log("[夸克懒得点] ✅ 上传成功");
} else {
console.error(`[夸克懒得点] ❌ 上传失败: ${response.status} ${response.statusText}`);
// 提示用户检查 F12
if(response.status === 401) alert("WebDAV同步失败:账号或密码错误 (401)");
if(response.status === 403) alert("WebDAV同步失败:权限不足 (403)");
if(response.status === 405) alert("WebDAV同步失败:服务器不支持 PUT 方法 (405)");
}
},
onerror: function(err) {
console.error("[夸克懒得点] 网络请求错误 (Push)", err);
alert("WebDAV 连接失败,请检查 F12 控制台错误信息");
}
});
},
mergeData: function(cloudData) {
if (!cloudData) return;
// 1. 合并屏蔽列表
let localBlocked = getBlockedList();
const localHashes = new Set(localBlocked.map(u => u.hash));
let hasChange = false;
if (cloudData.blocked && Array.isArray(cloudData.blocked)) {
cloudData.blocked.forEach(u => {
if (!localHashes.has(u.hash)) {
localBlocked.push(u);
hasChange = true;
}
});
}
if (hasChange) setBlockedList(localBlocked);
// 2. 合并日志
let localLogs = getLogs();
if (cloudData.logs && Array.isArray(cloudData.logs)) {
const uniqueSet = new Set(localLogs.map(l => l.url));
cloudData.logs.forEach(l => {
if (!uniqueSet.has(l.url)) {
localLogs.push(l);
uniqueSet.add(l.url);
}
});
localLogs.sort((a, b) => new Date(b.time) - new Date(a.time));
if (localLogs.length > MAX_LOGS) localLogs = localLogs.slice(0, MAX_LOGS);
setLogs(localLogs);
}
}
};
// ============================================================
// 2. 核心逻辑:提取信息
// ============================================================
function getTargetSharerInfo() {
const shareContainer = document.querySelector('.share-info-wrap');
if (!shareContainer) return null;
const imgElement = shareContainer.querySelector('img');
if (!imgElement || !imgElement.src) return null;
const hashID = computeStringHash(imgElement.src);
const nameElement = shareContainer.querySelector('.author-name');
let nickName = "Unknown";
if (nameElement) {
nickName = nameElement.innerText.trim();
} else {
const possibleNames = shareContainer.querySelectorAll('div');
if(possibleNames.length > 1) {
nickName = possibleNames[1].innerText.trim();
}
}
return { name: nickName, hash: hashID };
}
function getFileTitle() {
const titleEl = document.querySelector('.filename-text');
if (titleEl) {
return titleEl.getAttribute('title') || titleEl.innerText.trim();
}
return document.title.replace(' - 夸克网盘', '') || "未知标题";
}
// ============================================================
// 3. 日志记录逻辑
// ============================================================
function recordLog(user) {
const fileName = getFileTitle();
const currentTime = formatTime(new Date());
const currentUrl = window.location.href;
let logs = getLogs().filter(l => l.url !== currentUrl);
const newLog = {
time: currentTime,
name: user.name,
hash: user.hash,
title: fileName,
url: currentUrl
};
logs.unshift(newLog);
if (logs.length > MAX_LOGS) logs = logs.slice(0, MAX_LOGS);
setLogs(logs);
console.log(`[夸克懒得点] 日志已记录: ${fileName}`);
WebDAV.push();
}
// ============================================================
// 4. UI 交互
// ============================================================
function showBlockedOverlay(user) {
var overlay = document.createElement('div');
Object.assign(overlay.style, {
position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.95)', zIndex: '999999',
display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column'
});
var text = document.createElement('h1');
text.innerText = "⚠️ 已屏蔽该分享者";
text.style.cssText = "color: red; font-size: 60px; font-weight: bold; text-shadow: 2px 2px 10px black; margin: 0;";
var subText = document.createElement('div');
subText.innerHTML = `<p style='font-size:24px; color:white'>昵称:<span style='color:#ff6a00'>${user.name}</span></p>`;
var unlockBtn = document.createElement('button');
unlockBtn.innerText = "本次临时允许";
unlockBtn.style.cssText = "margin-top: 30px; padding: 10px 20px; cursor: pointer; background: #333; color: #fff; border: 1px solid #666;";
unlockBtn.onclick = function() { overlay.remove(); };
overlay.appendChild(text);
overlay.appendChild(subText);
overlay.appendChild(unlockBtn);
document.body.appendChild(overlay);
}
function showDuplicateOverlay(log, callback) {
var overlay = document.createElement('div');
Object.assign(overlay.style, {
position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.90)', zIndex: '999999',
display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column'
});
var text = document.createElement('h1');
text.innerText = "🔁 此链接已保存过";
text.style.cssText = "color: #FFD700; font-size: 50px; font-weight: bold; text-shadow: 2px 2px 5px black; margin: 0;";
var infoDiv = document.createElement('div');
infoDiv.style.cssText = "margin-top:20px; color: #ddd; text-align:center; font-size: 16px; line-height: 1.6;";
infoDiv.innerHTML = `
<p>上次保存时间: <span style="color:white; font-weight:bold">${log.time}</span></p>
<p>文件标题: ${log.title}</p>
`;
var btnContainer = document.createElement('div');
btnContainer.style.marginTop = "40px";
var cancelBtn = document.createElement('button');
cancelBtn.innerText = "我知道了 (关闭页面)";
cancelBtn.style.cssText = "padding: 10px 20px; cursor: pointer; background: #444; color: #fff; border: none; border-radius: 4px; margin-right: 20px;";
cancelBtn.onclick = function() { window.close(); overlay.remove(); };
var forceBtn = document.createElement('button');
forceBtn.innerText = "强制再次保存";
forceBtn.style.cssText = "padding: 10px 20px; cursor: pointer; background: #007bff; color: #fff; border: none; border-radius: 4px;";
forceBtn.onclick = function() {
overlay.remove();
if (callback) callback();
};
btnContainer.appendChild(cancelBtn);
btnContainer.appendChild(forceBtn);
overlay.appendChild(text);
overlay.appendChild(infoDiv);
overlay.appendChild(btnContainer);
document.body.appendChild(overlay);
}
function showLogViewer() {
const logs = getLogs();
let logText = "时间\t\t\t昵称\t\t文件标题\t\t\t网址\n";
logText += "--------------------------------------------------------------------------------------\n";
logs.forEach(log => {
logText += `[${log.time}] ${log.name} >>> ${log.title} >>> ${log.url}\n`;
});
var overlay = document.createElement('div');
Object.assign(overlay.style, {
position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
backgroundColor: 'rgba(0,0,0,0.8)', zIndex: '999999',
display: 'flex', justifyContent: 'center', alignItems: 'center'
});
var box = document.createElement('div');
Object.assign(box.style, {
width: '85%', height: '85%', backgroundColor: '#fff', borderRadius: '8px',
padding: '20px', display: 'flex', flexDirection: 'column', color: '#333'
});
var title = document.createElement('h2');
title.innerText = `📜 保存记录 (共 ${logs.length} 条)`;
title.style.margin = '0 0 10px 0';
var textarea = document.createElement('textarea');
textarea.value = logText;
Object.assign(textarea.style, {
flex: '1', width: '100%', fontSize: '12px', fontFamily: 'monospace',
whiteSpace: 'pre', overflow: 'auto', border: '1px solid #ccc', padding: '10px'
});
var btnContainer = document.createElement('div');
btnContainer.style.marginTop = '10px';
btnContainer.style.textAlign = 'right';
var copyBtn = document.createElement('button');
copyBtn.innerText = "复制";
copyBtn.style.marginRight = "10px";
copyBtn.onclick = function() { GM_setClipboard(logText); alert("✅ 已复制!"); };
var forceSyncBtn = document.createElement('button');
forceSyncBtn.innerText = "☁️ 立即同步";
forceSyncBtn.style.marginRight = "10px";
forceSyncBtn.style.color = "blue";
forceSyncBtn.onclick = function() {
WebDAV.pull(() => {
WebDAV.push();
alert("同步指令已发送,请查看控制台或稍后重试");
overlay.remove();
showLogViewer();
});
};
var clearBtn = document.createElement('button');
clearBtn.innerText = "清空";
clearBtn.style.color = "red";
clearBtn.style.marginRight = "10px";
clearBtn.onclick = function() {
if(confirm("确定要清空?")) { setLogs([]); WebDAV.push(); textarea.value = ""; }
};
var closeBtn = document.createElement('button');
closeBtn.innerText = "关闭";
closeBtn.onclick = function() { overlay.remove(); };
btnContainer.appendChild(forceSyncBtn);
btnContainer.appendChild(clearBtn);
btnContainer.appendChild(copyBtn);
btnContainer.appendChild(closeBtn);
box.appendChild(title);
box.appendChild(textarea);
box.appendChild(btnContainer);
overlay.appendChild(box);
document.body.appendChild(overlay);
}
function showWebDAVConfig() {
const conf = WebDAV.getConfig();
const url = prompt("WebDAV 地址:", conf.url);
if (url === null) return;
const user = prompt("WebDAV 账号:", conf.user);
if (user === null) return;
const pass = prompt("WebDAV 密码:", conf.pass);
if (pass === null) return;
WebDAV.setConfig({ url, user, pass });
alert("✅ 配置保存,正在尝试连接...");
WebDAV.pull();
}
function registerMenus() {
GM_registerMenuCommand("🚫 屏蔽当前分享者", function() {
const currentUser = getTargetSharerInfo();
if (!currentUser) { alert("页面未加载完成"); return; }
const list = getBlockedList();
if (!list.some(u => u.name === currentUser.name && u.hash === currentUser.hash)) {
list.push(currentUser);
setBlockedList(list);
WebDAV.push();
if(confirm(`✅ 已屏蔽: ${currentUser.name}\n刷新?`)) location.reload();
}
});
GM_registerMenuCommand("⚙️ 管理屏蔽列表", function() {
const list = getBlockedList();
let msg = "屏蔽列表:\n";
list.forEach((u, i) => msg += `【${i+1}】${u.name}\n`);
const input = prompt(msg + "\n输入序号删除:");
if (input) {
list.splice(parseInt(input)-1, 1);
setBlockedList(list);
WebDAV.push();
alert("✅ 已删除");
location.reload();
}
});
GM_registerMenuCommand("📜 查看日志", showLogViewer);
GM_registerMenuCommand("☁️ WebDAV 设置", showWebDAVConfig);
}
registerMenus();
// ============================================================
// 5. 主程序执行
// ============================================================
function executeSaveAction(currentUser) {
console.log("[夸克懒得点] 执行转存...");
var checkboxElement = document.querySelector('.ant-checkbox-input');
try { if (checkboxElement && !checkboxElement.checked) checkboxElement.click(); } catch (e) {}
var saveButtonElement = document.querySelector('.share-save');
if (saveButtonElement) {
if (currentUser) recordLog(currentUser);
saveButtonElement.click();
} else {
var saveButtonElement2 = document.querySelector('.file-info_r');
if (saveButtonElement2) {
if (currentUser) recordLog(currentUser);
saveButtonElement2.click();
}
}
setTimeout(function() {
var confirmButtonElement = document.querySelector('.confirm-btn');
if (confirmButtonElement) confirmButtonElement.click();
var intervalId = setInterval(function() {
var viewButtonElement = document.querySelector('.path');
if (viewButtonElement) {
viewButtonElement.click();
clearInterval(intervalId);
}
}, 1000);
}, 1000);
}
if (window.location.href.startsWith("https://pan.quark.cn/s/")) {
window.onload = function() {
WebDAV.pull(() => {
setTimeout(function() {
const currentUser = getTargetSharerInfo();
if (currentUser) {
const blockedList = getBlockedList();
if (blockedList.some(u => u.name === currentUser.name && u.hash === currentUser.hash)) {
console.warn(`[夸克懒得点] 已屏蔽: ${currentUser.name}`);
showBlockedOverlay(currentUser);
return;
}
}
const currentUrl = window.location.href;
const logs = getLogs();
const existingLog = logs.find(l => l.url === currentUrl);
if (existingLog) {
console.warn("[夸克懒得点] 检测到重复链接,暂停保存");
showDuplicateOverlay(existingLog, function() {
executeSaveAction(currentUser);
});
return;
}
executeSaveAction(currentUser);
}, 1000);
});
};
}
if (window.location.href.startsWith("https://pan.quark.cn/list")) {
window.onload = function() {
setTimeout(function() {
var checkboxElement = document.querySelector('.ant-checkbox-wrapper');
try { if(checkboxElement) checkboxElement.click(); } catch (error) {}
}, 1000);
};
}
})();