Skip to content

Instantly share code, notes, and snippets.

@alebanes
Last active June 9, 2025 14:56
Show Gist options
  • Save alebanes/3a3ac73db696dedd2993c8c252013121 to your computer and use it in GitHub Desktop.
Save alebanes/3a3ac73db696dedd2993c8c252013121 to your computer and use it in GitHub Desktop.
// ==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