← back to browse
goat command

toplevel

Premium animated level leaderboard

0
views
0
likes
0
installs

Aliases: toplvl, leveltop, toplevel

raw source
"use strict";

/**
 * =====================================================
 * TOPLEVEL.JS - PREMIUM LEVEL LEADERBOARD
 * ARIYAN AI
 *
 * Same GIF setup as working spy.js
 * NO mongoose
 * NO utils
 * NO extra database
 * =====================================================
 */

if (typeof process.stderr.clearLine !== "function") {
  process.stderr.clearLine = function () {};
}

if (typeof process.stderr.cursorTo !== "function") {
  process.stderr.cursorTo = function () {};
}

const fs = require("fs-extra");
const path = require("path");
const Canvas = require("canvas");
const moment = require("moment");
const GIFEncoder = require("gifencoderv2");


// =====================================================
// NUMBER FORMAT
// =====================================================

const units = [
  "",
  "K",
  "M",
  "B",
  "T",
  "Q",
  "S",
  "O",
  "N",
  "D"
];

function shortenNumber(num) {
  num = Number(num) || 0;

  if (num < 1000) {
    return Math.floor(num).toString();
  }

  let unitIndex = 0;
  let n = num;

  while (
    n >= 1000 &&
    unitIndex < units.length - 1
  ) {
    n /= 1000;
    unitIndex++;
  }

  return (
    n.toFixed(2).replace(/\.?0+$/, "") +
    units[unitIndex]
  );
}


// =====================================================
// LEVEL SYSTEM
// =====================================================

function getLevelInfo(exp) {
  let level = 1;
  let expNeed = 100;

  exp = Number(exp) || 0;

  while (exp >= expNeed) {
    exp -= expNeed;
    level++;

    expNeed =
      level * 100 + 70;
  }

  return {
    level,
    curExp: exp,
    expNeed
  };
}


// =====================================================
// CANVAS HELPERS
// =====================================================

function roundedRect(
  ctx,
  x,
  y,
  width,
  height,
  radius
) {
  radius = Math.min(
    radius,
    width / 2,
    height / 2
  );

  ctx.beginPath();

  ctx.moveTo(
    x + radius,
    y
  );

  ctx.lineTo(
    x + width - radius,
    y
  );

  ctx.quadraticCurveTo(
    x + width,
    y,
    x + width,
    y + radius
  );

  ctx.lineTo(
    x + width,
    y + height - radius
  );

  ctx.quadraticCurveTo(
    x + width,
    y + height,
    x + width - radius,
    y + height
  );

  ctx.lineTo(
    x + radius,
    y + height
  );

  ctx.quadraticCurveTo(
    x,
    y + height,
    x,
    y + height - radius
  );

  ctx.lineTo(
    x,
    y + radius
  );

  ctx.quadraticCurveTo(
    x,
    y,
    x + radius,
    y
  );

  ctx.closePath();
}


function text(
  ctx,
  value,
  x,
  y,
  size,
  color,
  align = "left",
  bold = false
) {
  ctx.font =
    `${bold ? "bold " : ""}${size}px Sans`;

  ctx.fillStyle =
    color;

  ctx.textAlign =
    align;

  ctx.textBaseline =
    "middle";

  ctx.fillText(
    String(value),
    x,
    y
  );
}


// =====================================================
// PROGRESS BAR
// =====================================================

function drawProgressBar(
  ctx,
  x,
  y,
  width,
  height,
  percent
) {
  percent = Math.max(
    0,
    Math.min(100, percent)
  );

  ctx.fillStyle =
    "#24263d";

  roundedRect(
    ctx,
    x,
    y,
    width,
    height,
    height / 2
  );

  ctx.fill();

  const gradient =
    ctx.createLinearGradient(
      x,
      y,
      x + width,
      y
    );

  gradient.addColorStop(
    0,
    "#00f5a0"
  );

  gradient.addColorStop(
    0.5,
    "#00d9ff"
  );

  gradient.addColorStop(
    1,
    "#c86cff"
  );

  const fillWidth =
    width * (percent / 100);

  if (fillWidth > 0) {
    ctx.fillStyle =
      gradient;

    roundedRect(
      ctx,
      x,
      y,
      fillWidth,
      height,
      height / 2
    );

    ctx.fill();
  }
}


// =====================================================
// DIMENSIONS
// =====================================================

const W = 1200;

const HEADER_Y = 30;
const HEADER_H = 190;

const ROW_Y = 240;
const ROW_H = 105;

const FOOTER_H = 75;

const H =
  ROW_Y +
  ROW_H * 10 +
  FOOTER_H;


// =====================================================
// MODULE
// =====================================================

module.exports = {

  config: {

    name: "toplevel",

    aliases: [
      "toplvl",
      "leveltop",
      "toplevel"
    ],

    version: "2.0.0",

    author:
      "ARIYAN AI",

    countDown: 5,

    role: 0,

    shortDescription: {
      en:
        "Premium animated level leaderboard"
    },

    longDescription: {
      en:
        "Shows the top 10 users according to their experience and level."
    },

    category:
      "system",

    guide: {
      en:
        "{pn}"
    }
  },


  // ===================================================
  // START
  // ===================================================

  onStart: async function ({
    api,
    event,
    usersData
  }) {

    let tmp = null;

    try {

      // ===============================================
      // GET USERS
      // ===============================================

      const allUsers =
        (await usersData.getAll()) ||
        [];


      if (
        !Array.isArray(allUsers) ||
        allUsers.length === 0
      ) {

        return api.sendMessage(
          "❌ কোনো user database-এ পাওয়া যায়নি!",
          event.threadID,
          event.messageID
        );
      }


      // ===============================================
      // SORT BY EXP
      // ===============================================

      const sortedUsers =
        allUsers
          .filter(
            u =>
              u &&
              u.userID &&
              u.exp !== undefined &&
              u.exp !== null
          )
          .sort(
            (a, b) =>
              Number(b.exp || 0) -
              Number(a.exp || 0)
          );


      const top10 =
        sortedUsers.slice(0, 10);


      // ===============================================
      // PREPARE DATA
      // ===============================================

      const leaderboard = [];


      for (
        let i = 0;
        i < top10.length;
        i++
      ) {

        const user =
          top10[i];


        const exp =
          Number(user.exp) || 0;


        const levelInfo =
          getLevelInfo(exp);


        let name =
          "Unknown User";


        try {

          name =
            await usersData.getName(
              user.userID
            ) ||
            "Unknown User";

        } catch {}


        leaderboard.push({

          rank:
            i + 1,

          userID:
            String(user.userID),

          name:
            String(name),

          exp,

          level:
            levelInfo.level,

          currentExp:
            levelInfo.curExp,

          expNeed:
            levelInfo.expNeed

        });
      }


      // ===============================================
      // TEMP DIRECTORY
      // ===============================================

      const tempDir =
        path.join(
          __dirname,
          "../temp"
        );


      await fs.ensureDir(
        tempDir
      );


      tmp =
        path.join(
          tempDir,
          `toplevel-${Date.now()}.gif`
        );


      // ===============================================
      // GIF ENCODER
      // Same setup as spy.js
      // ===============================================

      const enc =
        new GIFEncoder(
          W,
          H
        );


      const gifStream =
        fs.createWriteStream(
          tmp
        );


      enc
        .createReadStream()
        .pipe(
          gifStream
        );


      enc.start();

      enc.setRepeat(0);

      enc.setDelay(1000 / 8);

      enc.setQuality(10);


      // ===============================================
      // FRAMES
      // ===============================================

      const FRAMES = 8;


      for (
        let frame = 0;
        frame < FRAMES;
        frame++
      ) {

        const cv =
          Canvas.createCanvas(
            W,
            H
          );


        const ctx =
          cv.getContext(
            "2d"
          );


        // =============================================
        // BACKGROUND
        // =============================================

        const bg =
          ctx.createLinearGradient(
            0,
            0,
            W,
            H
          );


        bg.addColorStop(
          0,
          "#090b1c"
        );

        bg.addColorStop(
          0.35,
          "#11142d"
        );

        bg.addColorStop(
          0.7,
          "#12112d"
        );

        bg.addColorStop(
          1,
          "#090918"
        );


        ctx.fillStyle =
          bg;


        ctx.fillRect(
          0,
          0,
          W,
          H
        );


        // =============================================
        // PARTICLES
        // =============================================

        for (
          let i = 0;
          i < 110;
          i++
        ) {

          const x =
            Math.random() * W;

          const y =
            Math.random() * H;


          ctx.beginPath();


          ctx.arc(
            x,
            y,
            Math.random() * 1.8 + 0.3,
            0,
            Math.PI * 2
          );


          ctx.fillStyle =
            `rgba(255,255,255,${
              (
                Math.random() *
                0.65 +
                0.15
              ).toFixed(2)
            })`;


          ctx.fill();
        }


        // =============================================
        // ANIMATED BORDER
        // =============================================

        const phase =
          frame / FRAMES;


        const border =
          ctx.createLinearGradient(
            0,
            0,
            W,
            H
          );


        border.addColorStop(
          0,
          `hsl(${
            phase * 360
          },100%,60%)`
        );


        border.addColorStop(
          0.5,
          `hsl(${
            (phase + 0.5) * 360
          },100%,60%)`
        );


        border.addColorStop(
          1,
          `hsl(${
            (phase + 1) * 360
          },100%,60%)`
        );


        ctx.strokeStyle =
          border;


        ctx.lineWidth =
          10;


        ctx.strokeRect(
          5,
          5,
          W - 10,
          H - 10
        );


        // =============================================
        // HEADER CARD
        // =============================================

        ctx.fillStyle =
          "rgba(20,22,45,0.96)";


        roundedRect(
          ctx,
          35,
          HEADER_Y,
          W - 70,
          HEADER_H,
          28
        );


        ctx.fill();


        // =============================================
        // HEADER ICON
        // =============================================

        text(
          ctx,
          "🏆",
          95,
          90,
          55,
          "#ffffff",
          "center"
        );


        // =============================================
        // HEADER TITLE
        // =============================================

        text(
          ctx,
          "TOP 10",
          150,
          75,
          43,
          "#ffffff",
          "left",
          true
        );


        text(
          ctx,
          "LEVEL LEADERBOARD",
          150,
          125,
          38,
          "#c86cff",
          "left",
          true
        );


        // =============================================
        // HEADER RIGHT
        // =============================================

        text(
          ctx,
          `TOTAL USERS: ${allUsers.length}`,
          1120,
          75,
          21,
          "#858aa5",
          "right",
          true
        );


        text(
          ctx,
          "RANKED BY EXPERIENCE",
          1120,
          115,
          18,
          "#00d9ff",
          "right",
          true
        );


        // =============================================
        // LEADERBOARD
        // =============================================

        for (
          let i = 0;
          i < leaderboard.length;
          i++
        ) {

          const user =
            leaderboard[i];


          const y =
            ROW_Y +
            i * ROW_H;


          // -------------------------------------------
          // ROW COLOR
          // -------------------------------------------

          let rowColor =
            "#171a32";


          if (
            user.rank === 1
          ) {

            rowColor =
              "rgba(255,212,59,0.17)";

          } else if (
            user.rank === 2
          ) {

            rowColor =
              "rgba(220,225,235,0.13)";

          } else if (
            user.rank === 3
          ) {

            rowColor =
              "rgba(205,127,50,0.15)";
          }


          ctx.fillStyle =
            rowColor;


          roundedRect(
            ctx,
            35,
            y,
            W - 70,
            ROW_H - 10,
            20
          );


          ctx.fill();


          // -------------------------------------------
          // RANK COLOR
          // -------------------------------------------

          let rankColor =
            "#777d98";


          if (
            user.rank === 1
          ) {

            rankColor =
              "#ffd43b";

          } else if (
            user.rank === 2
          ) {

            rankColor =
              "#e2e8f0";

          } else if (
            user.rank === 3
          ) {

            rankColor =
              "#cd7f32";
          }


          // -------------------------------------------
          // RANK
          // -------------------------------------------

          ctx.beginPath();


          ctx.arc(
            90,
            y + 43,
            31,
            0,
            Math.PI * 2
          );


          ctx.fillStyle =
            "rgba(255,255,255,0.06)";


          ctx.fill();


          text(
            ctx,
            `#${user.rank}`,
            90,
            y + 43,
            22,
            rankColor,
            "center",
            true
          );


          // -------------------------------------------
          // NAME
          // -------------------------------------------

          let displayName =
            user.name;


          if (
            displayName.length > 24
          ) {

            displayName =
              displayName.slice(
                0,
                21
              ) + "...";
          }


          text(
            ctx,
            displayName,
            145,
            y + 34,
            27,
            "#ffffff",
            "left",
            true
          );


          text(
            ctx,
            `UID: ${user.userID}`,
            145,
            y + 68,
            15,
            "#707690",
            "left"
          );


          // -------------------------------------------
          // LEVEL
          // -------------------------------------------

          text(
            ctx,
            `LEVEL ${user.level}`,
            650,
            y + 35,
            27,
            "#c86cff",
            "center",
            true
          );


          text(
            ctx,
            "CURRENT LEVEL",
            650,
            y + 68,
            14,
            "#777d98",
            "center",
            true
          );


          // -------------------------------------------
          // EXP
          // -------------------------------------------

          text(
            ctx,
            `${shortenNumber(user.exp)} XP`,
            1080,
            y + 34,
            27,
            "#00f5a0",
            "right",
            true
          );


          text(
            ctx,
            `${shortenNumber(
              user.currentExp
            )} / ${shortenNumber(
              user.expNeed
            )} NEXT`,
            1080,
            y + 68,
            14,
            "#777d98",
            "right",
            true
          );


          // -------------------------------------------
          // EXP PROGRESS
          // -------------------------------------------

          const progress =
            user.expNeed > 0
              ? (
                  user.currentExp /
                  user.expNeed
                ) * 100
              : 0;


          drawProgressBar(
            ctx,
            810,
            y + 79,
            230,
            9,
            progress
          );
        }


        // =============================================
        // EMPTY ROWS
        // =============================================

        if (
          leaderboard.length < 10
        ) {

          for (
            let i =
              leaderboard.length;
            i < 10;
            i++
          ) {

            const y =
              ROW_Y +
              i * ROW_H;


            ctx.fillStyle =
              "rgba(255,255,255,0.025)";


            roundedRect(
              ctx,
              35,
              y,
              W - 70,
              ROW_H - 10,
              20
            );


            ctx.fill();


            text(
              ctx,
              `#${i + 1}`,
              90,
              y + 43,
              22,
              "#454a62",
              "center",
              true
            );


            text(
              ctx,
              "No user data",
              145,
              y + 43,
              22,
              "#454a62",
              "left"
            );
          }
        }


        // =============================================
        // FOOTER
        // =============================================

        const footerY =
          ROW_Y +
          ROW_H * 10;


        ctx.fillStyle =
          "rgba(5,6,17,0.95)";


        ctx.fillRect(
          0,
          footerY,
          W,
          FOOTER_H
        );


        text(
          ctx,
          "⚡ ARIYAN AI • LEVEL SYSTEM",
          40,
          footerY + 35,
          18,
          "#777d98",
          "left",
          true
        );


        text(
          ctx,
          moment().format(
            "DD/MM/YYYY • hh:mm A"
          ),
          W - 40,
          footerY + 35,
          17,
          "#777d98",
          "right"
        );


        // =============================================
        // ADD FRAME
        // =============================================

        enc.addFrame(ctx);
      }


      // ===============================================
      // FINISH
      // ===============================================

      enc.finish();


      await new Promise(
        (
          resolve,
          reject
        ) => {

          gifStream.on(
            "finish",
            resolve
          );

          gifStream.on(
            "error",
            reject
          );

        }
      );


      // ===============================================
      // SEND
      // ===============================================

      await api.sendMessage(
        {
          body:
            "🏆 TOP 10 LEVEL LEADERBOARD",
          attachment:
            fs.createReadStream(
              tmp
            )
        },

        event.threadID,

        () => {

          try {

            if (
              tmp &&
              fs.existsSync(tmp)
            ) {

              fs.unlinkSync(
                tmp
              );
            }

          } catch {}
        }
      );


    } catch (error) {

      console.error(
        "[TOPLEVEL ERROR]",
        error
      );


      try {

        if (
          tmp &&
          fs.existsSync(tmp)
        ) {

          fs.unlinkSync(
            tmp
          );
        }

      } catch {}


      return api.sendMessage(
        `⚠️ TopLevel Error:\n${error.message}`,
        event.threadID,
        event.messageID
      );
    }
  }
};

View raw file