mirror of https://github.com/dirtbags/moth.git
Toying around with API ideas
This commit is contained in:
parent
7c5b5b5ccf
commit
ceb0cb0edb
|
@ -1,10 +1,10 @@
|
||||||
*~
|
*~
|
||||||
*#
|
*#
|
||||||
.idea
|
/.idea
|
||||||
/vendor/
|
/vendor/
|
||||||
__debug_bin
|
/__debug_bin
|
||||||
*.tar.gz
|
/winmoth.*.zip
|
||||||
transpile
|
/*.tar.gz
|
||||||
winmoth.*.zip
|
/transpile
|
||||||
/mothd
|
/mothd
|
||||||
/*.exe
|
/*.exe
|
||||||
|
|
|
@ -0,0 +1,163 @@
|
||||||
|
MOTH Client API
|
||||||
|
===========
|
||||||
|
|
||||||
|
MOTH provides a WebDAV interface:
|
||||||
|
this is described in
|
||||||
|
[MOTH Client Directory Structure](client-structure.md).
|
||||||
|
|
||||||
|
This document explains the WebDAV directory structure
|
||||||
|
as though it were a REST API.
|
||||||
|
These endpoints are a subset of the functionality provided,
|
||||||
|
but should be sufficient for many use cases.
|
||||||
|
|
||||||
|
Theme
|
||||||
|
======
|
||||||
|
|
||||||
|
Theme files are served as static content,
|
||||||
|
just like any standard web server.
|
||||||
|
|
||||||
|
### `GET` `/theme/${path}` - Retrieve File
|
||||||
|
|
||||||
|
|
||||||
|
Puzzles
|
||||||
|
======
|
||||||
|
|
||||||
|
Static Files
|
||||||
|
----------
|
||||||
|
|
||||||
|
With the exception of the `answer` file,
|
||||||
|
puzzle files are served as static content.
|
||||||
|
|
||||||
|
The entry point to a puzzle is `index.html`:
|
||||||
|
see [Puzzle Format](puzzle-format.md)
|
||||||
|
for details on its structure.
|
||||||
|
|
||||||
|
### `GET` `/puzzles/${category}/${points}/${filename}` - Retrieve File
|
||||||
|
|
||||||
|
|
||||||
|
Answer Submission
|
||||||
|
------------------
|
||||||
|
|
||||||
|
### `GET` `/puzzles/${category}/${points}/answer` - not supported
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| http code | meaning |
|
||||||
|
| ---- | ---- |
|
||||||
|
| 405 | `GET` method is not supported |
|
||||||
|
|
||||||
|
|
||||||
|
### `POST` `/puzzles/${category}/${points}/answer` - Submit Answer
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| http code | meaning |
|
||||||
|
| ---- | ---- |
|
||||||
|
| 200 | Answer is correct, and points are awarded |
|
||||||
|
| 202 | Answer is correct, but points have already been awarded for this puzzle |
|
||||||
|
| 409 | Answer is incorrect |
|
||||||
|
| 401 | Authentication is invalid (bad team ID) |
|
||||||
|
|
||||||
|
|
||||||
|
State
|
||||||
|
====
|
||||||
|
|
||||||
|
Points Log
|
||||||
|
--------
|
||||||
|
|
||||||
|
The points log contains a history of correct answer submission.
|
||||||
|
Each submission is terminated by a newline (`\n`)
|
||||||
|
and consists of space-separated fields
|
||||||
|
of the format:
|
||||||
|
|
||||||
|
${timestamp} ${team_id} ${category} ${points}
|
||||||
|
|
||||||
|
### `GET` `/state/points.log` - Retrieve points log
|
||||||
|
|
||||||
|
| http code | meaning |
|
||||||
|
| ---- | ---- |
|
||||||
|
| 200 | Points log in payload (text/plain) |
|
||||||
|
| 401 | Authentication is invalid (bad team ID) |
|
||||||
|
|
||||||
|
|
||||||
|
Team Name
|
||||||
|
--------
|
||||||
|
|
||||||
|
### `GET` `/state/self/name` - Retrieve my team name
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| http code | meaning |
|
||||||
|
| ---- | ---- |
|
||||||
|
| 200 | Team ID in payload (text/plain) |
|
||||||
|
| 401 | Authentication is invalid (bad team ID) |
|
||||||
|
|
||||||
|
|
||||||
|
### `POST` `/state/self/name` - Set my team name
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| http code | meaning |
|
||||||
|
| ---- | ---- |
|
||||||
|
| 200 | Team ID is valid, and team name was recorded |
|
||||||
|
| 202 | Team ID is valid, but team name was previously set and cannot be changed |
|
||||||
|
| 401 | Authentication is invalid (bad team ID) |
|
||||||
|
|
||||||
|
|
||||||
|
Public Data
|
||||||
|
--------
|
||||||
|
|
||||||
|
Up to 4096 bytes of arbitrary public data per team may be stored on the server.
|
||||||
|
This data can be viewed by any authenticated team.
|
||||||
|
|
||||||
|
There are no restrictions on the content of the data:
|
||||||
|
clients are free to store whatever they want.
|
||||||
|
|
||||||
|
### `GET` `/state/${id}/public.bin` - Retrieve public data
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| http code | meaning |
|
||||||
|
| ---- | ---- |
|
||||||
|
| 200 | Data follows (application/octet-stream) |
|
||||||
|
| 401 | Authentication is invalid (bad team ID) |
|
||||||
|
|
||||||
|
|
||||||
|
### `PUT` `/state/${id}/public.bin` - Upload public data
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| http code | meaning |
|
||||||
|
| ---- | ---- |
|
||||||
|
| 200 | Data follows (application/octet-stream) |
|
||||||
|
| 401 | Authentication is invalid (bad team ID) |
|
||||||
|
|
||||||
|
|
||||||
|
Private Data
|
||||||
|
--------
|
||||||
|
|
||||||
|
Up to 4096 bytes of arbitrary data per team may be stored on the server.
|
||||||
|
This data is only accessible by an authenticated request,
|
||||||
|
and is private to the authenticated team.
|
||||||
|
|
||||||
|
There are no restrictions on the content of the data:
|
||||||
|
clients are free to store whatever they want.
|
||||||
|
|
||||||
|
### `GET` `/state/self/private.bin` - Retrieve private data
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| http code | meaning |
|
||||||
|
| ---- | ---- |
|
||||||
|
| 200 | Data follows (application/octet-stream) |
|
||||||
|
| 401 | Authentication is invalid (bad team ID) |
|
||||||
|
|
||||||
|
|
||||||
|
### `POST` `/state/self/private.bin` - Upload private data
|
||||||
|
|
||||||
|
#### Responses
|
||||||
|
|
||||||
|
| http code | meaning |
|
||||||
|
| ---- | ---- |
|
||||||
|
| 200 | Data follows (application/octet-stream) |
|
||||||
|
| 401 | Authentication is invalid (bad team ID) |
|
430
docs/api.md
430
docs/api.md
|
@ -1,430 +0,0 @@
|
||||||
Moth APIs
|
|
||||||
=======
|
|
||||||
|
|
||||||
This document covers the following interfaces:
|
|
||||||
|
|
||||||
* HTTP Endpoints: what the Moth client sends the Moth server
|
|
||||||
* Puzzle executable: how the transpiler communicates with executables that provide puzzles
|
|
||||||
* Category executable: how the transpiler communicates with executables that provide categories
|
|
||||||
* Provider executable: how Moth communicates with things that provide puzzles (like the transpiler)
|
|
||||||
|
|
||||||
The Puzzle, Category, and Provider executalbes are all very closely related, since each is a subset of the next.
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
Here'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.
|
|
||||||
|
|
||||||
HTTP provider API mothball API
|
|
||||||
🡗 🡗 🡗
|
|
||||||
client - mothd - mothball provider - category1.mb
|
|
||||||
|
|
||||||
- custom provider
|
|
||||||
category API
|
|
||||||
🡗
|
|
||||||
- internal transpiler - category2/mkcategory
|
|
||||||
|
|
||||||
- category3/1/puzzle.md
|
|
||||||
- category3/2/mkpuzzle
|
|
||||||
🡔
|
|
||||||
puzzle API
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# HTTP Endpoints
|
|
||||||
|
|
||||||
The Moth server accepts
|
|
||||||
standard HTTP `GET` and `POST`.
|
|
||||||
|
|
||||||
Parameters may be encoded with standard `GET` query parameters
|
|
||||||
(like `GET /endpoint?a=1&b=2`),
|
|
||||||
or with `POST` as `application/x-www-form-encoded` data.
|
|
||||||
|
|
||||||
## `/state`
|
|
||||||
|
|
||||||
Returns the current Moth event state as a JSON object.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
* `id`: team ID (optional)
|
|
||||||
|
|
||||||
### Return
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
"Config": {
|
|
||||||
"Devel": false // true means this is a development server
|
|
||||||
},
|
|
||||||
"TeamNames": {
|
|
||||||
"self": "Requesting team name", // Only if regestered team id is a provided
|
|
||||||
"0": "Team 1 Name",
|
|
||||||
"1": "Team 2 Name"
|
|
||||||
// ...
|
|
||||||
},
|
|
||||||
"PointsLog": [
|
|
||||||
[1602679698, "0", "category", 1] // epochTime, teamID, category, points
|
|
||||||
// ...
|
|
||||||
],
|
|
||||||
"Puzzles": {
|
|
||||||
"category": [1, 2, 3, 6] // list of unlocked puzzles for category
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example HTTP transaction
|
|
||||||
|
|
||||||
#### Request
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /state HTTP/1.0
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Response
|
|
||||||
|
|
||||||
This response has been reflowed for readability:
|
|
||||||
an actual on-wire response would not have newlines or indentation.
|
|
||||||
|
|
||||||
```
|
|
||||||
HTTP/1.0 200 OK
|
|
||||||
Content-Type: application/json
|
|
||||||
|
|
||||||
{"Config":
|
|
||||||
{"Devel":false},
|
|
||||||
"TeamNames":{
|
|
||||||
"0":"Mike and Jack",
|
|
||||||
"12":"Team 2",
|
|
||||||
"4":"Team 8"
|
|
||||||
},
|
|
||||||
"PointsLog":[
|
|
||||||
[1602702696,"0","nocode",1],
|
|
||||||
[1602702705,"0","sequence",1],
|
|
||||||
[1602702787,"0","nocode",2],
|
|
||||||
[1602702831,"0","sequence",2],
|
|
||||||
[1602702839,"4","nocode",3],
|
|
||||||
[1602702896,"0","sequence",8],
|
|
||||||
[1602702900,"4","nocode",4],
|
|
||||||
[1602702913,"0","sequence",16]
|
|
||||||
],
|
|
||||||
"Puzzles":{
|
|
||||||
"indy":[12],
|
|
||||||
"nocode":[1,2,3,4,10],
|
|
||||||
"sequence":[1,2,8,16,19],
|
|
||||||
"steg":[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## `/register`
|
|
||||||
|
|
||||||
Registers a name to a team ID.
|
|
||||||
|
|
||||||
This is only required once per team,
|
|
||||||
but user interfaces may find it less confusing to users
|
|
||||||
to present a "login" page.
|
|
||||||
For this reason "this team is already registered"
|
|
||||||
does not return an error.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
* `id`: team ID
|
|
||||||
* `name`: team name
|
|
||||||
|
|
||||||
### Return
|
|
||||||
|
|
||||||
An object inspired by [JSend](https://github.com/omniti-labs/jsend):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"status": "success/fail/error",
|
|
||||||
"data": {
|
|
||||||
"short": "short description",
|
|
||||||
"description": "long description"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example HTTP transaction
|
|
||||||
|
|
||||||
#### Request
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /register HTTP/1.0
|
|
||||||
Content-Type: application/x-www-form-urlencoded
|
|
||||||
Content-Length: 26
|
|
||||||
|
|
||||||
id=b387ca98&name=dirtbags
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Repsonse
|
|
||||||
|
|
||||||
```
|
|
||||||
HTTP/1.0 200 OK
|
|
||||||
Content-Type: application/json
|
|
||||||
Content-Length=86
|
|
||||||
|
|
||||||
{"status":"success","data":{"short":"registered","description":"Team ID registered"}}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## `/answer`
|
|
||||||
|
|
||||||
Submits an answer for points.
|
|
||||||
|
|
||||||
If the answer is wrong, no points are awarded 😉
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
* `id`: team ID
|
|
||||||
* `category`: along with `points`, uniquely identifies a puzzle
|
|
||||||
* `points`: along with `category`, uniquely identifies a puzzle
|
|
||||||
|
|
||||||
### Return
|
|
||||||
|
|
||||||
An object inspired by [JSend](https://github.com/omniti-labs/jsend):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"status": "success/fail/error",
|
|
||||||
"data": {
|
|
||||||
"short": "short description",
|
|
||||||
"description": "long description"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Example HTTP transaction
|
|
||||||
|
|
||||||
#### Request
|
|
||||||
|
|
||||||
```
|
|
||||||
POST /answer HTTP/1.0
|
|
||||||
Content-Type: application/x-www-form-urlencoded
|
|
||||||
Content-Length: 62
|
|
||||||
|
|
||||||
id=b387ca98&category=sequence&points=2&answer=achilles+turnip
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Repsonse
|
|
||||||
|
|
||||||
```
|
|
||||||
HTTP/1.0 200 OK
|
|
||||||
Content-Type: application/json
|
|
||||||
Content-Length=83
|
|
||||||
|
|
||||||
{"status":"fail","data":{"short":"not accepted","description":"Incorrect answer"}}
|
|
||||||
```
|
|
||||||
|
|
||||||
## `/content/{category}/{points}/puzzle.json`
|
|
||||||
|
|
||||||
Retrieves the JSON object describing a puzzle.
|
|
||||||
|
|
||||||
Parameters are all in the URL for this endpoint,
|
|
||||||
so `curl` and `wget` can be used.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
* `{category}` (in URL): along with `{points}`, uniquely identifies a puzzle
|
|
||||||
* `{points}` (in URL): along with `{category}`, uniquely identifies a puzzle
|
|
||||||
* `{filename}` (in URL): filename to retrieve
|
|
||||||
|
|
||||||
### Return
|
|
||||||
|
|
||||||
JSON object describing a puzzle.
|
|
||||||
|
|
||||||
#### JSON Puzzle Object
|
|
||||||
|
|
||||||
```js
|
|
||||||
{
|
|
||||||
"Pre": { // Things which appear before the puzzle is solved
|
|
||||||
"Authors": ["Neale Pickett"], // List of puzzle authors, usually rendered as a footnote
|
|
||||||
"Attachments": ["tiger.jpg"], // List of files attached to the puzzle
|
|
||||||
"Scripts": [], // List of scripts which should be included in the HTML render of the puzzle
|
|
||||||
"Body": "<p>Can you find the hidden text?</p><p><img src=\"tiger.jpg\" alt=\"Grr\" /></p>\n", // HTML puzzle body
|
|
||||||
"AnswerPattern": "", // Regular expression to include in HTML input tag for validation
|
|
||||||
"AnswerHashes": [ // List of SHA265 hashes of correct answers, for client-side answer checking
|
|
||||||
"f91b1fe875cdf9e969e5bccd3e259adec5a987dcafcbc9ca8da62e341a7f29c6"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Post": { // Things reveal after the puzzle is solved
|
|
||||||
"Objective": "Learn to examine images for hidden text", // Learning objective
|
|
||||||
"Success": { // Measures of learning success
|
|
||||||
"Acceptable": "Visually examine image to find hidden text",
|
|
||||||
"Mastery": "Visually examine image to find hidden text"
|
|
||||||
},
|
|
||||||
"KSAs": null // Knowledge, Skills, and Abilities covered by this puzzle
|
|
||||||
},
|
|
||||||
"Debug": { // Debugging output used in development: all fields are emptied when making mothballs
|
|
||||||
"Log": [ // Debug message log
|
|
||||||
"Input image size: 600x400",
|
|
||||||
"Applying gaussian blur",
|
|
||||||
"Text width 58, left offset 513",
|
|
||||||
"Complete in 0.028s"
|
|
||||||
],
|
|
||||||
"Errors": [], // Errors encountered generating this puzzzle
|
|
||||||
"Hints": [ // Hints for instructional assistants to provide to participants
|
|
||||||
"Zoom in to the image and examine all sections carefully"
|
|
||||||
],
|
|
||||||
"Summary": "text in image" // Summary of this puzzle, to help identify it in an overview of puzzles
|
|
||||||
},
|
|
||||||
"Answers": ["sandwich"] // List of answers: empty in production
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Example HTTP transaction
|
|
||||||
|
|
||||||
#### Request
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /content/sequence/1/puzzle.json HTTP/1.0
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Repsonse
|
|
||||||
|
|
||||||
```
|
|
||||||
HTTP/1.0 200 OK
|
|
||||||
Content-Type: application/json
|
|
||||||
Content-Length: 397
|
|
||||||
|
|
||||||
{"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":[]}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## `/content/{category}/{points}/{filename}`
|
|
||||||
|
|
||||||
Retrieves static content associated with a puzzle.
|
|
||||||
|
|
||||||
Parameters are all in the URL for this endpoint,
|
|
||||||
so `curl` and `wget` can be used.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
* `{category}` (in URL): along with `{points}`, uniquely identifies a puzzle
|
|
||||||
* `{points}` (in URL): along with `{category}`, uniquely identifies a puzzle
|
|
||||||
* `{filename}` (in URL): filename to retrieve
|
|
||||||
|
|
||||||
### Return
|
|
||||||
|
|
||||||
Raw file octets,
|
|
||||||
with a (hopefully) suitable
|
|
||||||
`Content-type` HTTP header field.
|
|
||||||
|
|
||||||
### Example HTTP transaction
|
|
||||||
|
|
||||||
#### Request
|
|
||||||
|
|
||||||
```
|
|
||||||
GET /content/sequence/1/attachment.txt HTTP/1.0
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Repsonse
|
|
||||||
|
|
||||||
```
|
|
||||||
HTTP/1.0 200 OK
|
|
||||||
Content-Type: text/plain
|
|
||||||
Content-Length: 98
|
|
||||||
|
|
||||||
This is an attachment file! This is just plain text for the example. Many attachments are JPEGs.
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
# Puzzle
|
|
||||||
|
|
||||||
A puzzle contains one question and one or more associated answers.
|
|
||||||
Puzzles are not aware of their point value: this is set by the category they are in.
|
|
||||||
|
|
||||||
Puzzle executables must be named `mkpuzzle`.
|
|
||||||
|
|
||||||
|
|
||||||
## `mkpuzzle puzzle`
|
|
||||||
|
|
||||||
puzzles/category3/1 $ ./mkpuzzle puzzle
|
|
||||||
{JSON PUZZLE OBJECT}
|
|
||||||
|
|
||||||
Also see [JSON Puzzle Object](#json-puzzle-object)
|
|
||||||
|
|
||||||
|
|
||||||
## `mkpuzzle file {filename}`
|
|
||||||
|
|
||||||
puzzles/category3/1 $ ./mkpuzzle file attachment.txt
|
|
||||||
This is an attachment file! It's just plain text for this example. Many attachments are JPEGs.
|
|
||||||
|
|
||||||
|
|
||||||
## `mkpuzzle answer {answer}`
|
|
||||||
|
|
||||||
puzzles/category3/1 $ ./mkpuzzle answer "cow goes moo"
|
|
||||||
{"Correct":false}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Category
|
|
||||||
|
|
||||||
Categories are collections of puzzles.
|
|
||||||
Each puzzle has a unique point value, determined by the category.
|
|
||||||
|
|
||||||
Category executables must be called `mkcategory`.
|
|
||||||
|
|
||||||
## `mkcategory inventory`
|
|
||||||
|
|
||||||
puzzles/category2 $ ./mkcategory inventory
|
|
||||||
{"Puzzles": [1, 2, 3, 5, 10, 20, 30, 50, 100]}
|
|
||||||
|
|
||||||
|
|
||||||
## `mkcategory puzzle {points}`
|
|
||||||
|
|
||||||
puzzles/category2 $ ./mkcategory puzzle 1
|
|
||||||
{JSON PUZZLE OBJECT}
|
|
||||||
|
|
||||||
Also see [JSON Puzzle Object](#json-puzzle-object)
|
|
||||||
|
|
||||||
|
|
||||||
## `mkcategory file {points} {filename}`
|
|
||||||
|
|
||||||
puzzles/category2 $ ./mkcategory file 1 attachment.txt
|
|
||||||
This is an attachment file's contents!
|
|
||||||
|
|
||||||
|
|
||||||
## `mkcategory answer {points} {answer}`
|
|
||||||
|
|
||||||
puzzles/category2 $ ./mkcategory answer 1 "cow goes moo"
|
|
||||||
{"Correct":false}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Provider API
|
|
||||||
|
|
||||||
This is how Claire gets her dynamic graders.
|
|
||||||
|
|
||||||
*Notice: this is not complete in the code base!*
|
|
||||||
I'm writing here how it *should* work.
|
|
||||||
If anybody wants this,
|
|
||||||
please let me know,
|
|
||||||
and I'll finish the code.
|
|
||||||
|
|
||||||
This could ostensibly be expanded to call HTTP servers,
|
|
||||||
with the four endpoints described here.
|
|
||||||
If somebody were to want such a thing.
|
|
||||||
|
|
||||||
## `provider inventory`
|
|
||||||
|
|
||||||
$ provider inventory
|
|
||||||
{
|
|
||||||
"category1": [1, 2, 3, 4, 5, 10, 20, 30],
|
|
||||||
"category2": [20, 40, 70, 150]
|
|
||||||
}
|
|
||||||
|
|
||||||
## `provider puzzle {category} {points}`
|
|
||||||
|
|
||||||
$ provider puzzle category1 20
|
|
||||||
{JSON PUZZLE OBJECT}
|
|
||||||
|
|
||||||
Also see [JSON Puzzle Object](#json-puzzle-object)
|
|
||||||
|
|
||||||
|
|
||||||
## `provider file {category} {points} {filename}`
|
|
||||||
|
|
||||||
$ provider file category1 20 attachment.txt
|
|
||||||
This is an attachment! Yay!
|
|
||||||
|
|
||||||
## `provider answer {category} {points} {answer}`
|
|
||||||
|
|
||||||
$ provider answer category1 20 "cow goes moo"
|
|
||||||
{"Correct":true}
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
MOTH Client Directory Structure
|
||||||
|
=======
|
||||||
|
|
||||||
|
MOTHv5 implements WebDAV.
|
||||||
|
Depending on the authentication level of a user,
|
||||||
|
files may be read-only, or read-write.
|
||||||
|
|
||||||
|
WebDAV allows you to mount MOTH as a local filesystem.
|
||||||
|
You are encouraged to do this,
|
||||||
|
and use this document as a reference.
|
||||||
|
|
||||||
|
|
||||||
|
Directory Structure: Participant
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Here is an example list of the files available to participants.
|
||||||
|
|
||||||
|
r- /state/points.log
|
||||||
|
rw /state/self/name
|
||||||
|
rw /state/self/private.dat
|
||||||
|
rw /state/self/public.dat
|
||||||
|
r- /state/1/name
|
||||||
|
r- /state/1/public.dat
|
||||||
|
r- /state/2/name
|
||||||
|
r- /state/2/public.dat
|
||||||
|
r- /puzzles/category-a/1/index.html
|
||||||
|
rw /puzzles/category-a/1/answer
|
||||||
|
r- /puzzles/category-a/2/index.html
|
||||||
|
rw /puzzles/category-a/2/answer
|
||||||
|
r- /puzzles/category-a/2/attachment.jpg
|
||||||
|
r- /puzzles/category-b/1/index.html
|
||||||
|
rw /puzzles/category-b/1/answer
|
||||||
|
r- /theme/*
|
||||||
|
|
||||||
|
Directory Structure: Anonymous
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Anonymous (unauthenticated) users
|
||||||
|
have a restricted view:
|
||||||
|
|
||||||
|
r- /state/points.log
|
||||||
|
rw /state/self/name
|
||||||
|
rw /state/self/private.dat
|
||||||
|
rw /state/self/public.dat
|
||||||
|
r- /state/1/name
|
||||||
|
r- /state/1/public.dat
|
||||||
|
r- /state/2/name
|
||||||
|
r- /state/2/public.dat
|
||||||
|
|
||||||
|
|
||||||
|
Directory Structure: Administrator
|
||||||
|
------------
|
||||||
|
|
||||||
|
Here is an example list of the files available
|
||||||
|
to an administrator.
|
|
@ -0,0 +1,50 @@
|
||||||
|
MOTH Metadata
|
||||||
|
============
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Standard Metadata
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The following are considered "standard" MOTH metadata.
|
||||||
|
Clients *should* check for,
|
||||||
|
and take appropriate action on,
|
||||||
|
all of these metadata names.
|
||||||
|
|
||||||
|
|
||||||
|
| name | description | permitted values | example |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| author | Puzzle author(s). | free text [(ref)](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name) | `Neale Pickett` |
|
||||||
|
| moth.style | Whether the client should inject a style sheet. Default: `inherit` | `override`, `inherit` | `override` |
|
||||||
|
| moth.answerhash | Answer hash, used for "possibly correct" check in client. | MOTHv5: first 8 characters of answer's SHA1 checksum | `a5b6bb92` |
|
||||||
|
| moth.answerpattern | Answer pattern, to use as `pattern` attribute of `<input>` element for answer. | Regular Expression [(ref)](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/pattern) | `\w{3,16}`
|
||||||
|
| moth.ksa | [NICE KSA](https://niccs.cisa.gov/workforce-development/nice-framework) achieved by completing this puzzle. | NICE KSA identifier | `K0052` |
|
||||||
|
| moth.objective | Learning objective of this puzzle. | free text | `Count in octal` |
|
||||||
|
| moth.success.acceptable | The minimum work required to be considered successfully understanding this puzzle's concepts | free text | `Recognize pattern` |
|
||||||
|
| moth.success.mastery | The work required to be considered mastering this puzzle's concepts | free text | `Understand 8s place in octal` |
|
||||||
|
|
||||||
|
|
||||||
|
Standard Debugging Metadata
|
||||||
|
----------------
|
||||||
|
|
||||||
|
These metadata names are for debugging purposes.
|
||||||
|
The *must not* be present in a production instance.
|
||||||
|
|
||||||
|
| name | description | permitted values | example |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| moth.debug.answer | An accepted answer | free text | `pink hat horse race` |
|
||||||
|
| moth.debug.summary | A summary of the puzzle, to help staff remember what it is | free text | `Hidden white text in the rendered image` |
|
||||||
|
| moth.debug.hint | A hint that staff can provide to participants | free text | `This puzzle can be solved by a grade school student with no special tools` |
|
||||||
|
| moth.debug.notes | Notes to staff intended to help better understand the puzzle | free text | `We used this image because Scott likes tigers` |
|
||||||
|
| moth.debug.log | A log message | free text | `iterations: 5` |
|
||||||
|
| moth.debug.errors | Error messages | free text | `unable to open foo.bin` |
|
||||||
|
|
||||||
|
|
||||||
|
Client Metadata
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Clients wishing to implement additional metadata
|
||||||
|
*should* either submit a merge request to this document,
|
||||||
|
or use a `moth/$client.` prefix.
|
||||||
|
For example, the "tofu" client might use a
|
||||||
|
`moth/tofu.difficulty` name.
|
|
@ -0,0 +1,183 @@
|
||||||
|
MOTH Puzzle Format
|
||||||
|
===========
|
||||||
|
|
||||||
|
MOTH puzzles are HTML5 documents,
|
||||||
|
with optional metadata.
|
||||||
|
Puzzles may contain stylesheets and scripts,
|
||||||
|
or any other feature made available by HTML5.
|
||||||
|
|
||||||
|
Typically, a puzzle will be rendered in an `<object>` tag
|
||||||
|
in the MOTH client.
|
||||||
|
Some clients may copy over scripts, stylesheets,
|
||||||
|
and embed the puzzle's `<body>` in the page.
|
||||||
|
|
||||||
|
Within a puzzle directory,
|
||||||
|
the puzzle itself is named `index.html`.
|
||||||
|
|
||||||
|
|
||||||
|
MOTH Metadata
|
||||||
|
=============
|
||||||
|
|
||||||
|
Puzzles may contain metadata,
|
||||||
|
which can be used by MOTH clients to alter display of puzzles,
|
||||||
|
or provide additional information in the UI.
|
||||||
|
|
||||||
|
Metadata is provided in HTML `<meta>` elements,
|
||||||
|
with the `name` attribute specifying the metadata name,
|
||||||
|
and the `content` attribute specifying the metadata content.
|
||||||
|
Multiple elements with the same `name` are generally permitted.
|
||||||
|
|
||||||
|
Metadata names are defined in detail in
|
||||||
|
[MOTH Metadata](metadata.md).
|
||||||
|
|
||||||
|
For example, the following `<meta>` elements
|
||||||
|
could appear in the `<head>` section of a puzzle's HTML:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<meta name="author" content="Neale Pickett">
|
||||||
|
<meta name="moth.answerhash" content="87bcc390">
|
||||||
|
<meta name="moth.answerhash" content="622fcbe8">
|
||||||
|
<meta name="moth.objective" content="Understand radix 8 (octal)">
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Images, Attachments, Scripts, and Style Sheets
|
||||||
|
===================
|
||||||
|
|
||||||
|
Related files can be referenced directly in HTML.
|
||||||
|
Related files *should* be located in the same directory as `index.html`,
|
||||||
|
but situations may exist where it makes more sense
|
||||||
|
to locate a file in the parent directory.
|
||||||
|
|
||||||
|
Related files are not hidden:
|
||||||
|
they can be discovered with an http `PROPFIND` method.
|
||||||
|
|
||||||
|
For example, assuming `honey.jpg` exists in the same directory
|
||||||
|
as `index.html`, a standard `<img>` tag will work:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<img src="honey.jpg"
|
||||||
|
alt="A clay jar with the word 'honey' printed on the front."
|
||||||
|
title="Honey jar">
|
||||||
|
```
|
||||||
|
|
||||||
|
Puzzle Events
|
||||||
|
==============
|
||||||
|
|
||||||
|
As HTML5 documents,
|
||||||
|
MOTH puzzles can communicate with the MOTH client
|
||||||
|
using HTML5 events.
|
||||||
|
|
||||||
|
setAnswer
|
||||||
|
--------
|
||||||
|
|
||||||
|
A MOTH Puzzle may advise the client to fill the answer field with text
|
||||||
|
by emitting an `setAnswer` custom event.
|
||||||
|
|
||||||
|
For example, the following code will advice the client to set the answer field to the string `bloop`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
let answerEvent = new CustomEvent(
|
||||||
|
"setAnswer",
|
||||||
|
{
|
||||||
|
detail: {value: 'bloop'},
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true
|
||||||
|
},
|
||||||
|
)
|
||||||
|
document.dispatchEvent(answerEvent)
|
||||||
|
```
|
||||||
|
|
||||||
|
MOTH clients *should* listen for such events,
|
||||||
|
and fill the answer input field with the event's value.
|
||||||
|
Puzzles *must* provide the user with a copy/paste-able representation of the answer, in the event the event is not handled correctly by the client.
|
||||||
|
|
||||||
|
|
||||||
|
Example Puzzles
|
||||||
|
=========
|
||||||
|
|
||||||
|
Minimally Valid Puzzle
|
||||||
|
---------
|
||||||
|
|
||||||
|
This puzzle provides the absolute minimum required:
|
||||||
|
a title, and puzzle contents.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<title>Counting</title>
|
||||||
|
<p>1 2 3 4 5 _</p>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Puzzle with metadata
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Typically, puzzles will provide metadata,
|
||||||
|
to enable client features such as "possibly correct" validation,
|
||||||
|
author display, learning objectives
|
||||||
|
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Counting Sheep</title>
|
||||||
|
<meta name="author" content="Neale Pickett">
|
||||||
|
<meta name="moth.answerhash" content="089c7244">
|
||||||
|
<meta name="moth.answerhash" content="92837b4f">
|
||||||
|
<meta name="moth.objective" content="Recognize the difference between a sheep and a wolf">
|
||||||
|
<meta name="moth.objective" content="Count to a high number">
|
||||||
|
<meta name="moth.success.acceptable" content="Count using fingers">
|
||||||
|
<meta name="moth.success.mastery" content="Count using software tools, and provide answer in hexadecimal">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>🐑🐑🐑🐑🐑🐑🐑🐑🐺🐑🐑</p>
|
||||||
|
<p>How many sheep?</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Puzzle with images, scripts, and style
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Since they are rendered as HTML documents,
|
||||||
|
puzzles may include any HTML5 feature.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Basic Sight Reading</title>
|
||||||
|
<meta name="author" content="Neale Pickett">
|
||||||
|
<meta name="moth.answerhash" content="baabaa08">
|
||||||
|
<meta name="moth.objective" content="Play a tune provided in sheet music">
|
||||||
|
<meta name="moth.success.acceptable" content="Play the requested tune with no mistakes">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
<script src="midi-transcriber.mjs" type="module"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Using the provided sheet music,
|
||||||
|
play "May Had A Little Lamb" on your MIDI keyboard.
|
||||||
|
If you make a mistake,
|
||||||
|
press the "reset" button and start over.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Once you have played from start to finish with no mistakes,
|
||||||
|
paste the computed answer into the answer box.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<img src="mary-lamb.png"
|
||||||
|
alt="Sheet music: |EDCD|EEE.|DDD.|EEE.|"
|
||||||
|
title="Sheet music for 'Mary Had A Little Lamb">
|
||||||
|
|
||||||
|
<label for="notes">Notes Played</label>
|
||||||
|
<output id="notes"></output>
|
||||||
|
<button id="reset">Reset</button>
|
||||||
|
|
||||||
|
<label for="answer">Answer</label>
|
||||||
|
<output id="answer"></output>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
|
@ -78,7 +78,7 @@ type Puzzle struct {
|
||||||
// Acceptable describes the minimum work required to be considered successfully understanding this puzzle's concepts
|
// Acceptable describes the minimum work required to be considered successfully understanding this puzzle's concepts
|
||||||
Acceptable string
|
Acceptable string
|
||||||
|
|
||||||
// Mastery describes the work required to be considered mastering this puzzle's conceptss
|
// Mastery describes the work required to be considered mastering this puzzle's concepts
|
||||||
Mastery string
|
Mastery string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
html {
|
||||||
|
background: #333;
|
||||||
|
color: white;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Puzzle Viewer</title>
|
||||||
|
<link rel="stylesheet" href="index.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<object type="text/html" data="puzzle.html"></object>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,42 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Example MOTHv5 Puzzle</title>
|
||||||
|
|
||||||
|
<!-- style tells the client whether to inject its own stylesheet.
|
||||||
|
|
||||||
|
By default, a client will try to style a puzzle using its own stylesheet.
|
||||||
|
If you want to override this behavior, provide "style" with
|
||||||
|
content="override".
|
||||||
|
Omitting this meta element is the same as content="inherit".
|
||||||
|
-->
|
||||||
|
<meta name="moth.style" content="inherit">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- moth.answerhash is used for the "possibly correct" check.
|
||||||
|
|
||||||
|
This is the first 8 characters of the hex-encoded sha1 checksum of the answer.
|
||||||
|
If you have multiple acceptable answers, provide multiple answerhash elements.
|
||||||
|
-->
|
||||||
|
<meta name="moth.answerhash" content="2c26b46b">
|
||||||
|
|
||||||
|
<!-- author specifies the author of this puzzle.
|
||||||
|
|
||||||
|
If you have multiple authors, specify multiple meta elements,
|
||||||
|
one author per element.
|
||||||
|
-->
|
||||||
|
<meta name="author" content="Neale Pickett">
|
||||||
|
<meta name="author" content="Ford Powers">
|
||||||
|
|
||||||
|
<script href="example.mjs" type="module"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Example Puzzle</h1>
|
||||||
|
<p>
|
||||||
|
This is an example puzzle, yo.
|
||||||
|
You can put whatever you want in the HTML.
|
||||||
|
If you do crazy tricks, it might break the client, though.
|
||||||
|
</p>
|
||||||
|
<img src="salad.jpg">
|
||||||
|
</body>
|
||||||
|
</html>
|
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
After Width: | Height: | Size: 29 KiB |
Loading…
Reference in New Issue