Codelab: создание расширения Chrome на JavaScript с помощью Gemini

1. Введение

Вы хотите присоединиться к онлайн-встрече, но не хотите быть первым? Если это про вас, у нас есть для вас решение!

В ходе этого практического занятия вы создадите расширение для Chrome, которое будет оповещать вас, когда к звонку присоединится первый участник.

Вы изучите различные элементы расширения Chrome, а затем подробно рассмотрите каждую его часть. Вы узнаете о функциях расширения, таких как скрипты контента, сервис-воркеры и передача сообщений.

Для получения уведомлений о присоединении участников к онлайн-встрече необходимо использовать версию манифеста v3 .

2. Прежде чем начать

Предварительные требования

Хотя этот практический урок подходит для начинающих, базовое понимание JavaScript значительно расширит ваши возможности.

Настройка/Требования

  • Браузер Chrome
  • IDE/редактор, установленный на вашем локальном компьютере.
  • Установите cli-интерфейс gcloud, если хотите включить API Gemini с помощью gcloud.

Включите API Gemini

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>"
}

Временно удалите все остальные дополнительные поля, сгенерированные в вашем файле манифеста, поскольку в этом практическом занятии предполагается наличие этих полей в вашем файле манифеста.

Итак, как же проверить работоспособность расширения? Давайте спросим нашего друга Близнецов, хорошо?

Быстрый:

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

В инструкции даны шаги для тестирования. Перейдем на "Extensions Page" , используя chrome://extensions , и убедимся, что включена кнопка "Developer Mode" . После этого должна отобразиться кнопка "Load unpacked" , с помощью которой можно перейти в папку с файлами расширения, расположенную локально. После этого расширение должно отобразиться на "Extensions Page" .

3d802a497ce0cfc2.png

92db1999a1800ecd.png

Отлично! Мы видим наше расширение, но давайте начнем добавлять в него функциональность.

4. Добавьте скрипт контента.

Мы хотим запускать некоторый JavaScript-код только на https://meet.google.com , что можно сделать с помощью скриптов контента. Давайте спросим у 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, а также необходимый JavaScript-код для файла content.js .

С добавлением параметра 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 внедрять скрипт content.js всякий раз, когда мы переходим на страницу в поддомене " https://meet.google.com ". Давайте добавим этот файл и протестируем его, хорошо?

Добавим этот код в файл content.js .

console.log("Hello Gemini");

И правда! Когда мы заходим на meet.google.com , в консоли JavaScript появляется надпись "Hello Gemini" (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("Привет, Близнецы!");

6216bab627c31e6c.png

d61631cd9962ffe5.png

Отлично! Теперь мы можем добавить в наше приложение функциональность, специфичную для JavaScript. Давайте на мгновение задумаемся о том, чего мы хотим достичь.

Улучшение сценария контента.

Мы хотим получать уведомления о присоединении к собранию, когда кто-то уже находится на странице собрания (где есть возможность присоединиться к собранию). Для этого давайте посмотрим, как визуально меняется экран, когда собрание пустое, и когда к нему присоединился кто-то.

Вот как это выглядит, когда на совещании никого нет.

fe5a0c95b20e7f72.png

На этих изображениях показано, как выглядит встреча с участием нескольких человек.

7a5ef60521d961cc.png

Сразу же бросаются в глаза два существенных различия:

  1. Статус сообщения меняется с "Никого больше нет" на "[Пользователь] участвует в этом звонке".
  2. Мы можем видеть изображения пользователей, присоединившихся к звонку.

Оба этих изменения подойдут нам, если мы хотим узнать, присоединился ли кто-нибудь к собранию, но второе позволяет получить информацию о пользователях, которые уже присоединились, поэтому давайте попробуем использовать его.

Откройте «Инспектор элементов» в Chrome с помощью сочетания клавиш (Mac: Cmd + Opt + C / Win: Ctrl + Shift + C ), затем щелкните по изображению пользователя, который присоединился к сайту.

Мы можем заметить, что есть изображение с несколькими строковыми классами, а атрибут title изображения содержит имя пользователя, присоединившегося к встрече. Более того, этот тег изображения обернут в div с классом U04fid . После добавления участников к нашей тестовой встрече мы видим, что этот родительский div содержит несколько изображений (соответствующих разным пользователям).

Итак, у нас есть частичная стратегия:

  1. Определяет, содержит ли div с классом U04fid какие-либо элементы.
  2. Если это так, то это значит, что к нашей встрече присоединились другие участники.
  3. Уведомить пользователя.

Вернемся к Близнецам и зададимся вопросом, как мы можем достичь этого шаг за шагом.

Быстрый:

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 или даже изменить подсказки, чтобы понять, что нам нужно, но по сути нам необходимо использовать API chrome.notifications.create , и вызов этого метода должен осуществляться из фонового сервиса.

Быстрый:

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

Мы видим несколько подробных этапов, среди которых наиболее важными являются:

  • Добавьте разрешение на notifications в файл манифеста.
  • Вызовите метод chrome.notifications.create
  • Вызов должен выполняться в фоновом режиме с помощью скрипта.

Для добавления фонового скрипта в расширение Chrome в manifest version 3 нам необходимо добавить объявление background.service_worker в файл manifest.json .

Итак, мы создаём файл с именем 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, как этого добиться.

Быстрый:

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

Gemini отвечает соответствующими вызовами методов chrome.runtime.sendMessage и chrome.runtime.onMessage.addListener .

По сути, мы будем использовать sendMessage для отправки сообщения из скрипта контента о том, что кто-то присоединился к встрече, а onMessage.addListener — в качестве обработчика событий для реакции на сообщение, отправленное скриптом контента. В этом случае мы будем вызывать метод sendNotification из этого обработчика событий.

Мы будем передавать сообщение уведомления и свойство 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"
    });
}

Давайте попробуем настроить сообщение уведомления и получить уникальный идентификатор уведомления. В сообщение уведомления мы можем включить имя пользователя. Как мы уже вспоминали на предыдущем шаге, имя пользователя можно увидеть в атрибуте title изображения. Таким образом, мы можем получить имя участника, используя document.querySelector('div.U04fid > img').getAttribute('title').

Что касается идентификатора уведомления, мы можем получить идентификатор вкладки скрипта содержимого и использовать его в качестве идентификатора уведомления. Это можно сделать внутри нашего обработчика событий 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.storage , alarms и т.д. Если вы столкнетесь с трудностями, используйте Gemini или документацию, чтобы понять, что вы делаете неправильно, или найти разные способы решения проблемы.

Часто для получения необходимой справки требуется изменить подсказку, но мы можем сделать это из одной вкладки, которая сохраняет весь контекстный интерфейс.