程式碼研究室:使用 Gemini 以 JavaScript 建構 Chrome 擴充功能

1. 簡介

你是否想加入 Meet 通話,但不想成為第一位這麼做的參與者?如果這個情況符合您的描述,我們就有適合您的解決方案!

跟著本程式碼研究室學習,您將建立 Chrome 擴充功能,在第一位參與者加入通話時通知您。

您將瞭解 Chrome 擴充功能的不同元素,並深入瞭解擴充功能的各個部分。您將瞭解擴充功能函式,例如內容指令碼、服務工作處理程序和訊息傳遞。

您必須遵循資訊清單 v3 版本,才能在參與者加入會議通話時收到通知。

2. 事前準備

必要條件

雖然本程式碼研究室適合初學者使用,但具備 JavaScript 基礎知識,可大幅改善使用者體驗。

設定/需求

  • Chrome 瀏覽器
  • 本機系統上的 IDE/Editor 設定。
  • 如要透過 gcloud 啟用 Gemini API,請安裝 gcloud cli

啟用 Gemini API

Note that if you're writing the code in the Cloud Shell editor,
then you will have to download the folder somewhere on your local filesystem to test the extension locally.

3. 讓我們一同展開歡樂旅程吧

基本擴充功能安裝

我們要建立一個目錄,用來做為專案的根目錄。

mkdir gemini-chrome-ext
cd gemini-chrome-ext

開始向 Gemini 詢問特定問題前,讓我們先思考一些問題,瞭解 Chrome 擴充功能的一般結構。

提示:

What are the important parts to build a chrome extension?

我們會收到回應,指出 manifest 檔案、background script 以及使用者介面的細節。現在就來進一步瞭解這些特定檔案。

提示:

Create a manifest.json file to build a chrome extension.
Make the name of the extension "Meet Joinees Notifier"
and the author "<YOUR_EMAIL>"

您可以在作者欄位中使用需要的名稱和電子郵件地址。

Gemini 會傳回我們需要的資訊清單檔案內容,但會得到一些我們不需要的額外欄位,例如 action 欄位。同時也需要說明讓我們一起解決這個問題!

提示:

Remove the "action" field and make the description as
"Adds the ability to receive a notification when a participant joins a Google meet".

讓我們將這項內容放入專案根目錄的 manifest.json 檔案中。

在這個階段,資訊清單檔案應如下所示。

{
    "name": "Meet Joinees Notifier",
    "version": "1.0",
    "manifest_version": 3,
    "description": "Adds the ability to receive a notification when a participant joins a Google Meet",
    "author": "<YOUR_EMAIL>"
}

目前請移除資訊清單檔案中產生的任何其他欄位,因為本程式碼研究室會假設這類欄位位於資訊清單檔案中。

那麼,該如何測試擴充功能是否正常運作?我們來問朋友 Gemini,對吧?

提示:

Guide me on the steps needed to test a chrome extension on my local filesystem.

它還提供了測試步驟。讓我們前往 chrome://extensions 前往 "Extensions Page",並務必啟用 "Developer Mode" 按鈕。這個按鈕應會顯示 "Load unpacked" 按鈕,方便我們前往本機含有副檔名檔案的資料夾。完成後,我們應該就能在 "Extensions Page" 中看到擴充內容。

3d802a497ce0cfc2.png

92db1999a1800ecd.png

太好了!我們可以看到擴充功能,但讓我們開始新增一些功能。

4. 新增內容指令碼

我們只想在 https://meet.google.com 上執行部分 JavaScript 程式碼,以便我們使用內容指令碼進行操作。讓我們問 Gemini,如何利用擴充功能達成這個目標。

提示:

How to add a content script in our chrome extension?

其他可更具體的說明:

提示:

How to add a content script to run on meet.google.com subdomain in our chrome extension?

其他版本:

提示:

Help me add a content script named content.js to run on meet.google.com subdomain
in our chrome extension. The content
script should simply log "Hello Gemini" when we navigate to "meet.google.com".

Gemini 提供了在 manifest.json 檔案和 content.js 檔案中所需的 JavaScript 確切變更。

加入 content_scripts 後,資訊清單檔案會變成:

{
    "name": "Meet Joinees Notifier",
    "version": "1.0",
    "manifest_version": 3,
    "description": "Adds the ability to receive a notification when a participant joins a Google Meet",
    "author": "abc@example.com",
    "content_scripts": [
        {
          "matches": ["https://meet.google.com/*"],
          "js": ["content.js"]
        }
    ]
}

當我們瀏覽到子網域中的網頁時,content.js就會指示 Chrome 插入內容指令碼。https://meet.google.com。」我們來新增這個檔案,然後進行測試吧?

讓我們在 content.js 檔案中新增這段程式碼。

console.log("Hello Gemini");

當然可以!造訪 meet.google.com 時會看到「Hello Gemini」,即可開啟 JavaScript 控制台(Mac:Cmd + Opt + J / Win/Linux:Ctrl + Shift + J)。

manifest.json

{

"name": "Meet Joinees Notifier",

"version": "1.0",

"manifest_version": 3,

"description": "Adds the ability to receive a notification when a participant joins a Google Meet",

"author": "luke@cloudadvocacyorg.joonix.net",

"permissions": [

    "tabs",

    "notifications"

],

"content_scripts": [

    {

        "matches": [

            "https://meet.google.com/*"

        ],

        "js": [

            "content.js"

        ]

    }

]

}

content.js

console.log("你好,Gemini!");

6216bab627c31e6c.png

d61631cd9962ffe5.png

太好了!現在,我們可以在應用程式中新增一些 JavaScript 專屬功能。請花點時間,想想我們想達成什麼目標

改善內容腳本

我們希望能夠在使用者加入會議時接收通知(如果系統提供加入會議的選項),要達成這個目標,我們要觀察當會議沒有參與者和加入會議時,螢幕在視覺上會有什麼變化。

這是會議沒有參與者時顯示的樣子。

fe5a0c95b20e7f72.png

而這些影像只是部分會議參與者的影像。

7a5ef60521d961cc.png

從畫面開始,我們發現有 2 種顯著差異:

  1. 狀態文字會從「沒有其他人在這裡」變更變更為「[使用者] 已加入這場通話」。
  2. 我們可以看到加入通話的使用者圖片。

如果我們想知道是否有人參加會議,上述兩項變更都是可行的,但後者或許也能取得已經加入的使用者相關資訊,所以我們來試試這項功能吧。

開啟「元素檢查器」在 Chrome 中使用快速鍵 ( Mac:Cmd + Opt + C / Win: Ctrl + Shift + C),然後按一下已加入的使用者的圖片。

您會發現圖片含有少量類別字串,圖片的標題屬性則會顯示加入會議的使用者名稱。此外,這個圖片代碼會包裝在 U04fid 類別的 div 中。新增幾位參與者至測試會議後,我們就能看到這個父項 div 代管多張圖片(對應至不同使用者)。

因此,我們制訂了其中一部分的策略:

  1. 偵測含有 U04fid 類別的 div 是否包含任何元素。
  2. 如果有,就代表我們有與會者。
  3. 通知使用者。

請回到 Gemini,逐步詢問我們如何逐步達成這項目標。

提示:

How to detect for element changes in the HTML page?

我們確實提供了與 MutationObserver 相關的解決方案,但這沒有與我們的要求明確有關,我們可以嘗試修改提示。

提示:

How can I detect if an element "div.U04fid" has any child images in the HTML page?

這次我們取得的解決方案不含 MutationObserver,而是使用父項 div 檢查彙整人員的解決方案。

我們收到的內容如下:

const divU04fid = document.querySelector('div.U04fid');
const childImages = divU04fid.querySelectorAll('img');
if (childImages.length > 0) {
  // div.U04fid has at least one child image.
}

讓我們來進一步修改做法:

提示:

Create a method which checks if the element div.U04fid has any child elements,
if it does it should log it on the console. The method should be called checkJoinees
and it should be called as a callback for MutationObserver which is observing the document.body.

它會傳回以下程式碼片段:

function checkJoinees(mutations) {
    for (const mutation of mutations) {
      if (mutation.type === 'childList') {
        // A child node was added or removed.
        if (document.querySelector('div.U04fid') && document.querySelector('div.U04fid').childElementCount > 0) {
          // div.U04fid has at least one child element.
          console.log('div.U04fid has at least one child element.');
        }
      }
    }
    return false;
}

const observer = new MutationObserver(checkJoinees);
observer.observe(document.body, {
  childList: true,
  delay: 1000
});

請將此程式碼複製到 content.js 檔案中,重新載入擴充功能(然後重新載入會議頁面)。

現在,每當有人加入會議時,即可在控制台中看到記錄陳述式。

5. 傳送通知給使用者

我們現在能偵測參與者是否加入會議,現在請嘗試在 Chrome 擴充功能中新增通知部分。我們可以瀏覽 Chrome 擴充功能的說明文件,甚至調整提示來掌握我們要尋找的項目,但基本上,我們需要使用 chrome.notifications.create API,而此方法的呼叫應來自背景 Service Worker。

提示:

Using the documentation for chrome notifications tell me how to use the chrome.notifications.create method.

主要重點如下:

  • 在資訊清單檔案中新增 notifications 權限。
  • 呼叫 chrome.notifications.create
  • 呼叫應在背景指令碼中。

如要在 manifest version 3 的 Chrome 擴充功能中新增背景指令碼,您需要 manifest.json 檔案中的 background.service_worker 宣告。

因此,我們建立一個名為 background.js 的檔案,並將下列內容加入 manifest.json 檔案:

"background": {
        "service_worker": "background.js"
},
"permissions": [
        "notifications"
]

上述新增完成後,資訊清單檔案就會變成:

{
    "name": "Meet Joinees Notifier",
    "version": "1.0",
    "manifest_version": 3,
    "description": "Adds the ability to receive a notification when a participant joins a Google Meet",
    "author": "<YOUR_EMAIL>",
    "content_scripts": [
        {
          "matches": ["https://meet.google.com/*"],
          "js": ["content.js"]
        }
    ],
    "background": {
        "service_worker": "background.js"
    },
    "permissions": [
            "notifications"
    ]
}

提示:

Create a method sendNotification that calls the chrome.notifications.create
method with the message, "A user joined the call" for a chrome extension with manifest v3,
the code is in the background service worker

將此圖片儲存在資料夾的根目錄中,並重新命名為 success.png

b2c22f064a3f2d9c.png

然後將下列程式碼片段加進 background.js

function sendNotification(notificationId, message) {
    chrome.notifications.create(notificationId, {
      type: "basic",
      title: "A user joined the call",
      message: message,
      iconUrl: "./success.png"
    });
}

sendNotification("notif-id", "test message");

現在,從擴充功能頁面重新載入擴充功能,應該就會立即顯示通知彈出式視窗。

6. 在 Chrome 擴充功能中新增訊息

現在,最後一個主要步驟是將內容指令碼對參與者的偵測建立連結,以及背景指令碼中的 sendNotification 方法。在 Chrome 擴充功能中,方法就是透過名為 message passing 的技術。

如此一來,Chrome 擴充功能的各部分之間就能相互通訊,以本例來說內容指令碼到背景服務工作站。讓我們問問 Gemini,幫助 Gemini 達成這個目標。

提示:

How to send a message from the content script to the background script in a chrome extension

Gemini 會使用對chrome.runtime.sendMessagechrome.runtime.onMessage.addListener的來電進行回覆。

基本上,我們將使用 sendMessage 透過使用者加入 Meet 通話的內容指令碼傳送訊息,並使用 onMessage.addListener 做為事件監聽器來回應內容指令碼傳送的訊息。在此情況下,我們會從這個事件監聽器觸發對 sendNotification 方法的呼叫。

我們會將通知訊息和 action 屬性傳遞至背景 Service Worker。action 屬性說明背景指令碼回應的內容。

以下是 content.js 程式碼:

function checkJoinees(mutations) {
    for (const mutation of mutations) {
      if (mutation.type === 'childList') {
        // A child node was added or removed.
        if (document.querySelector('div.U04fid') && document.querySelector('div.U04fid').childElementCount > 0) {
          // div.U04fid has at least one child element.
          sendMessage();
        }
      }
    }
    return false;
}

const observer = new MutationObserver(checkJoinees);
observer.observe(document.body, {
  childList: true,
  delay: 1000
});

function sendMessage() {
    chrome.runtime.sendMessage({
        txt: "A user has joined the call!",
        action: "people_joined"
    });
}

以下是我們的 background.js 程式碼:

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === "people_joined") {
      sendNotification("notif-id", message.txt);
    }
  });
  

function sendNotification(notificationId, message) {
    chrome.notifications.create(notificationId, {
      type: "basic",
      title: "A user joined the call",
      message: message,
      iconUrl: "./success.png"
    });
}

我們可以試著自訂通知訊息並取得專屬的通知 ID。至於通知訊息,我們可以加入使用者名稱,如果我們從上一個步驟中召回使用者,可在圖片的標題屬性中看到使用者名稱。我們可以使用 document.querySelector('div.U04fid > img').getAttribute('title'). 擷取參與者的名稱

關於通知 ID,我們可以擷取內容指令碼的分頁 ID 並用做通知 ID。這可透過使用 sender.tab.id. 在事件監聽器 chrome.runtime.onMessage.addListener 內完成

最後,檔案應如下所示:

manifest.json

{
    "name": "Meet Joinees Notifier",
    "version": "1.0",
    "manifest_version": 3,
    "description": "Adds the ability to receive a notification when a participant joins a Google Meet",
    "author": "<YOUR_EMAIL>",
    "content_scripts": [
        {
          "matches": ["https://meet.google.com/*"],
          "js": ["content.js"]
        }
    ],
    "background": {
        "service_worker": "background.js"
    },
    "permissions": [
            "notifications"
    ]
}

content.js

function checkJoinees(mutations) {
    for (const mutation of mutations) {
      if (mutation.type === 'childList') {
        // A child node was added or removed.
        if (document.querySelector('div.U04fid') && document.querySelector('div.U04fid').childElementCount > 0) {
            const name = document.querySelector('div.U04fid > img').getAttribute('title');
            sendMessage(name);
        }
      }
    }
    return false;
}

const observer = new MutationObserver(checkJoinees);
observer.observe(document.body, {
  childList: true,
  delay: 1000
});

function sendMessage(name) {
    const joinee = (name === null ? 'Someone' : name),
        txt = `${joinee} has joined the call!`;

    chrome.runtime.sendMessage({
        txt,
        action: "people_joined",
    });
}

background.js

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === "people_joined") {
      sendNotification("" + sender.tab.id, message.txt); // We are casting this to string as notificationId is expected to be a string while sender.tab.id is an integer.
    }
  });
  

function sendNotification(notificationId, message) {
    chrome.notifications.create(notificationId, {
      type: "basic",
      title: "A user joined the call",
      message: message,
      iconUrl: "./success.png"
    });
}

7. 恭喜

短時間內,我們借助 Gemini 建構 Chrome 擴充功能。無論你是經驗豐富的 Chrome 擴充功能開發人員,還是剛開始使用擴充功能,Gemini 都能協助完成任何工作。

建議你詢問 Chrome 擴充功能的用途。有許多 API 值得瀏覽,例如 chrome.storagealarms 等。無論遇到困難,都可以透過 Gemini 或說明文件瞭解問題所在,或是收集其他解決問題的方式。

您通常需要修改提示才能取得所需協助,但我們可以在單一分頁中保留所有背景資訊。