Codelab: สร้างส่วนขยาย Chrome ใน JavaScript ด้วย Gemini

1. บทนำ

หากคุณเข้าร่วมการประชุมผ่าน Meet แต่ไม่อยากเป็นคนแรกที่โทร หากข้อมูลดังกล่าวตรงกับคุณ เรามีทางออกสำหรับคุณ

หลังจาก Codelab นี้ คุณจะสร้างส่วนขยาย Chrome ที่จะแจ้งเตือนคุณเมื่อผู้เข้าร่วมคนแรกเข้ามาเข้าร่วมการประชุม

คุณจะได้เรียนรู้องค์ประกอบต่างๆ ของส่วนขยาย Chrome จากนั้นเจาะลึกแต่ละส่วนของส่วนขยาย คุณจะได้ศึกษาฟังก์ชันของส่วนขยาย เช่น สคริปต์เนื้อหา โปรแกรมทำงานของบริการ และการส่งข้อความ

คุณต้องปฏิบัติตามรุ่นไฟล์ Manifest v3 เพื่อรับการแจ้งเตือนเมื่อมีผู้เข้าร่วมเข้าร่วมการประชุมผ่าน Meet

2. ก่อนเริ่มต้น

ข้อกำหนดเบื้องต้น

แม้ Codelab นี้จะเหมาะสำหรับผู้เริ่มต้น แต่การมีความเข้าใจพื้นฐานเกี่ยวกับ JavaScript จะยกระดับประสบการณ์การใช้งานของคุณได้เป็นอย่างมาก

การตั้งค่า/ข้อกำหนด

  • เบราว์เซอร์ Chrome
  • การตั้งค่า IDE/Editor ในระบบภายใน
  • ติดตั้ง gcloud cli หากต้องการเปิดใช้ Gemini API โดยใช้ gcloud

เปิดใช้ 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 แสดงเนื้อหาของไฟล์ Manifest ที่เราต้องการ แต่เรามีฟิลด์พิเศษบางส่วนที่เราไม่จำเป็นต้องใช้ เช่น ฟิลด์ 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 ที่รูทของโปรเจ็กต์

ในขั้นตอนนี้ ไฟล์ Manifest ควรมีลักษณะเช่นนี้

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

นำช่องอื่นๆ ที่สร้างขึ้นในไฟล์ Manifest ออกในขณะนี้ เนื่องจาก Codelab จะถือว่าช่องเหล่านี้อยู่ในไฟล์ Manifest

ตอนนี้เราจะทดสอบว่าส่วนขยายทำงานอย่างไร เรามาถาม 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 ไฟล์ Manifest ของเราจะเป็น:

{
    "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 เราจะเห็นข้อความ "สวัสดี Gemini" ในคอนโซล JavaScript(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("สวัสดี Gemini");

🰂 6216bab627c31e6c.png

d61631cd9962ffe5.png

เยี่ยม! ตอนนี้เราอยู่ในจุดที่สามารถเพิ่มฟังก์ชันเฉพาะ JavaScript ลงในแอปของเราแล้ว ลองใช้เวลาสักครู่และนึกถึงสิ่งที่เราพยายามทำให้สำเร็จ

การปรับปรุงสคริปต์เนื้อหา

เราต้องการได้รับการแจ้งเตือนเมื่อมีคนเข้าร่วมการประชุมเมื่อเราอยู่ในหน้าการประชุม(ที่เรามีตัวเลือกในการเข้าร่วมการประชุม) เพื่อบรรลุเป้าหมายนี้ เราจะมาสังเกตการณ์ว่าหน้าจอเปลี่ยนไปอย่างไรเมื่อการประชุมว่างเปล่า และเทียบกับตอนที่มีคนเข้าร่วมการประชุม

ซึ่งจะมีลักษณะเช่นนี้เมื่อไม่มีคนอยู่ในการประชุม

fe5a0c95b20e7f72.png

ในขณะที่ภาพเหล่านี้จะเป็นภาพเมื่อมีผู้เข้าร่วมการประชุมบางคน

7a5ef60521d961cc.png

เราจะเห็นความแตกต่าง 2 ประการที่เห็นได้ชัดคือ

  1. ข้อความสถานะจะเปลี่ยนจาก "ไม่มีใครอยู่ที่นี่" "[ผู้ใช้] อยู่ในสายนี้"
  2. เราจะดูรูปภาพของผู้ใช้ที่เข้าร่วมในสายได้

การเปลี่ยนแปลงทั้ง 2 อย่างนี้ได้ผลสำหรับเราหากเราต้องการทราบว่ามีใครเข้าร่วมการประชุมบ้างหรือไม่ แต่ผู้เข้าร่วมหลังมีโอกาสได้รับข้อมูลเกี่ยวกับผู้ใช้ที่เข้าร่วมแล้ว เรามาลองใช้แบบนั้นกัน

เปิด "เครื่องมือตรวจสอบองค์ประกอบ" ใน Chrome โดยใช้คีย์ลัด ( Mac: Cmd + Opt + C / Win: Ctrl + Shift + C) แล้วคลิกรูปภาพของผู้ใช้ที่ได้เข้าร่วม

เราจะสังเกตได้ว่ามีรูปภาพที่มีสตริงของชั้นเรียนไม่กี่รายการ และแอตทริบิวต์ชื่อของรูปภาพมีชื่อของผู้ใช้ที่เข้าร่วมการประชุม ยิ่งไปกว่านั้น แท็กรูปภาพนี้ยังรวมอยู่ใน 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 หรือแม้กระทั่งปรับแต่งข้อความแจ้งเพื่อให้ทราบถึงสิ่งที่เราต้องการ แต่โดยพื้นฐานแล้วเราต้องใช้ chrome.notifications.create API และการเรียกใช้วิธีการนี้ควรมาจากโปรแกรมทำงานของบริการที่ทำงานอยู่เบื้องหลัง

พรอมต์:

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

เราเห็นขั้นตอนโดยละเอียดของไฮไลต์หลักๆ ดังนี้

  • เพิ่มสิทธิ์ notifications ในไฟล์ Manifest
  • โทรไปที่ chrome.notifications.create
  • การโทรควรอยู่ในสคริปต์ที่ทำงานอยู่เบื้องหลัง

เราต้องประกาศ background.service_worker ในไฟล์ manifest.json จึงจะเพิ่มสคริปต์พื้นหลังลงในส่วนขยาย Chrome ใน manifest version 3 ได้

เราจึงสร้างไฟล์ชื่อ background.js และเพิ่มไฟล์ Manifest.json ต่อไปนี้

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

ด้วยบริการเพิ่มเติมข้างต้น ไฟล์ Manifest ของเราจะเป็น:

{
    "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 เพื่อส่งข้อความจากสคริปต์เนื้อหาซึ่งมีบุคคลเข้าร่วมการประชุม Meet และใช้ onMessage.addListener เป็นผู้ฟังเหตุการณ์เพื่อแสดงความรู้สึกต่อข้อความที่ส่งโดยสคริปต์เนื้อหา ในกรณีนี้ เราจะทริกเกอร์การเรียกไปยังเมธอด sendNotification จาก 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').

ในส่วนของรหัสการแจ้งเตือน เราสามารถดึงข้อมูลรหัสแท็บของสคริปต์เนื้อหาและใช้รหัสนั้นเป็นรหัสการแจ้งเตือนได้ สามารถทำได้ใน 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 หรือเอกสารประกอบเพื่อดูว่าคุณกำลังทำอะไรผิด หรือเพื่อหาวิธีต่างๆ ในการแก้ปัญหา

การปรับเปลี่ยนพรอมต์เพื่อให้ความช่วยเหลือที่จำเป็นนั้นต้องทำหลายครั้ง แต่เราทำได้จากแท็บเดียวซึ่งเก็บเส้นทางตามบริบททั้งหมดไว้