ChatGPTの古いQ&Aも1クリックで印刷したい――画像生成まで対応するChrome拡張を作った

CodexやChatGPTで調べ物をしていると、「この質問と回答だけ紙で残しておきたい」と思うことがあります。

回答だけならコピーして別のアプリへ貼り付ければ済みます。しかし、あとから読み返す資料として考えると、質問と回答がセットになっている方が圧倒的に分かりやすくなります。

そこで最初に考えたのが、CodexのQ&Aを印刷するSkillでした。

ところが試しているうちに、「これをChatGPTでもできないだろうか」と思うようになりました。

さらに欲が出て、「昔のQ&Aまでスクロールして、その回答の横にあるボタンを押すだけで印刷できないか」と考えました。

最終的には、ChatGPTの各回答に「🖨 Q&A印刷」ボタンを追加し、通常の文章だけでなく画像生成Q&Aまで印刷できるChrome拡張機能へ発展しました。

今回は、その完成までの経緯を順番に振り返ります。


注)本記事の内容は正確性を保証するものではありません。

目次

まずCodexのQ&Aを印刷するSkillを考えた

直前の質問と回答を自動取得して印刷したい

最初に考えたのは、Codexの回答をコピーしてSkillを実行すると、その質問と回答をまとめて印刷する仕組みです。

単純な方法なら、コピーした回答をmacOSのクリップボードから、

pbpaste

で取得できます。

しかし、これだけでは「回答」しか取得できません。

質問も一緒に印刷するには、Codexのセッション履歴を利用する必要があります。

Codexのローカルセッションは、

~/.codex/sessions/

配下に保存されています。

そこで、最新のセッションから直前のユーザー質問とCodex回答を取得し、MarkdownをHTMLへ変換してA4向けに整形し、macOSの印刷ダイアログへ渡す仕組みを考えました。

操作としては、

Codexで質問
   ↓
回答が表示される
   ↓
Q&A印刷Skillを実行
   ↓
直前の質問+回答を取得
   ↓
Markdown → HTML印刷

となります。

この方式なら、回答をコピーする操作そのものも不要です。

3種類の印刷Skillを作る

用途によって使い分けられるよう、最初に3種類のSkillを用意しました。

Skill動作
print-latest-qa最新セッションの直前の質問+回答を印刷
print-clipboard-answerクリップボードにある回答だけを印刷
print-latest-session最新セッション全体をまとめて印刷

いずれもMarkdownをHTMLへ変換し、見出し、箇条書き、コードブロック、表、リンクなどをできるだけ維持した状態で印刷します。

印刷用HTMLは、

~/Documents/Codex_Print/

へ保存する仕様にしました。

Skill「print-latest-qa」を実施した結果、chromeに下記の新規ページが開き、MacOSの印刷ダイアログの画面が表示されました。

ただし、下記の「なお書き」に表示されている通り、質問内容に「print-latest-qa」という言葉が含まれ、Skillの実行指示だと判断された場合は、直前のQ&Aとは認められず、正しく表示・印刷できないことがあった。
結構、脆弱な機能であり、まだ、問題点を含んでいるようだ。


過去の任意のQ&Aも印刷したい

次に欲しくなったのが、「直前」ではなく、任意の過去の回答を指定して印刷する機能です。

そこで追加したのが、

print-copied-qa

です。

操作は、

Codexの任意の回答
   ↓
回答の最後の「コピーアイコン」をクリック
   ↓
print-copied-qaを実行
   ↓
クリップボードの回答を取得
   ↓
~/.codex/sessions/ を検索
   ↓
一致する回答を特定
   ↓
その直前の質問を取得
   ↓
質問+回答をHTML化
   ↓
印刷

となります。

これなら過去のセッションにある回答でも、コピーできれば対応する質問を探してQ&A単位で印刷できます。

SkillをFinderで確認する場合は、

open ~/.codex/skills/

で開けます。

Finderの「フォルダへ移動」を使うなら、⌘ + Shift + Gを押して、

~/.codex/skills/

を指定します。

同じことをChatGPTでもできないか

Codex版ができると、当然次に考えたのがChatGPTです。

ChatGPTでも、その場のQ&Aなら、「このQ&Aを印刷用HTMLにして」と頼めば、質問と回答をまとめてHTML化できます。

しかし、今回やりたかったのはもっと単純な操作です。

特に、長いチャットセッションのかなり前にあるQ&Aを印刷したい場合、毎回コピーして貼り付けるのは面倒です。

スクロールした古いQ&Aを印刷したい

長いセッションを上へスクロールして、昔のQ&Aを画面に表示させます。

ただし、画面上に表示しただけでは、その位置をChatGPT側が「印刷対象」と認識するわけではありません。

そこで、

  • 回答をコピーして指定する
  • 質問の冒頭を指定する
  • スクリーンショットを使う

といった方法を考えました。

しかし、もっと簡単にできそうです。

各回答に「Q&A印刷」ボタンを付ける

Chrome拡張機能を使えば、ChatGPTの画面DOMを監視して、各回答の近くに独自ボタンを追加できます。

つまり、

古いQ&Aまでスクロール
   ↓
回答がDOMに読み込まれる
   ↓
Chrome拡張機能が回答を検出
   ↓
[🖨 Q&A印刷]を追加
   ↓
クリック
   ↓
回答と直前の質問を取得
   ↓
HTML化印刷

という操作にできます。

古い回答がスクロールによって後から読み込まれる場合も、MutationObserverでDOMの変化を監視すれば対応できます。

これならコピーすら必要ありません。

ChatGPT Q&A Print 第1版を作る

そこで第1版のChrome拡張機能を作りました。

現在の最終バージョンの拡張機能

ChatGPTの各回答付近に、

🖨 Q&A印刷

というボタンを追加します。

目的の回答をクリックすると、その回答と直前の質問を取得し、A4印刷用HTMLを新しいページに表示して、印刷ダイアログを開く仕組みです。

v1でBlankページになった

最初の実機テストではボタン自体は表示されました。

しかし、クリックするとBlankページが表示され、正常に印刷ページを作れませんでした。

原因は、新しいabout:blankタブは開いているのに、

window.open(..., "noopener,noreferrer")

の戻り値がnullとなり、拡張機能側が「新規ページを開けなかった」と判断していたことでした。

そこでv1.1では、

window.open("", "_blank")

で通常のタブを開き、その後に印刷用HTMLを書き込む方式へ変更しました。

印刷と閉じるボタンが動かない

次は印刷ページ自体は表示されました。

ところがページ上の「印刷」「閉じる」ボタンが動作しません。

原因として考えられたのがChromeのCSPです。

印刷ページ内のonclickやインライン<script>を使うのをやめ、v1.2ではChrome拡張機能側からイベント処理を登録する方式へ変更しました。

これで、

Q&A印刷
   ↓
質問+回答ページ
   ↓
印刷ダイアログ

という基本機能は正常に動作するようになりました。

ここまではかなり順調でした。

セッションの画面で、上にスクロールすることによって、表示されていなかった古いQ&Aが呼び出される。
[🖨 Q&A印刷]のアイコンも、最初表示されていなくても、時間経過により現れるようになった。


問題は画像生成Q&Aです。

画像生成Q&Aも印刷したい

長いチャットで試したところ、通常の回答には「🖨 Q&A印刷」が表示されました。

しかし、「画像を作成」で生成した回答にはボタンが表示されません。

v1.3で画像生成回答を認識

そこで通常の文章回答だけでなく、画像生成結果やツール実行結果もAssistant回答として認識するようにしました。

これがv1.3です。

画像生成Q&Aにも「🖨 Q&A印刷」が表示されるようになりました。

ところが今度は、ボタンを押しても印刷ページに画像が出ません。

v1.4で画像読み込みを待つ

v1.4では、画面上に表示されている画像のcurrentSrcを取得し、それを印刷HTMLの<img src>へ設定する方法を試しました。

さらにsrcsetや遅延読み込み設定を外し、画像の読み込みが完了してから印刷ダイアログを開くようにしました。

それでも画像は表示されません。

v1.5でData URL化

次は画像URLをそのまま渡す方法をやめました。

画像データそのものを取得し、

生成画像
   ↓
画像データ取得
   ↓
Data URLへ変換
   ↓
印刷HTMLへ埋め込み
   ↓
画像読み込み完了
   ↓
印刷

という方式へ変更しました。

ところが、ここで再びCSP関連の問題が出ました。

CSPを避けるため印刷ページの構成を変更

原因を追った結果、about:blank + document.write()という構成そのものをやめることにしました。

v1.6では、

ChatGPT画面
   ↓
質問・回答・生成画像を取得
   ↓
画像をData URL化
   ↓
chrome.storageへ一時保存
   ↓
拡張機能専用 print.html
   ↓
質問+回答+画像を表示
   ↓
印刷

という構成へ変更しました。

print.htmlは、

chrome-extension://.../print.html

として開くため、ChatGPTページ側のCSPをそのまま受けません。

「印刷」「閉じる」の処理も外部のprint.jsへ移しました。

これでCSPに関する設計上の問題はかなり整理されました。

Extension context invalidated

ところが拡張機能を更新した直後、

Error: Extension context invalidated.

というエラーが発生しました。

これは新しいv1.6自体のエラーというより、拡張機能を差し替えたあと、開いたままのChatGPTタブに古いContent Scriptが残っていたことが原因でした。

Chrome拡張機能を更新すると、既存ページに注入されていた以前の拡張機能コンテキストは無効になります。

そのため、更新後にはChatGPTの対象タブも必ずリロードする必要があります。

この点は、その後のバージョンでも重要な確認事項になりました。

本当に画像を読み込んでいるのか

v1.6ではエラーが出なくなりましたが、まだ画像は表示されません。

しかも「Q&A印刷」を押してから新しいページが表示されるまでが非常に速く感じられました。

複数の画像があるのに、本当に画像を処理しているのだろうか。

この疑問から、v1.7では印刷準備の進捗を画面に表示することにしました。

例えば、

Q&Aを解析しています…
↓
画像を検出: 元画面 6枚 / 印刷対象 6枚
↓
画像を取得中: 1 / 6枚
↓
画像を取得中: 2 / 6枚
↓
……
↓
画像の埋め込み完了: 6枚
↓
印刷ダイアログ

という具合です。

同時に、生成画像を含んでいるbutton[role="button"]を不要要素として丸ごと削除しないよう修正しました。

ここまで来て、ようやく画像取得処理そのものが動き始めました。

今度は保存容量の上限に当たった

v1.7を試すと、

Resource::kQuotaBytes quota exceeded

というエラーが発生しました。

これはむしろ前進でした。

複数の高解像度画像をData URLへ変換できたため、そのデータ量がchrome.storage.local通常上限を超えてしまったのです。

そこでv1.8では、

  • unlimitedStorage権限を追加
  • 生成画像を最大2000×2800pxへ最適化
  • WebPを優先して圧縮
  • 印刷データの容量を画面表示
  • 古い一時印刷データを削除

といった対策を行いました。

複数の画像を扱えるところまで、ようやく進みました。

複数画像を正しく印刷する

次に起きたのは、少し意外な問題でした。

画像自体は印刷できるようになったものの、同じ画像が何度も出力されました。

ChatGPTの画像表示部分には、メイン画像だけでなく、サムネイルやスライダー内部の画像など、同じ画像を指す複数の要素が存在します。

それらをすべて取得した結果、同じ図解を何枚も印刷してしまったのです。

v1.9で重複除去

v1.9では、

  • メイン画像
  • サムネイル
  • スライダー内部画像

などを候補として取得し、同一画像を重複除去する方式にしました。

また、ChatGPTの画像ギャラリーDOMそのものは印刷せず、ユニークな画像だけで印刷ページを再構成しました。

画像がページ境界で途中分断されにくい指定も追加しました。

ところが、今度は1枚しか残りません。

v1.10ではサムネイルを除外しすぎていた

原因の一つは候補画像の絞り込みでした。

そこでv1.10では条件を緩め、

  • メイン表示画像
  • 小さなサムネイル
  • 非表示の複製画像

をまず候補に含め、その後で重複を判定する方式にしました。

それでも重複除去後は1枚でした。

v1.11でSHA-256による重複判定へ

最終的な原因は重複除去ロジックでした。

それまで画像URLを加工して比較していたため、別の画像まで同一画像と判断していた可能性がありました。

そこでv1.11では、URLではなく実際の画像データを使って判定します。

画像データのSHA-256ハッシュが完全一致した場合だけ同じ画像として除外する方式へ変更しました。

これによって、異なる生成画像はそれぞれ残り、本当に重複している画像だけを除外できるようになりました。

結果として、

  • 複数の生成画像を取得
  • 同一画像は重複除去
  • 異なる画像はすべて印刷
  • 画像がページ途中で分断されにくい

という、当初欲しかった動作にたどり着きました。

印刷プレビューで画像が分断されていた不具合も修正した

通常Q&Aでは画面に見えないブロックが印刷されることもある

念のため通常のテキストだけのQ&Aも印刷してみました。

すると、一部にChatGPT画面では見えていなかった灰色のブロックが表示されました。

これはv1.11固有の大きな不具合というより、現在採用している印刷方式による副作用です。

ChatGPTの回答DOMには、実際の本文だけでなく、

ChatGPTの回答DOM
├─ 本文
├─ 表
├─ コードブロック
├─ ファイル参照用内部要素
├─ 出典・リンク用内部要素
├─ 展開時だけ使う要素
└─ CSSで非表示になっている要素

などが含まれています。

ChatGPTの画面では本体CSSによって、

display: none;
overflow: hidden;
position: absolute;
visibility: hidden;

などが適用されているため、一部の内部要素は見えません。

ところが印刷ページは、

chrome-extension://.../print.html

という別ページです。

回答DOMを複製しても、ChatGPT本体のCSSまでは引き継がれません。

そのため、

ChatGPTでは非表示
   ↓
DOMをprint.htmlへコピー
   ↓
ChatGPTのCSSがない
   ↓
普通のHTML要素として表示

という現象が起こります。

将来的にはgetComputedStyle()を使って、本当に非表示になっている要素を判定して削除する方法も考えられます。

ただし、そこまで厳密に削除し始めると、今度は本来印刷したい表、コード、ファイル名、引用などまで誤って消してしまう可能性があります。

現状では実用上大きな支障はありません。

v1.11をひとまず完成版にする

今回の開発では、最初からv1.11のような構成を考えていたわけではありません。

実機で試すたびに問題が見つかりました。

最初は通常のQ&Aを印刷できればよかったものが、古いQ&A、画像生成、複数画像、重複画像へと対象が広がっていきました。

最終的にv1.11(ChatGPTチャット用)では、

  • 通常のQ&Aを質問+回答で印刷できる
  • 古いQ&Aもスクロールして表示すれば印刷できる
  • 各回答の「🖨 Q&A印刷」から実行できる
  • 表やコードを比較的崩さず印刷できる
  • 画像生成Q&Aを印刷できる
  • 複数の生成画像を取得できる
  • 重複画像を除外できる
  • 画像がページ途中で切れにくい

ところまで確認できました。

特に便利なのは、長いChatGPTセッションから必要なQ&Aだけを紙に残せることです。

操作も、

目的のQ&Aまでスクロール
   ↓
回答横の「🖨 Q&A印刷」
   ↓
質問+回答の印刷ページ
   ↓
印刷

だけです。

Codex用の印刷Skillを考えたところから始まった今回の試みでしたが、最終的にはChatGPTの長い会話を「必要なQ&Aだけ取り出して資料化する」ためのツールになりました。

細かな表示上の副作用は残っていますが、主要な機能は正常に動作しています。

そのため、現時点ではv1.11を安定版としていったん固定するのがよさそうです。

今後さらに改善する場合も、このv1.11を残した状態で別バージョンとして試す方が安全だと思います。

ChatGPTチャットの印刷に関するchrome拡張機能をChatGPTに作成してもらい、不具合修正を進めたが、バージョン番号(1.11)が示す通り、何度も修正を行う必要があった。細かい修正もあったので、少なくとも12回以上は不具合修正を繰り返した。本調査で得た教訓は「見えないものが多すぎる」である。ユーザーには見えない情報が多く隠れていて、それらが不具合の要因となる。
もう1つの教訓は、「AIと何度もやり取りをする状態になったら、『思考量』に目をやる」である。Codexのモデルは「ChatGPT-5.6 Sol」であったが、ChatGPT(チャット)の「思考量」が知らないうちに「Instant」になっていた。自分で「Instant」にするはずはないので、システム側で設定されたようだ。過去にも、ChatGPT 5.4になっていたりすることはあったが、「思考量」が変わっていたのには初めて気づいた。

画像の印刷については、今回の画像では問題なく印刷できたが、画像のサイズなどが変わった場合、A4に収まるように縮小したりする処理が必要になるかもしれない。使っているうちに、まだ、不具合が出てきそうである。

目次