どこでも見れるメモ帳

とあるSEの備忘録。何かあれば気軽にコメントください〜

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以外のサービスにコピペすると、インデント情報が消えてしまう

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

クリップボードの中身を書式情報ありなし、画像それぞれを確認できるWebツール。Slackメッセージのインデント機能付き。

公開サイト

こちらサイトにデプロイしていますので、ぜひお試しくださいませ。 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

*1:lsのjpgをpngなどにも対応させたらいろんな形式で対応できるはず。

Ubuntu(Jetson)のWiFi接続先をSSH経由(CLI)で変更するメモ

f:id:ni66ling:20210302235652p:plain

はじめに

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 "[接続名]"
  • [SSID], [SSIDに対応するパスワード]はWiFiルーターの設定値を入力ください。
  • [接続名]は任意で入力ください。

参考

qiita.com

YouTubeDataAPIv3をPythonから使って最近の動画傾向を調べてみた

f:id:ni66ling:20201230192240p:plain
YouTubeDataAPIv3をPythonから使って最近の動画傾向を調べてみた

はじめに

2020年はコロナ特需でYouTubeは大変賑わいましたね。
そこで、特にプログラミングや機械学習に関してYouTube動画の現状を知りたく、
YouTubeDataAPIv3を用いて簡単に調べてみました。

やったこと

  • Youtube Data API v3 を pythonから呼び出して公開されている動画のメタ情報を取得
  • 閲覧数やコメント数、いいね数などを簡単に基礎集計
  • 動画タイトルの共起ネットワークをGephiで確認
続きを読む