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

1. Wprowadzenie

Dołączasz do rozmowy w Meet, ale nie chcesz być pierwszą osobą, która to zrobi? Jeśli to dotyczy Ciebie, mamy rozwiązanie dla Ciebie.

W ramach tego ćwiczenia w programie utworzysz rozszerzenie do Chrome, które powiadomi Cię, gdy do rozmowy dołączy pierwszy uczestnik.

Poznaj różne elementy rozszerzenia do Chrome, a następnie szczegółowo zapoznaj się z każdą jego częścią. Poznasz funkcje rozszerzeń, takie jak skrypty treści, mechanizmy Service Worker i przekazywanie wiadomości.

Musisz przestrzegać pliku manifestu w wersji 3, aby móc otrzymywać powiadomienia za każdym razem, gdy uczestnik dołącza do rozmowy w Meet.

2. Zanim zaczniesz

Wymagania wstępne

To ćwiczenie w Codelabs jest odpowiednie dla początkujących, ale podstawowa znajomość języka JavaScript może znacznie ułatwić jego naukę.

Konfiguracja/wymagania

  • Przeglądarka Chrome
  • Konfiguracja IDE lub edytora w systemie lokalnym.
  • Jeśli chcesz włączyć Gemini API za pomocą gcloud, zainstaluj gcloud cli.

Włączanie 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 rozszerzeń

Utwórzmy katalog, którego użyjemy jako głównego katalogu naszego projektu.

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

Zanim zaczniemy zadawać Gemini konkretne pytania, zadajmy sobie kilka pytań o ogólną strukturę rozszerzenia do Chrome.

Prompt:

What are the important parts to build a chrome extension?

Otrzymujesz odpowiedź z informacją o drobnych szczegółach dotyczących pliku manifest, pola background script i szczegółów interfejsu użytkownika. 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 zawartość potrzebnego pliku manifestu, ale pojawia się kilka dodatkowych pól, które nie są potrzebne, np. pola 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ż to ćwiczenia z programowania zakładają, że te pola są uwzględniane w pliku manifestu.

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

Prompt:

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

Znajdziesz tam instrukcje, jak to przetestować. Otwórz chrome://extensions i przejdź do "Extensions Page". Upewnij się, że jest włączony przycisk "Developer Mode". Powinien wyświetlić się przycisk "Load unpacked", który pozwala przejść do folderu z lokalnymi plikami rozszerzeń. Gdy to zrobimy, rozszerzenie powinno być widoczne w interfejsie "Extensions Page".

3d802a497ce0cfc2.png

92db1999a1800ecd.png

Świetnie. Nasze rozszerzenie jest widoczne, ale zaczynamy dodawać do nich funkcje.

4. Dodaj skrypt treści

Na stronie https://meet.google.com chcemy uruchomić tylko fragmenty kodu JavaScript w skryptach treści. Zapytajmy Gemini, jak to osiągnąć 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 inna wersja:

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 nam dokładne zmiany, które musimy wprowadzić, w pliku manifest.json oraz JavaScript, którego potrzebujemy w pliku content.js.

Po dodaniu skryptu content_scripts plik manifestu zmieni się w następujący sposób:

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

Dzięki temu Chrome ma wstrzyknąć skrypt treści content.js za każdym razem, gdy przechodzimy na stronę w subdomenie „ https://meet.google.com”. Dodajmy ten plik i przetestujemy go.

Dodajmy ten kod do pliku content.js.

console.log("Hello Gemini");

Jasne! Gdy wchodzimy na meet.google.com, widzimy „Cześć Gemini”. w konsoli JavaScript(Mac: Cmd + Opt + J / Windows/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("Witaj Gemini!");

6216bab627c31e6c.png

d61631cd9962ffe5.png

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

Ulepszanie skryptu treści

Chcemy móc otrzymywać powiadomienie, gdy ktoś dołączy do spotkania na stronie spotkania(gdzie dostępna jest opcja dołączenia do spotkania). Aby to osiągnąć, przyjrzyjmy się, jak ekran zmienia się wizualnie, gdy spotkanie jest puste, a kiedy ktoś dołączył do spotkania.

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

fe5a0c95b20e7f72.png

Są to natomiast elementy wizualne, w przypadku których w spotkaniu biorą udział niektórzy uczestnicy.

7a5ef60521d961cc.png

Od razu widzimy 2 istotne różnice:

  1. Tekst stanu zmieni się z „Nikt inny tu nie ma” do „[Użytkownik] uczestniczy w tej rozmowie”.
  2. Widzimy zdjęcia użytkowników, którzy dołączyli do rozmowy.

Obie te zmiany zadziałają, jeśli chcemy wiedzieć, czy ktoś dołączył do spotkania. Jednak druga opcja daje pewne możliwości uzyskania informacji o użytkownikach, którzy już dołączyli do spotkania, więc warto korzystać z tych informacji.

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

Widzimy, że znajduje się na nim obraz z niewielką liczbą ciągów znaków dotyczących zajęć, a w atrybucie tego obrazu znajduje się nazwa użytkownika, który dołączył do spotkania. Dodatkowo ten tag graficzny jest zawarty w elemencie div z klasą U04fid. Po dodaniu uczestników do spotkania testowego zauważymy, że nadrzędny element div zawiera wiele obrazów(odpowiadających różnym użytkownikom).

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

  1. Wykrywa, kiedy element div z klasą U04fid zawiera jakiekolwiek elementy.
  2. Jeśli tak, oznacza to, że mamy uczestników spotkania.
  3. Powiadom użytkownika.

Wróćmy do Gemini i zapytajmy, jak krok po kroku możemy to osiągnąć.

Prompt:

How to detect for element changes in the HTML page?

Podpowiada nam, jak używać MutationObserver, ale nie ma związku z naszymi wymaganiami. Spróbujmy zmodyfikować prompt.

Prompt:

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

Tym razem uzyskujemy rozwiązanie, które nie zawiera elementu MutationObserver, lecz zawiera rozwiązanie, które sprawdza połączone elementy za pomocą nadrzędnego tagu div.

Oto, co dostaliś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 zmienić 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.

Zwracana jest z następującym fragmentem 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, załaduj ponownie rozszerzenie(a następnie załaduj ponownie stronę Meet).

Teraz gdy ktoś dołączy do spotkania, będzie można zobaczyć instrukcję dziennika w konsoli.

5. Wysyłanie powiadomienia do użytkownika

Udało nam się wykryć, kiedy uczestnik dołączył do spotkania, więc spróbujmy dodać część powiadomienia do naszego rozszerzenia do Chrome. Możemy przeglądać dokumentację rozszerzenia do Chrome lub nawet ulepszać prompty, aby dowiedzieć się, czego szukamy, ale zasadniczo musimy użyć interfejsu API chrome.notifications.create, a wywołanie tej metody powinno pochodzić z skryptu service worker w tle.

Prompt:

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

Oto kilka szczegółowych kroków, z najważniejszymi:

  • Dodaj uprawnienie notifications w pliku manifestu.
  • Uruchom połączenie na chrome.notifications.create
  • Rozmowa powinna odbywać się w skrypcie w tle.

Aby dodać skrypt działający w tle do rozszerzenia do Chrome w języku manifest version 3, w pliku manifest.json potrzebujemy deklaracji background.service_worker.

Tworzymy więc plik o nazwie background.js i dodajemy poniższy kod do pliku manifest.json.

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

Po dodaniu powyższych zmian plik manifestu zmienia się w:

{
    "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ń go na success.png.

b2c22f064a3f2d9c.png

Następnie dodaj do 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 od razu wyświetlić się wyskakujące okienko z powiadomieniem.

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 kontekście rozszerzeń do Chrome można to zrobić za pomocą metody o nazwie message passing.

Umożliwia to komunikację między różnymi częściami rozszerzenia do Chrome, w tym przypadku od skryptu treści do skryptu service worker w tle. Zapytajmy naszego znajomego Gemini, jak to zrobić.

Prompt:

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

Gemini odpowiada, wysyłając odpowiednie wezwania do: chrome.runtime.sendMessage i chrome.runtime.onMessage.addListener.

Będziemy używać usługi sendMessage do wysyłania ze skryptu treści wiadomości z informacją, że ktoś dołączył do rozmowy w Meet, a narzędzia onMessage.addListener jako detektora zdarzeń, aby zareagować na wiadomość wysłaną przez skrypt treści. W tym przypadku wywołamy metodę sendNotification z tego detektora zdarzeń.

Przekażemy komunikat z powiadomieniem i właściwość action do procesu roboczego usługi w tle. Właściwość action określa, na co odpowiada skrypt działający w tle.

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

A 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ść z powiadomieniem i uzyskać unikalny identyfikator powiadomienia. W wiadomości z powiadomieniem możemy podać imię i nazwisko użytkownika. Jeśli wspomnimy w poprzednim kroku, możemy zobaczyć imię i nazwisko użytkownika w atrybucie tytułu zdjęcia. Możemy więc pobrać imię i nazwisko uczestnika za pomocą funkcji document.querySelector('div.U04fid > img').getAttribute('title').

Możemy pobrać identyfikator karty skryptu treści i użyć go jako identyfikatora powiadomienia. Można to zrobić w detektorze zdarzeń chrome.runtime.onMessage.addListener za pomocą funkcji sender.tab.id.

Na koniec 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 twórcą rozszerzeń do Chrome, czy dopiero zaczynasz przygodę z rozszerzeniami, Gemini pomoże Ci w każdej sytuacji.

Zachęcam do spytania o różne możliwości rozszerzenia do Chrome. Jest wiele interfejsów API, które warto przejrzeć, np. chrome.storage, alarms itp. Jeśli napotkasz problem, skorzystaj z Gemini lub dokumentacji, aby dowiedzieć się, co robisz nie tak, albo znaleźć 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 zawiera wszystkie informacje o kontekście.