Initialise repository
This commit is contained in:
commit
747569f1d1
2 changed files with 382 additions and 0 deletions
379
index.js
Normal file
379
index.js
Normal file
|
|
@ -0,0 +1,379 @@
|
|||
// ========== 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();
|
||||
Loading…
Add table
Add a link
Reference in a new issue