Gemini会話履歴のJavaScript取得(Chromeデベロッパーツール経由)

概要
GeminiのWebアプリ版は、Geminiとの会話履歴をエクスポートする機能が標準でありません。 そこで、Chrome等の開発者ツールのコンソール機能(JavaScript)を通じて無理やりエクスポートさせよう、というのが本記事の主旨です。
やり方・実装
Geminiから取得する箇所(コア要素)
const xpathExpr = '//*[starts-with(@id, "message-content-id-")] | //*[starts-with(@id, "user-query-content-")]'; const result = document.evaluate(xpathExpr, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); for (let i = 0; i < result.snapshotLength; i++) { const node = result.snapshotItem(i); console.log(node.id, node.innerText); // 例:idとテキストをログ出力 }
CSV形式でダウンロードする
// CSVダウンロード処理: // - 参考にさせていただきました: https://qiita.com/ooooooo_q/items/69d67aabf78820d45e46 var downloadCsv = (function() { var tableToCsvString = function(table) { var str = '\uFEFF'; for (var i = 0, imax = table.length - 1; i <= imax; ++i) { var row = table[i]; for (var j = 0, jmax = row.length - 1; j <= jmax; ++j) { str += '"' + row[j].replace(/"/g, '""') + '"'; if (j !== jmax) { str += ','; } } str += '\n'; } return str; }; var createDataUriFromString = function(str) { return 'data:text/csv,' + encodeURIComponent(str); } var downloadDataUri = function(uri, filename) { var link = document.createElement('a'); link.download = filename; link.href = uri; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; return function(table, filename) { if (!filename) { filename = 'output.csv'; } var uri = createDataUriFromString(tableToCsvString(table)); downloadDataUri(uri, filename); }; })(); // Gemimiからメッセージ取得 const xpathExpr = '//*[starts-with(@id, "message-content-id-")] | //*[starts-with(@id, "user-query-content-")]'; const result = document.evaluate(xpathExpr, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); let result_for_csv = []; for (let i = 0; i < result.snapshotLength; i++) { const node = result.snapshotItem(i); let user_or_bot = ''; if (node.id.startsWith('message-content-id-')) { user_or_bot = 'bot'; } else if (node.id.startsWith('user-query-content-')) { user_or_bot = 'user'; } result_for_csv.push([user_or_bot, node.id, node.innerText]); } // CSV形式でダウンロード downloadCsv(result_for_csv);
注意
- 現在画面に表示されている要素しかダウンロードできません。そのため、会話履歴が長い場合は、先頭までスクロールした後に実行する必要があります。
- 「開く」より参照するワード形式のようなものだったり、添付画像についてはここでは取得していません。
追記おまけ)2026/04/08 GoogleNotebookLMの会話履歴のJavaScript取得(Chromeデベロッパーツール経由)
同様にNotebookLMから会話履歴を取得したい状況になりましたので。 今回はAIエージェントさんが勝手にスクリプト化してくれました。
(() => { // 1メッセージペア(ユーザー+AI)を囲う要素 const pairSelector = '.chat-message-pair'; const normalizeText = (text) => (text || '').replace(/\s+/g, ' ').trim(); // UI系のノイズを消す const cleanElementText = (el) => { const cloned = el.cloneNode(true); // ボタン類やthumb_up/downは削除 cloned.querySelectorAll('button, svg, img').forEach(n => n.remove()); let text = cloned.innerText || ''; return normalizeText( text .replace(/keep メモに保存/g, '') .replace(/keepメモに保存/g, '') .replace(/copy_all/g, '') .replace(/thumb_up/g, '') .replace(/thumb_down/g, '') ); }; const pairs = Array.from(document.querySelectorAll(pairSelector)); if (pairs.length === 0) { console.warn('chat-message-pair が見つかりませんでした。セレクタを確認してください。'); return; } const rows = []; let index = 1; pairs.forEach(pair => { // ユーザー側 const userNode = pair.querySelector('.to-user-container') || pair.querySelector('[data-author="user"]'); if (userNode) { const content = cleanElementText(userNode); if (content) { rows.push({ index: index++, role: 'user', content, }); } } // AI側(from-lm-container が本命。なければ user 以外のテキストブロックを推測) let assistantNode = pair.querySelector('.from-lm-container') || pair.querySelector('[data-author="assistant"]'); if (!assistantNode) { // フォールバック: pair 内の大きめテキストブロックから userNode 以外を拾う const divs = Array.from(pair.querySelectorAll('div')); assistantNode = divs.find(d => d !== userNode && normalizeText(d.innerText).length > 0); } if (assistantNode) { const content = cleanElementText(assistantNode); if (content) { rows.push({ index: index++, role: 'assistant', content, }); } } }); if (rows.length === 0) { console.warn('メッセージが抽出できませんでした。DOM 構造を再確認してください。'); return; } // CSV 化 const escapeCsv = (value) => { const str = String(value ?? ''); if (/[",\n]/.test(str)) { return `"${str.replace(/"/g, '""')}"`; } return str; }; const header = ['index', 'role', 'content']; const csvLines = [ header.join(','), ...rows.map(row => [ escapeCsv(row.index), escapeCsv(row.role), escapeCsv(row.content), ].join(',')), ]; const csvText = csvLines.join('\n'); // ダウンロード const blob = new Blob([csvText], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); const timeStr = new Date().toISOString().replace(/[:.]/g, '-'); a.href = url; a.download = `notebooklm_chat_${timeStr}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); console.log(`CSV 出力完了: ${rows.length}件のメッセージ`); })();
クリップボードの中身を確認するWebツールを公開しました(書式情報あり/なしと画像)
はじめに
slackのメッセージをコピペしたら、箇条書きにしていたインデントが消えてしまって悲しい気持ちに。

そもそもコピーしたslackのメッセージの状態を書式情報ありで確認したいなと思ったのですが、クリップボードの中身を書式情報ありとして確認するためのツールが意外と無いなと気付きまして、そこでwebでサクッと確認できたらいいなと思いGlitchにデプロイしてみました。

公開サイト
こちらサイトにデプロイしていますので、ぜひお試しくださいませ。 clipboard-check-and-slack-message-indenter.glitch.me
突貫で作成しましたので、バグがあるかもしれません。
続きを読むEXIFのOrientationをコマンドラインで取得・更新する
やりかた
# EXIF Orientation取得 $ exiftool -Orientation hoge.jpg # EXIF Orientation更新 $ exiftool -Orientation="Horizontal" hoge.jpg
※事前にexiftoolをインストールしておく必要があります。macの場合はbrew install exiftool
おまけ
ディレクトリにあるjpg画像*1ファイル全てに対して、
EXIF Orientationが設定されていないファイルに"Horizontal"を一括設定するBashワンライナー
$ for file in `ls *.jpg`; do if [ "$(exiftool -Orientation $file)" = "" ]; then exiftool -Orientation="Horizontal" $file ; fi ; done
Ubuntu(Jetson)のWiFi接続先をSSH経由(CLI)で変更するメモ

はじめに
WiFiルーターの買い替えでUbuntu(Jetson)のWiFi接続先の変更が必要になったのですが、
設定変更するにあたりGUIを使わずSSH通信経由(CLI)で変更する手順メモです。
SSH経由でも通信が切断されずに無事変更できました。
やりかた
# 現状の確認 $ nmcli device wifi list $ nmcli device status # WiFi接続先追加(プロファイル作成) $ sudo nmcli con add type wifi con-name "[接続名]" ifname wlan0 ssid "[SSID]" $ sudo nmcli con modify "[接続名]" wifi-sec.key-mgmt wpa-psk $ sudo nmcli con modify "[接続名]" wifi-sec.psk "[SSIDに対応するパスワード]" # WiFi接続先変更 $ sudo nmcli con up "[接続名]"
