Ćwiczenie z programowania: tworzenie rozszerzenia do Chrome w języku JavaScript za pomocą Gemini

1. Wprowadzenie

Chcesz dołączyć do rozmowy w Meet, ale nie chcesz być pierwszą osobą, która to zrobi? Jeśli tak jest w Twoim przypadku, mamy dla Ciebie rozwiązanie.

Po ukończeniu tych ćwiczeń utworzysz rozszerzenie do Chrome, które będzie Cię powiadamiać, gdy pierwszy uczestnik dołączy do połączenia.

Poznasz różne elementy rozszerzenia do Chrome, a potem szczegółowo omówimy każdą jego część. Poznasz funkcje rozszerzeń, takie jak skrypty treści, procesy robocze i przekazywanie wiadomości.

Aby otrzymywać powiadomienia o dołączeniu uczestnika do rozmowy w Meet, musisz przestrzegać zasad wersji 3 pliku manifestu.

2. Zanim zaczniesz

Wymagania wstępne

To ćwiczenie jest odpowiednie dla początkujących, ale podstawowa znajomość JavaScriptu może znacznie ułatwić Ci pracę.

Konfiguracja i wymagania

  • Przeglądarka Chrome
  • Środowisko IDE lub edytor na komputerze lokalnym.
  • Zainstaluj gcloud CLI, jeśli chcesz włączyć interfejs Gemini API za pomocą gcloud.

Włączanie interfejsu 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. Zaczynamy zabawę

Podstawowa instalacja rozszerzenia

Utwórzmy katalog, który będzie używany jako katalog główny projektu.

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

Zanim zaczniemy zadawać Gemini konkretne pytania, zapytajmy o ogólną strukturę rozszerzenia do Chrome.

Prompt:

What are the important parts to build a chrome extension?

Otrzymujemy odpowiedź zawierającą drobne szczegóły dotyczące pliku manifest, background script i interfejsu. Przyjrzyjmy się bliżej tym konkretnym plikom.

Prompt:

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

W polu autora możesz wpisać wybraną nazwę i swój adres e-mail.

Gemini zwraca potrzebną nam zawartość pliku manifestu, ale zawiera też dodatkowe pola, których nie potrzebujemy, np. pole action. Potrzebujemy też opisu. Zajmijmy się tym.

Prompt:

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

Umieśćmy tę treść w pliku manifest.json w katalogu głównym projektu.

Na tym etapie plik manifestu powinien wyglądać mniej więcej tak:

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

Na razie usuń wszystkie inne dodatkowe pola wygenerowane w pliku manifestu, ponieważ w tym ćwiczeniu zakładamy, że te pola znajdują się w pliku manifestu.

Jak sprawdzić, czy rozszerzenie działa? Zapytajmy naszego przyjaciela Gemini.

Prompt:

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

Zawiera ona instrukcje, jak to przetestować. Przejdźmy do "Extensions Page", a następnie do chrome://extensions. Upewnij się, że przycisk "Developer Mode" jest włączony. Powinien się wtedy pojawić przycisk "Load unpacked", którego możesz użyć, aby przejść do folderu zawierającego lokalnie pliki rozszerzenia. Gdy to zrobimy, rozszerzenie powinno być widoczne w "Extensions Page".

3d802a497ce0cfc2.png

92db1999a1800ecd.png

Świetnie. Widzimy nasze rozszerzenie, ale zacznijmy dodawać do niego funkcje.

4. Dodawanie skryptu dotyczącego zawartości

Chcemy uruchomić kod JavaScript tylko na stronie https://meet.google.com, co możemy zrobić za pomocą skryptów dotyczących zawartości. Zapytajmy Gemini, jak to zrobić w naszym rozszerzeniu.

Prompt:

How to add a content script in our chrome extension?

A dokładniej:

Prompt:

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

lub inną wersję:

Prompt:

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 podaje dokładne zmiany, które musimy wprowadzić w pliku manifest.json, a także kod JavaScript, którego potrzebujemy w pliku content.js.

Po dodaniu content_scripts plik manifestu będzie wyglądać tak:

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

Informuje to Chrome, że skrypt treści content.js ma być wstrzykiwany za każdym razem, gdy przechodzimy na stronę w subdomenie „ https://meet.google.com”. Dodajmy ten plik i go przetestujmy.

Dodajmy ten kod do pliku content.js.

console.log("Hello Gemini");

Oczywiście! Gdy otworzymy stronę meet.google.com, w konsoli JavaScriptu(Mac: Cmd + Opt + J / Win/Linux: Ctrl + Shift + J) zobaczymy komunikat „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

Świetnie. Teraz możemy dodać do aplikacji funkcje specyficzne dla JavaScriptu. Zastanówmy się, co chcemy osiągnąć.

Ulepszanie skryptu dotyczącego treści

Chcemy otrzymywać powiadomienia, gdy ktoś dołącza do spotkania, gdy jesteśmy na stronie spotkania(gdzie mamy opcję dołączenia do spotkania). Aby to osiągnąć, przyjrzyjmy się, jak wizualnie zmienia się ekran, gdy spotkanie jest puste, a gdy ktoś do niego dołączy.

Tak wygląda widok, gdy na spotkaniu nie ma nikogo.

fe5a0c95b20e7f72.png

A tak wygląda widok, gdy w spotkaniu uczestniczy kilka osób.

7a5ef60521d961cc.png

Od razu widać 2 istotne różnice:

  1. Tekst stanu zmieni się z „Nikt inny nie jest tutaj” na „[Użytkownik] jest na tej rozmowie”.
  2. Widzimy zdjęcia użytkowników, którzy dołączyli do połączenia.

Obie te zmiany będą dla nas przydatne, jeśli chcemy wiedzieć, czy ktoś dołączył do spotkania, ale ta druga daje pewne możliwości uzyskania informacji o użytkownikach, którzy już dołączyli, więc spróbujmy jej użyć.

Otwórz „Inspektora elementów” w Chrome za pomocą skrótu klawiszowego ( Mac: Cmd + Opt + C / Win: Ctrl + Shift + C), a następnie kliknij obraz użytkownika, który dołączył.

Możemy zauważyć, że obraz ma kilka ciągów klas, a atrybut title obrazu zawiera nazwę użytkownika, który dołączył do spotkania. Dodatkowo tag obrazu jest otoczony elementem div z klasą U04fid. Po dodaniu do spotkania testowego kilku uczestników możemy zauważyć, że ten element nadrzędny zawiera wiele obrazów(odpowiadających różnym użytkownikom).

Mamy więc częściową strategię:

  1. Wykrywaj, kiedy element div z klasą U04fid zawiera jakiekolwiek elementy.
  2. Jeśli tak, oznacza to, że w spotkaniu są uczestnicy.
  3. Powiadom użytkownika.

Wróćmy do Gemini i zapytajmy, jak to zrobić krok po kroku.

Prompt:

How to detect for element changes in the HTML page?

Podaje rozwiązanie z użyciem MutationObserver, ale nie spełnia ono naszych wymagań. Spróbujmy zmodyfikować prompt.

Prompt:

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

Tym razem otrzymujemy rozwiązanie, które nie zawiera MutationObserver, ale sprawdza dołączających za pomocą elementu nadrzędnego div.

Oto co otrzymaliśmy:

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

Spróbujmy jeszcze bardziej zmodyfikować nasze podejście:

Prompt:

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.

Zwraca ten fragment kodu:

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
});

Skopiuj ten kod do pliku content.js, ponownie załaduj rozszerzenie(a następnie ponownie załaduj stronę spotkania).

Teraz, gdy ktoś dołączy do naszego spotkania, możemy zobaczyć instrukcję logowania w konsoli.

5. Wysyłanie powiadomienia do użytkownika

Teraz, gdy możemy wykryć, kiedy uczestnik dołączył do spotkania, spróbujmy dodać część powiadomienia w naszym rozszerzeniu do Chrome. Możemy przejrzeć dokumentację rozszerzenia do Chrome, a nawet dostosować prompty, aby wiedzieć, czego szukamy, ale zasadniczo musimy użyć interfejsu chrome.notifications.create API, a wywołanie tej metody powinno pochodzić ze skryptu service worker działającego w tle.

Prompt:

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

Widzimy szczegółowe kroki, a najważniejsze z nich to:

  • Dodaj uprawnienia notifications do pliku manifestu.
  • Wywołanie funkcji chrome.notifications.create
  • Wywołanie powinno znajdować się w skrypcie w tle.

Aby dodać skrypt działający w tle do rozszerzenia Chrome w manifest version 3, musimy umieścić deklarację background.service_worker w pliku manifest.json.

Utwórz plik o nazwie background.js i dodaj do pliku manifest.json ten kod:

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

Po dodaniu powyższych elementów plik manifestu będzie wyglądać tak:

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

Prompt:

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

Zapisz ten obraz w katalogu głównym folderu i zmień jego nazwę na success.png.

b2c22f064a3f2d9c.png

Następnie dodaj do pliku background.js ten fragment kodu:

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");

Teraz ponownie załaduj rozszerzenie na stronie rozszerzeń. Powinno się od razu pojawić wyskakujące powiadomienie.

6. Dodawanie przekazywania wiadomości w rozszerzeniu do Chrome

Ostatnim ważnym krokiem jest połączenie wykrywania uczestnika przez skrypt treści z metodą sendNotification w skrypcie działającym w tle. W przypadku rozszerzeń do Chrome można to zrobić za pomocą techniki o nazwie message passing.

Umożliwia to komunikację między różnymi częściami rozszerzenia do Chrome, w naszym przypadku między skryptem treści a skryptem service worker w tle. Zapytajmy Gemini, jak to zrobić.

Prompt:

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

Gemini odpowiada odpowiednimi wywołaniami do chrome.runtime.sendMessagechrome.runtime.onMessage.addListener.

Użyjemy funkcji sendMessage, aby wysłać ze skryptu treści wiadomość o tym, że ktoś dołączył do spotkania w Meet, a funkcji onMessage.addListener jako detektora zdarzeń, aby reagować na wiadomości wysyłane przez skrypt treści. W tym przypadku wywołamy metodę sendNotification z tego detektora zdarzeń.

Do skryptu service worker w tle przekażemy wiadomość z powiadomieniem i właściwość action. Właściwość action opisuje, na co reaguje skrypt działający w tle.

Oto nasz content.js kod:

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

Oto nasz kod 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"
    });
}

Spróbujmy dostosować wiadomość powiadomienia i uzyskać unikalny identyfikator powiadomienia. W wiadomości z powiadomieniem możemy uwzględnić imię i nazwisko użytkownika. Jak pamiętasz z poprzedniego kroku, nazwa użytkownika była widoczna w atrybucie title obrazu. Możemy więc pobrać imię i nazwisko uczestnika za pomocą document.querySelector('div.U04fid > img').getAttribute('title').

Jeśli chodzi o identyfikator powiadomienia, możemy pobrać identyfikator karty skryptu treści i użyć go jako identyfikatora powiadomienia. Można to zrobić w ramach naszego detektora zdarzeń chrome.runtime.onMessage.addListener, korzystając z sender.tab.id.

Ostatecznie nasze pliki powinny wyglądać mniej więcej tak:

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. Gratulacje

W krótkim czasie udało nam się stworzyć rozszerzenie do Chrome przy pomocy Gemini. Niezależnie od tego, czy jesteś doświadczonym deweloperem rozszerzeń do Chrome, czy dopiero zaczynasz przygodę z rozszerzeniami, Gemini pomoże Ci w wykonywaniu dowolnych zadań.

Zachęcam do zadawania pytań o różne możliwości rozszerzeń do Chrome. Warto przejrzeć wiele interfejsów API, np. chrome.storage czy alarms. Jeśli napotkasz trudności, użyj Gemini lub dokumentacji, aby dowiedzieć się, co robisz źle, lub poznać różne sposoby rozwiązania problemu.

Aby uzyskać potrzebną pomoc, często trzeba zmodyfikować prompt, ale możemy to zrobić na jednej karcie, która zachowuje całą naszą ścieżkę kontekstową.