Skip to content

Instantly share code, notes, and snippets.

@heguro
Created July 29, 2026 12:44
Show Gist options
  • Select an option

  • Save heguro/b57542a88f675e68743c5263622caeb8 to your computer and use it in GitHub Desktop.

Select an option

Save heguro/b57542a88f675e68743c5263622caeb8 to your computer and use it in GitHub Desktop.
(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