commit 747569f1d18a9f3826a8fb68ead7476bd71b23f7 Author: pathetic Date: Wed Nov 26 18:07:48 2025 +0000 Initialise repository diff --git a/README.md b/README.md new file mode 100644 index 0000000..deb34b7 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +Silly little bugs found in https://hl3countdown.com/ + +The stock duping mechanism has now been patched, so we're focusing on constructing a table of a million rows of rouly results to cheat the system, timer needs to be added to bypass AntiC. \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..7339744 --- /dev/null +++ b/index.js @@ -0,0 +1,379 @@ +// ========== CONFIG ========== + +const BASE_URL = 'https://hl3countdown.com/api'; + +const ACCOUNTS = [ + { + name: 'Account 1', + headers: { + 'Cookie': 'identity=', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + } + } +]; + +const FIXED_PRICE = 1000; // Fixed price for buying stocks + +// ========== HELPERS ========== + +async function doJsonFetch(url, options = {}) { + const res = await fetch(url, { + ...options, + }); + + let bodyText = ''; + try { + bodyText = await res.text(); + } catch { + bodyText = ''; + } + + let json; + try { + json = bodyText ? JSON.parse(bodyText) : null; + } catch (e) { + json = null; + } + + if (!res.ok) { + throw new Error( + `HTTP ${res.status} ${res.statusText} for ${url}. Body: ${bodyText}` + ); + } + + return json; +} + +// Compute quantity from total_score (thousands, capped at 5000) +function computeQuantity(total_score) { + if (typeof total_score !== 'number') { + throw new Error(`total_score is not a number: ${total_score}`); + } + const pointsK = Math.floor(total_score / 1000); + return Math.min(pointsK, 5000); +} + +// Try to extract orderId from buy response +function extractOrderId(buyResponse) { + if (!buyResponse) return null; + + if (typeof buyResponse.orderId === 'number' || typeof buyResponse.orderId === 'string') { + return buyResponse.orderId; + } + if (buyResponse.order && (buyResponse.order.id || buyResponse.order.orderId)) { + return buyResponse.order.id || buyResponse.order.orderId; + } + if (buyResponse.id) { + return buyResponse.id; + } + + return null; +} + +// ========== PER-ACCOUNT FLOW ========== + +async function processAccount(account, isFirstRun) { + const { name, headers } = account; + console.log(`\n=== Processing ${name} ===`); + + // 0. Set username on first run + if (isFirstRun) { + const accountNumber = name.match(/\d+/)?.[0] || '0'; + const setUsernameUrl = `https://hl3countdown.com/leaderboard/set-username`; + + const num = parseInt(accountNumber); + const isOdd = num % 2 === 1; + const username = isOdd ? "pee" : "poo"; + + const usernameBody = { + username: username, + }; + + console.log(`[${name}] POST ${setUsernameUrl} with body`, usernameBody); + + try { + const usernameResponse = await doJsonFetch(setUsernameUrl, { + method: 'POST', + headers: { + ...headers, + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/plain, */*', + }, + body: JSON.stringify(usernameBody), + }); + + console.log(`[${name}] ✓ Username set successfully to: ${username}`); + console.log(`[${name}] Set username response:`, usernameResponse); + } catch (err) { + console.error(`[${name}] ✗ Failed to set username:`, err.message || err); + } + } + + // 1. GET leaderboard/user to fetch total_score + const leaderboardUrl = `${BASE_URL}/leaderboard/user`; + console.log(`[${name}] GET ${leaderboardUrl}`); + + const userData = await doJsonFetch(leaderboardUrl, { + method: 'GET', + headers, + }); + + if (userData == null) { + throw new Error(`[${name}] Empty response from leaderboard/user`); + } + + const total_score = userData.total_score; + console.log(`[${name}] total_score = ${total_score}`); + + const quantity = computeQuantity(total_score); + console.log(`[${name}] computed quantity = ${quantity}`); + + if (quantity <= 0) { + console.log(`[${name}] quantity <= 0, skipping buy.`); + return; + } + + // 2. POST stocks/hlx/buy + // If total_score < 1000, use total_score as price; otherwise use FIXED_PRICE + const price = FIXED_PRICE; + + const buyUrl = `${BASE_URL}/stocks/hlx/buy`; + const buyBody = { + quantity, + price, + }; + + console.log(`[${name}] POST ${buyUrl} with body`, buyBody); + + const buyResponse = await doJsonFetch(buyUrl, { + method: 'POST', + headers: { + ...headers, + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/plain, */*', + }, + body: JSON.stringify(buyBody), + }); + + console.log(`[${name}] Buy response:`, buyResponse); + + // 3. Extract orderId + const orderId = extractOrderId(buyResponse); + + if (!orderId) { + console.warn(`[${name}] Could not find orderId in buy response, not deleting.`); + return; + } + + // 4. DELETE stocks/orders/buy/{orderId} + const deleteUrl = `${BASE_URL}/stocks/orders/buy/${orderId}`; + console.log(`[${name}] DELETE ${deleteUrl}`); + + const deleteResponse = await doJsonFetch(deleteUrl, { + method: 'DELETE', + headers, + }); + + console.log(`[${name}] Delete response:`, deleteResponse); + + // 5. Roulette spin 100 times + const accountNumber = parseInt(name.match(/\d+/)?.[0] || '1'); + const betType = accountNumber % 2 === 1 ? 'red' : 'black'; + + for (let i = 0; i < 100; i++) { + const rouletteBody = { + bets: [{ + type: betType, + amount: 1e-17 + }] + }; + + const rouletteUrl = `https://hl3countdown.com/api/roulette/spin`; + + console.log(`[${name}] Spin ${i + 1}/100 - POST ${rouletteUrl}`); + + try { + const rouletteResponse = await doJsonFetch(rouletteUrl, { + method: 'POST', + headers: { + ...headers, + 'Content-Type': 'application/json', + 'Accept': 'application/json, text/plain, */*', + }, + body: JSON.stringify(rouletteBody), + }); + + // Collect result for table + if (rouletteResponse) { + rouletteResults.push({ + account: name, + accountNumber: accountNumber, + spinNumber: i + 1, + winningNumber: rouletteResponse.winningNumber, + color: rouletteResponse.color, + betType: rouletteBody.bets[0].type, + betAmount: rouletteBody.bets[0].amount, + won: rouletteResponse.bets[0].won, + payout: rouletteResponse.totalPayout, + netChange: rouletteResponse.netChange + }); + } + } catch (err) { + console.error(`[${name}] Spin ${i + 1} failed:`, err.message || err); + } + } + + console.log(`[${name}] Completed 100 spins`); +} + +// ========== MAIN ========== + +// Global array to collect roulette results +const rouletteResults = []; + +async function main() { + const fs = require('fs'); + const path = require('path'); + const flagFile = path.join(__dirname, '.first_run_complete'); + + const isFirstRun = !fs.existsSync(flagFile); + + // First, fetch balances for all accounts + console.log('=== Fetching account balances ==='); + const accountBalances = []; + + for (const account of ACCOUNTS) { + try { + const leaderboardUrl = `${BASE_URL}/leaderboard/user`; + const userData = await doJsonFetch(leaderboardUrl, { + method: 'GET', + headers: account.headers, + }); + + const total_score = userData?.total_score || 0; + accountBalances.push({ + account, + total_score + }); + console.log(`${account.name}: ${total_score}`); + } catch (err) { + console.error(`Failed to fetch balance for ${account.name}:`, err.message); + accountBalances.push({ + account, + total_score: 0 + }); + } + } + + // Sort by balance (highest to lowest) + accountBalances.sort((a, b) => b.total_score - a.total_score); + + // Rename accounts: highest = Account 1, lowest = Account 10 + accountBalances.forEach((item, index) => { + const newNumber = index + 1; + item.account.name = `Account ${newNumber}`; + }); + + console.log('\n=== Accounts renamed by balance (highest to lowest) ==='); + accountBalances.forEach(item => { + console.log(`${item.account.name}: ${item.total_score}`); + }); + + try { + await Promise.all( + ACCOUNTS.map((account) => + processAccount(account, isFirstRun).catch((err) => { + console.error(`Error in ${account.name}:`, err.message || err); + }) + ) + ); + + if (isFirstRun) { + fs.writeFileSync(flagFile, new Date().toISOString()); + console.log('\nFirst run complete. Usernames set.'); + } + + console.log('\nAll accounts processed.'); + + // Display results table + if (rouletteResults.length > 0) { + let output = ''; + + output += '\n\n=== ROULETTE RESULTS TABLE (100 SPINS PER ACCOUNT) ===\n'; + output += '┌─────────────┬────────┬───────┬────────────┬───────┬────────────┬─────┬─────────┬────────────┐\n'; + output += '│ Account │ Acc # │ Spin │ Landed On │ Color │ Bet Type │ Won │ Payout │ Net Change │\n'; + output += '├─────────────┼────────┼───────┼────────────┼───────┼────────────┼─────┼─────────┼────────────┤\n'; + + rouletteResults.forEach(result => { + const account = result.account.padEnd(11); + const accNum = String(result.accountNumber).padStart(6); + const spin = String(result.spinNumber).padStart(5); + const number = String(result.winningNumber).padStart(10); + const color = result.color.padEnd(5); + const betType = result.betType.padEnd(10); + const won = (result.won ? '✓' : '✗').padEnd(3); + const payout = String(result.payout).padStart(7); + const netChange = String(result.netChange).padStart(10); + + output += `│ ${account} │ ${accNum} │ ${spin} │ ${number} │ ${color} │ ${betType} │ ${won} │ ${payout} │ ${netChange} │\n`; + }); + + output += '└─────────────┴────────┴───────┴────────────┴───────┴────────────┴─────┴─────────┴────────────┘\n'; + + // Summary statistics + const totalWins = rouletteResults.filter(r => r.won).length; + const totalLosses = rouletteResults.filter(r => !r.won).length; + const totalNetChange = rouletteResults.reduce((sum, r) => sum + r.netChange, 0); + + output += '\n=== SUMMARY ===\n'; + output += `Total Spins: ${rouletteResults.length}\n`; + output += `Wins: ${totalWins} (${((totalWins / rouletteResults.length) * 100).toFixed(1)}%)\n`; + output += `Losses: ${totalLosses} (${((totalLosses / rouletteResults.length) * 100).toFixed(1)}%)\n`; + output += `Total Net Change: ${totalNetChange}\n`; + + // Number frequency + output += '\n=== NUMBER FREQUENCY ===\n'; + const numberFreq = {}; + rouletteResults.forEach(r => { + numberFreq[r.winningNumber] = (numberFreq[r.winningNumber] || 0) + 1; + }); + + Object.entries(numberFreq) + .sort((a, b) => b[1] - a[1]) + .forEach(([num, count]) => { + output += `Number ${num}: ${count} time(s)\n`; + }); + + // Color distribution + output += '\n=== COLOR DISTRIBUTION ===\n'; + const colorFreq = {}; + rouletteResults.forEach(r => { + colorFreq[r.color] = (colorFreq[r.color] || 0) + 1; + }); + + Object.entries(colorFreq) + .sort((a, b) => b[1] - a[1]) + .forEach(([color, count]) => { + output += `${color}: ${count} time(s) (${((count / rouletteResults.length) * 100).toFixed(1)}%)\n`; + }); + + // Print to console + console.log(output); + + // Write to file in results directory + const resultsDir = path.join(__dirname, 'results'); + if (!fs.existsSync(resultsDir)) { + fs.mkdirSync(resultsDir, { recursive: true }); + } + + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const filename = path.join(resultsDir, `roulette_results_${timestamp}.txt`); + fs.writeFileSync(filename, output, 'utf8'); + console.log(`\n✓ Results written to: ${filename}`); + } + } catch (err) { + console.error('Fatal error:', err); + } +} + +main();