// ==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 Mark // @match *://*.popmundo.com/World/Popmundo.aspx/Character/OfferItem/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @run-at document-idle // @downloadURL none // ==/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 = `



Status: Pronto.
`; // 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(); })();