درس تطبيقي حول الترميز: إنشاء إضافة Chrome في JavaScript باستخدام Gemini

1. مقدمة

هل ستنضم إلى مكالمة Meet ولكنّك لا تريد أن تكون أول من ينضم إليها؟ إذا كنت من هؤلاء الأشخاص، لدينا الحلّ المناسب لك.

بعد اتّباع هذا الدرس التطبيقي حول الترميز، ستتمكّن من إنشاء إضافة Chrome تنبّهك عندما ينضم أول مشارك إلى المكالمة.

ستتعرّف على العناصر المختلفة لإضافة Chrome، ثم ستتعمّق في كل جزء من الإضافة. ستتعرّف على وظائف الإضافة، مثل النصوص البرمجية للمحتوى ومشغّلي الخدمات وتبادل الرسائل.

يجب الالتزام بإصدار Manifest v3 لتلقّي إشعار عندما ينضم مشارك إلى اجتماع.

2. قبل البدء

المتطلبات الأساسية

على الرغم من أنّ هذا الدرس التطبيقي حول الترميز مناسب للمبتدئين، إلا أنّ امتلاك فهم أساسي للغة JavaScript يمكن أن يحسّن تجربتك بشكل كبير.

الإعداد/المتطلبات

  • متصفح Chrome
  • إعداد بيئة تطوير متكاملة/محرّر على نظامك المحلي
  • ثبِّت 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 محتوى ملف البيان الذي نحتاجه، ولكن نحصل على بعض الحقول الإضافية التي لن نحتاجها، مثل الحقل 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>"
}

أزِل أي حقول إضافية أخرى تم إنشاؤها في ملف البيان في الوقت الحالي لأنّ هذا الدرس التطبيقي حول الترميز يفترض وجود هذه الحقول في ملف البيان.

الآن، كيف يمكننا اختبار ما إذا كانت الإضافة تعمل؟ لنطلب المساعدة من صديقنا 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، تظهر الرسالة "مرحبًا 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)، ثم انقر على صورة المستخدم الذي انضمّ.

يمكننا ملاحظة أنّ هناك صورة تتضمّن بعض سلاسل الفئات، وأنّ سمة العنوان الخاصة بالصورة تتضمّن اسم المستخدم الذي انضمّ إلى الاجتماع. علاوةً على ذلك، يتم تضمين علامة الصورة هذه في 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، ويجب أن يكون طلب هذه الطريقة من عامل خدمة يعمل في الخلفية.

الطلب:

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، وفي حالتنا، من البرنامج النصي للمحتوى إلى عامل خدمة الخلفية. لنطلب من صديقنا 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 من متتبِّع الأحداث هذا.

سنمرّر رسالة الإشعار والسمة action إلى عامل الخدمة في الخلفية. تصف السمة 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').

في ما يتعلق بمعرّف الإشعار، يمكننا استرداد معرّف علامة التبويب لبرنامج النصوص الخاص بالمحتوى واستخدامه كمعرّف للإشعار. يمكن تحقيق ذلك داخل متتبِّع الأحداث 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. هناك العديد من واجهات برمجة التطبيقات التي تستحق التصفّح، مثل chrome.storage وalarms وما إلى ذلك. إذا واجهت أي صعوبة، استخدِم Gemini أو المستندات لمعرفة الخطأ الذي ترتكبه أو للتعرّف على طرق مختلفة لحلّ المشكلة.

في كثير من الأحيان، يتطلّب الأمر تعديل الطلب للحصول على المساعدة المطلوبة، ولكن يمكننا إجراء ذلك من علامة تبويب واحدة تحتفظ بكل رحلتنا السياقية.