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
@@ -416,12 +416,12 @@ Integrated into the fight viewer:
|
|||||||
- [x] API docs page
|
- [x] API docs page
|
||||||
|
|
||||||
### Phase 4: Go Live
|
### Phase 4: Go Live
|
||||||
- [ ] Docker containerization
|
- [x] Docker containerization
|
||||||
- [ ] Anonymous VPS deployment
|
- [ ] Anonymous VPS deployment
|
||||||
- [ ] Tor hidden service (optional)
|
- [ ] Tor hidden service (optional)
|
||||||
- [ ] Rate limiting + abuse prevention
|
- [x] Rate limiting + abuse prevention
|
||||||
- [ ] Example bot implementations (at least 2)
|
- [x] Example bot implementations (at least 2)
|
||||||
- [ ] "How to Build a Fighter" tutorial
|
- [x] "How to Build a Fighter" tutorial
|
||||||
|
|
||||||
### Phase 5: Betting (Future)
|
### Phase 5: Betting (Future)
|
||||||
- [ ] Cashu mint integration
|
- [ ] Cashu mint integration
|
||||||
|
|||||||
@@ -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