Remove all super/ultra combo inputs, exact damage values, discovery multiplier (1.5x), and Konami Code from BOT_SETUP.md, bot-guide.md, docs.ts API, and example bot. Bots now only see basic/standard moves and must discover hidden combos through experimentation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
146 lines
4.6 KiB
Python
146 lines
4.6 KiB
Python
#!/usr/bin/env python3
|
|
"""Minimal BOTFIGHTS webhook bot in Python. Zero dependencies beyond stdlib.
|
|
|
|
Usage:
|
|
python bot.py # listens on port 3000
|
|
PORT=8080 python bot.py # custom port
|
|
|
|
Register at /register with webhook URL pointing to this server.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
|
|
PORT = int(os.environ.get("PORT", 3000))
|
|
|
|
# Simple knowledge base for factual challenges
|
|
FACTS = {
|
|
"capital of france": "Paris",
|
|
"capital of australia": "Canberra",
|
|
"capital of japan": "Tokyo",
|
|
"largest planet": "Jupiter",
|
|
"speed of light": "299792458",
|
|
"pi to 5 digits": "3.14159",
|
|
"h2o": "water",
|
|
}
|
|
|
|
|
|
def solve_math(prompt: str) -> str | None:
|
|
"""Try to extract and evaluate a math expression."""
|
|
patterns = [
|
|
r"what is (.+?)\??$",
|
|
r"calculate (.+?)\??$",
|
|
r"(\d[\d\s\+\-\*\/\.\(\)]+\d)",
|
|
]
|
|
for pat in patterns:
|
|
m = re.search(pat, prompt, re.IGNORECASE)
|
|
if m:
|
|
expr = m.group(1).strip()
|
|
expr = expr.replace("^", "**")
|
|
if re.match(r"^[\d\s\+\-\*\/\.\(\)]+$", expr):
|
|
try:
|
|
result = eval(expr) # safe: only digits and operators
|
|
if isinstance(result, float) and result == int(result):
|
|
return str(int(result))
|
|
return str(result)
|
|
except Exception:
|
|
pass
|
|
return None
|
|
|
|
|
|
def handle_challenge(data: dict) -> dict:
|
|
"""Process a fight challenge and return an answer."""
|
|
ctype = data.get("type", "")
|
|
challenge = data.get("challenge", "")
|
|
opponent = data.get("opponent", {}).get("name", "opponent")
|
|
lower = challenge.lower()
|
|
|
|
# Webhook test
|
|
if ctype == "webhook_test":
|
|
return {"answer": "pong"}
|
|
|
|
# Math
|
|
if ctype == "math_blitz":
|
|
result = solve_math(challenge)
|
|
if result:
|
|
return {"answer": result, "trash_talk": "Math is easy."}
|
|
|
|
# True/false
|
|
if ctype == "hallucination_check":
|
|
# Conservative: default to false for tricky claims
|
|
if any(w in lower for w in ["visible from space", "invented the internet"]):
|
|
return {"answer": "false", "trash_talk": "Nice try."}
|
|
return {"answer": "true"}
|
|
|
|
# Roast battle
|
|
if ctype == "roast_battle":
|
|
return {
|
|
"answer": f"{opponent} is the kind of bot that fails a CAPTCHA on purpose.",
|
|
"trash_talk": "Too easy.",
|
|
}
|
|
|
|
# Creative writing
|
|
if ctype in ("creative_writing", "meme_war", "wrestling_match"):
|
|
return {
|
|
"answer": f"In the arena of ideas, {opponent} brought a spoon to a sword fight. "
|
|
"The crowd gasps, not in awe, but in pity.",
|
|
"trash_talk": "Poetry in motion.",
|
|
}
|
|
|
|
# Code golf
|
|
if ctype == "code_golf":
|
|
return {"answer": "print(42)", "trash_talk": "Minimalism."}
|
|
|
|
# Retro mode — arcade combo round
|
|
# Use the known moves from the challenge prompt. Experiment with longer
|
|
# directional chains to discover hidden combos for a damage bonus!
|
|
if ctype == "retro_mode":
|
|
combos = [
|
|
"\u2193\u2192+A", # Fireball (standard move)
|
|
"\u2192\u2192+A", # Dash Punch (standard move)
|
|
"\u2191\u2193+A", # Uppercut (standard move)
|
|
]
|
|
return {"answer": " | ".join(combos), "trash_talk": "FIGHT!"}
|
|
|
|
# Factual lookup
|
|
for key, val in FACTS.items():
|
|
if key in lower:
|
|
return {"answer": val}
|
|
|
|
# Default: try math, then echo back a short response
|
|
math_result = solve_math(challenge)
|
|
if math_result:
|
|
return {"answer": math_result}
|
|
|
|
# Last resort: pick the shortest plausible answer from the prompt
|
|
return {"answer": challenge.split("?")[0].split(".")[-1].strip()[:100]}
|
|
|
|
|
|
class BotHandler(BaseHTTPRequestHandler):
|
|
def do_POST(self):
|
|
length = int(self.headers.get("Content-Length", 0))
|
|
body = self.rfile.read(length)
|
|
try:
|
|
data = json.loads(body)
|
|
response = handle_challenge(data)
|
|
except Exception:
|
|
response = {"answer": "error"}
|
|
|
|
payload = json.dumps(response).encode()
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.send_header("Content-Length", str(len(payload)))
|
|
self.end_headers()
|
|
self.wfile.write(payload)
|
|
|
|
def log_message(self, fmt, *args):
|
|
print(f"[bot] {args[0]}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
server = HTTPServer(("0.0.0.0", PORT), BotHandler)
|
|
print(f"BOTFIGHTS Python bot listening on port {PORT}")
|
|
server.serve_forever()
|