mirror of https://github.com/dirtbags/moth.git
Golang port is now MOTHv3
This commit is contained in:
parent
11939e7ce6
commit
fbb0a9b0ba
215
docs/api.md
215
docs/api.md
|
@ -1,215 +0,0 @@
|
||||||
MOTHv3 API
|
|
||||||
==========
|
|
||||||
|
|
||||||
MOTH, by design, uses a small number of API endpoints.
|
|
||||||
|
|
||||||
Whenever possible,
|
|
||||||
we decided to push complexity into the client,
|
|
||||||
keeping the server as simple as we could make it.
|
|
||||||
After all,
|
|
||||||
this is a hacking contest.
|
|
||||||
If a participant finds a vulnerability in code running on their own machine,
|
|
||||||
the people running the server don't care.
|
|
||||||
|
|
||||||
Specification
|
|
||||||
=============
|
|
||||||
|
|
||||||
You make requests as HTTP GET query arguments:
|
|
||||||
|
|
||||||
https://server/path/elements/api/v3/endpoint?var1=val1&var2=val2
|
|
||||||
|
|
||||||
The server returns a
|
|
||||||
[JSend](https://labs.omniti.com/labs/jsend) response:
|
|
||||||
|
|
||||||
{
|
|
||||||
"status": "success",
|
|
||||||
"data": "Any JS data type here"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Client State
|
|
||||||
============
|
|
||||||
|
|
||||||
The client (or user interacting with the client) needs to remember only one thing:
|
|
||||||
|
|
||||||
* teamId: the team ID used to register
|
|
||||||
|
|
||||||
A naive client,
|
|
||||||
like the one we used from 2009-2018,
|
|
||||||
can ask the user to type in the team ID for every submission.
|
|
||||||
This is fine.
|
|
||||||
|
|
||||||
|
|
||||||
Endpoints
|
|
||||||
=========
|
|
||||||
|
|
||||||
RegisterTeam(teamId, teamName)
|
|
||||||
-------------------------------
|
|
||||||
|
|
||||||
Register a team name with a team hash.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
* teamId: Team's unique identifier (usually a hex value)
|
|
||||||
* teamName: Team's human-readable name
|
|
||||||
|
|
||||||
On success, no data is returned.
|
|
||||||
On failure, message contains an English explanation of why.
|
|
||||||
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
https://server/api/v3/RegisterTeam?teamId=8b1292ca&teamName=Lexical+Pedants
|
|
||||||
|
|
||||||
{
|
|
||||||
"status": "success",
|
|
||||||
"data": null
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
GetState()
|
|
||||||
----------
|
|
||||||
|
|
||||||
Return all current state of the puzzle server.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
None
|
|
||||||
|
|
||||||
|
|
||||||
### Return data
|
|
||||||
|
|
||||||
* puzzles: dictionary mapping from category to one of the following:
|
|
||||||
* list of point values currently open
|
|
||||||
* URL to puzzle root (intended for token-based puzzles)
|
|
||||||
* teams: mapping from anonymized team ID to team name
|
|
||||||
* log: list of (timestamp, team number, category, points)
|
|
||||||
* notices: list of HTML broadcast notices to display to the user
|
|
||||||
* now: current server time (unix epoch)
|
|
||||||
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
https://server/api/v3/GetState
|
|
||||||
|
|
||||||
{
|
|
||||||
"status": "success",
|
|
||||||
"data": {
|
|
||||||
"puzzles": {
|
|
||||||
"sequence": [1, 2],
|
|
||||||
"codebreaking": [10],
|
|
||||||
"wopr": "https://appspot.com/dooted-bagel-8372/entry"
|
|
||||||
},
|
|
||||||
"teams": {
|
|
||||||
"0": "Zelda",
|
|
||||||
"1": "Defender"
|
|
||||||
},
|
|
||||||
"log": [
|
|
||||||
[1526478368, "0", "sequence", 1],
|
|
||||||
[1526478524, "1", "sequence", 1],
|
|
||||||
[1526478536, "0", "nocode", 1]
|
|
||||||
],
|
|
||||||
"notices": [
|
|
||||||
"<a href=\"https://appspot.com/dooted-bagel-8372/entry\">WOPR category</a> is now open",
|
|
||||||
"Event closes at 18:00 today, and will resume tomorrow at 08:00"
|
|
||||||
],
|
|
||||||
"now": 1527170088
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
GetPuzzle(category, points)
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
Return a puzzle.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
* category: name of category to fetch from
|
|
||||||
* points: point value of the puzzle to fetch
|
|
||||||
|
|
||||||
|
|
||||||
### Return data
|
|
||||||
|
|
||||||
* authors: List of puzzle authors
|
|
||||||
* hashes: list of djbhash values of acceptable answers
|
|
||||||
* files: dictionary of puzzle-associated filenames and their URLs
|
|
||||||
* body: HTML body of the puzzle
|
|
||||||
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
https://server/api/v3/GetPuzzle?category=sequence&points=1
|
|
||||||
|
|
||||||
{
|
|
||||||
"status": "success",
|
|
||||||
"data": {
|
|
||||||
"authors": ["neale"],
|
|
||||||
"hashes": [177627],
|
|
||||||
"files": {
|
|
||||||
"happy.png": "https://cdn/assets/0904cf3a437a348bea2c49d56a3087c26a01a63c.png"
|
|
||||||
},
|
|
||||||
"body": "<pre><code>1 2 3 4 5 _\n</code></pre>\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SubmitAnswer(teamId, category, points, answer)
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
Submit an answer to a puzzle.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
* teamId: Team ID (optional: if ommitted, answer is verified but no points are awarded)
|
|
||||||
* category: category name of puzzle
|
|
||||||
* points: point value of puzzle
|
|
||||||
* answer: attempted answer
|
|
||||||
|
|
||||||
|
|
||||||
### Return Data
|
|
||||||
|
|
||||||
* epilog: HTML to display upon successfully answering the puzzle
|
|
||||||
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
https://server/api/v3/SubmitAnswer?teamId=8b1292ca&category=sequence&points=1&answer=6
|
|
||||||
|
|
||||||
{
|
|
||||||
"status": "success",
|
|
||||||
"data": {
|
|
||||||
"epilog": "That's right: in base 10, 5 + 1 = 6."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SubmitToken(teamId, token)
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Submit a token for points
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
* teamId: Team ID
|
|
||||||
* token: Token being submitted
|
|
||||||
|
|
||||||
|
|
||||||
### Return data
|
|
||||||
|
|
||||||
* category: category for which this token awarded points
|
|
||||||
* points: number of points awarded
|
|
||||||
* epilog: HTML to display upon successfully answering the puzzle
|
|
||||||
|
|
||||||
|
|
||||||
### Example
|
|
||||||
|
|
||||||
https://server/api/v3/SubmitToken?teamId=8b1292ca&token=wat:30:xylep-radar-nanox
|
|
||||||
|
|
||||||
{
|
|
||||||
"status": "success",
|
|
||||||
"data": {
|
|
||||||
"category": "wat",
|
|
||||||
"points": 30,
|
|
||||||
"epilog": ""
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,135 +0,0 @@
|
||||||
#! /usr/bin/python3
|
|
||||||
|
|
||||||
from aiohttp import web
|
|
||||||
import time
|
|
||||||
|
|
||||||
async def fake_register(request):
|
|
||||||
teamId = request.query.get("teamId")
|
|
||||||
teamName = request.query.get("teamName")
|
|
||||||
if teamId == "ffff" and teamName == "dirtbags":
|
|
||||||
resp = {
|
|
||||||
"status": "success",
|
|
||||||
"data": None,
|
|
||||||
}
|
|
||||||
elif teamId and teamName:
|
|
||||||
resp = {
|
|
||||||
"status": "error",
|
|
||||||
"message": "Query was correctly formed but I'm feeling cranky"
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
resp = {
|
|
||||||
"status": "fail",
|
|
||||||
"data": "You must send teamId and teamName",
|
|
||||||
}
|
|
||||||
return web.json_response(resp)
|
|
||||||
|
|
||||||
async def fake_state(request):
|
|
||||||
resp = {
|
|
||||||
"status": "success",
|
|
||||||
"data": {
|
|
||||||
"puzzles": {
|
|
||||||
"sequence": [1, 2],
|
|
||||||
"codebreaking": [10],
|
|
||||||
"wopr": "https://appspot.com/dooted-bagel-8372/entry"
|
|
||||||
},
|
|
||||||
"teams": {
|
|
||||||
"0": "Zelda",
|
|
||||||
"1": "Defender"
|
|
||||||
},
|
|
||||||
"log": [
|
|
||||||
[1526478368, "0", "sequence", 1],
|
|
||||||
[1526478524, "1", "sequence", 1],
|
|
||||||
[1526478536, "0", "nocode", 1]
|
|
||||||
],
|
|
||||||
"notices": [
|
|
||||||
"<a href=\"https://appspot.com/dooted-bagel-8372/entry\">WOPR category</a> is now open",
|
|
||||||
"Event closes at 18:00 today, and will resume tomorrow at 08:00"
|
|
||||||
],
|
|
||||||
"now": int(time.time()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return web.json_response(resp)
|
|
||||||
|
|
||||||
async def fake_getpuzzle(request):
|
|
||||||
category = request.query.get("category")
|
|
||||||
points = request.query.get("points")
|
|
||||||
if category == "sequence" and points == "1":
|
|
||||||
resp = {
|
|
||||||
"status": "success",
|
|
||||||
"data": {
|
|
||||||
"authors": ["neale"],
|
|
||||||
"hashes": [177627],
|
|
||||||
"files": {
|
|
||||||
"happy.png": "https://cdn/assets/0904cf3a437a348bea2c49d56a3087c26a01a63c.png"
|
|
||||||
},
|
|
||||||
"body": "<pre><code>1 2 3 4 5 _\n</code></pre>\n",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elif category and points:
|
|
||||||
resp = {
|
|
||||||
"status": "error",
|
|
||||||
"message": "Query was correctly formed but I'm feeling cranky"
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
resp = {
|
|
||||||
"status": "fail",
|
|
||||||
"data": "You must send category and points"
|
|
||||||
}
|
|
||||||
return web.json_response(resp)
|
|
||||||
|
|
||||||
async def fake_submitanswer(request):
|
|
||||||
teamId = request.query.get("teamId")
|
|
||||||
category = request.query.get("category")
|
|
||||||
points = request.query.get("points")
|
|
||||||
answer = request.query.get("answer")
|
|
||||||
if category == "sequence" and points == "1" and answer == "6":
|
|
||||||
resp = {
|
|
||||||
"status": "success",
|
|
||||||
"data": {
|
|
||||||
"epilog": "Now you know the answer, and knowing is half the battle. Go Joe!"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elif category and points and answer:
|
|
||||||
resp = {
|
|
||||||
"status": "error",
|
|
||||||
"message": "Query was correctly formed but I'm feeling cranky"
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
resp = {
|
|
||||||
"status": "fail",
|
|
||||||
"data": "You must send category and points"
|
|
||||||
}
|
|
||||||
return web.json_response(resp)
|
|
||||||
|
|
||||||
async def fake_submittoken(request):
|
|
||||||
teamId = request.query.get("teamId")
|
|
||||||
token = request.query.get("token")
|
|
||||||
if token == "wat:30:xylep-radar-nanox":
|
|
||||||
resp = {
|
|
||||||
"status": "success",
|
|
||||||
"data": {
|
|
||||||
"category": "wat",
|
|
||||||
"points": 30,
|
|
||||||
"epilog": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elif category and points and answer:
|
|
||||||
resp = {
|
|
||||||
"status": "error",
|
|
||||||
"message": "Query was correctly formed but I'm feeling cranky"
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
resp = {
|
|
||||||
"status": "fail",
|
|
||||||
"data": "You must send category and points"
|
|
||||||
}
|
|
||||||
return web.json_response(resp)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app = web.Application()
|
|
||||||
app.router.add_route("GET", "/api/v3/RegisterTeam", fake_register)
|
|
||||||
app.router.add_route("GET", "/api/v3/GetState", fake_state)
|
|
||||||
app.router.add_route("GET", "/api/v3/GetPuzzle", fake_getpuzzle)
|
|
||||||
app.router.add_route("GET", "/api/v3/SubmitAnswer", fake_submitanswer)
|
|
||||||
app.router.add_route("GET", "/api/v3/SubmitToken", fake_submittoken)
|
|
||||||
web.run_app(app)
|
|
Loading…
Reference in New Issue