K3V / Oldtoons BON Giveaway

// ==UserScript==
// @name         Oldtoons BON Giveaway
// @description  BON Giveaway for Oldtoons.
// @version      1.9.5
// @updateURL	 https://openuserjs.org/meta/K3V/Oldtoons_BON_Giveaway.meta.js
// @downloadURL	 https://openuserjs.org/install/K3V/Oldtoons_BON_Giveaway.min.user.js
// @license      GPL-3.0-or-later
// @match        https://oldtoons.world
// @icon         https://oldtoons.world/favicon.ico
// @grant        none
// ==/UserScript==

// ==OpenUserJS==
// @author jacksaw
// @collaborator Coasty
// ==/OpenUserJS==

// Additional credits
// @TheEther - Integration with Aither + some additional features
// @olufsen - commands for !addTime, !addbons, !removebons, !taken (numbers taken), !entries (users and the numbers they took) and host cannot enter the contest using number or !random
// @rkeaves - Manual Entry
// @Nums - added new commands + trying to keep the public version updated
// @ahoimate - got BON gifting API polling working + added new commands
// @ruckus612 - fixed BON gift bug
// @ZukoXZuko - draggable giveaway menu
// @merah - muliple winners and !leave
// @K3V - Tidied up giveaway form, fixed some undeclared variables, added semicolons, added comments and added new commands !removetime, !kick, !status, !info, First/Second/Third Entry Rewards - Idea from Ahoi, added actions !me, !slap, !hug. Added !change command: Allows users who entered with !random to change their number once.

//  ____  __.____________   ____
// |    |/ _|\_____  \   \ /   /
// |      <    _(__  <\   Y   /
// |    |  \  /       \\     /
// |____|__ \/______  / \___/
//         \/       \/
// HTTPS://WWW.K3V.CO.UK

// BUGS
// Switching tabs reloads ALL messages in the cb, duplicating all BON gifts and spamming the chat. This can be achieved with the timestamps now included in the chatbox messages
// giveaway amount incorrect when BON donated after reset
// When doing a giveaway for 123 minutes, it will trigger a reminder right at the start (6 reminders, triggered at 2 hours, 2 minutes, 58 seconds)
// Modify timer so that timestamp is used in order to keep accuracy
// Validation of the entry range only sets one field red

/* jshint esversion: 6 */

const SCRIPT_VERSION = "1.9.5";

var GENERAL_SETTINGS = {
    default_mins_per_reminder: 5,
    mins_per_reminder_limit: 3,
    disable_random: false,
    disable_lucky: false,
    disable_free: false,
};

let giveawayStartTime;
let sponsorsInterval;
const processedGiftIds = new Set();

// These settings can be used to test different portions of the script. By default, all should be set to false.
var DEBUG_SETTINGS = {
    log_chat_messages: false,
    disable_chat_output: false,
};

// Chatroom ID's for Sponsor
const CHATROOM_IDS = {
    "oldtoons.world": "4",
};

const currentUrl = window.location.href;
const currentHost = window.location.hostname;
const chatroomId = CHATROOM_IDS[currentHost] || "5";
const chatboxID = "chatbox__messages-create";
var chatbox = null;
var observer;
var giveawayData;
var numberEntries = new Map();
var randomEntries = new Set(); // Track users who entered via !random
const changedEntries = new Set(); // Track users who have used !change
var fancyNames = new Map();
var regNum = /^-?\d+$/;
var regGift = /([^ \n]+)\shas\sgifted\s([0-9.]+)\sBON\sto\s([^ \n]+)/;
var regApi = /">([^<]+)<\/a> has gifted ([\d.]+) BON to <a href="[^"]+"[^>]*>([^<]+)<\/a>/;
var whitespace = document.createTextNode(" ");

var coinsIcon = document.createElement("i");
coinsIcon.setAttribute("class", "fas fa-coins");

var goldCoins = document.createElement("i");
goldCoins.setAttribute("class", "fas fa-coins");
goldCoins.style.color = "#ffc00a";
goldCoins.style.padding = "5px";

var giveawayBTN = document.createElement("a");
giveawayBTN.setAttribute("class", "form__button form__button--text");
giveawayBTN.textContent = "Giveaway";
giveawayBTN.prepend(coinsIcon.cloneNode(false));
giveawayBTN.onclick = toggleMenu;

var frameHTML = `
<section id="giveawayFrame" class="panelV2" style="width: 320px; height: 90%; position: fixed; z-index: 9999; inset: 50px 150px auto auto; overflow: auto; border-style: solid; border-width: 1px; border-color: black" hidden="true">
  <header class="panel__heading">
    <div class="button-holder no-space">
      <div class="button-left">
        <h4 class="panel__heading">
          <i class="fas fa-coins" style="padding: 5px;"></i>
          Oldtoons BON Giveaway v${SCRIPT_VERSION}
        </h4>
      </div>
      <div class="button-right">
        <button id="resetButton" class="form__button form__button--text">
          πŸ”„ Reset
        </button>
        <button id="closeButton" class="form__button form__button--text">
          ❎ Close
        </button>
        <button id="settingsButton" type="button" class="form__button form__button--text">
          βš›οΈ Settings
        </button>
      </div>
    </div>
  </header>
  <div class="panel__body" id="giveaway_body">

    <div id="giveaway_settings_menu" class="giveaway_settings_menu" style="display: none">
      <div>
        <button type="button" id="toggleAllButton" class="form__button form__button--filled">
          Toggle all
        </button>
        <br>
        <p style="display: inline-block; box-sizing: content-box; margin: 5px; width: 100px;"> Random:</p>
        <input type="checkbox" id="randomToggle" style="width: 15px; height: 15px; cursor: pointer;" checked>
        <br>
        <p style="display: inline-block; box-sizing: content-box; margin: 5px; width: 100px;"> Lucky:</p>
        <input type="checkbox" id="luckyToggle" style="width: 15px; height: 15px; cursor: pointer;" checked>
        <br>
        <p style="display: inline-block; box-sizing: content-box; margin: 5px; width: 100px;"> Free:</p>
        <input type="checkbox" id="freeToggle" style="width: 15px; height: 15px; cursor: pointer;" checked>
      </div>
    </div>

    <div align="center"><img src="https://images2.imgbox.com/ed/76/QRozrt1Y_o.png" /></div>

    <h3 id="coinHeader" class="panel__heading--centered"></h3>
    <form class="form" id="giveawayForm" style="display: flex; flex-flow: column; align-items: center;">
      <p class="form__group" style="width: 66%;">
        <input class="form__text" required="" id="giveawayAmount" pattern="[0-9]*" value="" inputmode="numeric" type="text">
        <label class="form__label form__label--floating" for="giveawayAmount">
          Giveaway Amount
        </label>
      </p>
      <div class="panel__body" style="display: flex; justify-content: center; gap: 25px">
        <p class="form__group" style="width: 30%;">
          <input class="form__text" required="" id="startNum" pattern="[-]{0,1}[0-9]*" value="1" inputmode="numeric" type="text" maxlength="9">
          <label class="form__label form__label--floating" for="startNum">
            Start #
          </label>
        </p>
        <p class="form__group" style="width: 30%;">
          <input class="form__text" required="" id="endNum" pattern="[-]{0,1}[0-9]*" value="50" inputmode="numeric" type="text" maxlength="9">
          <label class="form__label form__label--floating" for="endNum">
            End #
          </label>
        </p>
      </div>
      <div class="panel__body" style="display: flex; justify-content: center; gap: 25px">
        <p class="form__group" style="width: 30%;">
        <input class="form__text" required="" id="timerNum" pattern="[0-9]*" value="10" inputmode="numeric" type="text">
        <label class="form__label form__label--floating" for="timerNum">
          Time (mins)
        </label>
        </p>
        <p class="form__group" style="width: 30%;">
          <input class="form__text" required="" id="reminderNum" pattern="[0-9]*" value="1" inputmode="numeric" type="text">
          <label class="form__label form__label--floating" for="reminderNum">
            Reminders
          </label>
        </p>
      </div>
      <div class="panel__body" style="display: flex; justify-content: center; gap: 25px">
        <p class="form__group" style="width: 30%;">
        <input class="form__text" required="" id="winnersNum" pattern="[0-9]*" value="1" inputmode="numeric" type="text">
        <label class="form__label form__label--floating" for="winnersNum">
          Winners #
        </label>
        </p>
      </div>
        <p class="form__group" style="width: 66%;">
         <input class="form__text" id="customMessage" value="" type="text">
         <label class="form__label form__label--floating" for="customMessage">
          Custom Message
         </label>
        </p>
      <p class="form__group" style="text-align: center;">
        <button id="startButton" class="form__button form__button--filled">
          βœ… Start
        </button>
      </p>
    </form>
    <h2 id="countdownHeader" class="panel__heading--centered" hidden="">
    </h2>
    <div id="entriesWrapper" class="data-table-wrapper" hidden="">
      <table id="entriesTable" class="data-table">
        <thead>
          <tr>
            <th>
              User
            </th>
            <th>
              Entry #
            </th>
          </tr>
        </thead>
        <tbody>
        </tbody>
      </table>
    </div>

	<!-- Manual Entry Section -->
	<div id="manualEntrySection" style="text-align: center;">
	  <h4>Manual Entry</h4>
	  <div class="panel__body" style="display: flex; justify-content: center; gap: 10px">
		<p class="form__group" style="width: 30%;">
			<input class="form__text" id="manualUsername" placeholder="" type="text">
			 <label class="form__label form__label--floating" for="manualUsername">
			  Username
			 </label>
		</p>
		<p class="form__group" style="width: 30%;">
			<input class="form__text" id="manualfancyName" placeholder="" type="text">
			 <label class="form__label form__label--floating" for="manualfancyName">
			  Class
			 </label>
		</p>
		<p class="form__group" style="width: 15%;">
			<input class="form__text" id="manualNumber" placeholder="" pattern="[0-9]*" inputmode="numeric" type="text">
			 <label class="form__label form__label--floating" for="manualNumber">
			  #
			 </label>
		</p>
		<button id="manualAddButton" class="form__button--filled">+</button>
	  </div>
	</div>

  </div>
</section>
`;

var settingsMenuStyle = `
        .giveaway_settings_menu {
          background-color: #2C2C2C;
          color: #CCCCCC;
          border-radius: 5px;
          position: absolute;
          top: 100px;
          right: 10px;
          z-index: 998;
          max-height: 260px;
          padding: 20px;
          overflow: auto;
          width: 250px;
          flex-direction: column;
          justify-content: center;
          box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
        }
        .giveaway_settings_menu > div {
          margin: 5px 0;
        }
        .giveaway_settings_menu #img_cb,
        .giveaway_settings_menu #autofill_cb,
        .giveaway_settings_menu #show_label {
          cursor: pointer;
        }/*# sourceMappingURL=style.css.map */

    .panel__body {
      padding: 5px !important;
    }

	.form__text {
	  height: 30px;
	}
`;

var giveawayFrame, resetButton, closeButton, coinHeader, coinInput, startInput, endInput, timerInput, reminderInput, customMessageInput, startButton, countdownHeader, entriesWrapper, giveawayForm, toggleAllButton, settingsButton, fancyName, winnersNum;

injectMenu();

function reminderAutoScaling() {
    var reminders = Math.floor(parseInt(timerInput.value) / GENERAL_SETTINGS.default_mins_per_reminder) - 1;

    if (reminders < 0) {
        reminderInput.value = 0;
    } else {
        reminderInput.value = reminders;
    }

    reminderInput.setCustomValidity("");
}

function entryRangeValidation() {
    const startVal = startInput.value.trim();
    const endVal = endInput.value.trim();

    // Allow optional negative sign followed by digits (no letters)
    const integerRegex = /^-?\d+$/;

    // Clear previous custom validity messages
    startInput.setCustomValidity("");
    endInput.setCustomValidity("");

    // Check for any letters in the inputs
    const lettersRegex = /[A-Za-z]/;

    if (lettersRegex.test(startVal) || lettersRegex.test(endVal)) {
        startInput.setCustomValidity("Letters are not allowedβ€”please enter valid integers.");
        endInput.setCustomValidity("Letters are not allowedβ€”please enter valid integers.");
        return false;
    }

    // Ensure the inputs match the integer pattern
    if (!integerRegex.test(startVal) || !integerRegex.test(endVal)) {
        startInput.setCustomValidity("Please enter valid integers (e.g., -5, 0, 10).");
        endInput.setCustomValidity("Please enter valid integers (e.g., -5, 0, 10).");
        return false;
    }

    const startNum = parseInt(startVal, 10);
    const endNum = parseInt(endVal, 10);

    // Check for NaN just in case
    if (isNaN(startNum) || isNaN(endNum)) {
        startInput.setCustomValidity("Please enter numbers only.");
        endInput.setCustomValidity("Please enter numbers only.");
        return false;
    }

    // Ensure start is not greater than end
    if (startNum > endNum) {
        endInput.setCustomValidity("End # should be greater than or equal to Start #.");
        return false;
    }

    return true;
}

function remindersValidation() {
    const totalTime = parseFloat(timerInput.value);
    const reminderCount = parseFloat(reminderInput.value);
    const minInterval = GENERAL_SETTINGS.mins_per_reminder_limit;

    // Reset previous validation message
    reminderInput.setCustomValidity("");

    // Basic input validation
    if (isNaN(totalTime) || isNaN(reminderCount) || reminderCount <= 0) {
        reminderInput.setCustomValidity("Please enter a valid number of reminders greater than 0.");
        reminderInput.reportValidity();
        return false;
    }

    const minsPerReminder = totalTime / reminderCount;

    if (minsPerReminder < minInterval) {
        const limitText = parseTime(minInterval * 60000);
        reminderInput.setCustomValidity(`There cannot be more than 1 reminder every: ${limitText}.`);
        reminderInput.reportValidity();
        return false;
    }

    return true;
}

function injectMenu() {
    var chatbox_header = document.querySelector(`#chatbox_header div`);

    if (chatbox_header == null) {
        // Chatbox hasnt loaded, so wait another 100ms before checking again
        setTimeout(function () {
            injectMenu();
        }, 100);
    } else {
        addStyle(settingsMenuStyle, "settings-menu-style");

        document.body.insertAdjacentHTML("beforeend", frameHTML);

        // New panel name
        chatbox_header.prepend(giveawayBTN);
        giveawayBTN.parentNode.insertBefore(whitespace, giveawayBTN.nextSibling);

        giveawayFrame = document.getElementById("giveawayFrame");
        resetButton = document.getElementById("resetButton");
        resetButton.onclick = resetGiveaway;

        closeButton = document.getElementById("closeButton");
        closeButton.onclick = toggleMenu;

        settingsButton = document.getElementById("settingsButton");
        settingsButton.onclick = toggleSettings;

        const randomCheckbox = document.getElementById("randomToggle");
        randomCheckbox.checked = !GENERAL_SETTINGS.disbable_random;
        randomCheckbox.addEventListener("change", function () {
            GENERAL_SETTINGS.disable_random = !randomCheckbox.checked;
        });

        const luckyCheckbox = document.getElementById("luckyToggle");
        luckyCheckbox.checked = !GENERAL_SETTINGS.disable_lucky;
        luckyCheckbox.addEventListener("change", function () {
            GENERAL_SETTINGS.disable_lucky = !luckyCheckbox.checked;
        });

        const freeCheckbox = document.getElementById("freeToggle");
        freeCheckbox.checked = !GENERAL_SETTINGS.disable_free;
        freeCheckbox.addEventListener("change", function () {
            GENERAL_SETTINGS.disable_free = !freeCheckbox.checked;
        });

        coinHeader = document.getElementById("coinHeader");
        coinHeader.textContent = document.getElementsByClassName("ratio-bar__points")[0].firstElementChild.textContent.trim();
        coinHeader.prepend(goldCoins.cloneNode(false));

        coinInput = document.getElementById("giveawayAmount");
        startInput = document.getElementById("startNum");
        endInput = document.getElementById("endNum");
        timerInput = document.getElementById("timerNum");
        reminderInput = document.getElementById("reminderNum");
		winnersNum = document.getElementById("winnersNum");
        customMessageInput = document.getElementById("customMessage");

        startButton = document.getElementById("startButton");
        startButton.onclick = startGiveaway;

        toggleAllButton = document.getElementById("toggleAllButton");
        toggleAllButton.onclick = toggleAll;

        countdownHeader = document.getElementById("countdownHeader");
        entriesWrapper = document.getElementById("entriesWrapper");
        giveawayForm = document.getElementById("giveawayForm");

        document.body.appendChild(giveawayFrame);

		// β€”β€”β€” Make the panel draggable by its header β€”β€”β€”
		const frameHeader = giveawayFrame.querySelector('header.panel__heading');
		frameHeader.style.cursor = 'move';
		frameHeader.style.userSelect = 'none';

		let isDragging = false;
		let dragOffsetX = 0;
		let dragOffsetY = 0;

		frameHeader.addEventListener('mousedown', e => {
		isDragging = true;
		const rect = giveawayFrame.getBoundingClientRect();
		// calculate pointer offset within panel
		dragOffsetX = e.clientX - rect.left;
		dragOffsetY = e.clientY - rect.top;
		// switch to explicit left/top so we can reposition it
		giveawayFrame.style.left = rect.left + 'px';
		giveawayFrame.style.top = rect.top + 'px';
		giveawayFrame.style.right = 'auto';
		giveawayFrame.style.bottom = 'auto';
		});

		document.addEventListener('mousemove', e => {
		if (!isDragging) return;
		// keep the same offset between pointer and panel origin
		giveawayFrame.style.left = (e.clientX - dragOffsetX) + 'px';
		giveawayFrame.style.top = (e.clientY - dragOffsetY) + 'px';
		});

		document.addEventListener('mouseup', () => {
		isDragging = false;
		});

        // Attach event listener to scale the number of reminders automatically
        timerInput.addEventListener("input", function () {
            reminderAutoScaling();
        });

        // Add validation of the reminders to ensure that the frequency is not too high
        reminderInput.addEventListener("input", function () {
            remindersValidation();
        });

        // Add entry range validation to ensure endInput > startInput
        startInput.addEventListener("input", function () {
            entryRangeValidation();
        });
        endInput.addEventListener("input", function () {
            entryRangeValidation();
        });
    }

        // Set up Manual Entry functionality
        const manualAddButton = document.getElementById("manualAddButton");
        if (manualAddButton) {
            manualAddButton.onclick = function(e) {
                e.preventDefault();
                const author = document.getElementById("manualUsername").value.trim();
				const fancyName = document.getElementById("manualfancyName").value.trim();
                const manualNumber = document.getElementById("manualNumber").value.trim();
                if (author === "" || manualNumber === "") {
                    alert("Please enter both username and entry number.");
                    return;
                }
                if (!regNum.test(manualNumber)) {
                    alert("Please enter a valid number.");
                    return;
                }
                const number = parseInt(manualNumber, 10);
                if (giveawayData && (number < giveawayData.startNum || number > giveawayData.endNum)) {
                    alert("Entry number must be between " + giveawayData.startNum + " and " + giveawayData.endNum);
                    return;
                }
                if (numberEntries.has(author)) {
                    alert(author + " has already been entered.");
                    return;
                }
                if (Array.from(numberEntries.values()).includes(number)) {
                    alert("Entry number " + number + " is already taken.");
                    return;
                }

				// Userclases
				const userClassColors = {
					"User": "#7289DA",
					"PowerUser": "#3C78D8",
					"SuperUser": "#1C4587",
					"ExtremeUser": "#1C4587",
					"InsaneUser": "#1C4587",
					"Veteran": "#1C4587",
					"Seeder": "#1C4587",
					"Archivist": "#1C4587",
					"Uploader": "#2ECC71",
					"Trustee": "#BF55EC",
					"VIP": "#FFBE76",
					"Internal": "#BAAF92",
					"Editor": "#15B097",
					"Coder": "#8FBC8F",
					"Moderator": "#4ECDC4",
					"Administrator": "#F92672",
					"Owner": "#00ABFF"
				};

				let color = userClassColors[fancyName];
				let fancy_author = color ? `<font color="${color}"><b>&bull; ${author}</b></font>` : author;

                numberEntries.set(author, number);
				fancyNames.set(author, fancy_author);
                updateEntries();
                var hostName = giveawayData.host;
				var ManualMessage = `[url=https://oldtoons.world/users/${hostName}][color=#1C4587][b]${hostName}[/b][/color][/url] [Host] Manually added entry: [url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] with number [b][color=#1DDC5D]${number}[/color][/b] 🀞`;
				sendMessage(ManualMessage);
            };
        }

}

function toggleMenu() {
    giveawayFrame.hidden = !giveawayFrame.hidden;
}

function startGiveaway() {
    if (!giveawayForm[0].checkValidity() || !giveawayForm[1].checkValidity() || !giveawayForm[2].checkValidity() || !giveawayForm[3].checkValidity() || !giveawayForm[4].checkValidity() || !giveawayForm[5].checkValidity()) {
        return;
    }

    // Chatbox isnt caught at the beginning when the script loads, so I moved it here for now
    if (chatbox == null) {
        chatbox = document.querySelector(`#${chatboxID}`);
    }

    startButton.disabled = true;
    coinInput.disabled = true;
    startInput.disabled = true;
    endInput.disabled = true;
    timerInput.disabled = true;
    reminderInput.disabled = true;

    startButton.parentElement.hidden = true;
    entriesWrapper.hidden = false;

    var totalTimeMs = timerInput.value * 60000;
    var reminderNum = parseInt(reminderInput.value);

    // Using this to pass by reference
    giveawayData = {
        host: document.getElementsByClassName("top-nav__username")[0].children[0].textContent.trim(),
        amount: parseInt(coinInput.value),
        startNum: parseInt(startInput.value),
        endNum: parseInt(endInput.value),
        totalEntries: parseInt(endInput.value) - parseInt(startInput.value) + 1,
        winningNumber: null,
        totalSeconds: totalTimeMs / 1000,
        timeLeft: totalTimeMs / 1000,
        reminderNum: reminderNum,
        customMessage: customMessageInput.value,
        hostAdded: parseInt(coinInput.value),
        sponsorContribs: {},
        reminderFreqSec: (totalTimeMs / 1000 / (reminderNum + 1)).toFixed(0),
        sponsors: [],
		winnersNum: winnersNum,
    };

    giveawayStartTime = new Date();
    sponsorsInterval = setInterval(getSponsors, 15000);

    var currentBon = parseInt(document.getElementsByClassName("ratio-bar__points")[0].textContent.trim().replace(/\s/g, ""), 10);

    if (currentBon < giveawayData.amount) {
        window.alert(`GIVEAWAY ERROR: The amount entered [b][color=#ffc00a](${giveawayData.amount}) BON[/color][/b], is above your current [b][color=#ffc00a](${currentBon}) BON[/color][/b]. You may need to refresh the page to update your BON amount. πŸ›‘`);
        resetGiveaway(giveawayData);
    } else {
        giveawayData.winningNumber = getRandomInt(giveawayData.startNum, giveawayData.endNum);

        // Setup an alert when trying to exit the tab during a giveaway
        window.onbeforeunload = function () {
            return "Giveaway in progress";
        };

        var introMessage =
            `[url=https://oldtoons.world/users/${giveawayData.host}][b][color=#1C4587]${giveawayData.host}[/color][/b][/url] is hosting a giveaway for [b][color=#ffc00a]${giveawayData.amount.toLocaleString()} BON[/color][/b] for [b][color=#5DE2E7]${giveawayData.winnersNum.value} Winner(s)[/color][/b]. ` +
            `Entries will be open for [b][color=#1DDC5D]${parseTime(totalTimeMs)}[/color][/b]. ` +
            `You may enter by submitting a whole number between [b][color=#1DDC5D]${giveawayData.startNum}[/color][/b] and [b][color=#1DDC5D]${giveawayData.endNum}[/color][/b] inclusive. ` +
            `[b][color=#5DE2E7]${giveawayData.customMessage} [/color][/b]\n` +
            `🎁Any [color=#ffc00a]BON[/color] [color=#FB4F4F]/gift[/color] to the host during the duration of the Giveaway is automatically added to the Pot!🎁 - 🀞 GOOD LUCK ALL! 🀞 - [b][color=#5DE2E7]!Help for commands.[/color][/b]`;

        sendMessage(introMessage);

        if (observer) {
            startObserver();
        } else {
            addObserver(giveawayData);
        }

        giveawayData.countdownTimerID = countdownTimer(countdownHeader, giveawayData);
    }
}

function addObserver(giveawayData) {
    observer = new MutationObserver((mutations) => {
        mutations.forEach((mutation) => {
            for (var i = 0; i < mutation.addedNodes.length; i++) {
                var tablist = document.querySelectorAll('[role="tablist"]');
                var general = tablist[0].childNodes[0];
                //console.log(general)
                //if(general.className != "panel__tab panel__tab--active") {
                //  console.log("General chat is not active")
                //}

                // !!! Here it is really important not to pass the giveawayData object to the parseMessage function. If done so, then it will for some reason always retain the pointer of the first
                // reference it was given, therefore when more than one giveaway is done in a row, the following giveaways messages will be parsed against the old giveawayData information.
                parseMessage(mutation.addedNodes[i]);
            }
        });
    });

    startObserver();
}

function startObserver() {
    var messageList = document.getElementsByClassName("chatroom__messages")[0];

    observer.observe(messageList, {
        childList: true,
    });
}

function parseMessage(messageNode) {
    var isBot = messageNode.querySelector(".chatbox-message__content") == null;

    if (isBot) {
        var messageContent = messageNode.querySelector(".chatbox-message__header").querySelector("div").textContent.trim();
    } else {
        //var author = messageNode.querySelector(".user-tag").textContent.trim();
		var author = messageNode.querySelector(".user-tag").textContent.replace('Unknown','').trim();
        var messageContent = messageNode.querySelector(".chatbox-message__content").textContent.trim();
        var fancyName = messageNode.querySelector(".user-tag").outerHTML;
    }

    if (regNum.test(messageContent)) {
        handleEntryMessage(parseInt(messageContent, 10), author, fancyName, giveawayData);
    } else if (isBot) {
        handleGiftMessage(messageContent, giveawayData);
    } else if (messageContent[0] == "!") {
        handleGiveawayCommands(author, messageContent, fancyName, giveawayData);
    }
}

async function getSponsors() {
    try {
        const res = await fetch(`${location.origin}/api/chat/messages/${chatroomId}`, {
            credentials: "include",
        });

        if (!res.ok) return;

        const json = await res.json();
        const messages = json.data;

        const giveawayStart = giveawayStartTime.getTime();

        messages.forEach((m) => {
            if (!m.message.includes("gifted")) return;
            if (new Date(m.created_at).getTime() < giveawayStart) return;
            if (processedGiftIds.has(m.id)) return;

            processedGiftIds.add(m.id);
            handleGiftMessage(m.message, giveawayData);
        });
    } catch (e) {
        if (DEBUG_SETTINGS.log_chat_messages) {
            console.error("Sponsor API Error:", e);
        }
    }
}

function handleGiftMessage(messageContent, giveawayData) {
    if (DEBUG_SETTINGS.log_chat_messages) {
        console.log("Processing gift message:", messageContent);
    }

    let gift = regApi.exec(messageContent) || regGift.exec(messageContent);

    if (!gift || gift.length < 4) {
        if (DEBUG_SETTINGS.log_chat_messages) {
            console.warn("Failed to parse gift message:", messageContent);
        }
        return;
    }

    const addAmount = parseFloat(gift[2]);
    const gifter = gift[1].trim();
    const recpt = gift[3].trim();

    if (DEBUG_SETTINGS.log_chat_messages) {
        console.log("Parsed gift:", {
            gifter,
            addAmount,
            recpt,
        });
    }

    if (recpt === giveawayData.host) {
        giveawayData.amount += addAmount;

        const giftMessage = `[url=https://oldtoons.world/users/${gifter}][b]${gifter}[/b][/url] is sponsoring an additional [b][color=#ffc00a]${addAmount} BON[/color][/b]! The total pot is now: [b][color=#ffc00a]${Number(
            cleanPotString(giveawayData.amount)
        ).toLocaleString()} BON[/color][/b], Thank You [url=https://oldtoons.world/users/${gifter}][b]${gifter}[/b][/url]. ❀️`;
        sendMessage(giftMessage);

        // If not yet included, add gifter to the list of giveaway sponsors
        if (!giveawayData.sponsors.includes(gifter)) {
            giveawayData.sponsors.push(gifter);
        }
    }
}

function handleGiveawayCommands(author, messageContent, fancyName, giveawayData) {
    var arguments = messageContent.substring(1).trim().split(" ");
    var command = arguments[0].toLowerCase();

	// Command to get giveaway time
    if (command == "time") {
        var TimeMessage = `Time left in the giveaway: [b][color=#1DDC5D]${parseTime(giveawayData.timeLeft * 1000)}[/color][/b]. βŒ›οΈ`;
        sendMessage(TimeMessage);

	// Command to get giveaway help/commands
    } else if (command == "help" || command == "commands") {
        var HelpMessage = `[spoiler=ℹ️ Help]
USER Commands
[code]
1. ![color=#FB4F4F][b]help[/b][/color]
Description - Displays help info for all available commands.
2. ![color=#FB4F4F][b]bon[/b][/color]
Description - Displays the current prize amount for the ongoing giveaway.
3. ![color=#FB4F4F][b]range[/b][/color]
Description - Displays the valid number range for current giveaways.
4. ![color=#FB4F4F][b]time[/b][/color]
Description - Displays the time left until the giveaway ends.
5. ![color=#FB4F4F][b]number[/b][/color]
Description - Submit a number guess for number-based giveaways.
6. ![color=#FB4F4F][b]taken[/b][/color]
Description - Shows the list of currently taken numbers that are not free free.
7. ![color=#FB4F4F][b]free[/b][/color]
Description - Shows the list of currently available numbers that anyone can claim for free, (if enabled).
8. ![color=#FB4F4F][b]random[/b][/color]
Description - Generates a random number within a predefined range (if enabled).
9. ![color=#FB4F4F][b]change[/b][/color]
Description - Allows users who entered with [color=#FB4F4F]!random[/color] to change their number once.
10. ![color=#FB4F4F][b]lucky[/b][/color]
Description - Displays the current lucky number for the ongoing giveaway, (if enabled).
11. ![color=#FB4F4F][b]entries[/b][/color]
Description - Shows how many entries you or others have made.
12. ![color=#FB4F4F][b]stats[/b][/color]
Description - Displays the current entries taken, entries free, time left and other giveaway statistics.
13. ![color=#FB4F4F][b]leave[/b][/color]
Description - Allows you to leave the giveaway.
[/code]
HOST Commands
[code]
1. ![color=#FB4F4F][b]addbon[/b][/color]
Description - Allows the HOST to add BON to the pot.
2. ![color=#FB4F4F][b]removebon[/b][/color]
Description - Allows the HOST to remove BON from the pot.
3. ![color=#FB4F4F][b]addtime[/b][/color]
Description - Allows the HOST to add time to the giveaway.
4. ![color=#FB4F4F][b]removetime[/b][/color]
Description - Allows the HOST to remove time from the giveaway.
5. ![color=#FB4F4F][b]reminder[/b][/color]
Description - Displayes the current giveaway info.
6. ![color=#FB4F4F][b]message[/b][/color]
Description - Displays the custom giveaway message, (if enabled).
7. ![color=#FB4F4F][b]kick[/b][/color]
Description - Allows the HOST to kick a user from the giveaway.
8. ![color=#FB4F4F][b]rig[/b][/color]
Description - (For testing) Simulates a rigged outcome (host/debug use).
9. ![color=#FB4F4F][b]unrig[/b][/color]
Description - (For testing) Restores fairness after a rigged outcome.
10. ![color=#FB4F4F][b]winners[/b][/color]
Description - Allows the HOST to change the number of Winners for the giveaway.
[/code]
Action Commands
[code]
1. ![color=#FB4F4F][b]me[/b][/color]
Description - usage is !me (action), EG !me goes for a nap.
2. ![color=#FB4F4F][b]slap[/b][/color]
Description - usage is !slap (user), EG !slap Jhonny.
3. ![color=#FB4F4F][b]hug[/b][/color]
Description - usage is !hug (user), EG !hug Jhonny.
4. ![color=#FB4F4F][b]lurk[/b][/color]
Description - usage is !lurk.
5. ![color=#FB4F4F][b]fpalm[/b][/color]
Description - usage is !fpalm.
6. ![color=#FB4F4F][b]vanish[/b][/color]
Description - usage is !vanish.
[/code]
Fun Commands
[code]
1. ![color=#FB4F4F][b]otw[/b][/color]
Description - πŸ’› [color=#A36D3B][b]O[/b][/color] [color=#D87F2C][b]L[/b][/color] [color=#E37717][b]D[/b][/color] [color=#D36809][b]T[/b][/color] [color=#D16303][b]O[/b][/color] [color=#CD6E43][b]O[/b][/color] [color=#D0542C][b]N[/b][/color] [color=#C42C00][b]S[/b][/color] 🧑
2. ![color=#FB4F4F][b]k3v[/b][/color]
Description - πŸ’° ALL YOUR [color=#ffc00a][b]BON[/b][/color] ARE BELONG TO [url=https://oldtoons.world/users/K3V][color=#DC3D1D][b]K3V[/b][/color][/url]! πŸ’°
3. ![color=#FB4F4F][b]ahoi[/b][/color]
Description - That one time when [url=https://oldtoons.world/users/ahoimate][b][color=#1C4587]Ahoimate[/color][/b][/url] cheated! [url=https://i.ibb.co/8DqPNqr8/Ahoi.png]PROOF[/url] πŸ‘€
4. ![color=#FB4F4F][b]christmas[/b][/color]
Description - 🀢🏼 [color=#ff0000][b]M[/b][/color] [color=#ffffff][b]E[/b][/color] [color=#ff0000][b]R[/b][/color] [color=#ffffff][b]R[/b][/color] [color=#ff0000][b]Y[/b][/color] [color=#ffffff][b]C[/b][/color] [color=#ff0000][b]H[/b][/color] [color=#ffffff][b]R[/b][/color] [color=#ff0000][b]I[/b][/color] [color=#ffffff][b]S[/b][/color] [color=#ff0000][b]T[/b][/color] [color=#ffffff][b]M[/b][/color] [color=#ff0000][b]A[/b][/color] [color=#ffffff][b]S[/b][/color] πŸŽ…πŸΌ
5. ![color=#FB4F4F][b]halloween[/b][/color]
Description - πŸŽƒ [color=#ffa500][b]H[/b][/color] [color=#008000][b]A[/b][/color] [color=#ffa500][b]P[/b][/color] [color=#008000][b]P[/b][/color] [color=#ffa500][b]Y[/b][/color] [color=#008000][b]H[/b][/color] [color=#ffa500][b]A[/b][/color] [color=#008000][b]L[/b][/color] [color=#ffa500][b]L[/b][/color] [color=#008000][b]O[/b][/color] [color=#ffa500][b]W[/b][/color] [color=#008000][b]E[/b][/color] [color=#ffa500][b]E[/b][/color] [color=#008000][b]N[/b][/color] πŸ‘»
6. ![color=#FB4F4F][b]easter[/b][/color]
Description - 🐰 [color=#808080][b]H[/b][/color] [color=#ffffff][b]A[/b][/color] [color=#808080][b]P[/b][/color] [color=#ffffff][b]P[/b][/color] [color=#808080][b]Y[/b][/color] [color=#ffffff][b]E[/b][/color] [color=#808080][b]A[/b][/color] [color=#ffffff][b]S[/b][/color] [color=#808080][b]T[/b][/color] [color=#ffffff][b]E[/b][/color] [color=#808080][b]R[/b][/color] πŸ₯š.
[/code]
Script Commands
[code]
1. ![color=#FB4F4F][b]version[/b][/color]
Description - Shows the latest script version and URL.
2. ![color=#FB4F4F][b]new[/b][/color]
Description - Shows the latest feature added to the script.
3. ![color=#FB4F4F][b]thx[/b][/color]
Description - Shows a Thank You message, (if enabled).
[/code]
[/spoiler]`;
        sendMessage(HelpMessage);

	// Command to get giveaway BON amount
    } else if (command == "bon") {
        var BonMessage = `Giveaway Amount: [b][color=#FFB700]${giveawayData.amount}[/color][/b]. πŸͺ™`;
        sendMessage(BonMessage);

	// Command to get giveaway range
    } else if (command == "range") {
        var RangeMessage = `Numbers between [b][color=#1DDC5D]${giveawayData.startNum}[/color][/b] and [b][color=#1DDC5D]${giveawayData.endNum}[/color][/b] inclusive are valid. πŸ’―`;
        sendMessage(RangeMessage);

	// Command to get giveaway random number
    } else if (command == "random") {
        // Prevent host from using !random
        if (author === giveawayData.host) {
            sendMessage(`Sorry [url=https://oldtoons.world/users/${author}][b]${author}[/b][/url], hosts can't enter their own giveaway. β›”`);
            return;
        }
        if (GENERAL_SETTINGS.disable_random) {
            var DisabledRandomMessage = `Sorry [url=https://oldtoons.world/users/${author}][b]${author}[/b][/url], but [color=#999999]!random[/color] has been disabled for this giveaway. 🚫`;
            sendMessage(DisabledRandomMessage);
            return;
        }
        userNumber = numberEntries.get(author);
        if (userNumber != undefined) {
            var UserNumberMessage = `Sorry [url=https://oldtoons.world/users/${author}][b]${author}[/b][/url], but you already entered with number [color=#1DDC5D][b]${userNumber}[/b][/color]. ❗`;
			sendMessage(UserNumberMessage);
        } else {
            var randomNum = 0;
            var currentNumbers = Array.from(numberEntries.values());
            do {
                randomNum = Math.floor(Math.random() * (giveawayData.endNum - giveawayData.startNum + 1)) + giveawayData.startNum;
            } while (currentNumbers.includes(randomNum));
            addNewEntry(author, fancyName, randomNum);
			randomEntries.add(author); // Mark user as having entered via !random

		// First/Second/Third Entry Rewards - Idea from Ahoi
		if (numberEntries.size == 1) {

			var UserNumberMessage1 = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has entered with the number [color=#1DDC5D][b]${randomNum}[/b][/color]. πŸ₯‡ [b][color=#E6A65B]First Entry[/color][/b], You have been rewarded [b][color=#ffc00a]100 BON[/color][/b].`;
			sendMessage(UserNumberMessage1);

			var giftMessage1 = `/gift ${author} 100 Congratulations! πŸ₯‡ First Entry Reward!`;
			sendMessage(giftMessage1);

		} else if (numberEntries.size == 2) {

			var UserNumberMessage2 = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has entered with the number [color=#1DDC5D][b]${randomNum}[/b][/color]. πŸ₯ˆ [b][color=#CCC7EC]Second Entry[/color][/b], You have been rewarded [b][color=#ffc00a]75 BON[/color][/b].`;
			sendMessage(UserNumberMessage2);

			var giftMessage2 = `/gift ${author} 75 Congratulations! πŸ₯ˆ Second Entry Reward!`;
			sendMessage(giftMessage2);

		} else if (numberEntries.size == 3) {

			var UserNumberMessage3 = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has entered with the number [color=#1DDC5D][b]${randomNum}[/b][/color]. πŸ₯‰ [b][color=#C38D70]Third Entry[/color][/b], You have been rewarded [b][color=#ffc00a]50 BON[/color][/b].`;
			sendMessage(UserNumberMessage3);

			var giftMessage3 = `/gift ${author} 50 Congratulations! πŸ₯‰ Third Entry Reward!`;
			sendMessage(giftMessage3);

		} else {
			var UserNumberMessage = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has entered with the number [color=#1DDC5D][b]${randomNum}[/b][/color]. 🎲`;
			sendMessage(UserNumberMessage);
		}

            //var UserNumberMessage = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has entered with the number [color=#1DDC5D][b]${randomNum}[/b][/color]. 🎲`;
			//sendMessage(UserNumberMessage);
        }

    // Command to change random number
    } else if (command === "change") {
        if (!numberEntries.has(author)) {
            sendMessage(`[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url], you haven't entered the giveaway yet. Use ![color=#FB4F4F][b]random[/b][/color] to enter. ❗`);
            return;
        }

		// Test
        if (!randomEntries.has(author)) {
            sendMessage(`[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url], you did not enter using ![color=#FB4F4F][b]random[/b][/color] and cannot use ![color=#FB4F4F][b]change[/b][/color].`);
            return;
        }

        if (changedEntries.has(author)) {
            sendMessage(`[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url], you have already used ![color=#FB4F4F][b]change[/b][/color], you can only change your number once. ❗`);
            return;
        }

        const oldNumber = numberEntries.get(author);
        let newNumber;
        const takenNumbers = new Set(numberEntries.values());

        do {
            newNumber = Math.floor(Math.random() * (giveawayData.endNum - giveawayData.startNum + 1)) + giveawayData.startNum;
        } while (takenNumbers.has(newNumber));

        numberEntries.set(author, newNumber);
        changedEntries.add(author); // Mark user as having changed via !change
        updateEntries();

        sendMessage(`[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has changed their entry from [b][color=#1DDC5D]${oldNumber}[/color][/b] to [b][color=#1DDC5D]${newNumber}[/color][/b] using ![color=#FB4F4F][b]change[/b][/color]. πŸ†•`);

	// Command to get giveaway chosen number
    } else if (command == "number") {
        var userNumber = numberEntries.get(author);
        if (userNumber != undefined) {
            var NumberMessage = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] your number is [color=#1DDC5D][b]${userNumber}[/b][/color]. 🎲`;
        } else {
            var NumberMessage = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] you are not currently in the giveaway. ❗`;
        }
        sendMessage(NumberMessage);

	// Command to get giveaway free numbers
    } else if (command == "free") {
        if (GENERAL_SETTINGS.disable_free) {
            var DisabledFreeMessage = `Sorry [url=https://oldtoons.world/users/${author}][b]${author}[/b][/url], but [color=#999999]!free[/color] has been disabled for this giveaway. 🚫`;
            sendMessage(DisabledFreeMessage);
            return;
        }
        var free = new Array();
        var numOfFreeToPrint = 5;

        for (var k = giveawayData.startNum; k <= giveawayData.endNum; k++) {
            if (![...numberEntries.values()].includes(k)) {
                free.push(k);
            }
        }

        var randomValues = free.sort(() => Math.random() - 0.5).slice(0, numOfFreeToPrint);

        const remaining = giveawayData.totalEntries - numberEntries.size;
        let FreeNumberMessage = `Free: (${remaining}) - Some Free Numbers: ` + randomValues.join(", ") + `.`;
        sendMessage(FreeNumberMessage);

	// Command to get giveaway lucky number
    } else if (command == "lucky") {
        if (GENERAL_SETTINGS.disable_lucky) {
            var DisabledLuckyMessage = `Sorry [url=https://oldtoons.world/users/${author}][b]${author}[/b][/url], but [color=#999999]!lucky[/color] has been disabled for this giveaway. 🚫`;
            sendMessage(DisabledLuckyMessage);
            return;
        }
        var LuckyMessage = `The current giveaway lucky number is: [b][color=#1DDC5D]${getLuckyNumber(giveawayData)}[/color][/b]. πŸ€`;
        sendMessage(LuckyMessage);

	// Command to get giveaway taken numbers
    } else if (command == "taken") {
        if (numberEntries.size === 0) {
            sendMessage("No numbers taken yet!");
        } else {
            let takenNumbers = Array.from(numberEntries.values()).sort((a, b) => a - b);
            sendMessage(`Taken: (${numberEntries.size}) - Taken numbers: ${takenNumbers.join(", ")}`);
        }

	// Command to show giveaway Entries
	} else if (command == "entries") {
		if (numberEntries.size === 0) {
			sendMessage("No entries yet!");
		} else {
			let entriesList = `πŸ“‹ Entries: (${numberEntries.size}) - Participants and their numbers: `;
			numberEntries.forEach((number, user) => {
				entriesList += `[url=https://oldtoons.world/users/${user}][b]${user}[/b][/url] ([b]${number}[/b]), `;
			});
			// Remove last comma and space
			entriesList = entriesList.slice(0, -2);
			sendMessage(entriesList);
		}

	// Gift howto
    } else if (command === "gift") {
        var giftmessage = `To send a gift type: /gift [url=https://oldtoons.world/users/${giveawayData.host}][b][color=#1C4587]${giveawayData.host}[/color][/b][/url] amount message`;
        sendMessage(giftmessage)

	// Random howto
    } else if (command === "pokeball") {
        var pokeballmessage = `To choose a random number type: ![color=#FB4F4F][b]random[/b][/color]`;
        sendMessage(pokeballmessage)

    // !stats command: show current entry count and time left
    } else if (command == "stats") {
        const remaining = giveawayData.totalEntries - numberEntries.size;
		const totalTimeMs = timerInput.value * 60000;
		var StatsMessage = `[spoiler=πŸ“‰ Giveaway Stats]
		β€’ Host: [url=https://oldtoons.world/users/${giveawayData.host}][color=#1C4587][b]${giveawayData.host}[/b][/color][/url]
		β€’ Giveaway Amount: [b][color=#ffc00a]${giveawayData.amount} BON[/color][/b]
		β€’ Number Range: [b][color=#1DDC5D]${giveawayData.startNum}[/color][/b] to [b][color=#1DDC5D]${giveawayData.endNum}[/color][/b]
		β€’ Entries Taken: [b][color=#1DDC5D]${numberEntries.size}[/color][/b]
		β€’ Entries Free: [b][color=#1DDC5D]${remaining}[/color][/b]
		β€’ Total Time: [b][color=#1DDC5D]${parseTime(totalTimeMs)}[/color][/b]
		β€’ Time Left: [b][color=#1DDC5D]${parseTime(giveawayData.timeLeft * 1000)}[/color][/b]
		β€’ Winner(s): [b][color=#1DDC5D]${giveawayData.winnersNum.value}[/color][/b]
		β€’ Message: [b][color=#5DE2E7]${giveawayData.customMessage} [/color][/b]
		[/spoiler]`;
		sendMessage(StatsMessage);

	// K3Vs /me remake
	} else if (command == "me") {
		var action = ``;
		for (var i = 1; i < arguments.length; i++) {
			action += arguments[i] += ` `;
		}
		//return action;
		var meMessage = `* [url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] [b]${action}[/b] *`;
		sendMessage(meMessage);

	// K3Vs /slap remake
	} else if (command == "slap") {
		const user = arguments[1];
		if (user == "K3V") {
		var slapMessage = `You just can't do it, captain. You don't have the power. πŸ–•πŸΌ`;
		} else {
		var slapMessage = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has slapped [url=https://oldtoons.world/users/${user}][b]${user}[/b][/url] around a bit with a large trout! 🐟`;
		}
		sendMessage(slapMessage);

	// K3Vs /hug remake
	} else if (command == "hug") {
		const user = arguments[1];
		var hugMessage = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] gives [url=https://oldtoons.world/users/${user}][b]${user}[/b][/url] a hug!`;
		sendMessage(hugMessage);

	// K3Vs summon
	} else if (command == "summon") {
		const user = arguments[1];
		var summonMessage = `⚠️ [url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] [color=#EBAD47]has summoned[/color] [url=https://oldtoons.world/users/${user}][b]${user}[/b][/url] [color=#EBAD47]to the chat[/color]! ⚠️`;
		sendMessage(summonMessage);

	// Command to leave giveaway
	// Bit Hacky but works! =/
	} else if (command == "leave") {
        if (!numberEntries.get(author)) return;
        removeEntry(author, fancyName);
		var leaveMessage = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has decided to leave the giveaway, we are sorry to see you go! πŸ‘‹πŸ»`;
		sendMessage(leaveMessage);

	// K3Vs custom command
    } else if (command == "lurk") {
        var lurkMessage = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] is lurking! πŸ₯·πŸ»`;
        sendMessage(lurkMessage);

	// K3Vs custom command
    } else if (command == "fpalm") {
        var fpalmMessage = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has no words for this... πŸ€¦πŸ½β€β™‚οΈ`;
        sendMessage(fpalmMessage);

	// K3Vs custom command
    } else if (command == "vanish") {
        var vanishMessage = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] vanishes in a puff of smoke.️ πŸ˜Άβ€πŸŒ«οΈ`;
        sendMessage(vanishMessage);

	// K3Vs custom command
    } else if (command == "new") {
        var newMessage = `Added [color=#FB4F4F]!leave[/color] command: Allows users to leave the Giveaway.`;
        sendMessage(newMessage);

	// K3Vs custom command
    } else if (command == "multi") {
        var multiMessage = `Special Thank You to [url=https://oldtoons.world/users/merah][b][color=#7289DA]merah[/color][/b][/url] for the Multi-Winners code!`;
        sendMessage(multiMessage);

	// K3Vs custom command
    } else if (command == "thx") {
        var thxMessage = `🧑 MASSiVE THANK YOU TO OUR PREViOUS SPONSORS:  [url=https://oldtoons.world/users/conseederate][b][color=#1C4587]conseederate[/color][/b][/url], [url=https://oldtoons.world/users/GChors][b][color=#1C4587]GChors[/color][/b][/url], [url=https://oldtoons.world/users/goudan][b][color=#FFBE76]goudan[/color][/b][/url] ...`;
        sendMessage(thxMessage);

	// K3Vs custom command
    } else if (command == "scripts") {
        var scriptsMessage = `🌐 [:... MY OLDTOONS SCRiPTS ...:] [ .[url]https://openuserjs.org/users/K3V/scripts[/url]. ]`;
        sendMessage(scriptsMessage);

	// K3Vs custom command
    } else if (command == "uploads") {
        var uploadsMessage = `🌐 [:... MY OLDTOONS ENCODES ...:] [ .[url]https://oldtoons.world/torrents?uploader=K3V[/url]. ]`;
        sendMessage(uploadsMessage);

	// K3Vs custom command
    } else if (command == "version") {
        var versionMessage = `Oldtoons BON Giveaway - Version: 1.9.5 [ [url=https://openuserjs.org/install/K3V/Oldtoons_BON_Giveaway.min.user.js]DownloadURL[/url] ]`;
        sendMessage(versionMessage);

	// K3Vs custom command
    } else if (command == "message") {
        var customMessage = `Message: [b][color=#5DE2E7]${giveawayData.customMessage} [/color][/b]`;
        sendMessage(customMessage);

	// K3Vs custom command
    } else if (command == "ahoi") {
        var ahoiMessage = `That one time when [url=https://oldtoons.world/users/ahoimate][b][color=#1C4587]Ahoimate[/color][/b][/url] cheated! [url=https://i.ibb.co/8DqPNqr8/Ahoi.png]PROOF[/url] πŸ‘€`;
        sendMessage(ahoiMessage);

	// K3Vs custom command
    } else if (command == "otw") {
        var otwMessage = `πŸ’› [color=#A36D3B][b]O[/b][/color] [color=#D87F2C][b]L[/b][/color] [color=#E37717][b]D[/b][/color] [color=#D36809][b]T[/b][/color] [color=#D16303][b]O[/b][/color] [color=#CD6E43][b]O[/b][/color] [color=#D0542C][b]N[/b][/color] [color=#C42C00][b]S[/b][/color] 🧑`;
        sendMessage(otwMessage);

	// K3Vs custom command
    } else if (command == "k3v") {
        var k3vMessage = `πŸ’° ALL YOUR [color=#ffc00a][b]BON[/b][/color] ARE BELONG TO [url=https://oldtoons.world/users/K3V][color=#1C4587][b]K3V[/b][/color][/url]! πŸ’°`;
        sendMessage(k3vMessage);

	// K3Vs custom command
    } else if (command == "christmas") {
        var christmasMessage = `🀢🏼 [color=#ff0000][b]M[/b][/color] [color=#ffffff][b]E[/b][/color] [color=#ff0000][b]R[/b][/color] [color=#ffffff][b]R[/b][/color] [color=#ff0000][b]Y[/b][/color] [color=#ffffff][b]C[/b][/color] [color=#ff0000][b]H[/b][/color] [color=#ffffff][b]R[/b][/color] [color=#ff0000][b]I[/b][/color] [color=#ffffff][b]S[/b][/color] [color=#ff0000][b]T[/b][/color] [color=#ffffff][b]M[/b][/color] [color=#ff0000][b]A[/b][/color] [color=#ffffff][b]S[/b][/color] πŸŽ…πŸΌ`;
        sendMessage(christmasMessage);

	// K3Vs custom command
    } else if (command == "halloween") {
        var halloweenMessage = `πŸŽƒ [color=#ffa500][b]H[/b][/color] [color=#008000][b]A[/b][/color] [color=#ffa500][b]P[/b][/color] [color=#008000][b]P[/b][/color] [color=#ffa500][b]Y[/b][/color] [color=#008000][b]H[/b][/color] [color=#ffa500][b]A[/b][/color] [color=#008000][b]L[/b][/color] [color=#ffa500][b]L[/b][/color] [color=#008000][b]O[/b][/color] [color=#ffa500][b]W[/b][/color] [color=#008000][b]E[/b][/color] [color=#ffa500][b]E[/b][/color] [color=#008000][b]N[/b][/color] πŸ‘»`;
        sendMessage(halloweenMessage);

	// K3Vs custom command
    } else if (command == "easter") {
        var easterMessage = `🐰 [color=#808080][b]H[/b][/color] [color=#ffffff][b]A[/b][/color] [color=#808080][b]P[/b][/color] [color=#ffffff][b]P[/b][/color] [color=#808080][b]Y[/b][/color] [color=#ffffff][b]E[/b][/color] [color=#808080][b]A[/b][/color] [color=#ffffff][b]S[/b][/color] [color=#808080][b]T[/b][/color] [color=#ffffff][b]E[/b][/color] [color=#808080][b]R[/b][/color] πŸ₯š`;
        sendMessage(easterMessage);

	// FAiLED Commands (Should prolly remove, idk?!)

	// K3Vs custom command, needs [youtube] or [video] bbcode added to site =/
    } else if (command == "rickroll") {
        var rickrollMessage = `[youtube]dQw4w9WgXcQ[/youtube]`;
        sendMessage(rickrollMessage);

	// K3Vs custom command, FAiL, does not run after giveaway has finished.
	} else if (command == "grats") {
		const user = arguments[1];
		var gratsMessage = `✨✨🌟🌟🍻🍻 Congratulations [url=https://oldtoons.world/users/${user}][b]${user}[/b][/url]! 🍻🍻🌟🌟✨✨`;
		sendMessage(gratsMessage);

	// K3Vs custom command, FAiL, opens alert on MY computer.
    } else if (command == "alert") {
        alert("Here is your alert! what else was you expecting?");

	// HOST Only Commands
    } else if (author == giveawayData.host) {
        var hostName = giveawayData.host;
        // Command to AddBon to giveaway
        if (command == "addbon" || command == "add" || command == "addpot") {
            const amount = parseFloat(arguments[1]);
            if (!isNaN(amount) && amount > 0) {
                giveawayData.amount += amount;

                var addMsg = `[url=https://oldtoons.world/users/${hostName}][color=#1C4587][b]${hostName}[/b][/color][/url] [Host] Added [color=#FFB700][b]${amount} BON[/b][/color]! New pot: [b][color=#ffc00a]${cleanPotString(
                    giveawayData.amount
                )} BON[/color][/b]. πŸͺ™`;
                sendMessage(addMsg);
            } else {
                sendMessage("Usage: !addbon [amount] (e.g., !addbon 500)");
            }

		// Command to RemoveBon from giveaway
        } else if (command == "removebon" || command == "remove" || command == "removepot") {
            const amount = parseFloat(arguments[1]);
            if (!isNaN(amount) && amount > 0) {
                if (amount > giveawayData.amount) {
                    sendMessage(`Cannot remove more BON than the current pot (${cleanPotString(giveawayData.amount)} BON)`);
                } else if (giveawayData.amount - amount < 100) {
                    sendMessage(`Cannot remove ${amount} BON as it would leave less than 100 BON in the pot (current: ${cleanPotString(giveawayData.amount)} BON)`);
                } else {
                    giveawayData.amount -= amount;
                    var removeMsg = `[url=https://oldtoons.world/users/${hostName}][color=#1C4587][b]${hostName}[/b][/color][/url] [Host] Removed [color=#FFB700][b]${amount} BON[/b][/color]! New pot: [b][color=#ffc00a]${cleanPotString(
                        giveawayData.amount
                    )} BON[/color][/b] (Minimum 100 BON maintained). πŸͺ™`;
                    sendMessage(removeMsg);
                }
            } else {
                sendMessage("Usage: !removebon [amount] (e.g., !removebon 500)");
            }

		// Command to Rig to giveaway
        } else if (command == "rig") {
            var RigMessage = `[color=#DC3D1D][b]Giveaway is now rigged[/b][/color]. ☠️`;
            sendMessage(RigMessage);

		// Command to UnRig to giveaway
        } else if (command == "unrig") {
            var UnrigMessage = `[color=#DC3D1D][b]No, the giveaway will be rigged[/b][/color]. ☠️`;
            sendMessage(UnrigMessage);

		// Command to display giveaway Reminder
        } else if (command == "reminder") {
            var ReminderMessage =
                `There is an ongoing giveaway for [b][color=#ffc00a]${Number(cleanPotString(giveawayData.amount)).toLocaleString()} BON[/color][/b] for [b][color=#5DE2E7]${giveawayData.winnersNum.value} Winner(s)[/color][/b]. ` +
                `Time left: [b][color=#1DDC5D]${parseTime(giveawayData.timeLeft * 1000)}[/color][/b]. ` +
                `You may enter by submitting a whole number between [b][color=#1DDC5D]${giveawayData.startNum}[/color][/b] and [b][color=#1DDC5D]${giveawayData.endNum}[/color][/b] inclusive. ` +
                `[b][color=#5DE2E7]${giveawayData.customMessage} [/color][/b]\n` +
                `🎁Any [color=#ffc00a]BON[/color] [color=#FB4F4F]/gift[/color] to the host during the duration of the Giveaway is automatically added to the Pot!🎁 - 🀞 GOOD LUCK ALL! 🀞 - [b][color=#5DE2E7]!Help for commands.[/color][/b]`;
            sendMessage(ReminderMessage);

		// Command to AddTime to giveaway
        } else if (command == "addtime" || command == "extend") {
            const minutes = parseInt(arguments[1]);
            if (!isNaN(minutes) && minutes > 0) {
                giveawayData.timeLeft += minutes * 60;
                giveawayData.totalSeconds += minutes * 60;
                // Update reminder intervals
                giveawayData.reminderFreqSec = (giveawayData.totalSeconds / (giveawayData.reminderNum + 1)).toFixed(0);
                sendMessage(`[url=https://oldtoons.world/users/${hostName}][color=#1C4587][b]${hostName}[/b][/color][/url] [Host] Added [b][color=#1DDC5D]${minutes} minute${minutes !== 1 ? "s" : ""}[/color][/b]! New duration: [b]${parseTime(giveawayData.timeLeft * 1000)}[/b]. βŒ›`);
            } else {
                sendMessage("Usage: !addtime [minutes] (e.g., !addtime 15)");
            }

		// Command to RemoveTime from giveaway
        } else if (command == "removetime" || command == "shorten") {
            const minutes = parseInt(arguments[1]);
            if (!isNaN(minutes) && minutes > 0) {
                giveawayData.timeLeft -= minutes * 60;
                giveawayData.totalSeconds -= minutes * 60;
                // Update reminder intervals
                giveawayData.reminderFreqSec = (giveawayData.totalSeconds / (giveawayData.reminderNum - 1)).toFixed(0);
                sendMessage(`[url=https://oldtoons.world/users/${hostName}][color=#1C4587][b]${hostName}[/b][/color][/url] [Host] Removed [b][color=#1DDC5D]${minutes} minute${minutes !== 1 ? "s" : ""}[/color][/b]! New duration: [b]${parseTime(giveawayData.timeLeft * 1000)}[/b]. βŒ›`);
            } else {
                sendMessage("Usage: !removetime [minutes] (e.g., !removetime 15)");
            }

		// Command to Kick a user from giveaway
		// Bit Hacky but works! =/
        } else if (command == "kick" || command == "ban") {
            const author = arguments[1];
            const fancyName = arguments[1];
            const number = arguments[2];
            removeEntry(author, fancyName, number);
            var kickMessage = `Unfortunately [url=https://oldtoons.world/users/${author}][b]${author}[/b][/url], the HOST has kicked you from the giveaway! πŸ”¨`;
            sendMessage(kickMessage);

        // Command to Change Number of Winners
        } else if (command == "winners") {

			const oldWinners = winnersNum.value;
            const newWinners = parseInt(arguments[1]);
			giveawayData.winnersNum.value = newWinners;
			sendMessage(`[url=https://oldtoons.world/users/${hostName}][color=#1C4587][b]${hostName}[/b][/color][/url] [Host] changed the number of [b][color=#5DE2E7]Winners[/color][/b] from [b][color=#1DDC5D]${oldWinners}[/color][/b] to [b][color=#1DDC5D]${newWinners}[/color][/b]. πŸ†`);

            //var winnersMessage = `[url=https://oldtoons.world/users/${hostName}][color=#1C4587][b]${hostName}[/b][/color][/url] [Host] changed the number of winners to ${newWinners}.`;
            //sendMessage(winnersMessage);

		// Command to End the giveaway
        } else if (command == "end") {
            endGiveaway(giveawayData);
        }
    }
}

function handleEntryMessage(number, author, fancyName, giveawayData) {
    // Prevent host from entering their own giveaway
    if (author === giveawayData.host) {
        var hostMessage = `Sorry [url=https://oldtoons.world/users/${author}][color=#1C4587][b]${author}[/b][/color][/url], hosts can't enter their own giveaway. β›”`;
        sendMessage(hostMessage);
        return;
    }

    var repeatMessage;
    for (let [msgAuthor, msgValue] of numberEntries.entries()) {
		// You already entered with number
        if (msgAuthor == author) {
            repeatMessage = `Sorry [url=https://oldtoons.world/users/${author}][b]${author}[/b][/url], but you already entered with number [color=#1DDC5D][b]${msgValue}[/b][/color]. ❗`;
            sendMessage(repeatMessage);
            return;
		// Another user already entered with number
        } else if (msgValue == number) {
            repeatMessage = `Sorry [url=https://oldtoons.world/users/${author}][b]${author}[/b][/url], but [url=https://oldtoons.world/users/${msgAuthor}][b]${msgAuthor}[/b][/url] already entered with number [color=#1DDC5D][b]${number}[/b][/color]! Please try another number. ❗`;
            sendMessage(repeatMessage);
            return;
        }
    }

	// Number outside range
    if (number < giveawayData.startNum || number > giveawayData.endNum) {
        var outOfBoundsMessage = `Sorry [url=https://oldtoons.world/users/${author}][b]${author}[/b][/url], but the number [color=#1DDC5D][b]${number}[/b][/color] is outside of the given range! Enter a number between [color=#1DDC5D][b]${giveawayData.startNum}[/b][/color] and [color=#1DDC5D][b]${giveawayData.endNum}[/b][/color]. ❗`;
        sendMessage(outOfBoundsMessage);
        return;
    }

	// Number is free, assign to user
    if (!numberEntries.has(author)) {
        addNewEntry(author, fancyName, number);

		// First/Second/Third Entry Rewards - Idea from Ahoi
		if (numberEntries.size == 1) {

			var confirmationMessage1 = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has entered with the number [color=#1DDC5D][b]${number}[/b][/color]. πŸ₯‡ [b][color=#E6A65B]First Entry[/color][/b], You have been rewarded [b][color=#ffc00a]100 BON[/color][/b].`;
			sendMessage(confirmationMessage1);

			var giftMessage1 = `/gift ${author} 100 Congratulations! πŸ₯‡ First Entry Reward!`;
			sendMessage(giftMessage1);

		} else if (numberEntries.size == 2) {

			var confirmationMessage2 = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has entered with the number [color=#1DDC5D][b]${number}[/b][/color]. πŸ₯ˆ [b][color=#CCC7EC]Second Entry[/color][/b], You have been rewarded [b][color=#ffc00a]75 BON[/color][/b].`;
			sendMessage(confirmationMessage2);

			var giftMessage2 = `/gift ${author} 75 Congratulations! πŸ₯ˆ Second Entry Reward!`;
			sendMessage(giftMessage2);

		} else if (numberEntries.size == 3) {

			var confirmationMessage3 = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has entered with the number [color=#1DDC5D][b]${number}[/b][/color]. πŸ₯‰ [b][color=#C38D70]Third Entry[/color][/b], You have been rewarded [b][color=#ffc00a]50 BON[/color][/b].`;
			sendMessage(confirmationMessage3);

			var giftMessage3 = `/gift ${author} 50 Congratulations! πŸ₯‰ Third Entry Reward!`;
			sendMessage(giftMessage3);

		} else {
			var confirmationMessage = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has entered with the number [color=#1DDC5D][b]${number}[/b][/color]. 🎲`;
			sendMessage(confirmationMessage);
		}

        //var confirmationMessage = `[url=https://oldtoons.world/users/${author}][b]${author}[/b][/url] has entered with the number [color=#1DDC5D][b]${number}[/b][/color]. 🎲`;
        //sendMessage(confirmationMessage);
    }
}

	// Add number to entry list
function addNewEntry(author, fancyName, number) {
    numberEntries.set(author, number);
    fancyNames.set(author, fancyName);
    updateEntries();
}

	// Remove number from entry list, Used for !Kick
	// Bit Hacky but works! =/
function removeEntry(author, fancyName, number) {
    numberEntries.delete(author, number);
    fancyNames.delete(author, fancyName);
    updateEntries();
}

	// Create entry list
function updateEntries() {

    let tableStart = "<thead><tr><th>User</th><th>Entry #</th></tr></thead><tbody>";
    let tableEntries = "";
    let tableEnd = "</tbody>";

    numberEntries.forEach((entry, author) => {
        //fancyName = fancyNames.get(author);
        tableEntries += `<tr><td>${author}</td><td>${entry}</td></tr>`; //need ; to fix syntax highligthing
    });

    document.getElementById("entriesTable").innerHTML = tableStart + tableEntries + tableEnd;
}

	// End the giveaway
function endGiveaway(giveawayData) {
    observer.disconnect();

    if (numberEntries.size == 0) {
        var emptyMessage = `Unfortunately, no one has entered the giveaway, so no one wins! ☹`;
        sendMessage(emptyMessage);
    } else {

        const entries = Array.from(numberEntries.entries())
            .map(([author, guess], idx) => ({
                author,
                guess,
                gap:   Math.abs(guess - giveawayData.winningNumber),
                order: idx
            }))
            .sort((a, b) => a.gap - b.gap || a.order - b.order);

        const ties = entries.filter(e => e.gap === entries[0].gap);
        if (ties.length > 1) {
            const tieMessage = ties.map(e => `[b][color=#58A6FF]${e.author}[/color][/b]`).join(", ");
            sendMessage(`⚠️ We have a tie between ${tieMessage}! [b][color=#58A6FF]${entries[0].author}[/color][/b] wins the tie-breaker as their entry was submitted first!`);
        }

        const numWinners = Math.min(parseInt(giveawayData.winnersNum.value,10), entries.length);
        const winners = entries.slice(0, numWinners);
        const weights = winners.map((_, i) => numWinners - (i));
        const totalWeight = weights.reduce((sum, w) => sum + w, 0);
        let allocated = winners.map((_, i) => Math.floor(giveawayData.amount * weights[i] / totalWeight) );
        const sumAllocated = allocated.reduce((s, x) => s + x, 0);
        const leftover = giveawayData.amount - sumAllocated;
        allocated[0] += leftover > 0 ? leftover : 0;

        const header =
              `πŸ† The winning number was [color=#1DDC5D][b]${giveawayData.winningNumber}[/b][/color]. ` +
              `Congratulations to ` +
              (winners.length === 1
               ? ` `
               : `these [b][color=#5DE2E7]${winners.length} Winners[/color][/b]!`
              );

        if (winners.length === 1) {
            const prize = allocated[0].toLocaleString();
            const diff = Math.abs(winners[0].guess - giveawayData.winningNumber);
              sendMessage(
                  `${header}${winners[0].author}! ` +
                    `\n You guessed [color=#1DDC5D][b]${winners[0].guess}[/b][/color]${diff > 0 ? ` (off by ${diff})` : ``} ` +
                    `and will receive [b][color=#FFC00A]${prize} BON[/color][/b]!`);
        }
        else{
            const lines = winners.map((w, i) => {
                const diff = Math.abs(w.guess - giveawayData.winningNumber);
                const prize = allocated[i].toLocaleString();
                return `\nπŸ₯‡ [url=https://oldtoons.world/users/${w.author}][b]${w.author}[/b][/url] - ` +
                    `You guessed [color=#1DDC5D][b]${w.guess}[/b][/color]${diff > 0 ? ` (off by ${diff})` : ``} ` +
                    `and will receive [b][color=#FFC00A]${prize} BON[/color][/b]!`;
            });

            sendMessage( header + ' ' + lines.join(''));
        }

        if (winners.length === 1) {
            const w = winners[0];
            const amt = allocated[0];
            sendMessage(`/gift ${w.author} ${amt} ` + `πŸŽ‰ You won! Enjoy your ${amt} BON!`);
        } else {
            winners.forEach((w, i) => {
                sendMessage(`/gift ${w.author} ${allocated[i]} ` + `πŸŽ‰ Congratulations!`);
            });
        }

        if (giveawayData.sponsors.length > 0) {
            let sponsorsMessage = `Thank you to all the sponsors! [url=https://oldtoons.world/users/${giveawayData.sponsors[0]}][b]${giveawayData.sponsors[0]}[/b][/url] ❀️`;
            for (let i = 1; i < giveawayData.sponsors.length; i++) {
                sponsorsMessage += `, [url=https://oldtoons.world/users/${giveawayData.sponsors[i]}][b]${giveawayData.sponsors[i]}[/b][/url] ❀️`;
            }
            sendMessage(sponsorsMessage);
        }

    }

    // Clear onbeforeunload alert
    window.onbeforeunload = null;
    clearInterval(giveawayData.countdownTimerID);
    observer.disconnect();
    delete giveawayData;
}

	// Create random number
function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1)) + min;
}

// Unified all time related events in the countdown (endGiveaway and reminders) to avoid any kind of drifting between them.
function countdownTimer(display, giveawayData) {
    display.hidden = false;
    var minutes, seconds;
    var timerID = setInterval(function () {
        giveawayData.timeLeft--;

        minutes = parseInt(giveawayData.timeLeft / 60, 10);
        seconds = parseInt(giveawayData.timeLeft % 60, 10);

        minutes = minutes < 0 ? "0" + minutes : minutes;
        seconds = seconds < 10 ? "0" + seconds : seconds;
        display.textContent = minutes + ":" + seconds;

        if (giveawayData.timeLeft <= 0) {
            endGiveaway(giveawayData);
        } else if (giveawayData.totalEntries == numberEntries.size) {
            // Color scheme of this message could be improved
            var earlyFinishMessage = `All [b][color=#ffc00a]${giveawayData.totalEntries}[/color][/b] slot(s) filled up! Therefore, the giveaway is ending with [b][color=#1DDC5D]${parseTime(
                giveawayData.timeLeft * 1000
            )}[/color][/b] remaining. ⏳`;
            sendMessage(earlyFinishMessage);
            endGiveaway(giveawayData);
        } else if (giveawayData.timeLeft % giveawayData.reminderFreqSec == 0) {
            var reminderMessage =
                `There is an ongoing giveaway for [b][color=#ffc00a]${Number(cleanPotString(giveawayData.amount)).toLocaleString()} BON[/color][/b] for [b][color=#5DE2E7]${giveawayData.winnersNum.value} Winner(s)[/color][/b]. ` +
                `Time left: [b][color=#1DDC5D]${parseTime(giveawayData.timeLeft * 1000)}[/color][/b]. ` +
                `You may enter by submitting a whole number between [b][color=#1DDC5D]${giveawayData.startNum}[/color][/b] and [b][color=#1DDC5D]${giveawayData.endNum}[/color][/b] inclusive. ` +
                `[b][color=#5DE2E7]${giveawayData.customMessage} [/color][/b]\n` +
                `🎁Any [color=#ffc00a]BON[/color] [color=#FB4F4F]/gift[/color] to the host during the duration of the Giveaway is automatically added to the Pot!🎁 - 🀞 GOOD LUCK ALL! 🀞 - [b][color=#5DE2E7]!Help for commands.[/color][/b]`;
            sendMessage(reminderMessage);
        }
    }, 1000);

    return timerID;
}

	//  "Enter" or "Return" key
function sendMessage(messageStr) {
    if (!DEBUG_SETTINGS.disable_chat_output) {
        chatbox.value = messageStr;
        chatbox.dispatchEvent(
            new KeyboardEvent("keydown", {
				//keyCode: 13,
                key: `Enter`,
            })
        );
    }

    if (DEBUG_SETTINGS.log_chat_messages) {
        console.log(messageStr);
    }
}

	// Check for largest gap between two numbers to find the lucky number giving you the highest chance of winning
function getLuckyNumber(giveawayData) {
    var rangeStart = giveawayData.startNum;
    var rangeEnd = giveawayData.endNum;
    var numbers = Array.from(numberEntries.values()).sort((a, b) => {
        if (a < b) {
            return -1;
        } else {
            return 1;
        }
    });

    numbers.push(rangeEnd + 1);

    var bestGap = 0;
    var lucky = 0;

    var pastNum = rangeStart - 1;
    var currentNum, gap;
    for (var i = 0; i < numbers.length; i++) {
        currentNum = numbers[i];

        gap = currentNum - pastNum;

        if (gap > bestGap) {
            lucky = Math.floor(gap / 2) + pastNum;
            bestGap = gap;
        }

        pastNum = currentNum;
    }

    return lucky;
}

	// Remainder to equal 0
function cleanPotString(giveawayPotAmount) {
    if (giveawayPotAmount % 1 == 0) {
        return giveawayPotAmount;
    } else {
        return giveawayPotAmount.toFixed(2);
    }
}

	// Parses a string representation of a time, and returns the number
function parseTime(timeInMs) {
    var hours = Math.floor((timeInMs / 3600000) % 60);
    var minutes = Math.floor((timeInMs / 60000) % 60);
    var seconds = Math.floor((timeInMs / 1000) % 60);

    var timeString = ``;

    if (hours > 0) {
        timeString += `${hours} hour`;
        if (hours > 1) {
            timeString += `s`;
        }
    }

    if (minutes > 0) {
        if (timeString != ``) {
            timeString += `, `;
        }
        timeString += `${minutes} minute`;
        if (minutes > 1) {
            timeString += `s`;
        }
    }

    if (seconds > 0) {
        if (timeString != ``) {
            timeString += `, `;
        }
        timeString += `${seconds} second`;
        if (seconds > 1) {
            timeString += `s`;
        }
    }

    return timeString;
}

	// Reset giveaway
function resetGiveaway() {
    clearInterval(giveawayData.countdownTimerID);

    // Clear onbeforeunload alert
    window.onbeforeunload = null;

    numberEntries = new Map();
    fancyNames = new Map();

    entriesWrapper.hidden = true;
    countdownHeader.hidden = true;
    startButton.parentElement.hidden = false;

    startButton.disabled = false;
    coinInput.disabled = false;
    startInput.disabled = false;
    endInput.disabled = false;
    timerInput.disabled = false;
    reminderInput.disabled = false;

    observer.disconnect();
    delete observer;

    giveawayForm.reset();

    updateEntries();
}

	// For giveaway menu
function toggleAll() {
    let newStatus = !GENERAL_SETTINGS.disable_random;

    const randomCheckbox = document.getElementById("randomToggle");
    randomCheckbox.checked = !newStatus;
    GENERAL_SETTINGS.disable_random = newStatus;

    const luckyCheckbox = document.getElementById("luckyToggle");
    luckyCheckbox.checked = !newStatus;
    GENERAL_SETTINGS.disable_lucky = newStatus;

    const freeCheckbox = document.getElementById("freeToggle");
    freeCheckbox.checked = !newStatus;
    GENERAL_SETTINGS.disable_free = !newStatus;
}

	// For giveaway menu
function toggleSettings() {
    const giveawaySettingsMenu = document.getElementById("giveaway_settings_menu");
    const giveawayBody = document.getElementById("giveaway_body");

    if (giveawaySettingsMenu.style.display === "flex") {
        giveawaySettingsMenu.style.display = "none";
        giveawayBody.removeEventListener("click", handleBodyClick);
    } else {
        giveawaySettingsMenu.style.display = "flex";
        giveawayBody.addEventListener("click", handleBodyClick);
    }
}

	// For giveaway menu
function handleBodyClick(event) {
    const settings = document.getElementById("giveaway_settings_menu");
    if (!settings.contains(event.target)) {
        console.log("A");
        settings.style.display = "none";
    }
}

	// For giveaway menu
function addStyle(css, id) {
    const style = document.createElement("style");
    style.id = id;
    style.textContent = css;
    document.head.appendChild(style);
}