Bulk Offer Helper

Oferta itens por nome/quantidade/preço para o personagem da página atual.

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
// ==UserScript==
// @name         Bulk Offer Helper
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  Oferta itens por nome/quantidade/preço para o personagem da página atual.
// @author       Popper
// @match        *://*.popmundo.com/World/Popmundo.aspx/Character/OfferItem/*
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    // --- CONFIGURAÇÃO ---
    const ITEM_DROPDOWN_SELECTOR = '#ctl00_cphLeftColumn_ctl00_ddlItem';
    const OFFER_BUTTON_SELECTOR = '#ctl00_cphLeftColumn_ctl00_btnGive';
    const PRICE_INPUT_SELECTOR = '#ctl00_cphLeftColumn_ctl00_txtPriceTag'; // Seletor do campo de preço ORIGINAL da página
    const BASE_DELAY_MS = 2000; // Atraso principal entre ofertas
    const POST_PRICE_SET_DELAY_MS = 100; // Pequeno atraso após definir o preço (ms)
    const STORAGE_KEY_ITEMS = 'popmundo_offerItem_items_swqp';
    const STORAGE_KEY_RUNNING = 'popmundo_offerItem_running_swqp';
    const STORAGE_KEY_PRICE = 'popmundo_offerItem_targetPrice'; // Renomeado para clareza
    // --- FIM DA CONFIGURAÇÃO ---

    let itemDropdown = document.querySelector(ITEM_DROPDOWN_SELECTOR);
    let offerButton = document.querySelector(OFFER_BUTTON_SELECTOR);
    let pagePriceInput = document.querySelector(PRICE_INPUT_SELECTOR);

    if (!itemDropdown) { console.warn("Script Popmundo AdvOffer: Dropdown não encontrado:", ITEM_DROPDOWN_SELECTOR); return; }
    if (!offerButton) { console.warn("Script Popmundo AdvOffer: Botão Ofertar não encontrado:", OFFER_BUTTON_SELECTOR); }
    if (!pagePriceInput) { console.warn("Script Popmundo AdvOffer: Campo de Preço da página não encontrado:", PRICE_INPUT_SELECTOR, "- O preço não será definido."); }

    function createUI() {
        if (document.getElementById('bulkOfferUIScript')) return;
        const scriptUIArea = document.createElement('div');
        scriptUIArea.id = 'bulkOfferUIScript';
        // Ajuste na margem inferior do painel principal para dar espaço ao crédito
        scriptUIArea.style.border = '1px solid #ccc';
        scriptUIArea.style.padding = '15px';
        scriptUIArea.style.margin = '20px 0 5px 0'; // Reduzida margem inferior
        scriptUIArea.style.backgroundColor = '#f9f9f9';

        scriptUIArea.innerHTML = `
            <div style="display: flex; flex-wrap: wrap; gap: 15px; margin-bottom: 10px;">
                <div>
                    <label for="itemNameInputScript">Início do Nome:</label><br>
                    <input type="text" id="itemNameInputScript" style="width: 250px; padding: 5px;" placeholder="Ex: Analgésicos">
                </div>
                <div>
                    <label for="itemQuantityInputScript">Quantidade:</label><br>
                    <input type="number" id="itemQuantityInputScript" min="1" value="1" style="width: 70px; padding: 5px;">
                </div>
                <div>
                    <label for="itemPriceInputScript">Preço (M$):</label><br>
                    <input type="number" id="itemPriceInputScript" min="0" value="0" style="width: 90px; padding: 5px;">
                </div>
            </div>
            <div style="margin-top: 15px;">
                <button id="startOfferByNameQtyPriceBtnScript" type="button" style="background-color: #4CAF50; color: white; padding: 8px 12px; border: none; cursor: pointer;">Ofertar</button>
                <button id="stopBulkOfferBtnScript" type="button" style="margin-left: 10px; background-color: #f44336; color: white; padding: 8px 12px; border: none; cursor: pointer;">Parar Oferta</button>
            </div>
            <div id="bulkOfferStatusScript" style="margin-top: 15px; font-weight: bold;">Status: Pronto.</div>
        `;

        // Cria o elemento de crédito
        const creditElement = document.createElement('div');
        creditElement.textContent = 'Para Roque Alencar';
        creditElement.style.color = 'gray';
        creditElement.style.fontSize = 'small';
        creditElement.style.textAlign = 'right';
        creditElement.style.paddingRight = '5px';
        creditElement.style.marginTop = '0px'; // Sem margem superior extra
        creditElement.style.marginBottom = '15px'; // Espaço antes do dropdown original

        // Insere o painel principal
        itemDropdown.parentNode.insertBefore(scriptUIArea, itemDropdown);
        // Insere o crédito *depois* do painel principal
        scriptUIArea.insertAdjacentElement('afterend', creditElement);

        // Adiciona listeners e estilos CSS
        document.getElementById('startOfferByNameQtyPriceBtnScript').addEventListener('click', startOfferByNameQuantityPrice);
        document.getElementById('stopBulkOfferBtnScript').addEventListener('click', stopOffer);
        GM_addStyle(`
            #itemNameInputScript, #itemQuantityInputScript, #itemPriceInputScript {
                border: 1px solid #ccc; border-radius: 3px; box-sizing: border-box;
            }
            #startOfferByNameQtyPriceBtnScript:disabled, #stopBulkOfferBtnScript:disabled {
                cursor: not-allowed; opacity: 0.6;
            }
        `);
    }

    async function startOfferByNameQuantityPrice() {
        const itemNameInput = document.getElementById('itemNameInputScript');
        const quantityInput = document.getElementById('itemQuantityInputScript');
        const priceInput = document.getElementById('itemPriceInputScript');
        const statusDiv = document.getElementById('bulkOfferStatusScript');
        const inputText = itemNameInput.value.trim();
        const requestedQuantity = parseInt(quantityInput.value, 10);
        const requestedPrice = parseInt(priceInput.value, 10);

        // Re-seleciona elementos caso a página tenha mudado dinamicamente (improvável aqui, mas seguro)
        itemDropdown = document.querySelector(ITEM_DROPDOWN_SELECTOR);
        offerButton = document.querySelector(OFFER_BUTTON_SELECTOR);
        pagePriceInput = document.querySelector(PRICE_INPUT_SELECTOR);

        if (!itemDropdown || !offerButton) { statusDiv.textContent = "Erro Crítico: Elementos essenciais da página não encontrados."; console.error("StartOfferSWQP: Dropdown ou botão de oferta não encontrados."); return; }
        if (!inputText) { statusDiv.textContent = "Erro: Digite o início do nome do item."; itemNameInput.focus(); return; }
        if (isNaN(requestedQuantity) || requestedQuantity < 1) { statusDiv.textContent = "Erro: Quantidade inválida."; quantityInput.focus(); return; }
        if (isNaN(requestedPrice) || requestedPrice < 0) { statusDiv.textContent = "Erro: Preço inválido."; priceInput.focus(); return; }
        if (!pagePriceInput && requestedPrice > 0) {
            statusDiv.textContent = "Aviso: Campo de preço da página não encontrado. O preço não será definido.";
            console.warn("Campo de preço da página não encontrado, mas um preço > 0 foi solicitado.");
        }

        const allItemsFound = [];
        const inputTextLower = inputText.toLowerCase();
        console.log(`Buscando itens começando com "${inputText}" para ofertar ${requestedQuantity} por ${requestedPrice} M$ cada.`);

        for (let option of itemDropdown.options) {
            // Garante que é uma opção válida (não o placeholder) e que começa com o texto digitado
            if (option.value && option.value !== "-1" && option.textContent.trim().toLowerCase().startsWith(inputTextLower)) {
                allItemsFound.push({ value: option.value, text: option.textContent.trim() });
            }
        }

        if (allItemsFound.length === 0) {
            statusDiv.textContent = `Status: Nenhum item encontrado começando com "${inputText}".`;
            console.log("Nenhum item correspondente encontrado no dropdown.");
            return;
        }

        const actualQuantityToTransfer = Math.min(allItemsFound.length, requestedQuantity);
        const itemsToOfferThisRun = allItemsFound.slice(0, actualQuantityToTransfer);

        let startMessage = `Encontrado(s) ${allItemsFound.length} item(ns) começando com "${inputText}". `;
        startMessage += `Iniciando oferta de ${itemsToOfferThisRun.length} por ${requestedPrice} M$...`;
        statusDiv.textContent = startMessage;
        console.log(`Itens selecionados para esta execução (${itemsToOfferThisRun.length}):`, itemsToOfferThisRun.map(i => i.text));

        // Salva a lista de itens e o preço alvo para persistir entre reloads
        await GM_setValue(STORAGE_KEY_PRICE, requestedPrice);
        await GM_setValue(STORAGE_KEY_ITEMS, JSON.stringify(itemsToOfferThisRun));
        await GM_setValue(STORAGE_KEY_RUNNING, true);
        disableButtons(true);
        await processNextOffer(); // Inicia o processo
    }

    async function stopOffer() {
        console.log("StopOffer (SWQP) chamada!");
        const statusDiv = document.getElementById('bulkOfferStatusScript');
        await GM_deleteValue(STORAGE_KEY_ITEMS);
        await GM_deleteValue(STORAGE_KEY_RUNNING);
        await GM_deleteValue(STORAGE_KEY_PRICE); // Limpa preço alvo também
        if (statusDiv) { statusDiv.textContent = "Status: Oferta interrompida pelo usuário."; }
        console.log("Oferta em massa (SWQP) interrompida. Flags de estado, lista de itens e preço alvo limpos do armazenamento.");
        disableButtons(false); // Reabilita a UI
    }

    function disableButtons(disabled) {
         const startBtn = document.getElementById('startOfferByNameQtyPriceBtnScript');
         const stopBtn = document.getElementById('stopBulkOfferBtnScript');
         const nameInput = document.getElementById('itemNameInputScript');
         const quantityInput = document.getElementById('itemQuantityInputScript');
         const priceInput = document.getElementById('itemPriceInputScript');
         if(startBtn) startBtn.disabled = disabled;
         if(stopBtn) stopBtn.disabled = !disabled; // Botão Parar é habilitado quando rodando
         if(nameInput) nameInput.disabled = disabled;
         if(quantityInput) quantityInput.disabled = disabled;
         if(priceInput) priceInput.disabled = disabled;
    }

    async function processNextOffer() {
        const isRunning = await GM_getValue(STORAGE_KEY_RUNNING, false);
        if (!isRunning) {
            console.log("(SWQP) Processo interrompido (flag 'running' é false).");
            // Não chama stopOffer() aqui, pois pode ter sido chamada externamente. Apenas garante que a UI esteja habilitada.
            disableButtons(false);
            const statusDivCheck = document.getElementById('bulkOfferStatusScript');
            if (statusDivCheck && !statusDivCheck.textContent.includes("interrompida")) {
                 statusDivCheck.textContent = "Status: Parado (flag not set).";
            }
            return;
        }

        const itemsJson = await GM_getValue(STORAGE_KEY_ITEMS, '[]');
        let itemsToOffer = JSON.parse(itemsJson);
        const statusDiv = document.getElementById('bulkOfferStatusScript');
        const targetPrice = await GM_getValue(STORAGE_KEY_PRICE, 0); // Pega preço alvo do storage

        if (itemsToOffer.length === 0) {
            console.log("(SWQP) Lista de itens vazia. Concluindo a oferta.");
            if (statusDiv) statusDiv.textContent = "Status: Todas as ofertas solicitadas foram concluídas!";
            await stopOffer(); // Limpa tudo e reabilita a UI
            return;
        }

        if (!statusDiv) {
            console.error("(SWQP) Erro crítico: Elemento de status da UI não encontrado. Interrompendo.");
            await stopOffer();
            return;
        }

        // Re-seleciona elementos da página antes de cada oferta (importante após reload)
        itemDropdown = document.querySelector(ITEM_DROPDOWN_SELECTOR);
        offerButton = document.querySelector(OFFER_BUTTON_SELECTOR);
        pagePriceInput = document.querySelector(PRICE_INPUT_SELECTOR);

        if (!itemDropdown || !offerButton) {
            statusDiv.textContent = "Erro Crítico: Elementos da página desapareceram. Interrompendo.";
            console.error("(SWQP) Dropdown ou botão de oferta não encontrados no meio do processo. Interrompendo.");
            await stopOffer();
            return;
        }

        const itemToOffer = itemsToOffer.shift(); // Pega e remove o próximo item da lista

        // --- LÓGICA DE PREÇO ATUALIZADA ---
        let priceSetSuccessfully = true;
        if (pagePriceInput) {
            const targetPriceString = String(targetPrice);
            if (pagePriceInput.value !== targetPriceString) {
                 console.log(`(SWQP) Definindo preço na página para: ${targetPriceString} M$`);
                 pagePriceInput.value = targetPriceString;
                 // Disparar eventos para tentar simular interação humana e garantir que o backend reconheça
                 console.log("(SWQP) Disparando eventos 'input' e 'change' no campo de preço.");
                 pagePriceInput.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
                 pagePriceInput.dispatchEvent(new Event('change', { bubbles: true, cancelable: true }));
                 // Pequena verificação síncrona (pode não ser 100% eficaz se houver validação JS complexa)
                 if (pagePriceInput.value !== targetPriceString) {
                    console.warn("(SWQP) Aviso: O valor do campo de preço não foi definido corretamente após a atribuição e eventos.");
                    priceSetSuccessfully = false;
                 }
            } else {
                 console.log(`(SWQP) Preço na página já está correto (${targetPriceString} M$).`);
            }
        } else if (targetPrice > 0) {
            // Campo de preço da página não existe, mas um preço foi solicitado
            console.warn("(SWQP) Campo de preço da página não encontrado neste ciclo, não é possível definir o preço solicitado de " + targetPrice + " M$.");
            statusDiv.textContent = `Aviso: Não foi possível definir o preço ${targetPrice} M$.`;
            priceSetSuccessfully = false; // Considera falha se o preço era > 0
        }
        // --- FIM DA LÓGICA DE PREÇO ---

        // Pequeno atraso APÓS definir o preço e ANTES de selecionar o item,
        // caso haja alguma lógica JS na página que reaja à mudança de preço.
        await new Promise(resolve => setTimeout(resolve, POST_PRICE_SET_DELAY_MS));

        // Seleciona o item
        const initialListJsonForCount = await GM_getValue(STORAGE_KEY_ITEMS, '[]'); // Pega a lista ANTES de remover o item atual
        const initialTotalCount = JSON.parse(initialListJsonForCount).length + itemsToOffer.length + 1; // Calcula total inicial
        const currentItemNumber = initialTotalCount - itemsToOffer.length; // Calcula número atual
        statusDiv.textContent = `Ofertando ${currentItemNumber}/${initialTotalCount}: '${itemToOffer.text}' por ${targetPrice} M$...`;
        console.log(`(SWQP) Preparando oferta ${currentItemNumber}/${initialTotalCount}: ${itemToOffer.text} (ID: ${itemToOffer.value}) por ${targetPrice} M$`);
        itemDropdown.value = itemToOffer.value;

        // Validação da seleção do item no dropdown
        const selectedOption = itemDropdown.options[itemDropdown.selectedIndex];
        if (itemDropdown.value !== itemToOffer.value || !selectedOption || selectedOption.textContent.trim() !== itemToOffer.text) {
             statusDiv.textContent = `Erro: Falha ao selecionar '${itemToOffer.text}' no dropdown. Pulando item...`;
             console.warn(`(SWQP) Falha ao validar a seleção do item ${itemToOffer.text} (Value: ${itemToOffer.value}). O valor atual é ${itemDropdown.value} e o texto selecionado é '${selectedOption ? selectedOption.textContent.trim() : 'N/A'}'. Pulando.`);
             await GM_setValue(STORAGE_KEY_ITEMS, JSON.stringify(itemsToOffer)); // Salva a lista sem o item pulado
             // Tenta o próximo item mais rapidamente
             setTimeout(processNextOffer, BASE_DELAY_MS / 2); // Metade do delay normal
             return; // Pula o clique
        } else {
             console.log(`(SWQP) Item '${itemToOffer.text}' selecionado com sucesso.`);
        }

        // Salva a lista restante (já atualizada com shift()) ANTES do clique
        await GM_setValue(STORAGE_KEY_ITEMS, JSON.stringify(itemsToOffer));

        // Atraso principal ANTES do clique final para simular tempo de usuário e permitir que a página processe
        console.log(`(SWQP) Aguardando ${BASE_DELAY_MS}ms antes do clique final...`);
        await new Promise(resolve => setTimeout(resolve, BASE_DELAY_MS));

        // Verifica novamente se ainda está rodando antes do clique final (caso o usuário tenha clicado em parar durante o delay)
        const stillRunning = await GM_getValue(STORAGE_KEY_RUNNING, false);
        if (!stillRunning) {
            console.log("(SWQP) Oferta interrompida durante o delay final. Botão 'Ofertar' não será clicado.");
            disableButtons(false); // Garante que UI está habilitada
            return;
        }

        console.log("(SWQP) Clicando no botão 'Ofertar item'...");
        offerButton.click();
        // A página irá recarregar, e checkOfferStateOnLoad será chamado novamente.
    }

    async function checkOfferStateOnLoad() {
        createUI(); // Garante que a UI exista
        const statusDiv = document.getElementById('bulkOfferStatusScript');
        const isRunning = await GM_getValue(STORAGE_KEY_RUNNING, false);
        const itemsPendingJson = await GM_getValue(STORAGE_KEY_ITEMS, '[]');
        const itemsPending = JSON.parse(itemsPendingJson);

        if (isRunning && itemsPending.length > 0) {
            console.log("(SWQP) Script recarregado, continuando oferta. Itens restantes:", itemsPending.length);
            disableButtons(true); // Mantém UI desabilitada
            if (statusDiv) statusDiv.textContent = "Status: Recarregado, continuando oferta...";
            // Adiciona um pequeno delay antes de processar o próximo item após o recarregamento
            // Isso pode ajudar se a página ainda estiver carregando algum script.
            await new Promise(resolve => setTimeout(resolve, 500));
            await processNextOffer(); // Continua o processo
        } else {
             console.log("(SWQP) Nenhuma oferta em andamento detectada no carregamento da página.");
             if (isRunning && itemsPending.length === 0) {
                 // Estava rodando, mas a lista acabou (provavelmente a última oferta foi concluída)
                 console.log("(SWQP) Flag 'running' ativa, mas lista de itens vazia. Limpando estado e considerando concluído.");
                 if (statusDiv) statusDiv.textContent = "Status: Oferta concluída (detecção no reload).";
                 await stopOffer(); // Limpa flags e reabilita UI
             } else if (!isRunning && itemsPending.length > 0) {
                 // Não estava rodando, mas ainda há itens na lista? Estranho, talvez erro anterior. Limpar.
                 console.warn("(SWQP) Estado inconsistente detectado: 'running' é false, mas há itens na lista. Limpando estado.");
                 if (statusDiv) statusDiv.textContent = "Status: Estado inconsistente detectado. Limpando.";
                 await stopOffer();
             }
             else {
                 // Estado normal: não rodando, sem itens. UI pronta.
                 disableButtons(false); // Garante que UI esteja habilitada
                 if (statusDiv) statusDiv.textContent = "Status: Pronto.";
             }
        }
    }

    // Inicia a verificação do estado quando a página está pronta
    checkOfferStateOnLoad();

})();