Neopets - Karla's Battledome Bot

A bot that automatically fights for you in battledome

// ==UserScript==
// @name         Neopets - Karla's Battledome Bot
// @namespace    karla@neopointskarla
// @license      GPL3
// @version      0.1.3
// @description  A bot that automatically fights for you in battledome
// @author       Karla
// @match        *://*.neopets.com/dome/fight*
// @match        *://*.neopets.com/dome/arena*
// @match        *://*.neopets.com/dome/barracks*
// @icon         https://github.com/karlaneo/neopets-scripts/blob/main/favicon-32x32.png?raw=true
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

const MIN_WAIT = 700;
const MAX_WAIT = 1200;

GM_addStyle("#QuestLogStreakRewards { display: block !important; }")

let battleType = GM_getValue('battle_type') || 'fixed';
let fixedCount = GM_getValue('fixed_count') || 1;
let battlesLeft = GM_getValue('battles_left') || 0;
let rewardsNp = GM_getValue('rewards_np') || false;
let rewardsItems = GM_getValue('rewards_items') || false;
let rewardsPlot = GM_getValue('rewards_plot') || false;
let infiniteCount = GM_getValue('infinite_count') || 1;
let infiniteWait = GM_getValue('infinite_wait') || 1;
let battlesDone = GM_getValue('battles_done') || 0;

const random_in_range = (start, end) => {
    return Math.floor(Math.random() * (end - start + 1) + start);
};

const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time));

function extractVarObject(html, varName) {
    const assignIndex = html.indexOf(`var ${varName} =`);
    if (assignIndex === -1) return null;

    // Find the first '{' after the variable name
    const start = html.indexOf('{', assignIndex);
    if (start === -1) return null;

    let braceCount = 0;
    let inString = false;
    let stringChar = '';
    let escaped = false;
    let end = -1;

    for (let i = start; i < html.length; i++) {
        const char = html[i];

        if (inString) {
            if (!escaped && char === stringChar) {
                inString = false;
            }
            escaped = char === '\\' && !escaped;
        } else {
            if (char === '"' || char === "'") {
                inString = true;
                stringChar = char;
                escaped = false;
            } else if (char === '{') {
                braceCount++;
            } else if (char === '}') {
                braceCount--;
                if (braceCount === 0) {
                    end = i;
                    break;
                }
            }
        }
    }

    if (end === -1) return null;

    const objectString = html.slice(start, end + 1);
    return eval(`(${objectString})`); // safe only if source is trusted
}

function addRound(el, weapons, abilities, n, weaponSetting = {}) {
    const div = document.createElement('div');
    div.style.display = 'flex';
    div.className = 'round_row';
    const round = document.createElement('div');
    round.style.marginRight = '12px';
    round.style.width = '65px';
    round.style.textAlign = 'left';
    round.innerHTML = n === 0 ? 'Cycle' : `Step`;
    const weapon1 = document.createElement('select');
    weapon1.className = 'weapon1';
    weapon1.style.width = '25%';
    weapon1.innerHTML = "<option value=\"\">Select Weapon 1</option>";
    weapons.forEach(function(weapon) {
        const option = document.createElement('option');
        option.value = weapon;
        option.innerHTML = weapon;
        weapon1.appendChild(option);
    });
    weapon1.value = weaponSetting.weapon1;
    const weapon2 = document.createElement('select');
    weapon2.className = 'weapon2';
    weapon2.style.width = '25%';
    weapon2.innerHTML = "<option value=\"\">Select Weapon 2</option>";
    weapons.forEach(function(weapon) {
        const option = document.createElement('option');
        option.value = weapon;
        option.innerHTML = weapon;
        weapon2.appendChild(option);
    });
    weapon2.value = weaponSetting.weapon2;
    div.appendChild(round);
    div.appendChild(weapon1);
    div.appendChild(weapon2);
    if (abilities.length > 0) {
        const ability1 = document.createElement('select');
        ability1.className = 'ability1';
        ability1.style.width = '25%';
        ability1.innerHTML = "<option value=\"\">Select Ability</option>";
        abilities.forEach(function(ability) {
            const option = document.createElement('option');
            option.value = ability;
            option.innerHTML = ability;
            ability1.appendChild(option);
        });
        ability1.value = weaponSetting.ability1;
        div.appendChild(ability1);
    }

    const removeButton = document.createElement('button');
    removeButton.innerHTML = '-';
    removeButton.className = 'remove_button';
    if (n > 0) {
        div.appendChild(removeButton);
    }
    el.appendChild(div);
    return div;
}

function parseWeaponSetting(roundsPanel) {
    try {
        const rows = roundsPanel.querySelectorAll('.round_row');
        return Array.from(rows).map(row => ({
            weapon1: row.querySelector('.weapon1').value,
            weapon2: row.querySelector('.weapon2').value,
            ability1: row.querySelector('.ability1')?.value || '',
        }));
    } catch (e) {
        console.log(e);
    }
}

async function insertButtons (target, stamp) {
    const settingButton = document.createElement('button');
    settingButton.style.display = 'flex';
    settingButton.style.alignItems = 'center';
    settingButton.style.background = '#fcc604';
    settingButton.style.color = '#fff';
    settingButton.style.fontWeight = 'bold';
    settingButton.style.padding = '8px';
    settingButton.style.border = '1px solid black';
    settingButton.style.cursor = 'pointer';
    settingButton.innerHTML = '<section style="background-image: url(https://images.neopets.com/themes/h5/basic/images/v3/settings-icon.svg); width: 20px; height: 20px; background-size: contain; margin-right: 6px;"></section>Settings';
    const panel = document.querySelector('div');
    panel.className = 'togglePopup__2020 movePopup__2020';
    panel.id = 'bd_setting_popup';
    panel.style.display = 'none';
    panel.style.transform = 'translate(-50%, -50%)';
    panel.style.margin = '0 !important';
    panel.innerHTML = `<div class="popup-header__2020">
		<button tabindex="0" class="popup-exit button-default__2020 button-red__2020">
			<div class="popup-exit-icon"></div>
		</button>
		<h3>Battledome Bot Settings</h3>
		<div class="popup-header-pattern__2020"></div>
	</div>

	<div class="popup-body__2020" style="max-height: 679.9px; padding-left: 20px; padding-right: 20px;">
      <div>
        <div style="display: flex; align-items: center;">
          <label style="margin-right: 5px" for="fixed" class="settings-label">Fixed</label>
          <input class="settings-radio" id="fixed" name="battle_type" title="Fixed" type="radio" value="fixed" style="margin: 0">
        </div>
        <div id="fixed_panel" style="text-align: left; padding-left: 16px; display: none;">
          <label style="margin-right: 5px" for="fixed_count" class="settings-label">Number of rounds:</label>
          <input id="fixed_count" type="number" min="1">
        </div>
        <div style="display: flex; align-items: center;">
          <label style="margin-right: 5px" for="rewards" class="settings-label">For rewards</label>
          <input class="settings-radio" id="rewards" name="battle_type" title="Fixed number of rounds" type="radio" value="rewards" style="margin: 0">
        </div>
        <div id="rewards_panel" style="display: none;">
          <div style="text-align: left; padding-left: 16px;">
            <label style="margin-right: 5px" for="rewards_np" class="settings-label">Neopoints</label>
            <input id="rewards_np" type="checkbox">
          </div>
          <div style="text-align: left; padding-left: 16px;">
            <label style="margin-right: 5px" for="rewards_items" class="settings-label">Items</label>
            <input id="rewards_items" type="checkbox">
          </div>
          <div style="text-align: left; padding-left: 16px;">
            <label style="margin-right: 5px" for="rewards_plot" class="settings-label">Plot points</label>
            <input id="rewards_plot" type="checkbox">
          </div>
        </div>
        <div style="display: flex; align-items: center;">
          <label style="margin-right: 5px" for="infinite" class="settings-label">Infinite</label>
          <input class="settings-radio" id="infinite" name="battle_type" title="Fixed number of rounds" type="radio" value="rewards" style="margin: 0">
        </div>
        <div id="infinite_panel" style="display: none;">
          <div id="fixed_panel" style="text-align: left; padding-left: 16px;">
            <label style="margin-right: 5px" for="infinite_count" class="settings-label">After X Battles:</label>
            <input id="infinite_count" type="number" min="1">
          </div>
          <div id="fixed_panel" style="text-align: left; padding-left: 16px;">
            <label style="margin-right: 5px" for="infinite_count" class="settings-label">Wait for X (+/-20)s:</label>
            <input id="infinite_wait" type="number" min="1">
          </div>
        </div>
        <div style="margin-top: 16px;">
          <div style="text-align: left;">Battle Settings - <span id="pet_name_slot"></span></div>
          <div id="loading_state">Loading...</div>
          <div id="rounds_panel">

          </div>
          <div id="rounds_panel_cycle">

          </div>
        </div>
      </div>
	</div>

	<div class="popup-footer__2020 popup-grid3__2020">
		<div></div>
        <div style="text-align:center; font-weight: bold;">Settings is auto saved</div>
		<div class="popup-footer-pattern__2020"></div>
	</div>`

    const fixedToggle = panel.querySelector('#fixed');
    const rewardsToggle = panel.querySelector('#rewards');
    const infiniteToggle = panel.querySelector('#infinite');
    const fixedPanel = panel.querySelector('#fixed_panel');
    const rewardsPanel = panel.querySelector('#rewards_panel');
    const infinitePanel = panel.querySelector('#infinite_panel');

    const fixedCountInput = panel.querySelector('#fixed_count');
    const rewardsNpInput = panel.querySelector('#rewards_np');
    const rewardsItemsInput = panel.querySelector('#rewards_items');
    const rewardsPlotInput = panel.querySelector('#rewards_plot');
    const infiniteCountInput = panel.querySelector('#infinite_count');
    const infiniteWaitInput = panel.querySelector('#infinite_wait');

    const roundsPanel = panel.querySelector('#rounds_panel');
    const roundsPanelCycle = panel.querySelector('#rounds_panel_cycle');

    switch(battleType) {
        case 'fixed':
            fixedToggle.checked = true;
            fixedPanel.style.display = 'block';
            break;
        case 'rewards':
            rewardsToggle.checked = true;
            rewardsPanel.style.display = 'block';
            break;
        case 'infinite':
            infiniteToggle.checked = true;
            infinitePanel.style.display = 'block';
            break;
        default:
    }
    fixedCountInput.value = fixedCount;
    rewardsNpInput.checked = rewardsNp;
    rewardsItemsInput.checked = rewardsItems;
    rewardsPlotInput.checked = rewardsPlot;
    infiniteCountInput.value = infiniteCount;
    infiniteWaitInput.value = infiniteWait;

    fixedToggle.addEventListener('change', function(event) {
        if (event.target.checked) {
            fixedPanel.style.display = 'block';
            rewardsPanel.style.display = 'none';
            infinitePanel.style.display = 'none';
            GM_setValue('battle_type', 'fixed');
        }
    });
    rewardsToggle.addEventListener('change', function(event) {
        if (event.target.checked) {
            fixedPanel.style.display = 'none';
            rewardsPanel.style.display = 'block';
            infinitePanel.style.display = 'none';
            GM_setValue('battle_type', 'rewards');
        }
    });
    infiniteToggle.addEventListener('change', function(event) {
        if (event.target.checked) {
            fixedPanel.style.display = 'none';
            rewardsPanel.style.display = 'none';
            infinitePanel.style.display = 'block';
            GM_setValue('battle_type', 'infinite');
        }
    });
    fixedCountInput.addEventListener('change', function(event) {
        GM_setValue('fixed_count', Number(event.target.value));
        fixedCount = Number(event.target.value);
    });
    rewardsNpInput.addEventListener('change', function(event) {
        GM_setValue('rewards_np', event.target.checked);
    });
    rewardsItemsInput.addEventListener('change', function(event) {
        GM_setValue('rewards_items', event.target.checked);
    });
    rewardsPlotInput.addEventListener('change', function(event) {
        GM_setValue('rewards_plot', event.target.checked);
    });
    infiniteCountInput.addEventListener('change', function(event) {
        GM_setValue('infinite_count', Number(event.target.value));
    });
    infiniteWaitInput.addEventListener('change', function(event) {
        GM_setValue('infinite_wait', Number(event.target.value));
    });

    settingButton.innerHTML = 'Loading';
    settingButton.disabled = true;
    if (document.querySelector('.npcContainer')) {
        document.querySelector('.npcContainer').style.position = 'relative';
        settingButton.style.bottom = '14px';
        settingButton.style.left = '125px';
        settingButton.style.position = 'absolute';
        document.querySelector('.npcContainer').appendChild(settingButton);
    } else if (document.querySelector('#rbfightStep2')) {
        settingButton.style.margin = 'auto';
        document.querySelector('#rbfightStep2').appendChild(settingButton);
    }

    const htmlWeapon = await fetch('https://www.neopets.com/dome/neopets.phtml').then(function(res) {
        return res.text();
    });

    const htmlAbility = await fetch('https://www.neopets.com/dome/abilities.phtml').then(function(res) {
        return res.text();
    });

    settingButton.addEventListener('click', function() {
        panel.style.display = 'block';
        const activePet = typeof BDFight !== 'undefined' ? BDFight.selectedPet : Array.from(document.querySelectorAll('.rb-fight_petNameLabel')).find(n => n.style.display === 'grid')?.dataset?.name;
        document.querySelector('#pet_name_slot').innerHTML = activePet;

        let weaponSetting = JSON.parse(GM_getValue(`${activePet}_weapon`) || '[]');

        const divWeapon = new DOMParser().parseFromString(htmlWeapon, "text/html").querySelector("#bdStatus");
        const counts = {};
        const weapons = Array.from(divWeapon.querySelectorAll(`[data-name="${activePet}"] .equipTable .equipFrame`)).map(el => el.textContent.trim()).map(item => {
            counts[item] = (counts[item] || 0) + 1;
            return counts[item] > 1 ? `${item} ${counts[item]}` : item;
        }).filter(n => n !== '' && !n.startsWith(' '));

        const abilityData = extractVarObject(htmlAbility, 'BDAbilities');
        const abilities = Object.keys(abilityData.pets[activePet].abilities).map(function(n) {
            return abilityData.abilities[n].name;
        });
        document.querySelector('#loading_state').style.display = 'none';

        function getWeaponSetting() {
            weaponSetting = parseWeaponSetting(roundsPanel);
            GM_setValue(`${activePet}_weapon`, JSON.stringify(weaponSetting));
        }

        function addRow(ws) {
            const div = addRound(roundsPanel, weapons, abilities, weaponSetting.length, ws);

            div.querySelector('.remove_button').addEventListener('click', function () {
                event.target.parentNode.remove();
                getWeaponSetting();
            });
            Array.from(div.querySelectorAll('select')).forEach(select => {
                select.addEventListener('change', getWeaponSetting);
            });
        }

        weaponSetting.forEach((ws, i) => {
            addRow(ws);
        });

        const weaponCycleSetting = JSON.parse(GM_getValue(`${activePet}_weapon_cycle`) || '{"weapon1":"","weapon2":"","ability1":""}');
        const divCycle = addRound(roundsPanelCycle, weapons, abilities, 0, weaponCycleSetting);
        Array.from(divCycle.querySelectorAll('select')).forEach(select => {
            select.addEventListener('change', function() {
                weaponCycleSetting[select.className] = select.value;
                GM_setValue(`${activePet}_weapon_cycle`, JSON.stringify(weaponCycleSetting));
            });
        });

        const addRoundButton = document.createElement('button');
        addRoundButton.innerHTML = '+ Add Round';
        addRoundButton.addEventListener('click', function() {
            weaponSetting.push({
                weapon1: '',
                weapon2: '',
                ability1: '',
            });
            GM_setValue(`${activePet}_weapon`, JSON.stringify(weaponSetting));
            addRow();
            if (weaponSetting.length >= 10) {
                addRoundButton.disabled = true;
            }
        });
        roundsPanelCycle.appendChild(addRoundButton);
    });
    panel.querySelector('.popup-exit').addEventListener('click', function() {
        panel.style.display = 'none';
        roundsPanel.innerHTML = '';
        roundsPanelCycle.innerHTML = '';
    });

    settingButton.innerHTML = 'Battle Bot Settings';
    settingButton.disabled = false;
    document.querySelector('body').appendChild(panel);

    document.querySelector('#bdFightStep3FightButton, #BattleContinueButton').addEventListener('click', function() {
        GM_setValue('battles_left', fixedCount);
        GM_setValue('battles_done', 0);
    });
}

function getCurrentEquippedWeapon(id) {
    const weaponImages = Array.from(document.querySelectorAll('#p1usedequipment .item, #p1equipment .item')).reduce((acc, el) => ({ ...acc, [el.src]: el.title || el.alt }), {});
    const selectedWeaponImage = document.querySelector(`#p1e${id}m.p1.selected.menu div`);
    if (!selectedWeaponImage) {
        return '';
    }
    const selectedWeapon = weaponImages[selectedWeaponImage.style.backgroundImage.replace('url("', '').replace('")', '')]
    return selectedWeapon;
}

function getCurrentEquippedAbility() {
    const abilityImages = Array.from(document.querySelectorAll('#p1ability td')).reduce((acc, el) => ({ ...acc, [el.querySelector('img')?.src]: el.title || el.alt }), {});
    const selectedAbilityImage = document.querySelector('#p1am div').style.backgroundImage;
    if (!selectedAbilityImage) {
        return '';
    }
    const selectedAbility = abilityImages[selectedAbilityImage.replace('url("', '').replace('")', '')]
    return selectedAbility;
}

(async function() {
    'use strict';

    // Your code here...
    try {
        if (document.querySelector('.npcContainer, #rbfightStep2')) {
            await insertButtons();
        } else {
            const div = document.createElement('div');
            div.id = 'battle_status';
            document.querySelector('#arenacontainer').appendChild(div);
            div.style.position = 'absolute';
            div.style.bottom = '100%';
            if (battleType === 'fixed') {
                div.innerHTML = `${battlesLeft} / ${fixedCount} battles`;
            } else if (battleType === 'infinite') {
                div.innerHTML = `${battlesDone} battles done`;
            }
            let pet = '';
            let weaponSetting;
            let weaponCycleSetting;
            let round = 0;
            async function battle(n) {
                try {
                    while (document.querySelector('#p1e1m').style.cursor !== 'auto' && document.querySelector('#p1e1m').style.cursor !== 'pointer') {
                        await sleep(MAX_WAIT);
                    }
                    if (weaponSetting && weaponCycleSetting) {
                        const { weapon1, weapon2, ability1 } = (n < weaponSetting.length) ? weaponSetting[n] : weaponCycleSetting;
                        const weaponsToEquip = [weapon1, weapon2].map(n => n.replace(/\s\d+$/, "")) ;
                        let usedId = '';

                        for (let i = 1; i <= 2; i += 1) {
                            const equippedItemInSlot = getCurrentEquippedWeapon(i);
                            if (weaponsToEquip.indexOf(equippedItemInSlot) > -1) {
                                weaponsToEquip.splice(weaponsToEquip.indexOf(equippedItemInSlot), 1);
                                continue;
                            }
                            const weaponToEquip = weaponsToEquip.shift();
                            if (!weaponToEquip) {
                                break;
                            }
                            if (document.querySelector(`p1e${i}m.selected`)) {
                                document.querySelector(`#p1e${i}m`).click();
                                await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                            }
                            document.querySelector(`#p1e${i}m`).click();
                            await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                            const lis = document.querySelectorAll(`#p1equipment li`);

                            for (let j = 0; j < lis.length; j += 1) {
                                const li = lis[j];
                                if (li.style.display !== 'none' && li.querySelector(`[title="${weaponToEquip}"], [alt="${weaponToEquip}"]`)) {
                                    if (li.querySelector(`[title="${weaponToEquip}"], [alt="${weaponToEquip}"]`).id === usedId) {
                                        continue;
                                    }
                                    li.querySelector(`[title="${weaponToEquip}"], [alt="${weaponToEquip}"]`).click();
                                    usedId = li.querySelector(`[title="${weaponToEquip}"], [alt="${weaponToEquip}"]`).id
                                    break;
                                }
                            }
                            await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                        }

                        await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                        if (ability1) {
                            const currentSelectedAbility = getCurrentEquippedAbility();
                            if (currentSelectedAbility && currentSelectedAbility !== ability1) {
                                document.querySelector('#p1am').click();
                                await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                            }
                            if (!document.querySelector(`#p1ability [title="${ability1}"] .cooldown.act`)) {
                                document.querySelector('#p1am').click();
                                await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                                document.querySelector(`#p1ability [title="${ability1}"] .ability`)?.click();
                            }
                        }

                        while (document.querySelector('#fight .inactive')) {
                            await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                        }
                        await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                        document.querySelector('#fight').click();

                        while (!document.querySelector('#skipreplay:not(.replay')) {
                            await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                        }
                        while (!document.querySelector('.replay')) {
                            await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                            document.querySelector('#skipreplay:not(.replay').click();
                        }

                        await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                        while (!document.querySelector('#fight') && !document.querySelector('#start') && !document.querySelector('.end_ack.collect')) {
                            console.log(1);
                            await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                        }

                        // #playground .end_game
                        if (document.querySelector('#p2hp').textContent === '0') {
                            await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                            const itemLimit = document.querySelector('#bd_rewards').textContent.includes('You have reached the item limit for today');
                            const npLimit = document.querySelector('#bd_rewards').textContent.includes('You have reached the NP limit for today');
                            const plotLimit = !document.querySelector('#bd_rewards').textContent.includes('Plot') || document.querySelector('#bd_rewards').textContent.includes('You have reached the Plot Points limit');
                            switch (battleType) {
                                case 'fixed':
                                    if (battlesLeft > 1) {
                                        document.querySelector('#bdplayagain').click();
                                        GM_setValue('battles_left', battlesLeft - 1);
                                    }
                                    break;
                                case 'rewards':
                                    document.querySelector('.end_ack.collect').click();
                                    if ((rewardsNp && !npLimit) || (rewardsItems && !itemLimit) || (rewardsPlot && !plotLimit)) {
                                        document.querySelector('#bdplayagain').click();
                                    }
                                    break;
                                case 'infinite':
                                    if (battlesDone % infiniteCount === 0 && battlesDone > 0) {
                                        infiniteWait += random_in_range(0, 20);
                                        await new Promise(function(resolve) {
                                            const interval = setInterval(function() {
                                                infiniteWait -= 1;
                                                document.querySelector('#battle_status').innerHTML = `Waiting... ${infiniteWait}s left`;
                                                if (infiniteWait <= 0) {
                                                    clearInterval(interval);
                                                    resolve();
                                                }
                                            }, 1000);
                                        });
                                    }
                                    GM_setValue('battles_done', battlesDone + 1);
                                    document.querySelector('#bdplayagain').click();
                                    break;
                                default:
                            }
                        } else {
                            battle(n + 1);
                        }
                    }
                } catch (e) {
                    console.log(e);
                }
            }
            async function loop() {
                if (document.querySelector('#start')) {
                    await sleep(random_in_range(MIN_WAIT, MAX_WAIT));
                    document.querySelector('#start').click();
                }
                if (document.querySelector('#p1name')) {
                    pet = document.querySelector('#p1name').textContent;
                    if (!weaponSetting) {
                        weaponSetting = JSON.parse(GM_getValue(`${pet}_weapon`) || '[]');
                    }
                    if (!weaponCycleSetting) {
                        weaponCycleSetting = JSON.parse(GM_getValue(`${pet}_weapon_cycle`) || '{"weapon1":"","weapon2":"","ability1":""}');
                    }

                    battle(0);
                    return;
                }

                setTimeout(loop, MIN_WAIT);
            }
            loop();
        }
    } catch (e) {
        console.log(e);
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址