Codelab:使用 Gemini 使用 JavaScript 构建 Chrome 扩展程序

1. 简介

您是否要加入 Meet 通话,但不想成为第一个加入者?如果您属于这种情况,我们有适合您的解决方案!

完成本 Codelab 后,您将创建一个 Chrome 扩展程序,当第一个参与者加入通话时,该扩展程序会向您发出提醒。

您将了解 Chrome 扩展程序的各个元素,然后深入了解扩展程序的每个部分。您将了解内容脚本、服务工作线程和消息传递等扩展函数。

您需要遵循 Manifest V3 版本,才能在有参与者加入 Meet 通话时收到通知。

2. 准备工作

前提条件

虽然本 Codelab 适合初学者,但如果您对 JavaScript 有一定的基础了解,则可以大大提升学习体验。

设置/要求

  • Chrome 浏览器
  • 本地系统上的 IDE/编辑器设置。
  • 如果您希望使用 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>"
}

暂时移除清单文件中生成的所有其他额外字段,因为此 Codelab 假设清单文件中包含这些字段。

现在,我们如何测试扩展程序是否正常运行?让我们来问问 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"]
        }
    ]
}

这会告知 Chrome,每当导航到子网域“https://meet.google.com”中的网页时,都注入内容脚本 content.js。我们来添加此文件并进行测试吧。

我们来将此代码添加到 content.js 文件中。

console.log("Hello Gemini");

果然如此!当我们访问 meet.google.com 时,会在 JavaScript 控制台(Mac:Cmd + Opt + J / Windows/Linux:Ctrl + Shift + J)中看到“Hello Gemini”。

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("Hello Gemini!");

6216bab627c31e6c.png

d61631cd9962ffe5.png

太棒了!现在,我们可以向应用添加一些特定的 JavaScript 功能了。让我们花点时间思考一下我们想要实现什么。

改进内容脚本

我们希望能够在会议页面(我们可以在其中选择加入会议)上收到有人加入会议的通知。为了实现这一点,我们来观察一下会议为空时与有人加入会议时屏幕在视觉上的变化。

当会议中无人时,界面如下所示。

fe5a0c95b20e7f72.png

而当会议中有部分参与者时,界面会显示以下视觉效果。

7a5ef60521d961cc.png

我们可以立即看到 2 个显著差异:

  1. 状态文本从“没有其他人在通话中”更改为“[用户] 正在通话中”。
  2. 我们可以看到已加入通话的用户的图片。

如果我们想知道是否有人加入了会议,这两种更改都适用,但后者有可能获取有关已加入会议的用户的信息,因此我们不妨尝试使用后者。

使用快捷键(Mac:Cmd + Opt + C / Win: Ctrl + Shift + C)在 Chrome 中打开“元素检查器”,然后点击已加入的用户对应的图片。

我们可以注意到,有一张图片包含少量类字符串,并且该图片的 title 属性包含加入会议的用户的姓名。此外,此图片标记封装在具有 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 文件中,重新加载扩展程序(然后重新加载 Meet 页面)。

现在,当有人加入我们的会议时,我们能够在控制台中看到日志语句。

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 扩展程序的不同部分(在本例中为内容脚本到后台 Service Worker)之间能够进行通信。让我们问问 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 方法的调用。

我们将向后台 Service Worker 传递通知消息和 action 属性。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。对于通知消息,我们可以包含用户的姓名。如果您还记得上一步的内容,就会知道我们可以在图片的 title 属性中看到用户的姓名。因此,我们可以使用 document.querySelector('div.U04fid > img').getAttribute('title'). 获取参与者的姓名

关于通知 ID,我们可以获取内容脚本的标签页 ID,并将其用作通知 ID。这可以在我们的事件监听器 chrome.runtime.onMessage.addListener 中通过使用 sender.tab.id. 来实现

最后,我们的文件应如下所示:

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 或文档来了解自己哪里做错了,或者收集解决问题的不同方法。

通常需要修改提示才能获得所需的帮助,但我们可以在一个标签页中完成此操作,该标签页会保留我们的所有上下文历程。