Jefreesujit / CCO Gang Exp Ranking

// ==UserScript==
// @name         CCO Gang Exp Ranking
// @namespace    Jefreesujit
// @version      1.0.1
// @description  A simple script for CCO gangs to rank players by EXP.
// @author       Jefreesujit
// @match        https://cybercodeonline.com/clan-players*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=cybercodeonline.com
// @grant        none
// @license      MIT
// ==/UserScript==

/*jshint esversion: 8 */

(function() {
    'use strict';

    /* <--------------------------------- DOM ------------------------------> */

    const renderPlayerRankings = (rankingData) => {
        const startDate = localStorage.getItem('rankStartDate');
        const rankStatusText = `<span class="p-2"><b>Start Date:</b> ${startDate}</span><span class="p-2"> <b>Ranking Status:</b> In progress</span>`;
        console.table(rankingData);
        let rankingDomEl = `<div class="relative ion-activatable cursor-pointer w-full mb-2 border border-primary-dark pl-4 pr-4 p-1 items-center">
            <div class="rank-item flex-row whitespace-nowrap flex-wrap text-primary-dark justify-between" style="width: 100%; color: rgb(204, 197, 65)">
              <span style="width: 3em" class="flex-1 mr-4"> Rank </span>
              <span class="inline-flex flex-col justify-center rounded pl-2 pr-2 mr-4" style="width: 14em; cursor: pointer; height: 24px;">
                <span class="flex flex-row flex-nowrap">Player Name </span>
              </span>
              <span style="width: 6em" class="flex-1 mr-4"> Start Exp </span>
              <span style="width: 6em" class="flex-1 mr-4"> Current Exp </span>
              <span style="width: 6em" class="flex-1 mr-4"> Exp Gain </span>
            </div>
          </div>`;

        rankingData.forEach((player, index) => {
            rankingDomEl += `<div class="relative ion-activatable cursor-pointer w-full mb-2 border border-primary-dark pl-4 pr-4 p-1 items-center">
            <div class="rank-item flex-row whitespace-nowrap flex-wrap text-primary-dark justify-between" style="width: 100%">
              <span style="width: 3em" class="flex-1 mr-4">${index + 1}</span>
              <span class="inline-flex flex-col justify-center rounded pl-2 pr-2 hover:bg-black-normal mr-4 " style="width: 14em; cursor: pointer; height: 24px; color: rgb(192, 228, 220);">
                <span class="flex flex-row flex-nowrap">${player.PlayerName}</span>
              </span>
              <span style="width: 6em" class="flex-1 mr-4">${player.StartExp}</span>
              <span style="width: 6em" class="flex-1 mr-4">${player.CurrentExp}</span>
              <span style="width: 6em" class="flex-1 mr-4">${player.ExpGain}</span>
            </div>
          </div>`;
        });

        document.querySelector('#rankingTableEl').insertAdjacentHTML('beforeend', rankingDomEl);
        document.querySelector('#rankingStatus').insertAdjacentHTML('beforeend', rankStatusText);
    }

    const renderContainerElement = (isRankActive) => {
        const gangName = document.querySelector('.absolute .neon-text').innerText;
        const gangTag = gangName.substring(gangName.indexOf('[')+1, gangName.indexOf(']'));
        const btnText = isRankActive ? 'End Ranking' : 'Start Ranking';

        let ContentEl = `
          <div id="contentEl" class="border items-center mt-2 mb-2">
            <div id="contentTitle" style="font-weight: bold; font-size: 24px;" class="mb-2 mt-2"> ${gangTag} Gank Rankings </div>
            <div id="rankingStatus" style="display:block"></div>
            <div id="actionBtn" style="border: 1px solid rgb(204, 197, 65);padding: 5px 15px;color: rgb(204, 197, 65);" class="p-1 mt-4 mb-4 cursor-pointer">${btnText}</div>
            <div id="rankingTableEl" class="mb-2"></div>
          </div>
        `;
        document.querySelector('.p-4.min-h-full').childNodes[1].insertAdjacentHTML('beforeend', ContentEl);
        document.querySelector('#actionBtn').addEventListener("click", () => { handleActionClick(isRankActive, gangTag) });
    }

    /* <--------------------------------- Utils ---------------------------------> */

    const downloadTextFile = (text, name) => {
      const a = document.createElement('a');
      const type = name.split(".").pop();
      a.href = URL.createObjectURL(new Blob([text], {
        type: `text/${type === "txt" ? "plain" : type}`
      }));
      a.download = name;
      a.click();
    };

    // credits: https://stackoverflow.com/questions/8847766/how-to-convert-json-to-csv-format-and-store-in-a-variable
    const convertToCsv = (json) => {
        const replacer = function(key, value) { return value === null ? '' : value };
        const fields = Object.keys(json[0]);
        let csv = json.map(function(row) {
          return fields.map(function(fieldName){
            return JSON.stringify(row[fieldName], replacer)
          }).join(',')
        })
        csv.unshift(fields.join(',')) // add header column
        csv = csv.join('\r\n');

        return csv;
    }

    /* <--------------------------------- Ranking Script ----------------------------------> */

    const calculatePlayerRankings = (startExpMap, currentExpMap) => {
        console.log('startExpMap, currentExpMap', startExpMap, currentExpMap);
        let mappedData = Object.keys(currentExpMap).map((player) => {
            const curExp = currentExpMap[player];
            const stExp = startExpMap[player] || 0;
            const xpGain = curExp - stExp;

            return {
                PlayerName: player,
                StartExp: stExp,
                CurrentExp: curExp,
                ExpGain: xpGain
            };
        });
        const rankedData = mappedData.sort((a, b) => b.ExpGain - a.ExpGain);
        console.log('rankedData', rankedData);
        return rankedData;
    };

    const getPlayerExpDetails = () => {
        const playerExpMap = {};
        document.querySelectorAll('.mt-2 .relative.ion-activatable.items-center').forEach(subEl => {
            const nameEl = subEl.querySelector('.w-full .flex-nowrap');
            const expEl = subEl.querySelectorAll('.flex-row.whitespace-nowrap .flex-1.mr-4')[3];
            const nameValue = nameEl.innerText;
            const expValue = parseInt(expEl.innerText.replace(/[^0-9]/g,''), 10);
            playerExpMap[nameValue] = expValue;
        });
        return playerExpMap;
    };

    const handleStopTask = (gangTag) => {
        const playerExp = getPlayerExpDetails();
        const date = new Date().toDateString();
        const rankData = calculatePlayerRankings(playerExp, playerExp);
        renderPlayerRankings(rankData);
        const startDate = localStorage.getItem('rankStartDate');
        const fileName = `${gangTag}-gang-contest-${startDate}-${date}.csv`;
        const csvData = convertToCsv(rankData);
        downloadTextFile(csvData, fileName);
        localStorage.setItem('isRankingActive', false);
        localStorage.setItem('playerStartExp', {});
        document.querySelector('#actionBtn').innerText = 'Start Ranking';
        document.querySelector('#rankingStatus').innerHTML = '';
        document.querySelector('#rankingTableEl').innerHTML = '';
    };

    const handleStartTask = () => {
        const playerExp = getPlayerExpDetails();
        const date = new Date().toDateString();
        const rankData = calculatePlayerRankings(playerExp, playerExp);
        localStorage.setItem('isRankingActive', true);
        localStorage.setItem('rankStartDate', date);
        localStorage.setItem('playerStartExp', JSON.stringify(playerExp));
        renderPlayerRankings(rankData);
        document.querySelector('#actionBtn').innerText = 'End Ranking';
    };

    const handleActionClick = (isActive, gangTag) => {
        const rankAttr = localStorage.getItem('isRankingActive');
        const isRankActive = rankAttr && JSON.parse(rankAttr);
        if (!isRankActive) {
            console.log('Starting tracking gang progress');
            handleStartTask(gangTag);
        } else {
            console.log('Ending tracking gang progress');
            handleStopTask(gangTag);
        }
    }

    const checkIfHostGang = () => {
        const store = JSON.parse(localStorage.getItem('store'));
        const searchParams = new URLSearchParams(location.search);
        const currentGangId = searchParams.get('clanId');
        const playerGangId = store.player.cid;
        console.log('gangId', currentGangId, playerGangId);
        return currentGangId === playerGangId;
    }


    // Initialise Script after 10 seconds of page load (wait until page loads fully)
    setTimeout(() => {
        window.console.clear = () => {};
        window.console.log = (...args) => console.info.call(window, ...args);
        console.log('CCO Gang exp ranking script Initialized');
        // check if the host belongs to the gang, if so start the script.
        if (checkIfHostGang()) {
            const rankAttr = localStorage.getItem('isRankingActive');
            const isRankActive = rankAttr && JSON.parse(rankAttr);
            console.log('Ranking Status', isRankActive);
            renderContainerElement(isRankActive);
            if (isRankActive) {
                const startExp = JSON.parse(localStorage.getItem('playerStartExp'));
                const currentExp = getPlayerExpDetails();
                const rankData = calculatePlayerRankings(startExp, currentExp);
                renderPlayerRankings(rankData);
            }
        }
    }, 10000);
})();