// ==UserScript== // @name ふたば新着荒らし一括削除依頼ツール // @namespace http://tampermonkey.net/ // @version 1.4 // @description futaba-id.siteから最新荒らしIDを取得し、ふたば上で一括削除依頼を送るツール(全自動モード対応) // @author Antigravity // @match https://*.2chan.net/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @connect futaba-id.site // @connect img.2chan.net // @connect may.2chan.net // ==/UserScript== (function () { 'use strict'; // ====== スタイル ====== const style = document.createElement('style'); style.textContent = ` #spam-panel { position: fixed; top: 20px; right: 20px; width: 280px; min-width: 200px; min-height: 36px; max-height: 85vh; background: rgba(10, 10, 20, 0.55); backdrop-filter: blur(24px) saturate(180%); -webkit-backdrop-filter: blur(24px) saturate(180%); border: 1px solid rgba(255,255,255,0.18); border-radius: 14px; box-shadow: 0 8px 40px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.1); color: #f3f4f6; font-family: sans-serif; font-size: 12px; z-index: 999999; display: flex; flex-direction: column; overflow: hidden; } #spam-header { padding: 7px 10px; background: linear-gradient(135deg, rgba(124,58,237,0.75), rgba(79,70,229,0.75)); backdrop-filter: blur(10px); font-weight: bold; font-size: 11px; cursor: move; display: flex; justify-content: space-between; align-items: center; user-select: none; gap: 6px; flex-shrink: 0; border-bottom: 1px solid rgba(255,255,255,0.12); } #spam-header span { flex:1; } #sp-header-btns { display: flex; gap: 4px; } .sp-hbtn { background: rgba(255,255,255,0.15); border: none; color: #fff; font-size: 12px; width: 20px; height: 20px; border-radius: 4px; cursor: pointer; display: flex; align-items: center; justify-content: center; line-height: 1; padding: 0; transition: background 0.15s; } .sp-hbtn:hover { background: rgba(255,255,255,0.3); } #spam-body { padding: 10px; display: flex; flex-direction: column; gap: 8px; overflow-y: auto; flex: 1; } #spam-body.collapsed { display: none; } .sp-label { font-size: 9px; color: #9ca3af; text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 2px; } #sp-source-select, #sp-reason { background: rgba(30, 30, 50, 0.85); border: 1px solid rgba(255,255,255,0.15); border-radius: 6px; padding: 5px 8px; color: #e2e8f0; font-size: 11px; outline: none; width: 100%; -webkit-appearance: none; appearance: none; } #sp-source-select option, #sp-reason option { background: #1e1e3a; color: #e2e8f0; } #sp-scan-btn { background: linear-gradient(135deg, #7c3aed, #4f46e5); border: none; border-radius: 6px; padding: 6px; color: white; font-weight: bold; font-size: 11px; cursor: pointer; transition: opacity 0.2s, transform 0.1s; } #sp-scan-btn:active { transform: scale(0.97); } #sp-scan-btn:disabled { opacity: 0.5; cursor: not-allowed; } #sp-thread-wrap { border: 1px solid rgba(255,255,255,0.08); background: rgba(0,0,0,0.18); backdrop-filter: blur(8px); border-radius: 6px; max-height: 180px; overflow-y: auto; padding: 5px; display: flex; flex-direction: column; gap: 3px; } .sp-thread-item { display: flex; align-items: flex-start; gap: 5px; padding: 3px; border-radius: 4px; transition: background 0.15s; } .sp-thread-item:hover { background: rgba(255,255,255,0.07); } .sp-thread-item input[type=checkbox] { margin-top: 2px; cursor: pointer; flex-shrink: 0; } .sp-thread-meta { display: flex; flex-direction: column; overflow: hidden; } .sp-thread-link { color: #818cf8; font-size: 10px; font-weight: bold; text-decoration: none; } .sp-thread-link:hover { text-decoration: underline; } .sp-thread-sub { font-size: 9px; color: #9ca3af; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 210px; } .sp-row { display: flex; gap: 4px; } .sp-mini-btn { flex: 1; background: rgba(255,255,255,0.09); border: 1px solid rgba(255,255,255,0.1); border-radius: 5px; padding: 4px; color: #d1d5db; font-size: 10px; cursor: pointer; transition: background 0.15s; } .sp-mini-btn:hover { background: rgba(255,255,255,0.17); } #sp-send-btn { background: linear-gradient(135deg, #ef4444, #b91c1c); border: none; border-radius: 6px; padding: 6px; color: white; font-weight: bold; font-size: 11px; cursor: pointer; transition: opacity 0.2s, transform 0.1s; } #sp-send-btn:active { transform: scale(0.97); } #sp-send-btn:disabled { opacity: 0.5; cursor: not-allowed; } #sp-log { background: rgba(0,0,0,0.25); border: 1px solid rgba(255,255,255,0.06); border-radius: 6px; padding: 6px 8px; font-family: monospace; font-size: 9px; color: #6ee7b7; height: 80px; overflow-y: auto; white-space: pre-wrap; line-height: 1.5; backdrop-filter: blur(4px); } #sp-empty { font-size: 10px; color: #6b7280; text-align: center; padding: 10px 0; } /* ── 全自動モード行 ── */ #sp-auto-row { display: flex; align-items: center; justify-content: space-between; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.12); border-radius: 8px; padding: 6px 10px; backdrop-filter: blur(6px); } #sp-auto-label { font-size: 11px; font-weight: bold; color: #e2e8f0; display: flex; flex-direction: column; gap: 1px; } #sp-auto-sublabel { font-size: 9px; color: #6b7280; font-weight: normal; } /* トグルスイッチ */ .sp-toggle-wrap { position: relative; width: 38px; height: 20px; flex-shrink: 0; } .sp-toggle-wrap input { opacity: 0; width: 0; height: 0; } .sp-toggle-slider { position: absolute; inset: 0; background: rgba(255,255,255,0.15); border-radius: 20px; cursor: pointer; transition: background 0.25s; } .sp-toggle-slider::before { content: ''; position: absolute; width: 14px; height: 14px; left: 3px; top: 3px; background: white; border-radius: 50%; transition: transform 0.25s; } .sp-toggle-wrap input:checked + .sp-toggle-slider { background: linear-gradient(135deg, #10b981, #059669); } .sp-toggle-wrap input:checked + .sp-toggle-slider::before { transform: translateX(18px); } /* 全自動中のアニメーションバッジ */ #sp-auto-badge { display: none; font-size: 9px; background: linear-gradient(135deg, #10b981, #059669); color: white; border-radius: 4px; padding: 2px 5px; animation: sp-pulse 1.5s infinite; } @keyframes sp-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } /* 常駐ミニボタン(パネル非表示時に表示) */ #sp-mini-launcher { position: fixed; top: 20px; right: 20px; width: 32px; height: 32px; background: linear-gradient(135deg, rgba(124,58,237,0.8), rgba(79,70,229,0.8)); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border: 1px solid rgba(255,255,255,0.2); border-radius: 50%; box-shadow: 0 4px 16px rgba(0,0,0,0.4); cursor: pointer; z-index: 999999; display: none; align-items: center; justify-content: center; font-size: 16px; transition: transform 0.15s, opacity 0.2s; color: white; user-select: none; } #sp-mini-launcher:hover { transform: scale(1.12); } #sp-mini-launcher .sp-shortcut-hint { position: absolute; top: 36px; right: 0; font-size: 9px; color: rgba(255,255,255,0.6); white-space: nowrap; background: rgba(0,0,0,0.5); padding: 2px 5px; border-radius: 4px; pointer-events: none; opacity: 0; transition: opacity 0.2s; } #sp-mini-launcher:hover .sp-shortcut-hint { opacity: 1; } /* リサイズハンドル */ #sp-resize-handle { position: absolute; right: 0; bottom: 0; width: 16px; height: 16px; cursor: nwse-resize; z-index: 10; } #sp-resize-handle::after { content: ''; position: absolute; right: 3px; bottom: 3px; width: 8px; height: 8px; border-right: 2px solid rgba(255,255,255,0.25); border-bottom: 2px solid rgba(255,255,255,0.25); } `; document.head.appendChild(style); // ====== パネルHTML ====== // ====== 起動時:永続非表示フラグチェック ====== if (GM_getValue('panelHidden', false)) { // パネルは起動しない。全自動モードのみ裏で継続実行。 if (GM_getValue('autoMode', false)) { (async () => { await sleep(1500); const threads = await runScan(); if (threads.length > 0) await runSend(threads.map(t => ({ dataset: { board: t.board, no: t.threadNo, url: t.threadUrl } }))); })(); } // Tampermonkeyメニューから復活できるよう登録 GM_registerMenuCommand('🛡️ パネルを再表示する', () => { GM_setValue('panelHidden', false); location.reload(); }); return; // 以降のUI生成をスキップ } // Tampermonkeyメニュー(パネル表示中も一応登録しておく) GM_registerMenuCommand('🛡️ パネルを隠す (永続)', () => { GM_setValue('panelHidden', true); location.reload(); }); // ====== 常駐ミニボタン ====== const miniLauncher = document.createElement('div'); miniLauncher.id = 'sp-mini-launcher'; miniLauncher.innerHTML = '🛡️Alt+D'; document.body.appendChild(miniLauncher); const panel = document.createElement('div'); panel.id = 'spam-panel'; panel.innerHTML = `
🛡️ 荒らし一括削除依頼 ⚡ 全自動
⚡ 全自動モード ページ読込時に自動スキャン&一括送信
取得元リスト
生存スレ一覧
スキャンボタンを押してください
削除理由
実行ログ
待機中...\n
`; document.body.appendChild(panel); // ====== 要素取得 ====== const header = document.getElementById('spam-header'); const body = document.getElementById('spam-body'); const toggleBtn = document.getElementById('sp-toggle-btn'); const closeBtn = document.getElementById('sp-close-btn'); const autoToggle = document.getElementById('sp-auto-toggle'); const autoBadge = document.getElementById('sp-auto-badge'); const sourceSelect = document.getElementById('sp-source-select'); const scanBtn = document.getElementById('sp-scan-btn'); const threadWrap = document.getElementById('sp-thread-wrap'); const allBtn = document.getElementById('sp-all'); const noneBtn = document.getElementById('sp-none'); const reasonSel = document.getElementById('sp-reason'); const sendBtn = document.getElementById('sp-send-btn'); const logEl = document.getElementById('sp-log'); const resizeHandle = document.getElementById('sp-resize-handle'); // ====== 設定を永続化 ====== autoToggle.checked = GM_getValue('autoMode', false); sourceSelect.value = GM_getValue('source', 'https://futaba-id.site/id_list_recent.html'); reasonSel.value = GM_getValue('reason', '110'); autoToggle.addEventListener('change', () => { GM_setValue('autoMode', autoToggle.checked); autoBadge.style.display = autoToggle.checked ? 'inline-block' : 'none'; }); sourceSelect.addEventListener('change', () => GM_setValue('source', sourceSelect.value)); reasonSel.addEventListener('change', () => GM_setValue('reason', reasonSel.value)); // 起動時バッジ反映 autoBadge.style.display = autoToggle.checked ? 'inline-block' : 'none'; // ====== ユーティリティ ====== function log(msg) { logEl.innerText += msg + '\n'; logEl.scrollTop = logEl.scrollHeight; } function gmFetch(url) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: url, onload: r => resolve(r.responseText), onerror: e => reject(e) }); }); } function sleep(ms) { return new Promise(r => setTimeout(r, ms)); } // ====== スキャン処理(共通) ====== async function runScan() { scanBtn.disabled = true; sendBtn.disabled = true; threadWrap.innerHTML = '
スキャン中...
'; logEl.innerText = ''; log('📡 リスト取得中: ' + sourceSelect.value); let listHtml; try { listHtml = await gmFetch(sourceSelect.value); } catch (e) { log('❌ リスト取得失敗: ' + e); scanBtn.disabled = false; return []; } const parser = new DOMParser(); const listDoc = parser.parseFromString(listHtml, 'text/html'); const idLinks = [...listDoc.querySelectorAll('a[href^="id_view.php?id="]')]; const ids = idLinks.slice(0, 10).map(a => { const url = new URL(a.href, 'https://futaba-id.site/'); return { id: url.searchParams.get('id'), label: a.textContent.trim() }; }); log(`✅ ${ids.length} 件のIDを検出。生存スレをスキャン中...`); const allThreads = []; for (const { id, label } of ids) { log(`🔎 ${label} をスキャン中...`); let viewHtml; try { viewHtml = await gmFetch(`https://futaba-id.site/id_view.php?id=${encodeURIComponent(id)}`); } catch (e) { log(` ⚠️ 取得失敗: ${label}`); await sleep(500); continue; } const viewDoc = parser.parseFromString(viewHtml, 'text/html'); viewDoc.querySelectorAll('table tr').forEach((row, i) => { if (i === 0) return; const tds = row.querySelectorAll('td'); if (tds.length < 5) return; const board = tds[0]?.textContent.trim(); const link = tds[2]?.querySelector('a'); if (!link) return; const threadNo = (link.href.match(/\/res\/(\d+)\.htm/) || [])[1]; if (!threadNo) return; allThreads.push({ board, threadNo, threadUrl: link.href, text: tds[4]?.textContent.trim(), idLabel: label }); }); await sleep(400); } return allThreads; } // ====== スレ一覧を描画 ====== function renderThreads(allThreads) { threadWrap.innerHTML = ''; if (allThreads.length === 0) { threadWrap.innerHTML = '
生存スレが見つかりませんでした
'; log('😴 生存スレなし'); scanBtn.disabled = false; return; } allThreads.forEach(t => { const item = document.createElement('div'); item.className = 'sp-thread-item'; item.innerHTML = `
[${t.board.toUpperCase()}] No.${t.threadNo} (${t.idLabel}) ${t.text}
`; threadWrap.appendChild(item); }); log(`✅ 合計 ${allThreads.length} 件の生存スレを検出しました`); sendBtn.disabled = false; scanBtn.disabled = false; } // ====== 一括送信処理(共通) ====== async function runSend(targets) { sendBtn.disabled = true; scanBtn.disabled = true; const reason = reasonSel.value; log(`\n🚀 一括送信開始 (${targets.length}件, 理由コード:${reason})`); for (let i = 0; i < targets.length; i++) { const cb = targets[i]; const board = cb.dataset.board; const threadNo = cb.dataset.no; const threadUrl = cb.dataset.url; log(`[${i + 1}/${targets.length}] No.${threadNo} (${board}) → 送信中...`); const body = new URLSearchParams({ mode: 'post', b: 'b', d: threadNo, reason: reason, responsemode: 'ajax' }); await new Promise(resolve => { GM_xmlhttpRequest({ method: 'POST', url: `https://${board}.2chan.net/del.php`, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': threadUrl, 'Origin': `https://${board}.2chan.net` }, data: body.toString(), onload: r => { log(` → ${r.responseText.trim() || '送信完了'}`); resolve(); }, onerror: e => { log(` → ❌ エラー: ${e}`); resolve(); } }); }); await sleep(3000); } log('🎉 全件送信完了!'); sendBtn.disabled = false; scanBtn.disabled = false; } // ====== 手動スキャンボタン ====== scanBtn.addEventListener('click', async () => { const threads = await runScan(); renderThreads(threads); }); // ====== 手動送信ボタン ====== sendBtn.addEventListener('click', async () => { const targets = [...document.querySelectorAll('.sp-chk:checked')]; if (targets.length === 0) { alert('送信対象のスレを選択してください'); return; } if (!confirm(`${targets.length} 件のスレに削除依頼を送信しますか?`)) return; await runSend(targets); }); // ====== 全選択・解除 ====== allBtn.addEventListener('click', () => document.querySelectorAll('.sp-chk').forEach(cb => cb.checked = true)); noneBtn.addEventListener('click', () => document.querySelectorAll('.sp-chk').forEach(cb => cb.checked = false)); // ====== 全自動モード(ページ読込時) ====== if (autoToggle.checked) { (async () => { await sleep(1500); // ページ安定待ち log('⚡ 全自動モード起動中...'); const threads = await runScan(); if (threads.length === 0) { renderThreads([]); return; } renderThreads(threads); await sleep(500); // 全チェックを確認してそのまま全送信 const targets = [...document.querySelectorAll('.sp-chk:checked')]; await runSend(targets); })(); } // ====== 折りたたみ ====== let collapsed = false; toggleBtn.addEventListener('click', () => { collapsed = !collapsed; body.classList.toggle('collapsed', collapsed); toggleBtn.textContent = collapsed ? '+' : '-'; panel.style.maxHeight = collapsed ? '36px' : '85vh'; }); // ====== ドラッグ移動 ====== let dragging = false, sx, sy, il, it; header.addEventListener('mousedown', e => { if (e.target.closest('#sp-header-btns')) return; dragging = true; sx = e.clientX; sy = e.clientY; const r = panel.getBoundingClientRect(); il = r.left; it = r.top; panel.style.right = 'auto'; panel.style.left = il + 'px'; panel.style.top = it + 'px'; }); document.addEventListener('mousemove', e => { if (!dragging) return; panel.style.left = (il + e.clientX - sx) + 'px'; panel.style.top = (it + e.clientY - sy) + 'px'; }); document.addEventListener('mouseup', () => dragging = false); // ====== リサイズ ====== let resizing = false, rsx, rsy, rw, rh; resizeHandle.addEventListener('mousedown', e => { e.stopPropagation(); resizing = true; rsx = e.clientX; rsy = e.clientY; const r = panel.getBoundingClientRect(); rw = r.width; rh = r.height; panel.style.maxHeight = 'none'; }); document.addEventListener('mousemove', e => { if (!resizing) return; panel.style.width = Math.max(200, rw + (e.clientX - rsx)) + 'px'; panel.style.height = Math.max(80, rh + (e.clientY - rsy)) + 'px'; }); document.addEventListener('mouseup', () => resizing = false); // ====== パネル表示・非表示の切り替え ====== function showPanel() { panel.style.display = 'flex'; miniLauncher.style.display = 'none'; } function hidePanel() { panel.style.display = 'none'; // ミニボタンをパネルの位置に合わせて表示 const r = panel.getBoundingClientRect(); miniLauncher.style.top = (r.top) + 'px'; miniLauncher.style.left = (r.left) + 'px'; miniLauncher.style.right = 'auto'; miniLauncher.style.display = 'flex'; } function togglePanel() { panel.style.display === 'none' ? showPanel() : hidePanel(); } closeBtn.addEventListener('click', () => { GM_setValue('panelHidden', true); panel.style.display = 'none'; miniLauncher.style.display = 'none'; // 次回ロード時から永続非表示。Tampermonkeyメニューから「パネルを再表示」で復活可能。 }); miniLauncher.addEventListener('click', showPanel); // Alt + D でトグル document.addEventListener('keydown', e => { if (e.altKey && e.key === 'd') { e.preventDefault(); togglePanel(); } }); })();