Created
July 22, 2025 09:03
-
-
Save Axoloteera/afb005aea6be2572cd5161448f6f67ce to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name CCW Comment Annotator + Export | |
// @namespace https://ccw.site/ | |
// @version 0.2 | |
// @description 在评论旁标注“正常/坏”,并支持一键导出 JSONL | |
// @author ChatGPT | |
// @match https://*.ccw.site/* | |
// @grant none | |
// ==/UserScript== | |
(() => { | |
'use strict'; | |
/* ---------- 配置 ---------- */ | |
const LABELS = [ | |
{ key: 'normal', text: '✅ 正常' }, | |
{ key: 'juvenile', text: '🚸 坏' } | |
]; | |
const STORAGE_KEY = 'ccw-comment-labels-v1'; | |
const store = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}'); | |
/* ---------- 辅助函数 ---------- */ | |
function saveStore() { | |
localStorage.setItem(STORAGE_KEY, JSON.stringify(store)); | |
} | |
function createButtons(cid, text, time) { | |
const box = document.createElement('span'); | |
box.style.marginLeft = '8px'; | |
box.style.fontSize = '12px'; | |
LABELS.forEach(l => { | |
const btn = document.createElement('button'); | |
btn.textContent = l.text; | |
btn.style.margin = '0 2px'; | |
btn.style.padding = '1px 4px'; | |
btn.style.border = '1px solid #666'; | |
btn.style.borderRadius = '4px'; | |
const picked = store[cid]?.label === l.key; | |
if (picked) { | |
btn.style.background = '#2d8cff'; | |
btn.style.color = '#fff'; | |
} | |
btn.addEventListener('click', () => { | |
store[cid] = { label: l.key, text, time }; | |
saveStore(); | |
[...box.querySelectorAll('button')].forEach(b => { | |
b.style.background = ''; | |
b.style.color = ''; | |
}); | |
btn.style.background = '#2d8cff'; | |
btn.style.color = '#fff'; | |
}); | |
box.appendChild(btn); | |
}); | |
return box; | |
} | |
function annotate(root) { | |
root.querySelectorAll('.c-comment-item').forEach(item => { | |
if (item.dataset.annotated) return; | |
const content = item.querySelector('.c-comment-content'); | |
if (!content) return; | |
const timeText = item.querySelector('.c-reply-control-time') | |
?.textContent.trim() || 'unknown'; | |
const rawText = content.textContent.trim().replace(/\s+/g, ' '); | |
const cid = `${timeText}|${rawText.slice(0,30)}`; | |
content.appendChild(createButtons(cid, rawText, timeText)); | |
item.dataset.annotated = '1'; | |
}); | |
} | |
/* ---------- 导出功能 ---------- */ | |
function exportLabels() { | |
const out = Object.entries(store) | |
.map(([cid, v]) => JSON.stringify({ id: cid, ...v })) | |
.join('\n'); | |
const blob = new Blob([out], { type: 'application/json' }); | |
const url = URL.createObjectURL(blob); | |
const a = document.createElement('a'); | |
a.href = url; | |
a.download = | |
`ccw_labels_${new Date().toISOString().slice(0,10)}.jsonl`; | |
a.click(); | |
URL.revokeObjectURL(url); | |
} | |
function injectExportBtn() { | |
const btn = document.createElement('button'); | |
btn.textContent = '📤 导出标注'; | |
Object.assign(btn.style, { | |
position: 'fixed', right: '20px', bottom: '20px', | |
padding: '6px 10px', fontSize: '14px', | |
border: '1px solid #666', borderRadius: '6px', | |
background: '#fff', cursor: 'pointer', zIndex: 9999 | |
}); | |
btn.onclick = exportLabels; | |
document.body.appendChild(btn); | |
window.addEventListener('keydown', e => { | |
if (e.altKey && e.code === 'KeyE') { e.preventDefault(); exportLabels(); } | |
}); | |
} | |
/* ---------- 启动 ---------- */ | |
annotate(document); | |
injectExportBtn(); | |
new MutationObserver(muts => { | |
muts.forEach(m => m.addedNodes.forEach(node => { | |
if (node.nodeType === 1) annotate(node); | |
})); | |
}).observe(document.body, { childList: true, subtree: true }); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment