Neale Pickett
·
2023-09-29
api.md
1Moth APIs
2=======
3
4This document covers the following interfaces:
5
6* HTTP Endpoints: what the Moth client sends the Moth server
7* Puzzle executable: how the transpiler communicates with executables that provide puzzles
8* Category executable: how the transpiler communicates with executables that provide categories
9* Provider executable: how Moth communicates with things that provide puzzles (like the transpiler)
10
11The Puzzle, Category, and Provider executalbes are all very closely related, since each is a subset of the next.
12
13----
14
15Here's a bad diagram of how this all fits together. I don't know if this is going to help at all. Please submit a merge request with something better.
16
17 HTTP provider API mothball API
18 🡗 🡗 🡗
19 client - mothd - mothball provider - category1.mb
20
21 - custom provider
22 category API
23 🡗
24 - internal transpiler - category2/mkcategory
25
26 - category3/1/puzzle.md
27 - category3/2/mkpuzzle
28 🡔
29 puzzle API
30
31
32
33# HTTP Endpoints
34
35The Moth server accepts
36standard HTTP `GET` and `POST`.
37
38Parameters may be encoded with standard `GET` query parameters
39(like `GET /endpoint?a=1&b=2`),
40or with `POST` as `application/x-www-form-encoded` data.
41
42## `/state`
43
44Returns the current Moth event state as a JSON object.
45
46### Parameters
47* `id`: team ID (optional)
48
49### Return
50
51```js
52{
53 "Config": {
54 "Devel": false // true means this is a development server
55 },
56 "TeamNames": {
57 "self": "Requesting team name", // Only if regestered team id is a provided
58 "0": "Team 1 Name",
59 "1": "Team 2 Name"
60 // ...
61 },
62 "PointsLog": [
63 [1602679698, "0", "category", 1] // epochTime, teamID, category, points
64 // ...
65 ],
66 "Puzzles": {
67 "category": [1, 2, 3, 6] // list of unlocked puzzles for category
68 // ...
69 }
70}
71```
72
73### Example HTTP transaction
74
75#### Request
76
77```
78GET /state HTTP/1.0
79
80```
81
82#### Response
83
84This response has been reflowed for readability:
85an actual on-wire response would not have newlines or indentation.
86
87```
88HTTP/1.0 200 OK
89Content-Type: application/json
90
91{"Config":
92 {"Devel":false},
93 "TeamNames":{
94 "0":"Mike and Jack",
95 "12":"Team 2",
96 "4":"Team 8"
97 },
98 "PointsLog":[
99 [1602702696,"0","nocode",1],
100 [1602702705,"0","sequence",1],
101 [1602702787,"0","nocode",2],
102 [1602702831,"0","sequence",2],
103 [1602702839,"4","nocode",3],
104 [1602702896,"0","sequence",8],
105 [1602702900,"4","nocode",4],
106 [1602702913,"0","sequence",16]
107 ],
108 "Puzzles":{
109 "indy":[12],
110 "nocode":[1,2,3,4,10],
111 "sequence":[1,2,8,16,19],
112 "steg":[1]
113 }
114}
115```
116
117## `/register`
118
119Registers a name to a team ID.
120
121This is only required once per team,
122but user interfaces may find it less confusing to users
123to present a "login" page.
124For this reason "this team is already registered"
125does not return an error.
126
127### Parameters
128* `id`: team ID
129* `name`: team name
130
131### Return
132
133An object inspired by [JSend](https://github.com/omniti-labs/jsend):
134
135```json
136{
137 "status": "success/fail/error",
138 "data": {
139 "short": "short description",
140 "description": "long description"
141 }
142}
143```
144
145### Example HTTP transaction
146
147#### Request
148
149```
150POST /register HTTP/1.0
151Content-Type: application/x-www-form-urlencoded
152Content-Length: 26
153
154id=b387ca98&name=dirtbags
155```
156
157#### Repsonse
158
159```
160HTTP/1.0 200 OK
161Content-Type: application/json
162Content-Length=86
163
164{"status":"success","data":{"short":"registered","description":"Team ID registered"}}
165```
166
167
168## `/answer`
169
170Submits an answer for points.
171
172If the answer is wrong, no points are awarded 😉
173
174### Parameters
175* `id`: team ID
176* `category`: along with `points`, uniquely identifies a puzzle
177* `points`: along with `category`, uniquely identifies a puzzle
178
179### Return
180
181An object inspired by [JSend](https://github.com/omniti-labs/jsend):
182
183```json
184{
185 "status": "success/fail/error",
186 "data": {
187 "short": "short description",
188 "description": "long description"
189 }
190}
191```
192
193### Example HTTP transaction
194
195#### Request
196
197```
198POST /answer HTTP/1.0
199Content-Type: application/x-www-form-urlencoded
200Content-Length: 62
201
202id=b387ca98&category=sequence&points=2&answer=achilles+turnip
203```
204
205#### Repsonse
206
207```
208HTTP/1.0 200 OK
209Content-Type: application/json
210Content-Length=83
211
212{"status":"fail","data":{"short":"not accepted","description":"Incorrect answer"}}
213```
214
215## `/content/{category}/{points}/puzzle.json`
216
217Retrieves the JSON object describing a puzzle.
218
219Parameters are all in the URL for this endpoint,
220so `curl` and `wget` can be used.
221
222### Parameters
223* `{category}` (in URL): along with `{points}`, uniquely identifies a puzzle
224* `{points}` (in URL): along with `{category}`, uniquely identifies a puzzle
225* `{filename}` (in URL): filename to retrieve
226
227### Return
228
229JSON object describing a puzzle.
230
231#### JSON Puzzle Object
232
233```js
234{
235 "Pre": { // Things which appear before the puzzle is solved
236 "Authors": ["Neale Pickett"], // List of puzzle authors, usually rendered as a footnote
237 "Attachments": ["tiger.jpg"], // List of files attached to the puzzle
238 "Scripts": [], // List of scripts which should be included in the HTML render of the puzzle
239 "Body": "<p>Can you find the hidden text?</p><p><img src=\"tiger.jpg\" alt=\"Grr\" /></p>\n", // HTML puzzle body
240 "AnswerPattern": "", // Regular expression to include in HTML input tag for validation
241 "AnswerHashes": [ // List of SHA265 hashes of correct answers, for client-side answer checking
242 "f91b1fe875cdf9e969e5bccd3e259adec5a987dcafcbc9ca8da62e341a7f29c6"
243 ]
244 },
245 "Post": { // Things reveal after the puzzle is solved
246 "Objective": "Learn to examine images for hidden text", // Learning objective
247 "Success": { // Measures of learning success
248 "Acceptable": "Visually examine image to find hidden text",
249 "Mastery": "Visually examine image to find hidden text"
250 },
251 "KSAs": null // Knowledge, Skills, and Abilities covered by this puzzle
252 },
253 "Debug": { // Debugging output used in development: all fields are emptied when making mothballs
254 "Log": [ // Debug message log
255 "Input image size: 600x400",
256 "Applying gaussian blur",
257 "Text width 58, left offset 513",
258 "Complete in 0.028s"
259 ],
260 "Errors": [], // Errors encountered generating this puzzzle
261 "Hints": [ // Hints for instructional assistants to provide to participants
262 "Zoom in to the image and examine all sections carefully"
263 ],
264 "Summary": "text in image" // Summary of this puzzle, to help identify it in an overview of puzzles
265 },
266 "Answers": ["sandwich"] // List of answers: empty in production
267}
268```
269
270
271### Example HTTP transaction
272
273#### Request
274
275```
276GET /content/sequence/1/puzzle.json HTTP/1.0
277
278```
279
280#### Repsonse
281
282```
283HTTP/1.0 200 OK
284Content-Type: application/json
285Content-Length: 397
286
287{"Pre":{"Authors":["neale"],"Attachments":[],"Scripts":[],"Body":"\u003cp\u003e1 2 3 4 5 ⬜\u003c/p\u003e\n","AnswerPattern":"","AnswerHashes":["e7f6c011776e8db7cd330b54174fd76f7d0216b612387a5ffcfb81e6f0919683"]},"Post":{"Objective":"","Success":{"Acceptable":"","Mastery":""},"KSAs":null},"Debug":{"Log":[],"Errors":[],"Hints":[],"Summary":"Simple introduction to how this works"},"Answers":[]}
288```
289
290
291## `/content/{category}/{points}/{filename}`
292
293Retrieves static content associated with a puzzle.
294
295Parameters are all in the URL for this endpoint,
296so `curl` and `wget` can be used.
297
298### Parameters
299* `{category}` (in URL): along with `{points}`, uniquely identifies a puzzle
300* `{points}` (in URL): along with `{category}`, uniquely identifies a puzzle
301* `{filename}` (in URL): filename to retrieve
302
303### Return
304
305Raw file octets,
306with a (hopefully) suitable
307`Content-type` HTTP header field.
308
309### Example HTTP transaction
310
311#### Request
312
313```
314GET /content/sequence/1/attachment.txt HTTP/1.0
315
316```
317
318#### Repsonse
319
320```
321HTTP/1.0 200 OK
322Content-Type: text/plain
323Content-Length: 98
324
325This is an attachment file! This is just plain text for the example. Many attachments are JPEGs.
326```
327
328
329# Puzzle
330
331A puzzle contains one question and one or more associated answers.
332Puzzles are not aware of their point value: this is set by the category they are in.
333
334Puzzle executables must be named `mkpuzzle`.
335
336
337## `mkpuzzle puzzle`
338
339 puzzles/category3/1 $ ./mkpuzzle puzzle
340 {JSON PUZZLE OBJECT}
341
342Also see [JSON Puzzle Object](#json-puzzle-object)
343
344
345## `mkpuzzle file {filename}`
346
347 puzzles/category3/1 $ ./mkpuzzle file attachment.txt
348 This is an attachment file! It's just plain text for this example. Many attachments are JPEGs.
349
350
351## `mkpuzzle answer {answer}`
352
353 puzzles/category3/1 $ ./mkpuzzle answer "cow goes moo"
354 {"Correct":false}
355
356
357
358# Category
359
360Categories are collections of puzzles.
361Each puzzle has a unique point value, determined by the category.
362
363Category executables must be called `mkcategory`.
364
365## `mkcategory inventory`
366
367 puzzles/category2 $ ./mkcategory inventory
368 {"Puzzles": [1, 2, 3, 5, 10, 20, 30, 50, 100]}
369
370
371## `mkcategory puzzle {points}`
372
373 puzzles/category2 $ ./mkcategory puzzle 1
374 {JSON PUZZLE OBJECT}
375
376Also see [JSON Puzzle Object](#json-puzzle-object)
377
378
379## `mkcategory file {points} {filename}`
380
381 puzzles/category2 $ ./mkcategory file 1 attachment.txt
382 This is an attachment file's contents!
383
384
385## `mkcategory answer {points} {answer}`
386
387 puzzles/category2 $ ./mkcategory answer 1 "cow goes moo"
388 {"Correct":false}
389
390
391
392# Provider API
393
394This is how Claire gets her dynamic graders.
395
396*Notice: this is not complete in the code base!*
397I'm writing here how it *should* work.
398If anybody wants this,
399please let me know,
400and I'll finish the code.
401
402This could ostensibly be expanded to call HTTP servers,
403with the four endpoints described here.
404If somebody were to want such a thing.
405
406## `provider inventory`
407
408 $ provider inventory
409 {
410 "category1": [1, 2, 3, 4, 5, 10, 20, 30],
411 "category2": [20, 40, 70, 150]
412 }
413
414## `provider puzzle {category} {points}`
415
416 $ provider puzzle category1 20
417 {JSON PUZZLE OBJECT}
418
419Also see [JSON Puzzle Object](#json-puzzle-object)
420
421
422## `provider file {category} {points} {filename}`
423
424 $ provider file category1 20 attachment.txt
425 This is an attachment! Yay!
426
427## `provider answer {category} {points} {answer}`
428
429 $ provider answer category1 20 "cow goes moo"
430 {"Correct":true}