Bulk Join Raids

UI for selecting and joining raids by difficulty with a draggable button

// ==UserScript==
// @name         Bulk Join Raids
// @version      1.2
// @license MIT
// @namespace    https://greasyfork.org/users/1159361
// @description  UI for selecting and joining raids by difficulty with a draggable button
// @author       Zaregoto_Gaming
// @match        https://play.dragonsofthevoid.com/*
// @exclude      https://play.dragonsofthevoid.com/#/login
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const raidMap = {
        "r.lesser-tree-ent": "Lesser Tree Ent",
        "r.superior-watcher": "Superior Watcher",
        "r.elven-rangers": "Elven Rangers",
        "r.greater-ent": "Greater Ent",
        "r.sand-wyrm": "Sand Wyrm",
        "r.corrupted-golem": "Corrupted Golem",
        "r.naga-risaldar": "Naga Risaldar",
        "r.galeohog": "Galeohog",
        "r.naga-karamati": "Naga Karamati",
        "r.jagar-the-red": "Jagar the Red",
        "r.rotting-fen-lure": "Rotting Fen Lure",
        "r.sentry-ghoul": "Sentry Ghoul",
        "r.fallen-naga-subedar": "Fallen Naga Subedar",
        "r.bone-dragon": "Bone Dragon"
    };

    const difficulties = ["easy", "hard", "legendary"];

    function loadSettings() {
        return JSON.parse(localStorage.getItem('autojoinRaidSettings')) || {};
    }

    function saveSettings(settings) {
        localStorage.setItem('autojoinRaidSettings', JSON.stringify(settings));
    }

    function loadButtonPosition() {
        return JSON.parse(localStorage.getItem('autojoinButtonPosition')) || { top: '40px', left: '10px' };
    }

    function saveButtonPosition(position) {
        localStorage.setItem('autojoinButtonPosition', JSON.stringify(position));
    }

    function makeDraggable(element) {
        let offsetX, offsetY, isDragging = false;

        element.addEventListener('mousedown', (e) => {
            isDragging = true;
            offsetX = e.clientX - element.getBoundingClientRect().left;
            offsetY = e.clientY - element.getBoundingClientRect().top;
            element.style.cursor = 'grabbing';
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            let newX = e.clientX - offsetX;
            let newY = e.clientY - offsetY;

            newX = Math.max(0, Math.min(window.innerWidth - element.offsetWidth, newX));
            newY = Math.max(0, Math.min(window.innerHeight - element.offsetHeight, newY));

            element.style.top = `${newY}px`;
            element.style.left = `${newX}px`;
            element.style.right = 'auto';
        });

        document.addEventListener('mouseup', () => {
            if (!isDragging) return;
            isDragging = false;
            element.style.cursor = 'grab';
            saveButtonPosition({ top: element.style.top, left: element.style.left });
        });
    }

    function createUI(button) {
        const settings = loadSettings();
        const ui = document.createElement("div");
        ui.id = "autojoin-ui";
        ui.style.position = "fixed";
        ui.style.background = "#fff";
        ui.style.padding = "10px";
        ui.style.border = "1px solid black";
        ui.style.borderRadius = "8px";
        ui.style.boxShadow = "0 2px 6px rgba(0,0,0,0.2)";
        ui.style.zIndex = "1000";
        ui.style.maxHeight = "400px";
        ui.style.overflowY = "auto";

        const buttonRect = button.getBoundingClientRect();
        ui.style.top = `${Math.min(window.innerHeight - 410, buttonRect.bottom + window.scrollY)}px`;
        ui.style.left = `${Math.min(window.innerWidth - 300, buttonRect.left + window.scrollX)}px`;

        const title = document.createElement("div");
        title.innerHTML = `<strong>Bulk Join Settings</strong>`;
        ui.appendChild(title);

        const runButton = document.createElement("button");
        runButton.textContent = "Join Selected Raids";
        runButton.style.margin = "8px 0 12px 0";
        runButton.onclick = joinSelectedRaids;
        ui.appendChild(runButton);

        Object.entries(raidMap).forEach(([id, name]) => {
            const nameLabel = document.createElement("div");
            nameLabel.textContent = name;
            nameLabel.style.fontWeight = "bold";
            ui.appendChild(nameLabel);

            difficulties.forEach(diff => {
                const checkbox = document.createElement("input");
                checkbox.type = "checkbox";
                checkbox.checked = settings[id]?.includes(diff) || false;
                checkbox.onchange = () => {
                    if (!settings[id]) settings[id] = [];
                    if (checkbox.checked) {
                        if (!settings[id].includes(diff)) settings[id].push(diff);
                    } else {
                        settings[id] = settings[id].filter(d => d !== diff);
                    }
                    saveSettings(settings);
                };
                ui.appendChild(checkbox);
                ui.appendChild(document.createTextNode(" " + diff.charAt(0).toUpperCase() + diff.slice(1)));
                ui.appendChild(document.createElement("br"));
            });
            ui.appendChild(document.createElement("hr"));
        });

        document.body.appendChild(ui);
        const customJoinTitle = document.createElement("div");
        customJoinTitle.innerHTML = `<strong>Join by Paste:</strong>`;
        ui.appendChild(customJoinTitle);

        const textArea = document.createElement("textarea");
        textArea.rows = 10;
        textArea.cols = 40;
        textArea.placeholder = "Paste lines like:\njoinraid,6aca59f3..., Corrupted Golem hard";
        textArea.style.width = "100%";
        textArea.style.marginBottom = "6px";
        ui.appendChild(textArea);

        const submitKeysButton = document.createElement("button");
        submitKeysButton.textContent = "Join from Paste";
        submitKeysButton.onclick = async () => {
            const token = localStorage.token;
            const lines = textArea.value.trim().split("\n");
            const joinKeys = [];

            for (let line of lines) {
                const match = line.match(/^joinraid,([a-f0-9\-]+)/i);
                if (match) {
                    joinKeys.push(match[1]);
                }
            }

            let success = 0;
            for (let key of joinKeys) {
                try {
                    await joinraid(key, token);
                    success++;
                } catch (e) {
                    console.error("Failed to join:", key, e);
                }
            }

            alert(`Attempted to join ${joinKeys.length} raids. Successful: ${success}`);
        };
        ui.appendChild(submitKeysButton);
    }

    function addToggleButton() {
        const button = document.createElement("button");
        button.textContent = "⚔️ Bulk Join";
        button.style.position = "fixed";
        button.style.zIndex = "1001";
        button.style.cursor = "grab";

        const pos = loadButtonPosition();
        button.style.top = pos.top;
        button.style.left = pos.left;

        button.onclick = () => {
            const existingUI = document.getElementById("autojoin-ui");
            if (existingUI) {
                existingUI.remove();
            } else {
                createUI(button);
            }
        };

        document.body.appendChild(button);
        makeDraggable(button);
    }

    async function joinSelectedRaids() {
        const token = localStorage.token;
        const settings = loadSettings();

        const publicRaids = await getpublicraids(token);
        const activeRaids = await getactiveraids(token);

        const toJoin = publicRaids.filter(raid => {
            if (raid.health < 1000) return false;
            if (activeRaids.some(r => r.id === raid.id)) return false;
            return settings[raid.raidXmlId]?.includes(raid.difficulty);
        });

        let count = 0;
        for (let raid of toJoin) {
            await joinraid(raid.joinkey, token);
            count++;
        }
        alert("Joined " + count + " raids");
    }

    async function getpublicraids(token) {
        const res = await fetch("https://api.dragonsofthevoid.com/api/raid/public", {
            headers: { authorization: token }
        });
        return res.json();
    }

    async function getactiveraids(token) {
        const res = await fetch("https://api.dragonsofthevoid.com/api/raid/active", {
            headers: { authorization: token }
        });
        return res.json();
    }

    async function joinraid(joinkey, token) {
        const res = await fetch("https://api.dragonsofthevoid.com/api/raid/join/" + joinkey, {
            headers: { authorization: token }
        });
        return res.json();
    }

    window.addEventListener("load", () => {
        addToggleButton();
    });

})();