Created
July 29, 2026 12:44
-
-
Save heguro/b57542a88f675e68743c5263622caeb8 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
| (async () => { | |
| const sleep = ms => new Promise(r => setTimeout(r, ms)); | |
| const orig = { | |
| writeText: navigator.clipboard?.writeText, | |
| write: navigator.clipboard?.write, | |
| }; | |
| let pending = null; | |
| if (navigator.clipboard) { | |
| navigator.clipboard.writeText = t => { pending = t; return Promise.resolve(); }; | |
| navigator.clipboard.write = async items => { | |
| for (const item of items) { | |
| if ((item.types || []).includes('text/plain')) { | |
| pending = await (await item.getType('text/plain')).text(); | |
| } | |
| } | |
| return Promise.resolve(); | |
| }; | |
| } | |
| // ボタンが属するメッセージが User / Assistant のどちらかを判定する。 | |
| // ボタンから祖先をたどり、最初に「ユーザーメッセージ」か「アシスタント応答」を | |
| // 含む要素を見つけた時点で確定する(DOMのネスト深さに依存しない)。 | |
| const roleOf = (btn) => { | |
| let el = btn; | |
| while (el && el !== document.body) { | |
| if (el.querySelector?.('[data-testid="user-message"]')) return 'User'; | |
| if (el.querySelector?.('.font-claude-response, [data-is-streaming]')) return 'Assistant'; | |
| el = el.parentElement; | |
| } | |
| return 'Assistant'; | |
| }; | |
| const results = []; | |
| try { | |
| const btns = [...document.querySelectorAll('[data-testid="action-bar-copy"]')]; | |
| for (const btn of btns) { | |
| const role = roleOf(btn); | |
| pending = null; | |
| btn.click(); | |
| for (let t = 0; t < 20 && pending === null; t++) await sleep(50); // 最大1秒待機 | |
| results.push({ role, text: pending ?? '(capture failed)' }); | |
| } | |
| } finally { | |
| // 元のクリップボードAPIを必ず復元 | |
| if (navigator.clipboard) { | |
| if (orig.writeText) navigator.clipboard.writeText = orig.writeText; | |
| if (orig.write) navigator.clipboard.write = orig.write; | |
| } | |
| } | |
| // ログ出力 | |
| results.forEach((r, i) => | |
| console.log(`===== message ${i + 1} [${r.role}] (len=${(r.text || '').length}) =====\n${r.text}`)); | |
| window.__copied = results; | |
| // Markdown 生成(# User / # Assistant で区切る) | |
| const md = results.map(r => `# ${r.role}\n\n${r.text}`).join('\n\n'); | |
| // ファイル名: チャットタイトル + 日時。使用不可文字は _ に置換。 | |
| const pad = n => String(n).padStart(2, '0'); | |
| const d = new Date(); | |
| const stamp = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}`; | |
| const title = (document.title || 'claude-chat').replace(/\s*-\s*Claude\s*$/, '').trim() || 'claude-chat'; | |
| const safeTitle = title.replace(/[\\/:*?"<>|]/g, '_').slice(0, 80); | |
| const filename = `${safeTitle}_${stamp}.md`; | |
| // Blob を生成してダウンロード | |
| const blob = new Blob([md], { type: 'text/markdown;charset=utf-8' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = filename; | |
| document.body.appendChild(a); | |
| a.click(); | |
| a.remove(); | |
| URL.revokeObjectURL(url); | |
| console.log(`done. ${results.length} 件を "${filename}" として保存しました。window.__copied / window.__md でも参照できます。`); | |
| window.__md = md; | |
| return results; | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment