Last active
June 9, 2025 14:56
-
-
Save alebanes/3a3ac73db696dedd2993c8c252013121 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Dev_AutoTools | |
// @namespace http://tampermonkey.net/ | |
// @version 1.6 | |
// @description Automatización para SpaceAlpha | |
// @match https://spacealpha.net/* | |
// @grant none | |
// @updateURL https://gist.githubusercontent.com/alebanes/3a3ac73db696dedd2993c8c252013121/raw/AutoTools.user.js | |
// @downloadURL https://gist.githubusercontent.com/alebanes/3a3ac73db696dedd2993c8c252013121/raw/AutoTools.user.js | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
let autoClickerEnabled = false; | |
const button = document.createElement('button'); | |
button.innerText = 'Start AutoFarm'; | |
button.style.position = 'fixed'; | |
button.style.bottom = '10px'; | |
button.style.right = '80px'; | |
button.style.zIndex = '9999'; | |
button.style.padding = '12px 18px'; | |
button.style.backgroundColor = '#28a745'; | |
button.style.color = 'white'; | |
button.style.border = 'none'; | |
button.style.borderRadius = '8px'; | |
button.style.cursor = 'pointer'; | |
button.style.fontSize = '14px'; | |
button.style.fontWeight = 'bold'; | |
button.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; | |
button.style.transition = 'all 0.3s ease'; | |
button.style.width = 'auto'; | |
button.style.height = '40px'; | |
button.style.minWidth = '140px'; | |
button.style.minHeight = '40px'; | |
button.addEventListener('mouseenter', () => { | |
button.style.transform = 'scale(1.05)'; | |
button.style.boxShadow = '0 6px 12px rgba(0, 0, 0, 0.25)'; | |
}); | |
button.addEventListener('mouseleave', () => { | |
button.style.transform = 'scale(1)'; | |
button.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)'; | |
}); | |
document.body.appendChild(button); | |
button.addEventListener('click', () => { | |
autoClickerEnabled = !autoClickerEnabled; | |
button.innerText = autoClickerEnabled ? 'Stop AutoFarm' : 'Start AutoFarm'; | |
button.style.backgroundColor = autoClickerEnabled ? '#dc3545' : '#28a745'; | |
}); | |
function getAvailablePlots() { | |
const td = document.querySelector('td.clickable'); | |
if (!td) return 0; | |
const divs = td.querySelectorAll('div'); | |
for (const div of divs) { | |
if (div.innerText.includes('Available plots')) { | |
const match = div.innerText.match(/Available plots:\s*(\d+)/i); | |
if (match) return parseInt(match[1], 10); | |
} | |
} | |
return 0; | |
} | |
function getHarvestStatus() { | |
const td = document.querySelector('td.clickable'); | |
if (!td) return ''; | |
const divs = td.querySelectorAll('div'); | |
for (const div of divs) { | |
const text = div.innerText.toLowerCase(); | |
if (text.includes('until harvest') || text.includes('it\'s harvest time')) { | |
return text; | |
} | |
} | |
return ''; | |
} | |
function openPopup() { | |
const td = document.querySelector('td.clickable'); | |
if (td) { | |
td.click(); | |
return true; | |
} else { | |
return false; | |
} | |
} | |
function clickPlantOrHarvest() { | |
const botones = Array.from(document.querySelectorAll('button')); | |
const botonDeseado = botones.find(el => { | |
const texto = el.innerText.trim(); | |
return texto.includes('Plant') || texto.includes('Harvest'); | |
}); | |
if (botonDeseado) { | |
const texto = botonDeseado.innerText.trim(); | |
const isDisabled = botonDeseado.disabled; | |
if (texto.includes('Plant')) { | |
if (isDisabled) { | |
return false; | |
} | |
botonDeseado.click(); | |
return true; | |
} else if (texto.includes('Harvest')) { | |
botonDeseado.click(); | |
return true; | |
} | |
} | |
return false; | |
} | |
setInterval(() => { | |
if (!autoClickerEnabled) return; | |
const popup = document.querySelector('.popupWindow'); | |
if (!popup) { | |
if (window.location.pathname === '/train-units') { | |
const plots = getAvailablePlots(); | |
const harvestStatus = getHarvestStatus(); | |
if (plots > 0 || harvestStatus.includes('it\'s harvest time')) { | |
openPopup(); | |
} | |
} | |
return; | |
} | |
clickPlantOrHarvest(); | |
}, 500); | |
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// Market //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// | |
// ================================================================================================================================================================== | |
// BLOQUE 1: BOTONES TOP BUYER & LOWEST SELLER | |
// ================================================================================================================================================================== | |
setInterval(() => { | |
if (window.location.href.includes('/market') && isMyOffersSelected()) { | |
insertMarketButtons(); | |
} | |
}, 1000); | |
function isMyOffersSelected() { | |
const selected = document.querySelector('li.clickable.selected'); | |
return selected && selected.innerText.trim() === 'My Offers'; | |
} | |
function getTopBuyerTitle() { | |
const td = Array.from(document.querySelectorAll('td')).find(td => | |
td.innerHTML.includes('Top buyer')); | |
if (!td) return null; | |
const span = td.querySelector('span[title]'); | |
return span ? span.getAttribute('title') : null; | |
} | |
function getLowestSellerTitle() { | |
const td = Array.from(document.querySelectorAll('td')).find(td => | |
td.innerHTML.includes('Lowest seller')); | |
if (!td) return null; | |
const spans = td.querySelectorAll('span[title]'); | |
return spans.length >= 2 ? spans[1].getAttribute('title') : null; | |
} | |
function parseTitleToNumber(titleStr) { | |
return parseFloat(titleStr.replace(/,/g, '')); | |
} | |
function setPriceInputValue(value) { | |
const trPrice = Array.from(document.querySelectorAll('tr')).find(tr => { | |
const firstTd = tr.querySelector('td'); | |
return firstTd && firstTd.innerText.trim() === 'Price'; | |
}); | |
if (trPrice) { | |
const input = trPrice.querySelector('input[type="number"]'); | |
if (input) { | |
input.value = value; | |
input.dispatchEvent(new Event('input', { bubbles: true })); | |
} | |
} | |
} | |
function insertMarketButtons() { | |
if (document.getElementById('btn-top-buyer')) return; | |
const buttonTop = document.createElement('button'); | |
buttonTop.id = 'btn-top-buyer'; | |
buttonTop.innerText = 'Be Top Buyer'; | |
buttonTop.style.margin = '5px'; | |
buttonTop.style.padding = '6px 8px'; | |
buttonTop.style.backgroundColor = '#007bff'; | |
buttonTop.style.color = 'white'; | |
buttonTop.style.border = 'none'; | |
buttonTop.style.borderRadius = '5px'; | |
buttonTop.style.cursor = 'pointer'; | |
buttonTop.style.fontSize = '11px'; | |
buttonTop.style.maxWidth = '140px'; | |
buttonTop.style.width = '100%'; | |
buttonTop.style.boxSizing = 'border-box'; | |
const buttonLowest = document.createElement('button'); | |
buttonLowest.id = 'btn-lowest-seller'; | |
buttonLowest.innerText = 'Be Lowest Seller'; | |
buttonLowest.style.margin = '5px'; | |
buttonLowest.style.padding = '6px 8px'; | |
buttonLowest.style.backgroundColor = '#17a2b8'; | |
buttonLowest.style.color = 'white'; | |
buttonLowest.style.border = 'none'; | |
buttonLowest.style.borderRadius = '5px'; | |
buttonLowest.style.cursor = 'pointer'; | |
buttonLowest.style.fontSize = '11px'; | |
buttonLowest.style.maxWidth = '140px'; | |
buttonLowest.style.width = '100%'; | |
buttonLowest.style.boxSizing = 'border-box'; | |
buttonTop.addEventListener('click', () => { | |
if (!isMyOffersSelected()) return; | |
const topValue = getTopBuyerTitle(); | |
if (topValue) { | |
const numericValue = parseTitleToNumber(topValue); | |
const newValue = numericValue + 1; | |
setPriceInputValue(newValue); | |
} | |
}); | |
buttonLowest.addEventListener('click', () => { | |
if (!isMyOffersSelected()) return; | |
const lowestValue = getLowestSellerTitle(); | |
if (lowestValue) { | |
const numericValue = parseTitleToNumber(lowestValue); | |
const newValue = numericValue - 1; | |
setPriceInputValue(newValue); | |
} | |
}); | |
const targetTd = Array.from(document.querySelectorAll('td[rowspan="4"][style*="line-height: 2;"]')).find(td => td.children.length === 0); | |
if (targetTd) { | |
targetTd.appendChild(buttonTop); | |
targetTd.appendChild(buttonLowest); | |
} | |
} | |
// ================================================================================================================================================================== | |
// BLOQUE 2: REPEAT / BEST POSTOR | |
// ================================================================================================================================================================== | |
// ======================================================================== | |
// FUNCIONES GENERALES | |
// ======================================================================== | |
function parsePriceString(priceStr) { | |
if (!priceStr) return null; | |
const multipliers = { | |
'K': 1e3, 'M': 1e6, 'B': 1e9, 'T': 1e12, 'Qa': 1e15, 'Qi': 1e18, | |
'Sx': 1e21, 'Sp': 1e24, 'Oc': 1e27, 'No': 1e30, 'Dc': 1e33 | |
}; | |
const match = priceStr.match(/([\d.,]+)\s*([A-Za-z]*)/); | |
if (!match) return parseFloat(priceStr.replace(/,/g, '')); | |
let number = parseFloat(match[1].replace(/,/g, '')); | |
const suffix = match[2]; | |
if (suffix && multipliers[suffix]) { | |
number *= multipliers[suffix]; | |
} | |
return number; | |
} | |
// ======================================================================== | |
// SETUP COLUMNS | |
// ======================================================================== | |
function setupTableColumns(type) { | |
const tableSelector = (type === 'selling') ? 'table.clean_table:nth-of-type(1)' : 'table.clean_table:nth-of-type(2)'; | |
const table = document.querySelector(tableSelector); | |
if (!table) return; | |
const headerRow = table.querySelectorAll('tr.table_header')[1]; | |
const mainRow = table.querySelector('tr.table_header:first-child td[colspan]'); | |
if (mainRow) { | |
const totalCols = headerRow ? headerRow.children.length : 6; | |
mainRow.setAttribute('colspan', totalCols + 1); | |
} | |
if (headerRow) { | |
const headers = Array.from(headerRow.children).map(td => td.textContent.trim()); | |
if (!headers.includes(type === 'selling' ? 'Repeat Sell' : 'Repeat Buy')) { | |
const repeatTd = document.createElement('td'); | |
repeatTd.textContent = type === 'selling' ? 'Repeat Sell' : 'Repeat Buy'; | |
headerRow.appendChild(repeatTd); | |
} | |
if (!headers.includes(type === 'selling' ? 'Best Seller?' : 'Best Buyer?')) { | |
const bestTd = document.createElement('td'); | |
bestTd.textContent = type === 'selling' ? 'Best Seller?' : 'Best Buyer?'; | |
bestTd.classList.add('best-header'); | |
headerRow.appendChild(bestTd); | |
} | |
} | |
} | |
// ======================================================================== | |
// ADD REPEAT + BEST PER ROW | |
// ======================================================================== | |
function addRepeatAndBestToRow(row, type) { | |
if (row.querySelector('td[colspan]') && /Reached .* offers limit/.test(row.innerText)) { | |
return; | |
} | |
if (row.querySelector('button.btn-repeat-sell, button.btn-repeat-buy')) return; | |
const btnTd = document.createElement('td'); | |
btnTd.style.padding = '5px'; | |
btnTd.style.textAlign = 'center'; | |
const btn = document.createElement('button'); | |
btn.title = (type === 'selling') ? 'Repeat Sell' : 'Repeat Buy'; | |
btn.className = (type === 'selling') ? 'btn-repeat-sell' : 'btn-repeat-buy'; | |
btn.style.padding = '2px 6px'; | |
btn.style.border = 'none'; | |
btn.style.background = 'transparent'; | |
btn.style.color = (type === 'selling') ? '#28a745' : '#17a2b8'; | |
btn.style.cursor = 'pointer'; | |
btn.style.fontSize = '16px'; | |
btn.style.fontWeight = 'bold'; | |
btn.textContent = '🔄'; | |
btn.addEventListener('click', () => { | |
const resourceImg = row.querySelector('td:nth-child(2) img'); | |
const resourceTitle = resourceImg ? resourceImg.getAttribute('title') : null; | |
if (!resourceTitle) return; | |
const amountSpan = row.querySelector('td:nth-child(2) amount span'); | |
let quantity = amountSpan ? amountSpan.getAttribute('title') : null; | |
if (!quantity) return; | |
quantity = quantity.replace(/,/g, ''); | |
const priceSpan = row.querySelector('td:nth-child(3) amount span'); | |
let price = priceSpan ? priceSpan.getAttribute('title') : null; | |
if (!price) return; | |
price = price.replace(/,/g, ''); | |
const placeOfferTable = Array.from(document.querySelectorAll('table')).find(t => | |
t.querySelector('td.table_header')?.innerText.trim() === 'Place Offer' | |
); | |
if (!placeOfferTable) return; | |
const resourceSelect = placeOfferTable.querySelector('select'); | |
if (resourceSelect) { | |
for (const option of resourceSelect.options) { | |
if (option.text.toLowerCase() === resourceTitle.toLowerCase()) { | |
resourceSelect.value = option.value; | |
resourceSelect.dispatchEvent(new Event('change', { bubbles: true })); | |
break; | |
} | |
} | |
} | |
const quantityInput = Array.from(placeOfferTable.querySelectorAll('input[type="number"]')).find(input => | |
input.closest('tr')?.querySelector('td:first-child')?.innerText.trim() === 'Quantity' | |
); | |
if (quantityInput) { | |
quantityInput.value = quantity; | |
quantityInput.dispatchEvent(new Event('input', { bubbles: true })); | |
} | |
const priceInput = Array.from(placeOfferTable.querySelectorAll('input[type="number"]')).find(input => | |
input.closest('tr')?.querySelector('td:first-child')?.innerText.trim() === 'Price' | |
); | |
if (priceInput) { | |
priceInput.value = price; | |
priceInput.dispatchEvent(new Event('input', { bubbles: true })); | |
} | |
}); | |
btnTd.appendChild(btn); | |
row.appendChild(btnTd); | |
const bestTd = document.createElement('td'); | |
bestTd.classList.add('best-result-cell'); | |
bestTd.style.padding = '5px'; | |
bestTd.style.textAlign = 'center'; | |
bestTd.style.fontSize = '16px'; | |
bestTd.style.fontWeight = 'bold'; | |
bestTd.textContent = ''; | |
row.appendChild(bestTd); | |
} | |
// ======================================================================== | |
// AUTO BEST POSTOR CHECK | |
// ======================================================================== | |
let isCheckingBestPostor = false; | |
async function runAutoBestPostorCheck() { | |
if (isCheckingBestPostor) { | |
return; | |
} | |
isCheckingBestPostor = true; | |
await processTable('table.clean_table:nth-of-type(1)', 'selling'); | |
await processTable('table.clean_table:nth-of-type(2)', 'buying'); | |
isCheckingBestPostor = false; | |
} | |
async function getMarketTopLowest() { | |
const marketPriceDiv = Array.from(document.querySelectorAll('td[rowspan="4"]')).find(td => | |
td.innerText.includes('Top buyer:') && td.innerText.includes('Lowest seller:') | |
); | |
if (!marketPriceDiv) return { topBuyer: null, lowestSeller: null }; | |
const spans = marketPriceDiv.querySelectorAll('amount.clickable span'); | |
const spanArr = Array.from(spans); | |
const topBuyer = spanArr[0] ? parsePriceString(spanArr[0].getAttribute('title')) : null; | |
const lowestSeller = spanArr[1] ? parsePriceString(spanArr[1].getAttribute('title')) : null; | |
return { topBuyer, lowestSeller }; | |
} | |
function updateRowWithResult(row, isBest) { | |
let cell = row.querySelector('td.best-result-cell'); | |
if (!cell) return; | |
cell.textContent = isBest ? '✔️' : '❌'; | |
} | |
async function processRow(row, type) { | |
const resourceImg = row.querySelector('td img'); | |
const resource = resourceImg ? resourceImg.getAttribute('title') : null; | |
if (!resource) return; | |
const priceSpan = row.querySelector('td:nth-child(3) amount span'); | |
const myPrice = priceSpan ? parsePriceString(priceSpan.getAttribute('title')) : null; | |
const select = document.querySelector('select'); | |
const previousSelectedValue = select ? select.value : null; | |
const option = Array.from(select.options).find(opt => opt.value === resource || opt.text === resource); | |
if (option) { | |
select.value = option.value; | |
select.dispatchEvent(new Event('change')); | |
} else { | |
return; | |
} | |
await new Promise(resolve => setTimeout(resolve, 50)); | |
const { topBuyer, lowestSeller } = await getMarketTopLowest(); | |
if (type === 'selling') { | |
const isBestSeller = myPrice <= lowestSeller; | |
updateRowWithResult(row, isBestSeller); | |
} else if (type === 'buying') { | |
const isBestBuyer = myPrice >= topBuyer; | |
updateRowWithResult(row, isBestBuyer); | |
} | |
if (select) { | |
select.value = previousSelectedValue; | |
select.dispatchEvent(new Event('change')); | |
} | |
} | |
async function processTable(selector, type) { | |
const table = document.querySelector(selector); | |
if (!table) return; | |
const rows = table.querySelectorAll('tr:not(.table_header)'); | |
for (const row of rows) { | |
addRepeatAndBestToRow(row, type); | |
await processRow(row, type); | |
} | |
} | |
// ======================================================================== | |
// MAIN INIT + OBSERVER | |
// ======================================================================== | |
function initAutoMarket() { | |
if (!isMyOffersSelected()) return; | |
setupTableColumns('selling'); | |
setupTableColumns('buying'); | |
runAutoBestPostorCheck(); | |
setInterval(() => { | |
if (isMyOffersSelected()) { | |
runAutoBestPostorCheck(); | |
} | |
}, 30000); | |
const observer = new MutationObserver(mutations => { | |
mutations.forEach(mutation => { | |
mutation.addedNodes.forEach(node => { | |
if (node.nodeType === 1 && node.matches('tr')) { | |
const table = node.closest('table'); | |
if (!table) return; | |
if (table === document.querySelector('table.clean_table:nth-of-type(1)')) { | |
addRepeatAndBestToRow(node, 'selling'); | |
processRow(node, 'selling'); | |
} else if (table === document.querySelector('table.clean_table:nth-of-type(2)')) { | |
addRepeatAndBestToRow(node, 'buying'); | |
processRow(node, 'buying'); | |
} | |
} | |
}); | |
}); | |
}); | |
const sellingTable = document.querySelector('table.clean_table:nth-of-type(1)'); | |
const buyingTable = document.querySelector('table.clean_table:nth-of-type(2)'); | |
if (sellingTable) observer.observe(sellingTable, { childList: true, subtree: true }); | |
if (buyingTable) observer.observe(buyingTable, { childList: true, subtree: true }); | |
} | |
setInterval(() => { | |
if (isMyOffersSelected()) { | |
if (!document.querySelector('.best-result-cell')) { | |
initAutoMarket(); | |
} | |
} | |
}, 1000); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment