feat: Python example bot + mark completed PLAN items
Add minimal Python webhook bot (zero deps, stdlib only). Mark Docker, rate limiting, example bots, tutorial as done in PLAN.md — all were already implemented. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
530f46f9cf
commit
725bf290a8
@@ -0,0 +1,134 @@
|
||||
#!/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."}
|
||||
|
||||
# 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()
|
||||
Reference in New Issue
Block a user