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

1. บทนำ

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

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

คุณจะได้เรียนรู้องค์ประกอบต่างๆ ของส่วนขยาย Chrome แล้วเจาะลึกแต่ละส่วนของส่วนขยาย คุณจะได้เรียนรู้เกี่ยวกับฟังก์ชันส่วนขยาย เช่น Content Script, Service Worker และ Message Passing

คุณต้องปฏิบัติตามการเปิดตัว 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. เพิ่ม Content Script

เราต้องการเรียกใช้โค้ด JavaScript เฉพาะใน https://meet.google.com ซึ่งทำได้โดยใช้ Content Script มาขอความช่วยเหลือจาก 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 Script content.js ทุกครั้งที่เราไปยังหน้าในโดเมนย่อย " https://meet.google.com" มาเพิ่มไฟล์นี้แล้วทดสอบกัน

มาเพิ่มโค้ดนี้ในไฟล์ content.js กัน

console.log("Hello Gemini");

แน่นอน เมื่อไปที่ meet.google.com เราจะเห็นข้อความ "สวัสดี 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

เราจะเห็นความแตกต่างที่สำคัญ 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 และการเรียกใช้เมธอดนี้ควรมาจาก Service Worker ที่ทำงานอยู่เบื้องหลัง

พรอมต์:

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

เราเห็นขั้นตอนโดยละเอียดบางอย่าง โดยไฮไลต์สำคัญมีดังนี้

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

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

ดังนั้น เราจึงสร้างไฟล์ชื่อ 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

ตอนนี้ขั้นตอนสำคัญสุดท้ายที่เราต้องทำคือการเชื่อมต่อการตรวจหาผู้เข้าร่วมของ Content Script กับsendNotificationเมธอดใน Background Script ในบริบทของส่วนขยาย Chrome วิธีการทำเช่นนั้นคือผ่านเทคนิคที่เรียกว่า message passing

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

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

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