Grok-批量删除文件-油猴脚本
- 内容介绍
- 文章标签
- 相关推荐
用Grok的时候,发现每次生图和上传图片/文件之后,即使删除了对应的聊天话题,文件也会遗留在 https://grok.com/files中。
所以在网上找了脚本,不过大多数都是失效了,偶然在reddit中看到了一个 帖子,提到了 grok_files_delete.js 这个脚本文件,使用后发现虽然也失效了 ,但是界面倒是挺好看,于是结合Grok修复了一下。
PS:也可能有其他更方便的方法,我没有找到,哈哈哈哈哈哈,有的话佬友可以教教我(感觉油猴脚本这个容易失效),感谢佬友。
使用流程:
- 下载 油猴Tampermonkey 浏览器插件
- 新建脚本,将下面的内容复制粘贴进去,ctrl+s保存。
- https://grok.com/files 取消勾选仅模拟,开始即可。
- 注意:这个是依次删除所有文件,佬友使用前请确认一下是否都是垃圾文件。
- 效果图:
image2560×639 116 KB
// ==UserScript==
// @name Grok 文件批量删除
// @namespace alexds9
// @version 1.0
// @description 在 https://grok.com/files 页面添加控制面板,支持批量删除文件
// @match https://grok.com/files*
// @run-at document-start
// @grant GM_addStyle
// ==/UserScript==
(() => {
"use strict";
// -----------------------------
// 配置(可自行调整)
// -----------------------------
const CFG = {
stepDelayMs: 60,
dialogWaitMs: 3500,
deleteWaitMs: 80,
maxRetriesPerFile: 2,
scrollIdlePassesToStop: 5,
autoReloadEvery: 0, // >0 时每删除 N 个自动刷新页面(0=禁用)
};
// -----------------------------
// 状态
// -----------------------------
const S = {
running: false,
stopRequested: false,
dryRun: true,
processed: new Set(),
deletedOk: new Set(),
countTried: 0,
countDeleted: 0,
countSkipped: 0,
lastStatus: "",
lastDeleteSignals: new Map(),
deletionsSinceReload: 0,
};
// -----------------------------
// 辅助函数
// -----------------------------
const sleep = ms => new Promise(r => setTimeout(r, ms));
const now = () => new Date().toISOString().replace("T", " ").replace("Z", "");
function setStatus(msg) {
S.lastStatus = msg;
const el = document.getElementById("gbd_status");
if (el) el.textContent = msg;
}
function extractFileIdFromHref(href) {
if (!href) return null;
const m = String(href).match(/[?&]file=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
return m ? m[1] : null;
}
function isElementInDOM(el) {
return !!(el && el.ownerDocument && el.ownerDocument.contains(el));
}
function makeClickable(btn) {
if (!btn) return;
btn.classList.remove("hidden");
btn.style.display = "inline-flex";
btn.style.visibility = "visible";
btn.style.opacity = "1";
btn.style.pointerEvents = "auto";
btn.style.width = "auto";
}
function getFileRows() {
const anchors = Array.from(document.querySelectorAll('a[href^="/files?file="]'));
const rows = [];
for (const a of anchors) {
const fileId = extractFileIdFromHref(a.getAttribute("href"));
if (!fileId) continue;
const li = a.closest("li") || a;
rows.push({ fileId, a, li });
}
const seen = new Set();
return rows.filter(r => seen.has(r.fileId) ? false : (seen.add(r.fileId), true));
}
function findDeleteButton(row) {
return (
row.li.querySelector('button[aria-label="Delete file"]') ||
row.li.querySelector('button[aria-label*="Delete"]') ||
row.a.querySelector('button[aria-label="Delete file"]') ||
row.a.querySelector('button[aria-label*="Delete"]') ||
// 垃圾桶图标常见特征
[...row.li.querySelectorAll('button')].find(b => {
const svg = b.querySelector('svg');
return svg && (svg.innerHTML.includes('trash') || svg.innerHTML.includes('delete') || b.className.toLowerCase().includes('delete'));
})
) || null;
}
async function waitForInlineConfirmButton(row, timeoutMs) {
const t0 = performance.now();
while (performance.now() - t0 < timeoutMs) {
// 最高优先级:中文界面 aria-label="删除" 就是确认按钮
let btn = row.li.querySelector('button[aria-label="删除"]');
if (btn) {
console.debug("[GrokDelete] 找到确认按钮 (aria-label=删除)");
return btn;
}
// 备选:找包含“删除”文字或类似 aria 的
btn = row.li.querySelector('button[aria-label*="删除"]') ||
row.li.querySelector('button[aria-label*="Confirm"]');
if (btn) return btn;
// 找取消按钮 → 取旁边的那个(通常 × 左 ✓ 右)
const cancelBtn = row.li.querySelector('button[aria-label="取消"]');
if (cancelBtn) {
const siblings = [...cancelBtn.parentElement.querySelectorAll('button')];
const idx = siblings.indexOf(cancelBtn);
if (idx >= 0 && siblings[idx + 1]) {
console.debug("[GrokDelete] 通过取消按钮定位到确认按钮");
return siblings[idx + 1];
}
}
await sleep(80);
}
console.debug("[GrokDelete] 超时 - 未找到确认按钮");
return null;
}
async function waitForDeleteSignal(fileId, timeoutMs) {
const t0 = performance.now();
while (performance.now() - t0 < timeoutMs) {
const sig = S.lastDeleteSignals.get(fileId);
if (sig?.ok) return sig;
await sleep(100);
}
return null;
}
async function waitRowGone(row, timeoutMs) {
const t0 = performance.now();
while (performance.now() - t0 < timeoutMs) {
if (!isElementInDOM(row.li)) return true;
await sleep(100);
}
return false;
}
function markRowVisually(row, kind) {
if (!row?.li) return;
if (kind === "deleting") {
row.li.style.opacity = "0.55";
row.li.style.filter = "grayscale(0.6)";
} else if (kind === "deleted") {
row.li.style.opacity = "0.25";
row.li.style.textDecoration = "line-through";
} else if (kind === "skipped") {
row.li.style.opacity = "0.65";
row.li.style.outline = "1px solid #fb923c";
row.li.style.outlineOffset = "2px";
}
}
// -----------------------------
// 网络拦截 - 判断删除是否成功
// -----------------------------
function captureUuidFromUrl(url) {
const m = String(url).match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
return m ? m[1] : null;
}
function patchFetch() {
const orig = window.fetch;
window.fetch = async (...args) => {
const [input, init] = args;
const url = typeof input === 'string' ? input : input?.url || '';
const method = (init?.method || 'GET').toUpperCase();
const res = await orig(...args);
try {
const uuid = captureUuidFromUrl(url);
if (uuid && (method === 'DELETE' || /\/files\b/i.test(url))) {
S.lastDeleteSignals.set(uuid, {
ok: res?.ok ?? false,
ts: Date.now(),
url,
status: res?.status ?? 0,
});
}
} catch {}
return res;
};
}
function patchXHR() {
const origOpen = XMLHttpRequest.prototype.open;
const origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url) {
this.__gbd_method = method.toUpperCase();
this.__gbd_url = url;
return origOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function () {
this.addEventListener('loadend', () => {
try {
const uuid = captureUuidFromUrl(this.__gbd_url);
if (uuid && (this.__gbd_method === 'DELETE' || /\/files\b/i.test(this.__gbd_url))) {
const ok = this.status >= 200 && this.status < 300;
S.lastDeleteSignals.set(uuid, { ok, ts: Date.now(), status: this.status });
}
} catch {}
});
return origSend.apply(this, arguments);
};
}
patchFetch();
patchXHR();
// -----------------------------
// 核心删除逻辑
// -----------------------------
async function deleteOne(row) {
if (!row?.fileId) return false;
const fileId = row.fileId;
if (S.processed.has(fileId)) return false;
S.processed.add(fileId);
S.countTried++;
setStatus(`${now()} - 目标: ${fileId} (模拟=${S.dryRun})`);
markRowVisually(row, "deleting");
if (S.dryRun) {
await sleep(CFG.stepDelayMs);
markRowVisually(row, "skipped");
S.countSkipped++;
return true;
}
for (let attempt = 1; attempt <= CFG.maxRetriesPerFile; attempt++) {
if (!S.running || S.stopRequested) return false;
setStatus(`${now()} - 删除中 ${fileId} (第${attempt}次)`);
const delBtn = findDeleteButton(row);
if (!delBtn) {
setStatus(`${now()} - 未找到垃圾桶 ${fileId},跳过`);
markRowVisually(row, "skipped");
S.countSkipped++;
return true;
}
makeClickable(delBtn);
await sleep(40);
delBtn.click();
await sleep(CFG.stepDelayMs);
const confirmBtn = await waitForInlineConfirmButton(row, CFG.dialogWaitMs);
if (confirmBtn) {
console.debug("[GrokDelete] 即将点击确认按钮");
makeClickable(confirmBtn);
await sleep(60);
confirmBtn.click();
await sleep(CFG.stepDelayMs * 1.5);
} else {
setStatus(`${now()} - 未找到确认按钮 ${fileId},重试`);
await sleep(300);
continue;
}
const sig = await waitForDeleteSignal(fileId, CFG.deleteWaitMs * 2);
if (sig?.ok) {
S.countDeleted++;
S.deletionsSinceReload++;
markRowVisually(row, "deleted");
await waitRowGone(row, 1500);
setStatus(`${now()} - 成功 ${fileId}`);
return true;
}
if (await waitRowGone(row, 2000)) {
S.countDeleted++;
S.deletionsSinceReload++;
setStatus(`${now()} - 成功(行消失) ${fileId}`);
return true;
}
setStatus(`${now()} - 不确定 ${fileId},重试`);
await sleep(400);
}
markRowVisually(row, "skipped");
S.countSkipped++;
setStatus(`${now()} - 重试用尽,跳过 ${fileId}`);
return true;
}
async function scrollToLoadMore() {
const before = document.body.scrollHeight;
window.scrollTo(0, document.body.scrollHeight);
await sleep(800);
return document.body.scrollHeight > before;
}
async function mainLoop() {
let idle = 0;
while (S.running && !S.stopRequested) {
const rows = getFileRows().filter(r => !S.processed.has(r.fileId));
if (!rows.length) {
const grew = await scrollToLoadMore();
idle++;
setStatus(`${now()} - 无新文件(空闲${idle}/${CFG.scrollIdlePassesToStop})`);
if (!grew && idle >= CFG.scrollIdlePassesToStop) break;
await sleep(400);
continue;
}
idle = 0;
for (const r of rows) {
if (!S.running || S.stopRequested) break;
await deleteOne(r);
await sleep(CFG.stepDelayMs);
updateCounters();
}
}
S.running = false;
S.stopRequested = false;
updateButtons();
setStatus(`${now()} - 停止。尝试:${S.countTried} 成功:${S.countDeleted} 跳过:${S.countSkipped}`);
}
// -----------------------------
// UI
// -----------------------------
function updateCounters() {
const el = document.getElementById("gbd_counts");
if (el) {
el.textContent = `已尝试: ${S.countTried} | 已删除: ${S.countDeleted} | 已跳过: ${S.countSkipped} | 仅模拟: ${S.dryRun}`;
}
}
function updateButtons() {
document.getElementById("gbd_start").disabled = S.running;
document.getElementById("gbd_stop").disabled = !S.running;
}
function injectUI() {
if (document.getElementById("gbd_panel")) return;
GM_addStyle(`
#gbd_panel {
position: fixed;
top: 16px;
right: 16px;
z-index: 999999;
width: 360px;
background: rgba(15,15,18,0.96);
color: white;
border: 1px solid rgba(255,255,255,0.12);
border-radius: 12px;
padding: 14px;
font: 13.5px/1.45 system-ui, sans-serif;
box-shadow: 0 10px 40px #000c;
}
#gbd_panel button {
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.18);
background: rgba(255,255,255,0.08);
color: white;
padding: 8px 16px;
font-weight: 600;
cursor: pointer;
}
#gbd_panel button:disabled { opacity: 0.45; cursor: not-allowed; }
.row { display:flex; gap:14px; align-items:center; margin:12px 0 8px; }
.muted { color: rgba(255,255,255,0.75); }
.warn { color: #fbbf24; font-weight:700; }
.status {
margin-top: 12px;
padding: 10px;
background: rgba(0,0,0,0.35);
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.06);
max-height: 110px;
overflow-y: auto;
white-space: pre-wrap;
font-size: 12.8px;
}
.title { font-size: 16px; font-weight: 800; margin-bottom: 6px; }
`);
const div = document.createElement("div");
div.id = "gbd_panel";
div.innerHTML = `
<div class="title">Grok 文件批量删除</div>
<div class="muted">当前页面:/files(按列表顺序)</div>
<div class="row">
<button id="gbd_start">开始</button>
<button id="gbd_stop" disabled>停止</button>
<label class="muted" style="margin-left:auto; user-select:none;">
<input id="gbd_dryrun" type="checkbox" checked> 仅模拟
</label>
</div>
<div class="row muted">
<span class="warn">重要:</span> 确认安全后再取消“仅模拟”
</div>
<div id="gbd_counts" class="muted" style="margin:12px 0;font-size:13.5px;">
已尝试: 0 | 已删除: 0 | 已跳过: 0 | 仅模拟: true
</div>
<div id="gbd_status" class="status">待机中...</div>
`;
document.documentElement.appendChild(div);
document.getElementById("gbd_dryrun").onchange = e => {
S.dryRun = e.target.checked;
updateCounters();
};
document.getElementById("gbd_start").onclick = async () => {
if (S.running) return;
if (!location.pathname.startsWith("/files")) {
setStatus(`${now()} - 请在 /files 页面启动`);
return;
}
S.running = true;
S.stopRequested = false;
updateButtons();
updateCounters();
setStatus(`${now()} - 开始...`);
await sleep(120);
mainLoop().catch(err => {
S.running = false;
updateButtons();
setStatus(`${now()} - 异常:${err?.message || err}`);
console.error(err);
});
};
document.getElementById("gbd_stop").onclick = () => {
if (!S.running) return;
S.stopRequested = true;
setStatus(`${now()} - 正在停止...`);
updateButtons();
};
updateButtons();
updateCounters();
}
function onReady(fn) {
if (document.readyState !== "loading") fn();
else document.addEventListener("DOMContentLoaded", fn, {once: true});
}
onReady(() => {
injectUI();
console.log("[Grok批量删除] 脚本加载完成 v0.7.3");
});
})();
网友解答:
--【壹】--:
自动删除聊天记录有加吗
--【贰】--:
感谢大佬!
--【叁】--:
感谢大佬!
--【肆】--:
感谢大佬!如德芙般顺滑!已经删了五百多的文档了
用Grok的时候,发现每次生图和上传图片/文件之后,即使删除了对应的聊天话题,文件也会遗留在 https://grok.com/files中。
所以在网上找了脚本,不过大多数都是失效了,偶然在reddit中看到了一个 帖子,提到了 grok_files_delete.js 这个脚本文件,使用后发现虽然也失效了 ,但是界面倒是挺好看,于是结合Grok修复了一下。
PS:也可能有其他更方便的方法,我没有找到,哈哈哈哈哈哈,有的话佬友可以教教我(感觉油猴脚本这个容易失效),感谢佬友。
使用流程:
- 下载 油猴Tampermonkey 浏览器插件
- 新建脚本,将下面的内容复制粘贴进去,ctrl+s保存。
- https://grok.com/files 取消勾选仅模拟,开始即可。
- 注意:这个是依次删除所有文件,佬友使用前请确认一下是否都是垃圾文件。
- 效果图:
image2560×639 116 KB
// ==UserScript==
// @name Grok 文件批量删除
// @namespace alexds9
// @version 1.0
// @description 在 https://grok.com/files 页面添加控制面板,支持批量删除文件
// @match https://grok.com/files*
// @run-at document-start
// @grant GM_addStyle
// ==/UserScript==
(() => {
"use strict";
// -----------------------------
// 配置(可自行调整)
// -----------------------------
const CFG = {
stepDelayMs: 60,
dialogWaitMs: 3500,
deleteWaitMs: 80,
maxRetriesPerFile: 2,
scrollIdlePassesToStop: 5,
autoReloadEvery: 0, // >0 时每删除 N 个自动刷新页面(0=禁用)
};
// -----------------------------
// 状态
// -----------------------------
const S = {
running: false,
stopRequested: false,
dryRun: true,
processed: new Set(),
deletedOk: new Set(),
countTried: 0,
countDeleted: 0,
countSkipped: 0,
lastStatus: "",
lastDeleteSignals: new Map(),
deletionsSinceReload: 0,
};
// -----------------------------
// 辅助函数
// -----------------------------
const sleep = ms => new Promise(r => setTimeout(r, ms));
const now = () => new Date().toISOString().replace("T", " ").replace("Z", "");
function setStatus(msg) {
S.lastStatus = msg;
const el = document.getElementById("gbd_status");
if (el) el.textContent = msg;
}
function extractFileIdFromHref(href) {
if (!href) return null;
const m = String(href).match(/[?&]file=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
return m ? m[1] : null;
}
function isElementInDOM(el) {
return !!(el && el.ownerDocument && el.ownerDocument.contains(el));
}
function makeClickable(btn) {
if (!btn) return;
btn.classList.remove("hidden");
btn.style.display = "inline-flex";
btn.style.visibility = "visible";
btn.style.opacity = "1";
btn.style.pointerEvents = "auto";
btn.style.width = "auto";
}
function getFileRows() {
const anchors = Array.from(document.querySelectorAll('a[href^="/files?file="]'));
const rows = [];
for (const a of anchors) {
const fileId = extractFileIdFromHref(a.getAttribute("href"));
if (!fileId) continue;
const li = a.closest("li") || a;
rows.push({ fileId, a, li });
}
const seen = new Set();
return rows.filter(r => seen.has(r.fileId) ? false : (seen.add(r.fileId), true));
}
function findDeleteButton(row) {
return (
row.li.querySelector('button[aria-label="Delete file"]') ||
row.li.querySelector('button[aria-label*="Delete"]') ||
row.a.querySelector('button[aria-label="Delete file"]') ||
row.a.querySelector('button[aria-label*="Delete"]') ||
// 垃圾桶图标常见特征
[...row.li.querySelectorAll('button')].find(b => {
const svg = b.querySelector('svg');
return svg && (svg.innerHTML.includes('trash') || svg.innerHTML.includes('delete') || b.className.toLowerCase().includes('delete'));
})
) || null;
}
async function waitForInlineConfirmButton(row, timeoutMs) {
const t0 = performance.now();
while (performance.now() - t0 < timeoutMs) {
// 最高优先级:中文界面 aria-label="删除" 就是确认按钮
let btn = row.li.querySelector('button[aria-label="删除"]');
if (btn) {
console.debug("[GrokDelete] 找到确认按钮 (aria-label=删除)");
return btn;
}
// 备选:找包含“删除”文字或类似 aria 的
btn = row.li.querySelector('button[aria-label*="删除"]') ||
row.li.querySelector('button[aria-label*="Confirm"]');
if (btn) return btn;
// 找取消按钮 → 取旁边的那个(通常 × 左 ✓ 右)
const cancelBtn = row.li.querySelector('button[aria-label="取消"]');
if (cancelBtn) {
const siblings = [...cancelBtn.parentElement.querySelectorAll('button')];
const idx = siblings.indexOf(cancelBtn);
if (idx >= 0 && siblings[idx + 1]) {
console.debug("[GrokDelete] 通过取消按钮定位到确认按钮");
return siblings[idx + 1];
}
}
await sleep(80);
}
console.debug("[GrokDelete] 超时 - 未找到确认按钮");
return null;
}
async function waitForDeleteSignal(fileId, timeoutMs) {
const t0 = performance.now();
while (performance.now() - t0 < timeoutMs) {
const sig = S.lastDeleteSignals.get(fileId);
if (sig?.ok) return sig;
await sleep(100);
}
return null;
}
async function waitRowGone(row, timeoutMs) {
const t0 = performance.now();
while (performance.now() - t0 < timeoutMs) {
if (!isElementInDOM(row.li)) return true;
await sleep(100);
}
return false;
}
function markRowVisually(row, kind) {
if (!row?.li) return;
if (kind === "deleting") {
row.li.style.opacity = "0.55";
row.li.style.filter = "grayscale(0.6)";
} else if (kind === "deleted") {
row.li.style.opacity = "0.25";
row.li.style.textDecoration = "line-through";
} else if (kind === "skipped") {
row.li.style.opacity = "0.65";
row.li.style.outline = "1px solid #fb923c";
row.li.style.outlineOffset = "2px";
}
}
// -----------------------------
// 网络拦截 - 判断删除是否成功
// -----------------------------
function captureUuidFromUrl(url) {
const m = String(url).match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/i);
return m ? m[1] : null;
}
function patchFetch() {
const orig = window.fetch;
window.fetch = async (...args) => {
const [input, init] = args;
const url = typeof input === 'string' ? input : input?.url || '';
const method = (init?.method || 'GET').toUpperCase();
const res = await orig(...args);
try {
const uuid = captureUuidFromUrl(url);
if (uuid && (method === 'DELETE' || /\/files\b/i.test(url))) {
S.lastDeleteSignals.set(uuid, {
ok: res?.ok ?? false,
ts: Date.now(),
url,
status: res?.status ?? 0,
});
}
} catch {}
return res;
};
}
function patchXHR() {
const origOpen = XMLHttpRequest.prototype.open;
const origSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function (method, url) {
this.__gbd_method = method.toUpperCase();
this.__gbd_url = url;
return origOpen.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function () {
this.addEventListener('loadend', () => {
try {
const uuid = captureUuidFromUrl(this.__gbd_url);
if (uuid && (this.__gbd_method === 'DELETE' || /\/files\b/i.test(this.__gbd_url))) {
const ok = this.status >= 200 && this.status < 300;
S.lastDeleteSignals.set(uuid, { ok, ts: Date.now(), status: this.status });
}
} catch {}
});
return origSend.apply(this, arguments);
};
}
patchFetch();
patchXHR();
// -----------------------------
// 核心删除逻辑
// -----------------------------
async function deleteOne(row) {
if (!row?.fileId) return false;
const fileId = row.fileId;
if (S.processed.has(fileId)) return false;
S.processed.add(fileId);
S.countTried++;
setStatus(`${now()} - 目标: ${fileId} (模拟=${S.dryRun})`);
markRowVisually(row, "deleting");
if (S.dryRun) {
await sleep(CFG.stepDelayMs);
markRowVisually(row, "skipped");
S.countSkipped++;
return true;
}
for (let attempt = 1; attempt <= CFG.maxRetriesPerFile; attempt++) {
if (!S.running || S.stopRequested) return false;
setStatus(`${now()} - 删除中 ${fileId} (第${attempt}次)`);
const delBtn = findDeleteButton(row);
if (!delBtn) {
setStatus(`${now()} - 未找到垃圾桶 ${fileId},跳过`);
markRowVisually(row, "skipped");
S.countSkipped++;
return true;
}
makeClickable(delBtn);
await sleep(40);
delBtn.click();
await sleep(CFG.stepDelayMs);
const confirmBtn = await waitForInlineConfirmButton(row, CFG.dialogWaitMs);
if (confirmBtn) {
console.debug("[GrokDelete] 即将点击确认按钮");
makeClickable(confirmBtn);
await sleep(60);
confirmBtn.click();
await sleep(CFG.stepDelayMs * 1.5);
} else {
setStatus(`${now()} - 未找到确认按钮 ${fileId},重试`);
await sleep(300);
continue;
}
const sig = await waitForDeleteSignal(fileId, CFG.deleteWaitMs * 2);
if (sig?.ok) {
S.countDeleted++;
S.deletionsSinceReload++;
markRowVisually(row, "deleted");
await waitRowGone(row, 1500);
setStatus(`${now()} - 成功 ${fileId}`);
return true;
}
if (await waitRowGone(row, 2000)) {
S.countDeleted++;
S.deletionsSinceReload++;
setStatus(`${now()} - 成功(行消失) ${fileId}`);
return true;
}
setStatus(`${now()} - 不确定 ${fileId},重试`);
await sleep(400);
}
markRowVisually(row, "skipped");
S.countSkipped++;
setStatus(`${now()} - 重试用尽,跳过 ${fileId}`);
return true;
}
async function scrollToLoadMore() {
const before = document.body.scrollHeight;
window.scrollTo(0, document.body.scrollHeight);
await sleep(800);
return document.body.scrollHeight > before;
}
async function mainLoop() {
let idle = 0;
while (S.running && !S.stopRequested) {
const rows = getFileRows().filter(r => !S.processed.has(r.fileId));
if (!rows.length) {
const grew = await scrollToLoadMore();
idle++;
setStatus(`${now()} - 无新文件(空闲${idle}/${CFG.scrollIdlePassesToStop})`);
if (!grew && idle >= CFG.scrollIdlePassesToStop) break;
await sleep(400);
continue;
}
idle = 0;
for (const r of rows) {
if (!S.running || S.stopRequested) break;
await deleteOne(r);
await sleep(CFG.stepDelayMs);
updateCounters();
}
}
S.running = false;
S.stopRequested = false;
updateButtons();
setStatus(`${now()} - 停止。尝试:${S.countTried} 成功:${S.countDeleted} 跳过:${S.countSkipped}`);
}
// -----------------------------
// UI
// -----------------------------
function updateCounters() {
const el = document.getElementById("gbd_counts");
if (el) {
el.textContent = `已尝试: ${S.countTried} | 已删除: ${S.countDeleted} | 已跳过: ${S.countSkipped} | 仅模拟: ${S.dryRun}`;
}
}
function updateButtons() {
document.getElementById("gbd_start").disabled = S.running;
document.getElementById("gbd_stop").disabled = !S.running;
}
function injectUI() {
if (document.getElementById("gbd_panel")) return;
GM_addStyle(`
#gbd_panel {
position: fixed;
top: 16px;
right: 16px;
z-index: 999999;
width: 360px;
background: rgba(15,15,18,0.96);
color: white;
border: 1px solid rgba(255,255,255,0.12);
border-radius: 12px;
padding: 14px;
font: 13.5px/1.45 system-ui, sans-serif;
box-shadow: 0 10px 40px #000c;
}
#gbd_panel button {
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.18);
background: rgba(255,255,255,0.08);
color: white;
padding: 8px 16px;
font-weight: 600;
cursor: pointer;
}
#gbd_panel button:disabled { opacity: 0.45; cursor: not-allowed; }
.row { display:flex; gap:14px; align-items:center; margin:12px 0 8px; }
.muted { color: rgba(255,255,255,0.75); }
.warn { color: #fbbf24; font-weight:700; }
.status {
margin-top: 12px;
padding: 10px;
background: rgba(0,0,0,0.35);
border-radius: 8px;
border: 1px solid rgba(255,255,255,0.06);
max-height: 110px;
overflow-y: auto;
white-space: pre-wrap;
font-size: 12.8px;
}
.title { font-size: 16px; font-weight: 800; margin-bottom: 6px; }
`);
const div = document.createElement("div");
div.id = "gbd_panel";
div.innerHTML = `
<div class="title">Grok 文件批量删除</div>
<div class="muted">当前页面:/files(按列表顺序)</div>
<div class="row">
<button id="gbd_start">开始</button>
<button id="gbd_stop" disabled>停止</button>
<label class="muted" style="margin-left:auto; user-select:none;">
<input id="gbd_dryrun" type="checkbox" checked> 仅模拟
</label>
</div>
<div class="row muted">
<span class="warn">重要:</span> 确认安全后再取消“仅模拟”
</div>
<div id="gbd_counts" class="muted" style="margin:12px 0;font-size:13.5px;">
已尝试: 0 | 已删除: 0 | 已跳过: 0 | 仅模拟: true
</div>
<div id="gbd_status" class="status">待机中...</div>
`;
document.documentElement.appendChild(div);
document.getElementById("gbd_dryrun").onchange = e => {
S.dryRun = e.target.checked;
updateCounters();
};
document.getElementById("gbd_start").onclick = async () => {
if (S.running) return;
if (!location.pathname.startsWith("/files")) {
setStatus(`${now()} - 请在 /files 页面启动`);
return;
}
S.running = true;
S.stopRequested = false;
updateButtons();
updateCounters();
setStatus(`${now()} - 开始...`);
await sleep(120);
mainLoop().catch(err => {
S.running = false;
updateButtons();
setStatus(`${now()} - 异常:${err?.message || err}`);
console.error(err);
});
};
document.getElementById("gbd_stop").onclick = () => {
if (!S.running) return;
S.stopRequested = true;
setStatus(`${now()} - 正在停止...`);
updateButtons();
};
updateButtons();
updateCounters();
}
function onReady(fn) {
if (document.readyState !== "loading") fn();
else document.addEventListener("DOMContentLoaded", fn, {once: true});
}
onReady(() => {
injectUI();
console.log("[Grok批量删除] 脚本加载完成 v0.7.3");
});
})();
网友解答:
--【壹】--:
自动删除聊天记录有加吗
--【贰】--:
感谢大佬!
--【叁】--:
感谢大佬!
--【肆】--:
感谢大佬!如德芙般顺滑!已经删了五百多的文档了

