Classi開発者ブログ

教育プラットフォーム「Classi」を開発・運営するClassi株式会社の開発者ブログです。

ブラウザ拡張機能をChatGPTと作成した記録

先日「Google Meet Chat to Clipboard」というChrome拡張機能を公開しました。Google Meetから退出する時に、チャットが残っていれば自動でクリップボードに保存するというものです。

このアイデアは、社内のSlackでたびたび見かけた「Google Meetのチャットを保存し忘れた」という問題から生まれました。

個人のプロジェクトとして作成・公開しましたが、きっかけは社内にありました。そして、ChatGPTに併走してもらいながら制作した過程について少し話したところ、思った以上に興味をもってもらい、ここで紹介することになりました。

こういった弊社での小ネタ的ChatGPTの活用事例は、ChatGPTと大規模言語モデルのLT会を開きました - Classi開発者ブログでも紹介されていますので、興味があるかたはご覧ください。

制作過程

冒頭にもあるとおり、最初からChatGPTとやりとりしながら作成しました。

私自身はブラウザ拡張機能の制作経験はあったので、完全にピントがずれるようなことがあれば気づくことができます。期待度としては「お手並み拝見」もしくは「ちょっと楽できたらいいな」ぐらいでした。結果、期待を大きく越えてくれ、うれしい誤算でした。

利用したのはGPT-4です。(注:個人でChatGPT Plusを契約して利用しています。紹介している事例はあくまでも個人のブラウザ拡張機能制作のための利用です。)

当然一発で完成するとは思っていないので、まずはざっくりと作成してもらいます。

私:
GoogleMeetで退出するときにその時点のチャットの内容をクリップボードにコピーするアドオンを作ろうと思います。まずはボイラープレートを作ってください。

GPT:
Google Chromeの拡張機能を作成することで、この要件を満たすことができます。以下に基本的な構造を示します。

manifest.json まず、以下のようにmanifest.jsonファイルを作成します。これは拡張機能の設定を定義する重要なファイルです。

(略)

popup.html 次に、ユーザが拡張機能のアイコンをクリックしたときに表示するポップアップを作成します。

(略)

popup.js そして、ポップアップにロジックを追加します。この例では、エクスポートボタンがクリックされたときに、background scriptを呼び出すメッセージを送信します

(略)

background.js 最後に、バックグラウンドスクリプトを作成します。これが実際にページのDOMを解析し、チャットの内容をクリップボードにコピーします。ただし、DOMの解析に関してはGoogle Meetの現行のHTML構造に依存しています。その構造が変わると、このコードは機能しなくなる可能性があります。

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  if (request.action === 'exportChat') {
    chrome.tabs.executeScript({
      code: `
        let chatMessages = [...document.querySelectorAll('.chat-message-element-class')].map(el => el.innerText);
        navigator.clipboard.writeText(chatMessages.join('\\n'));
      `
    });
  }
});

注意: Google MeetのページのDOM構造については適宜確認して、querySelectorAllの引数(上記のコードでは.chat-message-element-class)を適切なクラス名に置き換えてください。

これらのファイルを一つのディレクトリに保存し、Google Chromeの拡張機能ページでディレクトリを読み込むことで、新しい拡張機能をインストールできます。ただし、このプロセスはChromeが開発者モードに設定されている必要があります。

chrome.runtime.onMessage の内容が実装のキモの部分だと理解したので、この動作をどのように起動させるかを詰めていきます。

私:
chrome.runtime.onMessage はどのタイミングで発火しますか?

GPT:
chrome.runtime.onMessageイベントは、Chrome拡張機能の異なる部分からメッセージが送信されたときに発火します。具体的には以下のような状況です:

Content scriptsやbackground scriptsからpopup scriptsへのメッセージ送信
Popup scriptsやbackground scriptsからcontent scriptsへのメッセージ送信
同じ拡張機能内の異なるbackground scripts間でのメッセージ送信

(略)

私:
meetから退出するタイミングで実行したい場合、イベント監視をどのようにすればいいでしょう?

GPT:
Google Meetから退出するタイミングを監視するためには、Content Scriptを使用してページのURLの変更を監視するのが一つの方法です。Google Meetから退出するとURLが変更されますので、この変更をトリガーにしてチャットの内容をクリップボードにコピーする処理を行います。

私:
でも、この場合、当初の目的である、「チャットをクリップボードに保存する」は実現できないことになりませんか?

GPT:
すみません、その通りです。Google Meetから退出すると、既にページがアンロードされ、チャットの情報も消えてしまうため、その時点でチャットの情報をクリップボードに保存することは難しいです。

そのため、代替案として以下の方法が考えられます:

定期的にチャットの情報を保存する: 例えば、Content Scriptを使って1分ごとにチャットの内容を保存し、その情報をBackground Scriptに送信します。これにより、最新のチャットの内容を常に保持できます。

「退出前に保存」ボタンを作成する: ユーザーがGoogle Meetから退出する前にボタンをクリックして、その時点のチャットの内容をクリップボードに保存することができます。

ページ移動の監視をより具体的にする: Google Meetのミーティングから退出する前に特定のDOM要素(例えば、"You have left the meeting" と表示されるエレメント)が現れるなど、より具体的な指標を探してそれを監視します。ただし、これはGoogle MeetのUIに依存するため、UIが変わると機能しなくなる可能性があります。

なお、上記の方法にはそれぞれ利点と欠点があります。要件や実現可能性によって適切な方法を選んでください。

私:
「aria-label="通話から退出"」をクリックしたタイミングでチャットを保存するという方針だと実装はどうなりますか?

GPT:
「通話から退出」のボタンがクリックされたタイミングをキャッチするには、MutationObserver APIを使用してDOMの変更を監視することができます。

次に示すのは、該当のボタンがクリックされたときにチャットを保存するためのcontent.jsのコードです。

(略)

ここで基本的な動作に関して一通り用意できたと判断して、実際にブラウザ上で動作させながらデバッグを開始しました。

私:
content.js で chrome.runtime.sendMessageするとエラーになりました。

GPT:
すみません、問題を理解しました。Content ScriptからBackground Scriptにメッセージを送るためには、manifest.jsonに"tabs"や"activeTab"などの適切なパーミッションが必要となります。

manifest.jsonは次のようになります。

(略)

なお、エラーメッセージが出ている場合は、具体的なエラーメッセージを教えていただけると、より詳しい解決策を提供することが可能です。

私:
Uncaught TypeError: Cannot read properties of undefined (reading 'sendMessage') というエラーでした

GPT:
(略)

Manifest V3ではnavigator.clipboard.writeTextのような非同期APIはService Workerでは使用できないため、代替の方法を探す必要があります。例えば、Background ScriptからPopup Scriptにメッセージを送り、Popup内でクリップボードにテキストを保存するなどの方法が考えられます。

私:
navigator.clipboard.writeText は content.js に記述すれば使用できますか?

GPT:
はい、その通りです。navigator.clipboard.writeTextはウェブページのコンテキスト(この場合はcontent.js)で使用可能です。そのため、content.js内でnavigator.clipboard.writeTextを使ってクリップボードにテキストを書き込むことができます。

ただし、navigator.clipboard.writeTextを使用するにはページがアクティブ(フォーカスがあたっている)である必要があります。この要件はContent Scriptに通常適用されますが、Google Chromeのバージョンや設定によってはこの操作がブロックされる可能性もあります。

そのため、以下のようにcontent.jsを修正することで、Google Meetから退出するタイミングでチャットの内容をクリップボードに保存することが可能となります。

(略)

この辺りで一通りの動作が実装完了。あとはパッケージ化するために無駄なファイルなどについて。

私:
background.js でやることがなくなった場合、どう書いておけば良いでしょう?

GPT:
background.jsが何も実行しない場合、空のファイルとして残すか、manifest.jsonから"background"フィールドを削除することで対応できます。

(略)

私:
popup.htmlでも特にやることないのですが、こちらも削除して良いですか?

GPT:
はい、popup.htmlが不要であれば、そのファイルとmanifest.json内の"popup"に関する参照を削除することができます。

(略)

これで完了、Chromeストアへの登録を行い無事公開されました。

私:
狙ったとおりにできました。ありがとうございました。

GPT:
それは素晴らしいニュースです!問題が解決し、目標を達成できたことを聞いて非常に嬉しいです。

このあと、社内Slackで公開しました。ChatGPTとのやりとりを始めて数時間でストア登録までさっくりと進みました。

社内でも一定のリアクションがあり、「退出ボタン押さずにタブ閉じた場合も対応してほしい」などのフィードバックがあったので、現在はv3となっています。

ChatGPTとふりかえりもしてみた

制作時のやりとりを一通り読み込んでもらい、レポートにしてもらいました。 さすがは要点をまとめるのが上手い。

プロジェクト名:Google Meetチャットのクリップボードエクスポーター プロジェクトの背景: Google Meetのチャット内容を簡単に保存したいというニーズがあった。それを可能にするためのGoogle Chrome拡張機能を開発することになった。

課題: 初期の設計ではbackground.jsでクリップボード操作を行う予定だったが、実際にはcontent.jsから直接navigator.clipboard.writeTextを使用してクリップボードにアクセスすることが可能であることが分かった。また、ポップアップUI(popup.html)も特に必要ないことが明らかになった。

解決策: 以下の手順でコードを改良した:

content.jsにnavigator.clipboard.writeTextを使用してクリップボードにテキストを書き込むように変更。これはGoogle Meetから退出するタイミングで行うようにした。

background.jsは何も実行しないようにした。つまり、background.jsファイルは空のまま残し、manifest.jsonから"background"フィールドを削除した。

ポップアップUI(popup.html)が不要であることが分かったので、そのファイルとmanifest.json内の"popup"に関する参照を削除した。

結果: 上記の改良により、Google Meetのチャット内容を簡単にクリップボードにコピーすることが可能なChrome拡張機能を開発することができた。これにより、ユーザーは会議のチャットログを容易に保存し、後で参照することができる。

さらには、私のプロンプトの使い方も評価してもらいました。

あなたのプロンプトの使い方は非常に効果的でした。以下に具体的な評価ポイントを列挙します:

  1. 明確な問い: 各プロンプトは明確で具体的な問いを提供し、特定の情報またはアクションを求めていました。これは効果的なコミュニケーションを促し、具体的な解答を引き出すのに役立ちました。

  2. コンテキストの提供: あなたは必要なコンテキストを適切に提供しました。これは特に技術的な質問において重要で、AIがあなたの問題を理解し、適切な解答を提供するのに役立ちました。

  3. フィードバックの提供: あなたが提案された解答についてフィードバックを提供したことは非常に有益でした。これによりAIはその解答が役立ったのか、または更なる情報が必要であったのかを理解することができました。

  4. ステップバイステップの進行: あなたは一つの問題が解決したら次に進み、全体のプロセスをスムーズに進めました。これによりAIは一つ一つの問題に集中して取り組むことができました。

総じて、あなたのプロンプトの使い方は非常に効果的であり、AIとの対話を円滑に進めるのに役立っていました。このような質問の立て方は、AIの能力を最大限に引き出す上で非常に重要です。

空気読んだな(笑)。それはともかくとして、ChatGPTと何かを進めていくときに上記4項目は大事なんだなと思いました。

業務外の個人活動の話ではありますが、ChatGPTを利用したツール制作の一例として参考になれば幸いです。

© 2020 Classi Corp.