// ==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();
})();