Jefreesujit / CCO Buff Alarm V2

// ==UserScript==
// @name         CCO Buff Alarm V2
// @namespace    CyberCodeOnline
// @version      2.0.1
// @description  Simple calibration alert bot for cyber code online
// @author       Jefreesujit
// @match        https://cybercodeonline.com/tabs/stats
// @icon         https://www.google.com/s2/favicons?sz=64&domain=cybercodeonline.com
// @grant        none
// @license      MIT

// ==/UserScript==

/*jshint esversion: 8 */

(function() {
    'use strict';
    
    // bot url endpoint
    const hookUrl = 'https://discord.com/api/webhooks/963049861220032582/0560pIXdu5ScAXDLBsbT7xaHkzeuMhn8lEXcE6BDEQvXr2MOwOuvbniJDFsnUR9c8qtrx';
    const role = '<@&960109485618241587>';

    // buff to be looked for
    const buffPrefix = 'calibration';
    let messageId, isTimeoutActive = false;

    const delay = async (timer = 5000) => {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                resolve(timer);
            }, timer);
        });
    }
    
    // calculate the buff score from percentage
    // Contribution credits: Rejid
    const getBuffScoreFromPerc = (percent) => {
      if (percent>=666){
        return "+10 Cali";
      }
      if (percent>=400){
        return "+9 Cali";
      }
      if (percent>=200){
        return "+8 Cali";
      }
      if (percent>=150){
        return "+7 Cali";
      }
      if (percent>=100){
        return "+6 Cali";
      }
      if (percent>=50){
        return "+5 Cali";
      }
      if (percent>=15){
        return "+4 Cali";
      }
      if (percent>=10){
        return "+3 Cali";
      }
      if (percent>=5){
        return "+2 Cali";
      }
      return "No Cali";
    };
    
    // Iterate through each active buffs to check for matching buff name
    // Fetch the percentage score for each buff and sum it up
    const getBuffPerc = async () => {
        let buffVal = 0, buffName = '';
        const buffContEl = document.querySelector('.z-10 div.flex-row.flex-wrap.mb-2');
        const buffArray = [];
        buffContEl.childNodes.forEach((childEl, index) => {
            console.log('childEl', childEl);
            const buffEl = childEl.childNodes[1];
            const bt = buffEl.innerText;
            if (bt.toLowerCase().includes(buffPrefix)) {
                buffArray.push(childEl);
                buffName = bt;
            }
        });
        for (let buffItem of buffArray) {
            await delay(1000);
            buffItem.childNodes[1].click();
            const buffModal = document.querySelector('div.border.neon.p-2.justify-center.items-center.mr-2');
            const buffPerc = buffModal ? buffModal.innerText.split('\n').shift() : '0';
            buffModal && document.querySelector('.dialog-backdrop').click();
            console.log('buffPerc', buffVal);
            buffVal += parseInt(buffPerc.replace(/[^0-9]/g,''), 10);
        }
        console.log('buffVal after for loop', buffVal);
        return [buffVal, buffName];
    };

    // Experimental : Push all events to another events bot
    const pushEvents = (eventText) => {
       fetch(`${hookUrl}?wait=true`, {
            method: 'POST',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ content: eventText })
        });
    };

    // Send message to bot
    const sendMessage = async (message) => {
        const rawResponse = await fetch(`${hookUrl}?wait=true`, {
            method: 'POST',
            headers: {
                'Accept': 'application/json',
                'Content-Type': 'application/json'
            },
            body: JSON.stringify({ content: message })
        });
        const content = await rawResponse.json();
        console.log('response', content);
        return content;
    };

    // Update existing message if messageId already present
    const updateMessage = async (message, msgId) => {
        const rawResponse = await fetch(`${hookUrl}/messages/${msgId}`, {
            method: 'PATCH',
            headers: {
              'Accept': 'application/json',
              'Content-Type': 'application/json'
            },
            body: JSON.stringify({ content: message })
        });
        const content = await rawResponse.json();
        return content;
    };
    
    // Listen for observed dom mutations and check for matching buff names
    // Fetch required metadata, construct request payload for sending message to bot
    const getMessage = async (mutation) => {
        let message = '';

        if (mutation.type == 'childList' && mutation.addedNodes.length) {
            const newEl = mutation.addedNodes[0];
            if (newEl.classList.length === 0) {
                const buffText = newEl.innerText;
                if (buffText.toLowerCase().includes(buffPrefix)) {
                    message = `${role} Buff Added `;
                    const activatedBy = newEl.querySelector('.text-mod').innerText;
                    try {
                        const [tBuffPerc, tBuffName] = await getBuffPerc();
                        const buffScore = getBuffScoreFromPerc(tBuffPerc);
                        message = `**Buff Name**: *${tBuffName}* \n**Buff Value**: ${buffScore} (estimated)\n**Mentions**: ${role} @here \n**Activated By**: *${activatedBy}*`;
                    } catch (e) {
                        console.log('Failed fetching buff percenatage', e);
                    }
                }
                console.log(buffText);
                // pushEvents(buffText);
            }
        }
        return message;
    };
    
    // Callback handler for observed dom mutations
    const callback = async (mutationsList) => {
        for(let mutation of mutationsList) {
            // console.log('buff object', mutation.type, mutation);
            const message = await getMessage(mutation);
            if (message) {
               if (messageId) {
                  console.log('updateMessage', message, messageId);
                  const { id } = await updateMessage(message, messageId);
               } else {
                   console.log('sendMessage', message);
                   const { id } = await sendMessage(message);
                   messageId = id;
               }
            }
        }
        // Persist the messageId for 4 minutes when new matching buff is added. 
        // Whenever a buff is added within that time, update existing message with this messageId
        // Erase the messageId after 4 minutes (active buff time) and push upcoming event as new buffs
        if (messageId && !isTimeoutActive) {
           isTimeoutActive = true;
           setTimeout(() => {
               console.log('messageId', messageId)
               messageId = null;
               isTimeoutActive = false;
           }, 240000);
        }
    };

    // Initialise Script after 10 seconds of page load (wait until page loads fully)
    setTimeout(() => {
        const config = { attributes: false, childList: true, subtree: false, characterData: false };
        // Buff observable DOM
        const buffContainerEl = document.querySelector('.h-full.relative.flex-col-reverse.overflow-y-scroll.overflow-x-hidden'); // chat window
        // const buffContainerEl = document.querySelector('.z-10 div.flex-row.flex-wrap.mb-2'); // buff window
        try {
            const observer = new MutationObserver(callback);
            observer.observe(buffContainerEl, config);
            console.log('Buff Observable v2 Initialized');
        } catch (e) {
            console.log('Buff Observable v2 Initialization error', e);
        }
    }, 10000);

})();