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.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, מופיעה ההודעה 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, ובמקרה שלנו בין סקריפט התוכן לבין שירות ה-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 או במאמרי העזרה כדי להבין מה לא בסדר או כדי לקבל דרכים שונות לפתרון הבעיה.

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