hl3countdown-bugs/index.js
2025-11-26 18:07:48 +00:00

379 lines
12 KiB
JavaScript

// ========== CONFIG ==========
const BASE_URL = 'https://hl3countdown.com/api';
const ACCOUNTS = [
{
name: 'Account 1',
headers: {
'Cookie': 'identity=<identity cookie here>',
'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();