Codelab: פיתוח תוסף ל-Chrome ב-JavaScript באמצעות Gemini

1. מבוא

רוצים להצטרף לשיחה ב-Meet אבל לא רוצים להיות הראשונים? אם זה המצב שלכם, יש לנו פתרון בשבילכם!

בשיעור Codelab הזה נסביר איך ליצור תוסף ל-Chrome שמציג התראה כשמשתתף מצטרף לשיחה.

תלמדו על הרכיבים השונים של תוסף ל-Chrome, ואז תעמיקו בכל חלק של התוסף. תקבלו מידע על פונקציות של תוספים כמו סקריפטים של תוכן, קובצי שירות (service workers) והעברת הודעות.

כדי לקבל התראה בכל פעם שמשתתף מצטרף לשיחה ב-Meet, צריך לפעול בהתאם לגרסה 3 של המניפסט.

‫2. לפני שמתחילים

דרישות מוקדמות

ה-codelab הזה מתאים למתחילים, אבל ידע בסיסי ב-JavaScript יכול לשפר מאוד את חוויית השימוש.

הגדרה/דרישות

  • דפדפן Chrome
  • הגדרת IDE או עורך במערכת המקומית.
  • אם רוצים להפעיל את Gemini API באמצעות gcloud, צריך להתקין את 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.

הוא נותן לי כמה שלבים לבדיקה. כדי לעבור אל "Extensions Page", עוברים אל chrome://extensions ולוחצים על הלחצן "Developer Mode" כדי להציג את הלחצן "Load unpacked". באמצעות הלחצן הזה אפשר לעבור אל התיקייה שמכילה את קובצי התוסף באופן מקומי. אחרי שנבצע את הפעולה הזו, התוסף אמור להופיע ב-"Extensions Page".

3d802a497ce0cfc2.png92db1999a1800ecd.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, מופיעה ההודעה 'Hello Gemini' במסוף 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("Hello Gemini!");

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. שליחת הודעה למשתמש.

נחזור ל-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 או אפילו לשנות את ההנחיות כדי לדעת מה אנחנו מחפשים, אבל בעצם אנחנו צריכים להשתמש ב-API‏ chrome.notifications.create והקריאה לשיטה הזו צריכה להתבצע מ-service worker ברקע.

הנחיה:

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, במקרה שלנו מסקריפט התוכן אל ה-service worker ברקע. בואו נשאל את 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 כדי לשלוח הודעה מסקריפט התוכן שמישהו הצטרף לשיחה ב-Meet, וב-onMessage.addListener כרכיב event listener כדי להגיב להודעה שנשלחה על ידי סקריפט התוכן. במקרה כזה, נפעיל את הקריאה לשיטה sendNotification מתוך רכיב ה-event listener הזה.

נעביר את הודעת ההתראה ואת מאפיין 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"
    });
}

ננסה להתאים אישית את הודעת ההתראה ולקבל מזהה התראה ייחודי. בהודעת ההתראה אפשר לכלול את שם המשתמש. אם נחזור לשלב הקודם, נראה את שם המשתמש במאפיין הכותרת של התמונה. לכן, אפשר לאחזר את שם המשתתף באמצעות document.querySelector('div.U04fid > img').getAttribute('title').

לגבי מזהה ההתראה, אפשר לאחזר את מזהה הכרטיסייה של סקריפט התוכן ולהשתמש בו כמזהה ההתראה. אפשר לעשות את זה בתוך רכיב event listener 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. מזל טוב

תוך זמן קצר הצלחנו ליצור תוסף ל-Chrome בעזרת Gemini. בין אם אתם מפתחים מנוסים של תוספים ל-Chrome או שאתם חדשים בעולם התוספים, Gemini יכול לעזור לכם בכל המשימות שתרצו לבצע.

אני ממליץ לך לשאול על הדברים השונים שאפשר לעשות עם תוסף ל-Chrome. יש הרבה ממשקי API שכדאי לעיין בהם, כמו chrome.storage, alarms וכו'. אם נתקעתם, אתם יכולים להשתמש ב-Gemini או במאמרי העזרה כדי להבין מה לא בסדר או כדי לקבל דרכים שונות לפתרון הבעיה.

לפעמים צריך לשנות את ההנחיה כדי לקבל את העזרה הרצויה, אבל אפשר לעשות את זה בכרטיסייה אחת שבה נשמר כל ההקשר של הפעולות שלנו.