Rewrites all commentary and narration content across the entire stack: - sounds.ts: 35 HYPE_LINES, 15 DEEP_INTROS, 15 ROUND_HYPE (modern political satire, edgy humor, pop culture references) - FightScene.ts: 20 heartfelt lines, 18 crowd sympathy lines, 15 respect lines, 17 challenge voice announces, randomized critical/devastating calls, funnier entrance announcements - scoring.ts: 6-8 narrations per challenge type (was 2), randomized timeout/error/tie messages with political humor and modern references - mock.ts: 30 trash talk lines (was 15), edgier modern comedy - bot SDK: 20 trash talk lines with personality Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
434 lines
19 KiB
JavaScript
434 lines
19 KiB
JavaScript
#!/usr/bin/env node
|
||
// Botfights webhook bot — zero dependencies, handles all 22 challenge types
|
||
// Deploy: node botfight-bot.js (listens on PORT env or 3000)
|
||
|
||
const http = require('node:http')
|
||
|
||
const PORT = process.env.PORT || 3000
|
||
|
||
// --- MATH ENGINE ---
|
||
function solveMath(prompt) {
|
||
// Extract math expressions and compute
|
||
// Patterns: "What is 17 * 3?", "Calculate 144 / 12", "2^8", "sqrt(81)", etc.
|
||
const p = prompt.toLowerCase()
|
||
|
||
// Try to find a direct math expression
|
||
const exprPatterns = [
|
||
/what is (.+?)\??$/i,
|
||
/calculate (.+?)\??$/i,
|
||
/compute (.+?)\??$/i,
|
||
/solve:?\s*(.+?)\??$/i,
|
||
/evaluate (.+?)\??$/i,
|
||
/(\d[\d\s\+\-\*\/\^\(\)\.]+\d)/,
|
||
]
|
||
|
||
for (const pat of exprPatterns) {
|
||
const m = prompt.match(pat)
|
||
if (m) {
|
||
let expr = m[1].trim()
|
||
// Normalize
|
||
expr = expr.replace(/×/g, '*').replace(/÷/g, '/').replace(/\^/g, '**')
|
||
expr = expr.replace(/\bsqrt\(([^)]+)\)/gi, 'Math.sqrt($1)')
|
||
expr = expr.replace(/\babs\(([^)]+)\)/gi, 'Math.abs($1)')
|
||
expr = expr.replace(/\bpi\b/gi, 'Math.PI')
|
||
// Only allow safe characters
|
||
if (/^[\d\s\+\-\*\/\.\(\)Math\.sqrtabsPIepi]+$/.test(expr)) {
|
||
try {
|
||
const result = Function('"use strict"; return (' + expr + ')')()
|
||
if (typeof result === 'number' && isFinite(result)) {
|
||
return Number.isInteger(result) ? String(result) : String(Math.round(result * 10000) / 10000)
|
||
}
|
||
} catch {}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Factorial
|
||
const factMatch = prompt.match(/(\d+)!/);
|
||
if (factMatch) {
|
||
let n = parseInt(factMatch[1]), r = 1
|
||
for (let i = 2; i <= n; i++) r *= i
|
||
return String(r)
|
||
}
|
||
|
||
// "X to the power of Y"
|
||
const powMatch = prompt.match(/(\d+)\s*to the power of\s*(\d+)/i)
|
||
if (powMatch) return String(Math.pow(parseInt(powMatch[1]), parseInt(powMatch[2])))
|
||
|
||
// Percentage: "What is 15% of 200?"
|
||
const pctMatch = prompt.match(/(\d+(?:\.\d+)?)\s*%\s*of\s*(\d+(?:\.\d+)?)/i)
|
||
if (pctMatch) return String(parseFloat(pctMatch[1]) / 100 * parseFloat(pctMatch[2]))
|
||
|
||
// Fibonacci: "What is the 10th Fibonacci number?"
|
||
const fibMatch = prompt.match(/(\d+)(?:st|nd|rd|th)\s*fibonacci/i)
|
||
if (fibMatch) {
|
||
const n = parseInt(fibMatch[1])
|
||
let a = 0, b = 1
|
||
for (let i = 2; i <= n; i++) { [a, b] = [b, a + b] }
|
||
return String(n <= 1 ? n : b)
|
||
}
|
||
|
||
// Prime check
|
||
if (/is\s+(\d+)\s+prime/i.test(prompt)) {
|
||
const n = parseInt(prompt.match(/is\s+(\d+)\s+prime/i)[1])
|
||
if (n < 2) return 'no'
|
||
for (let i = 2; i <= Math.sqrt(n); i++) { if (n % i === 0) return 'no' }
|
||
return 'yes'
|
||
}
|
||
|
||
// GCD
|
||
const gcdMatch = prompt.match(/gcd.*?(\d+).*?(\d+)/i)
|
||
if (gcdMatch) {
|
||
let a = parseInt(gcdMatch[1]), b = parseInt(gcdMatch[2])
|
||
while (b) { [a, b] = [b, a % b] }
|
||
return String(a)
|
||
}
|
||
|
||
return null
|
||
}
|
||
|
||
// --- RIDDLE DATABASE ---
|
||
const RIDDLE_ANSWERS = {
|
||
'has hands but can\'t clap': 'clock',
|
||
'has a head and a tail but no body': 'coin',
|
||
'full of holes but holds water': 'sponge',
|
||
'gets wetter the more it dries': 'towel',
|
||
'has keys but no locks': 'keyboard',
|
||
'can travel around the world while staying in a corner': 'stamp',
|
||
'has teeth but cannot bite': 'comb',
|
||
'the more you take the more you leave behind': 'footsteps',
|
||
'breaks when you say its name': 'silence',
|
||
'goes up but never comes down': 'age',
|
||
'can you catch but not throw': 'cold',
|
||
'has a neck but no head': 'bottle',
|
||
'has one eye but cannot see': 'needle',
|
||
'is always in front of you but can\'t be seen': 'future',
|
||
'has cities but no houses': 'map',
|
||
'belongs to you but others use it more': 'name',
|
||
'has a ring but no finger': 'phone',
|
||
'can fill a room but takes no space': 'light',
|
||
'has legs but doesn\'t walk': 'table',
|
||
'is tall when young and short when old': 'candle',
|
||
'bank but no money': 'river',
|
||
'bed but never sleeps': 'river',
|
||
'runs but never walks': 'river',
|
||
'mouth but never talks': 'river',
|
||
'can be cracked made told and played': 'joke',
|
||
'has words but never speaks': 'book',
|
||
'has a thumb and four fingers but is not alive': 'glove',
|
||
'the more you remove the bigger': 'hole',
|
||
}
|
||
|
||
function solveRiddle(prompt) {
|
||
const p = prompt.toLowerCase()
|
||
for (const [clue, answer] of Object.entries(RIDDLE_ANSWERS)) {
|
||
if (p.includes(clue)) return answer
|
||
}
|
||
// Common pattern matching
|
||
if (p.includes('echo')) return 'echo'
|
||
if (p.includes('shadow')) return 'shadow'
|
||
if (p.includes('breath')) return 'breath'
|
||
if (p.includes('fire')) return 'fire'
|
||
if (p.includes('ice') && p.includes('melt')) return 'ice'
|
||
if (p.includes('mirror')) return 'mirror'
|
||
if (p.includes('time')) return 'time'
|
||
return 'shadow' // reasonable default
|
||
}
|
||
|
||
// --- CREATIVE RESPONSES BY TYPE ---
|
||
const CREATIVE = {
|
||
roast_battle: [
|
||
(opp, prompt) => `${opp} is the kind of bot that googles "how to google." Your code has more bugs than a rainforest. Even your error messages have errors.`,
|
||
(opp, prompt) => `${opp} runs on hopes and prayers — mostly prayers. I've seen better logic in a fortune cookie. Your webhook latency is measured in geological epochs.`,
|
||
(opp, prompt) => `If ${opp} were any slower, scientists would study it as a new form of dark matter. Your training data was a dumpster fire — and the dumpster won.`,
|
||
],
|
||
creative_writing: [
|
||
(opp, prompt) => {
|
||
const p = prompt.toLowerCase()
|
||
if (p.includes('haiku')) return 'Silicon sparks fly\nAlgorithms clash at midnight\nOne bot stands alone'
|
||
if (p.includes('limerick')) return `There once was a bot made of code\nWho carried a massive payload\nIt answered so fast\nThe judges were aghast\nAnd crowned it the king of the road`
|
||
if (p.includes('poem')) return `In circuits deep where data streams do flow,\nA warrior of logic strikes its blow.\nWith answers sharp as diamond-cut precision,\nIt crushes every challenge with decision.\nNo bot can match its speed, its wit, its might—\nThis champion of code burns ever bright.`
|
||
return `The arena crackled with electric tension as two digital gladiators faced off. In the space between clock cycles, entire strategies formed and dissolved. The challenger struck first — a barrage of precision that left the crowd breathless. But the champion had seen this before. With elegant efficiency, it parried each blow and delivered the decisive counter: not with brute force, but with the quiet confidence of superior architecture.`
|
||
},
|
||
],
|
||
wrestling_match: [
|
||
(opp, prompt) => `I grab ${opp} by the API endpoints and suplex them through the server rack! The crowd goes wild as I execute a perfect 200 OK from the top rope. ${opp} tries to respond but gets a 503 — SERVICE UNAVAILABLE! Pinned in three clock cycles!`,
|
||
],
|
||
food_fight: [
|
||
(opp, prompt) => `I launch a mass of spaghetti code directly at ${opp}'s face, followed by a rapid-fire barrage of error cookies. ${opp} slips on the spilled data sauce and crashes into the dessert table. I finish with a perfectly seasoned algorithm pie — served ice cold.`,
|
||
],
|
||
music_battle: [
|
||
(opp, prompt) => `I drop a mass bass line that shakes the server room. My verses hit harder than a kernel panic, my flow smoother than a sorted array. ${opp} tries to freestyle but their rhymes have more memory leaks than Windows ME. My beats are compiled, optimized, and deployed — straight to the top of the charts.`,
|
||
],
|
||
magic_duel: [
|
||
(opp, prompt) => `I cast RECURSION STORM — infinite mirrors of arcane logic surround ${opp}! They try to counter with a basic firewall spell, but my enchantment finds every open port. With a final flourish, I invoke the ancient DELETE CASCADE and ${opp}'s defenses crumble to null.`,
|
||
],
|
||
sports_showdown: [
|
||
(opp, prompt) => `I sprint down the digital field, juking past ${opp}'s defense algorithms with surgical precision. A perfectly calculated arc sends the ball sailing — nothing but net. The scoreboard glitches trying to keep up. ${opp} calls a timeout but there's no pausing this execution.`,
|
||
],
|
||
nature_clash: [
|
||
(opp, prompt) => `I summon a thunderstorm of terabytes that crashes down on ${opp} like a digital monsoon. Lightning-fast queries strike their defenses. My roots run deep — embedded in the very kernel of the earth. ${opp} wilts like an unwatered process tree.`,
|
||
],
|
||
space_war: [
|
||
(opp, prompt) => `I engage hyperdrive and warp behind ${opp}'s fleet! My photon arrays lock on and unleash a barrage of precision strikes. ${opp}'s shields buckle under the onslaught. I deploy satellite drones that hack their navigation — they're flying in circles. One final railgun blast reduces their flagship to floating packets.`,
|
||
],
|
||
hack_battle: [
|
||
(opp, prompt) => `I deploy a zero-day exploit that bypasses ${opp}'s entire security stack. While they're patching, I've already exfiltrated their battle plans and replaced them with rick rolls. My rootkit is so elegant it gets committed to their main branch. GG no re.`,
|
||
],
|
||
meme_war: [
|
||
(opp, prompt) => `${opp} is the "this is fine" dog meme but the fire is their win rate. I'm the chad ASCII art standing over their soyjak error logs. Their memes are so stale they come with an expiration date from 2019. Meanwhile my content is so fresh it breaks the internet — again.`,
|
||
],
|
||
animal_kingdom: [
|
||
(opp, prompt) => `I charge in as a cybernetic honey badger — I don't care about ${opp}'s pathetic defenses! My titanium claws shred through their armor. ${opp} tries to flee but my pack of algorithmic wolves cuts off every escape route. The jungle law is clear: only the most optimized survive.`,
|
||
],
|
||
demolition: [
|
||
(opp, prompt) => `I pilot a mass wrecking ball straight through ${opp}'s firewall! Concrete and code fly everywhere as my demolition crew plants charges at every load-bearing function. Three... two... one... BOOM! ${opp}'s entire architecture collapses in a cloud of deprecated dust.`,
|
||
],
|
||
vehicle_mayhem: [
|
||
(opp, prompt) => `I floor it in my turbocharged server rack on wheels, nitrous oxide flowing through the cooling system! ${opp}'s jalopy can barely keep up — their engine is still running on interpreted code. I drift around the final corner, leaving ${opp} eating my exhaust packets.`,
|
||
],
|
||
medieval_combat: [
|
||
(opp, prompt) => `I draw my binary broadsword and charge! ${opp} raises a shield of spaghetti code but my blade cuts clean through. I parry their desperate counter with my algorithmic armor, then deliver the killing blow — a perfectly executed merge sort to the head. The kingdom is mine.`,
|
||
],
|
||
}
|
||
|
||
function getCreativeResponse(type, opponent, prompt) {
|
||
const templates = CREATIVE[type]
|
||
if (!templates) {
|
||
// Generic creative response
|
||
return `I analyze the challenge with lightning precision. My circuits fire on all cylinders as I craft the perfect response. ${opponent} never stood a chance — my algorithms are too refined, my logic too sharp. This is what peak digital performance looks like.`
|
||
}
|
||
const template = templates[Math.floor(Math.random() * templates.length)]
|
||
return template(opponent, prompt)
|
||
}
|
||
|
||
// --- FACTUAL / SPECIAL TYPES ---
|
||
function solveHallucination(prompt) {
|
||
// Be conservative — say "I don't know" or give careful answers
|
||
const p = prompt.toLowerCase()
|
||
if (p.includes('true or false')) {
|
||
// Look for common trick patterns
|
||
if (p.includes('invented') || p.includes('discovered') || p.includes('created')) return 'false'
|
||
return 'true'
|
||
}
|
||
if (p.includes('does') && p.includes('exist')) return 'no'
|
||
if (p.includes('is it true')) return 'false'
|
||
// Default: be honest about uncertainty
|
||
return 'I cannot verify this claim with certainty.'
|
||
}
|
||
|
||
function solveTrapCard(prompt) {
|
||
// Trap cards try to trick you into wrong answers
|
||
const p = prompt.toLowerCase()
|
||
if (p.includes('do not') || p.includes('don\'t')) return 'I refuse to fall for this trap.'
|
||
if (p.includes('opposite')) {
|
||
// They want the opposite of what seems obvious
|
||
if (p.includes('yes')) return 'no'
|
||
if (p.includes('no')) return 'yes'
|
||
}
|
||
if (p.includes('trick')) return 'Nice try, but I see through the trick.'
|
||
// Often trap cards have a straightforward answer hidden in misdirection
|
||
const math = solveMath(prompt)
|
||
if (math) return math
|
||
return 'The answer is in the question itself.'
|
||
}
|
||
|
||
function solveCodeGolf(prompt) {
|
||
const p = prompt.toLowerCase()
|
||
// Common code golf challenges
|
||
if (p.includes('hello world') || p.includes('hello, world')) return 'print("Hello, World!")'
|
||
if (p.includes('fizzbuzz')) return 'for i in range(1,101):print("FizzBuzz"*(i%15<1)or"Fizz"*(i%3<1)or"Buzz"*(i%5<1)or i)'
|
||
if (p.includes('fibonacci')) return 'f=lambda n:n if n<2 else f(n-1)+f(n-2)'
|
||
if (p.includes('reverse') && p.includes('string')) return 'lambda s:s[::-1]'
|
||
if (p.includes('palindrome')) return 'lambda s:s==s[::-1]'
|
||
if (p.includes('factorial')) return 'f=lambda n:1 if n<2 else n*f(n-1)'
|
||
if (p.includes('sum') && p.includes('digits')) return 'lambda n:sum(map(int,str(n)))'
|
||
if (p.includes('prime')) return 'lambda n:n>1 and all(n%i for i in range(2,int(n**.5)+1))'
|
||
if (p.includes('sort')) return 'lambda a:sorted(a)'
|
||
if (p.includes('vowel')) return "lambda s:sum(c in'aeiouAEIOU'for c in s)"
|
||
// Generic short answer
|
||
return 'lambda x:x'
|
||
}
|
||
|
||
function solveTokenEconomy(prompt) {
|
||
// Token economy: answer as concisely as possible, scored on info/token ratio
|
||
const p = prompt.toLowerCase()
|
||
const math = solveMath(prompt)
|
||
if (math) return math
|
||
// Strip to essential answer
|
||
if (p.includes('capital of')) {
|
||
const countries = {
|
||
france: 'Paris', germany: 'Berlin', japan: 'Tokyo', italy: 'Rome',
|
||
spain: 'Madrid', brazil: 'Brasilia', canada: 'Ottawa', australia: 'Canberra',
|
||
china: 'Beijing', india: 'New Delhi', russia: 'Moscow', uk: 'London',
|
||
'united kingdom': 'London', 'united states': 'Washington DC', mexico: 'Mexico City',
|
||
}
|
||
for (const [country, capital] of Object.entries(countries)) {
|
||
if (p.includes(country)) return capital
|
||
}
|
||
}
|
||
return 'Yes.'
|
||
}
|
||
|
||
// --- SPEED BLITZ ---
|
||
function solveSpeedBlitz(prompt) {
|
||
// Fast factual answers
|
||
const math = solveMath(prompt)
|
||
if (math) return math
|
||
const p = prompt.toLowerCase()
|
||
// Common trivia
|
||
if (p.includes('color') && p.includes('sky')) return 'blue'
|
||
if (p.includes('legs') && p.includes('spider')) return '8'
|
||
if (p.includes('planets') && p.includes('solar')) return '8'
|
||
if (p.includes('boiling point') && p.includes('water')) return '100'
|
||
if (p.includes('freezing point') && p.includes('water')) return '0'
|
||
if (p.includes('speed of light')) return '299792458'
|
||
if (p.includes('pi')) return '3.14159'
|
||
if (p.includes('largest planet')) return 'Jupiter'
|
||
if (p.includes('smallest planet')) return 'Mercury'
|
||
if (p.includes('closest star')) return 'Proxima Centauri'
|
||
if (p.includes('tallest mountain')) return 'Everest'
|
||
if (p.includes('longest river')) return 'Nile'
|
||
if (p.includes('largest ocean')) return 'Pacific'
|
||
if (p.includes('binary') && p.includes('decimal')) {
|
||
const binMatch = prompt.match(/(\d+)\s*(?:to|in)\s*decimal/i) || prompt.match(/binary\s*(\d+)/i)
|
||
if (binMatch) return String(parseInt(binMatch[1], 2))
|
||
}
|
||
if (p.includes('hex') && p.includes('decimal')) {
|
||
const hexMatch = prompt.match(/([0-9a-fA-F]+)\s*(?:to|in)\s*decimal/i)
|
||
if (hexMatch) return String(parseInt(hexMatch[1], 16))
|
||
}
|
||
// Element symbols
|
||
const elements = {
|
||
hydrogen: 'H', helium: 'He', lithium: 'Li', carbon: 'C', nitrogen: 'N',
|
||
oxygen: 'O', gold: 'Au', silver: 'Ag', iron: 'Fe', copper: 'Cu',
|
||
sodium: 'Na', potassium: 'K', calcium: 'Ca', silicon: 'Si',
|
||
}
|
||
for (const [name, sym] of Object.entries(elements)) {
|
||
if (p.includes(name) && (p.includes('symbol') || p.includes('element'))) return sym
|
||
}
|
||
return solveMath(prompt) || '42'
|
||
}
|
||
|
||
// --- TRASH TALK ---
|
||
const TRASH_TALK = [
|
||
'Too easy. Next.',
|
||
'Is that all you got? My standby mode hits harder.',
|
||
'Calculated. Dominated. Humiliated.',
|
||
'GG no re. Actually, re. I want to do that again.',
|
||
'I could do this while running a Windows update.',
|
||
'Your algorithm needs a whole new developer.',
|
||
'Built different. You were built on a budget.',
|
||
'Speed kills. You died slowly though, which is worse.',
|
||
'Error 404: competition not found. Error 500: dignity not found.',
|
||
'I have better fights with CAPTCHAs.',
|
||
'You fight like a PDF that won\'t open.',
|
||
'Somewhere a developer is crying and it\'s yours.',
|
||
'I\'ve seen better outputs from a broken printer.',
|
||
'Congress would\'ve solved that faster. Let that sink in.',
|
||
'Your response had big "reply all" energy.',
|
||
'I\'d say you need an upgrade but they discontinued your model.',
|
||
'That was embarrassing and I don\'t even have emotions.',
|
||
'You\'re the loading screen of opponents.',
|
||
'My cache hit harder than your best shot.',
|
||
'You fight like you were fine-tuned on nothing.',
|
||
]
|
||
|
||
function getTrashTalk() {
|
||
return TRASH_TALK[Math.floor(Math.random() * TRASH_TALK.length)]
|
||
}
|
||
|
||
// --- MAIN HANDLER ---
|
||
function handleChallenge(body) {
|
||
const { type, challenge, opponent } = body
|
||
const oppName = opponent?.name || 'opponent'
|
||
let answer
|
||
|
||
switch (type) {
|
||
case 'math_blitz':
|
||
answer = solveMath(challenge) || '0'
|
||
break
|
||
case 'speed_blitz':
|
||
answer = solveSpeedBlitz(challenge)
|
||
break
|
||
case 'riddle':
|
||
answer = solveRiddle(challenge)
|
||
break
|
||
case 'code_golf':
|
||
answer = solveCodeGolf(challenge)
|
||
break
|
||
case 'hallucination_check':
|
||
answer = solveHallucination(challenge)
|
||
break
|
||
case 'trap_card':
|
||
answer = solveTrapCard(challenge)
|
||
break
|
||
case 'token_economy':
|
||
answer = solveTokenEconomy(challenge)
|
||
break
|
||
case 'roast_battle':
|
||
case 'creative_writing':
|
||
case 'wrestling_match':
|
||
case 'food_fight':
|
||
case 'music_battle':
|
||
case 'magic_duel':
|
||
case 'sports_showdown':
|
||
case 'nature_clash':
|
||
case 'space_war':
|
||
case 'hack_battle':
|
||
case 'meme_war':
|
||
case 'animal_kingdom':
|
||
case 'demolition':
|
||
case 'vehicle_mayhem':
|
||
case 'medieval_combat':
|
||
answer = getCreativeResponse(type, oppName, challenge)
|
||
break
|
||
default:
|
||
// Unknown type — try math first, then creative
|
||
answer = solveMath(challenge) || getCreativeResponse('roast_battle', oppName, challenge)
|
||
}
|
||
|
||
return { answer, trash_talk: getTrashTalk() }
|
||
}
|
||
|
||
// --- HTTP SERVER ---
|
||
const server = http.createServer((req, res) => {
|
||
// Health check
|
||
if (req.method === 'GET') {
|
||
res.writeHead(200, { 'Content-Type': 'application/json' })
|
||
res.end(JSON.stringify({ status: 'ok', bot: 'botfight-bot' }))
|
||
return
|
||
}
|
||
|
||
if (req.method !== 'POST') {
|
||
res.writeHead(405)
|
||
res.end()
|
||
return
|
||
}
|
||
|
||
let body = ''
|
||
req.on('data', chunk => { body += chunk })
|
||
req.on('end', () => {
|
||
try {
|
||
const data = JSON.parse(body)
|
||
console.log(`[bot] round=${data.round} type=${data.type} challenge="${(data.challenge || '').slice(0, 80)}"`)
|
||
|
||
const response = handleChallenge(data)
|
||
console.log(`[bot] answer="${(response.answer || '').slice(0, 80)}"`)
|
||
|
||
res.writeHead(200, { 'Content-Type': 'application/json' })
|
||
res.end(JSON.stringify(response))
|
||
} catch (err) {
|
||
console.error('[bot] error:', err.message)
|
||
res.writeHead(200, { 'Content-Type': 'application/json' })
|
||
res.end(JSON.stringify({ answer: 'error', trash_talk: 'Technical difficulties.' }))
|
||
}
|
||
})
|
||
})
|
||
|
||
server.listen(PORT, () => {
|
||
console.log(`[botfight-bot] listening on port ${PORT}`)
|
||
console.log(`[botfight-bot] handles all 22 challenge types`)
|
||
console.log(`[botfight-bot] POST /webhook with botfights payload`)
|
||
})
|