ทดลองเขียน Edge/Chrome extension สำหรับอ่าน comment ใน Facebook live แบบไม่ใช้ API

Cover image

พอดีวันนี้เป็นวันหยุดชดเชย ผมก็ไถ facebook feed ไปเรื่อยๆก็สังเกตได้ว่าบ้านเรานี่ live ขายของกันเยอะจริงๆ แถมหาวิธีสื่อสารในการขายกันได้สมบูรณ์ด้วยทั้งๆที่ facebook เองก็ไม่ได้ให้เครื่องมืออะไรนอกจากช่อง comment แต่แทบทุกคนที่ขายจะต้องมีทีมงานหลังบ้านคอยนับจำนวนคน cf หรือคนตอบลูกค้าอยู่ ผมเลยนึกเล่นๆว่ามันจะมีวิธีทำให้พ่อค้าแม่ค้าสามารถปิดการขายใน live ด้วยตัวคนเดียวได้ไหม

ปัญหา

พ่อค้าแม่ค้าจะ live ถ้ามีลูกค้าจำนวนมากจะเกิดปัญหาอ่าน comment ไม่ทัน จดชื่อลูกค้าไม่ทัน อยากขายไปด้วยเก็บข้่อมูลไปด้วยคนเดียวดูไม่มีทางเป็นไปได้แน่นอน จำเป็นต้องหาคนหรือจ้างพนักงานมาช่วย

แนวทางแก้ปัญหา

เขียน extension เพื่อช่วยรับ comment ใหม่ๆเข้ามาแล้ว พิจารณาด้วย regular expression ว่า message ที่ถูกพิมพ์ส่งมาตรงกับเงื่อนไขของพ่อค้าแม่ค้าไหม

สร้าง Extension กัน

อ่านคู่มือก่อนทำอย่างอื่น!!

ก่อนจะไปถึงจุดอื่นๆในบทความนี้ผมอยากให้ทุกๆท่านอ่านพื้นฐานการสร้าง extension สำหรับ browser กันก่อนนะครับจะได้เห็นภาพคร่าวๆกันก่อน

เริ่มต้นจากความว่างเปล่า

  1. สร้าง index.js แล้วเขียน function ง่ายๆทิ้งไว้ก่อน
const hello = () => {
    console.log('Hello Edge');
}

hello();
  1. สร้าง manifest.json เพื่อกำหนดค่าพื้นฐานของ extension ตั้งชื่อ กำหนด match url ด้วยนะ
{
    "name": "Facebook Live Helper",
    "version": "0.1",
    "description": "Check customer name and price",
    "content_scripts": [
        {
            "matches": [
                "https://www.facebook.com/*/videos/*"
            ],
            "js": [
                "index.js"
            ]
        }
    ],
    "manifest_version": 2
}

create-facebook-live-extension-for-edge-or-chrome

ติดตั้ง Extension ใน browser แบบไม่ต้องผ่าน store

  1. ไป settings / extension

create-facebook-live-extension-for-edge-or-chrome

  1. เปิด developer

create-facebook-live-extension-for-edge-or-chrome

  1. load unpacked เลือก folder ของ extension เราซะ

create-facebook-live-extension-for-edge-or-chrome

create-facebook-live-extension-for-edge-or-chrome

  1. ทดลองเข้า url เปิด live สักคลิปที่มี url match ของ manifest.json แบบนี้ "https://www.facebook.com/*/videos/*" แล้วดูว่าใน console มี Hello Edge อยู่ไหม

create-facebook-live-extension-for-edge-or-chrome

พิจารณาหน้า page ของ Facebook Live เพื่อหาว่าข้อมูลเรามาจากตรงไหน

create-facebook-live-extension-for-edge-or-chrome

  1. ส่วนที่เราสนใจจะอยู่แค่ในกรอบสีแดง(ภาพด้านบน) เพราะนั่นคือส่วนของ comment ที่มีการ update ตลอดเวลา
  2. ในกรอบแดงจะประกอบด้วย <div> ที่มี class ชื่อว่า UFICommentActorAndBodySpacing ซึ่งจะครอบชื่อและข้อความของ comment เอาไว้
  3. ชื่อของผู้คอมเมนต์จะเป็น <a> ที่มี class ชื่อว่า UFICommentActorName
  4. comment จะอยู่ใน <span> ที่มี class ชื่อว่า UFICommentBody
  5. ส่วน pinned comment ให้สังเกตว่าจะมี <span> ที่มี class ชื่อว่า UFILivePinnedCommentLabel แปะอยู่ด้านบน
  6. วิธีดึงข้อมูลของเราก็คือจะวน loop ดึงเอาข้อมูลจาก .UFICommentActorAndBodySpacing ทุกๆ 0.5 วินาที โดยเราจะเลือกเอาตัวที่อยู่ล่างสุดหรือตัวสุดท้ายใน NodeList เพราะมันคือข้อความล่าสุด แต่หากมี pinned comment เราจะเลือกตัวรองสุดท้ายแทน

create-facebook-live-extension-for-edge-or-chrome

ได้เวลาลงไม้ลงมือกันล่ะครับ

  1. เนื่องจาก facebook ใช้ javascript render ตัว element ทำให้ element บางตัวจะถูกแสดงขึ้นมาหลังจาก load เสร็จดังนั้นเราต้องเขียน function ที่จะรอทำงานก็ต่อเมื่อมี element ที่เราต้องการปรากฏขึ้นมาก่อน เพื่อไม่ให้เสียเวลาผมก็จะขอหยิบเอา function elementReady ของคุณ Joshua Wilson มาใช้เลยละกัน
Gist ของ function elementReady

jwilson8767/es6-element-ready.js

เมื่อเข้าใจที่มาที่ไปแล้วเราก็หยิบเอา function แปะลงไปที่ index.js เลย

const elementReady = (selector) => {
    return new Promise((resolve, reject) => {
      let el = document.querySelector(selector);
      if (el) {resolve(el);}
      new MutationObserver((mutationRecords, observer) => {
        // Query for elements matching the specified selector
        Array.from(document.querySelectorAll(selector)).forEach((element) => {
          resolve(element);
          //Once we have resolved we don't need the observer anymore.
          observer.disconnect();
        });
      })
        .observe(document.documentElement, {
          childList: true,
          subtree: true
        });
    });
}
  1. เขียน function ไล่จับ comment โดยผมก็จะไล่อ่านไปตาม node ที่มีชื่อ class ตามที่ผมพูดถึงในหัวข้อก่อนหน้า ส่วนนี้ผมจะขอเขียนเนื้อหาใน comment ของ code แทนนะครับ
let commentSet = new Set();
let temporarySize = 0;

const commentReader = () => {
    //Pinned comment node
    const pinnedNodes = document.querySelectorAll('.UFILivePinnedCommentLabel');

    //Comment nodes
    const commentNodes = document.querySelectorAll('.UFICommentActorAndBodySpacing');

    //ถ้ามี pinned ให้ถอยมาพิจารณา comment รองสุดท้าย แต่ถ้าไม่มีก็เอาอันสุดท้ายเลย
    const index = (pinnedNodes.length > 0 ? commentNodes.length - 2 : commentNodes.length - 1);

    //ถ้า index แล้วก็เรียก comment node ออกมา
    const commentNode = commentNodes[index];

    //ดึง node ของชื่อและข้อความใน comment
    const customerNameNode = commentNode.querySelector('.UFICommentActorName');
    const commentMessageNode = commentNode.querySelector('.UFICommentBody');

    if (customerNameNode) {
        //ดึงข้อมูลออกมาด้วย innerText และ href ในกรณีที่เป็น facebook profile url
        const name = customerNameNode.innerText;
        const url = customerNameNode.href;
        const message = commentMessageNode.innerText;

        //เก็บไว้ใน comment object
        const comment = { name, message, url }

        //พิจารณาว่า message มันมีตัวเลขอยู่ไหม ถ้ามีถือว่าลูกค้าสนใจ
        //ตรงนี้แก้ไขไปตามเงื่อนไขที่ต้องการได้เลย
        if (/\d+/.test(message.toLowerCase())) {

            //บันทึก comment object ลงใน set เพราะมันจัดการข้อมูลซ้ำให้ได้เองในตัว
            //แต่ต้องเปลี่ยนมันเป็น string กันก่อน ตอนนี้ก็ถือว่าเป็น Set ของ string แล้ว
            commentSet.add(JSON.stringify(comment));

            //add ใส่ set ไปแล้วจำนวนมันมากขึ้นจากเดิมไหม ถ้ามากขึ้นแปลว่ามี comment ใหม่เข้ามา
            if (commentSet.size > temporarySize) {

                //ปรับจำนวนของ size ว่าเปลี่ยนไปแล้วนะ จะได้เอาไว้เทียบกับอีก 500ms ต่อไป
                temporarySize = commentSet.size;
                
                //เรียบร้อยแล้วก็ log ออกมาดูหน่อยว่าได้ผลไหม แต่อันนี้ขอ censor หน่อยเผื่อตอนทำ screenshot จะได้ไม่โดนฟ้อง :3
                console.log({
                    name: `${comment.name.substring(0,4)}...(censor)`,
                    profileUrl: `${comment.url.substring(0,44)}...(censor)`,
                    message: comment.message,
                })
            }
        }
    }

    // วนใหม่อีกใน 0.5 วินาที
    setTimeout(commentReader, 500);
}

สรุปผลการทดลอง

create-facebook-live-extension-for-edge-or-chrome

  1. ใน console ก็จะแสดงรายชื่อ profile url พร้อมกับ message ที่เรา filter แล้วอย่างสวยงาม
  2. ถึงแม้จะเห็นผลดี แต่ยังไม่เคยลองกับ page ใหญ่ๆที่มี comment เข้ามารัวๆตรงนี้ก็ยังการันตีไม่ได้ชัดเจนนักว่าจะตกหล่นไหม อาจจะปรับรอบการวนให้ถี่ขึ้นได้ แต่ก็ได้ข้อความตามที่แสดงขึ้นมาใหม่ในหน้า web เท่านั้น ไม่รู้ว่า facebook เองจะส่งมาให้เราทั้งหมดหรือเปล่า
  3. ต้องเปิดตั้งแต่เริ่ม live เพราะถ้าเปิดทีหลัง คนก่อนหน้าก็จะตกหล่น
  4. ถ้าเน็ตหลุดหรือเผลอ refresh ตัวนี้จะนับใหม่เลย ข้อมูลก็หายไป
  5. ไม่สามารถดึงข้อมูลก่อนหน้ามาได้

แนวทางการพัฒนาต่อ

  1. เขียน function ดึง comment บนๆมาด้วยตอนที่โหลดครั้งแรก
  2. save เป็นไฟล์ออกมาก่อนได้ ป้องกันปัญหาเวลาเน็ตหลุดหรือเผลอปิด
  3. เพิ่ม field สำหรับกำหนด pattern ของ message เพราะการขายแต่ละอย่างจะเปลี่ยนตามราคาหรือชื่อไปเรื่ือยๆ
  4. ถ้ามี stock ก็ใส่จำนวนแล้วค่อยๆหัก stock ไปก็ได้
  5. เปลี่ยนไปใช้ Facebook Live Comments API

ตัวอย่าง code

Github: facebook-live-helper-for-edge

จบไปแล้วครับสำหรับการทดลองง่ายๆในวันนี้ เชื่อว่าหลายๆท่านน่าจะเห็นช่องทางในการพัฒนาต่อได้นะครับ สำหรับท่านไหนที่มีข้อเสนอแนะสามารถบอกผมได้เลยครับ ผมยินดีมากที่จะได้ปรับปรุงครับ สำหรับท่านใดที่มีคำถามสามารถสอบถามเข้ามาที่ Inbox ของ Facebook Page ได้เลยนะครับ

บทความใกล้เคียง