mirror of https://github.com/dirtbags/moth.git
Now you can hit enter to sign in
This commit is contained in:
parent
02ed2162c2
commit
8e67abe0c0
|
@ -29,6 +29,16 @@ def get_seed(request):
|
||||||
return int(seedstr)
|
return int(seedstr)
|
||||||
|
|
||||||
|
|
||||||
|
def get_puzzle(request):
|
||||||
|
seed = get_seed(request)
|
||||||
|
category = request.match_info.get("category")
|
||||||
|
points = int(request.match_info.get("points"))
|
||||||
|
filename = request.match_info.get("filename")
|
||||||
|
cat = moth.Category(request.app["puzzles_dir"].joinpath(category), seed)
|
||||||
|
puzzle = cat.puzzle(points)
|
||||||
|
return puzzle
|
||||||
|
|
||||||
|
|
||||||
async def handle_puzzlelist(request):
|
async def handle_puzzlelist(request):
|
||||||
seed = get_seed(request)
|
seed = get_seed(request)
|
||||||
puzzles = {
|
puzzles = {
|
||||||
|
@ -88,6 +98,16 @@ async def handle_puzzlefile(request):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def handle_answer(request):
|
||||||
|
seed = get_seed(request)
|
||||||
|
category = request.match_info.get("category")
|
||||||
|
points = int(request.match_info.get("points"))
|
||||||
|
filename = request.match_info.get("filename")
|
||||||
|
cat = moth.Category(request.app["puzzles_dir"].joinpath(category), seed)
|
||||||
|
puzzle = cat.puzzle(points)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async def handle_mothballer(request):
|
async def handle_mothballer(request):
|
||||||
seed = get_seed(request)
|
seed = get_seed(request)
|
||||||
category = request.match_info.get("category")
|
category = request.match_info.get("category")
|
||||||
|
@ -113,19 +133,37 @@ async def handle_index(request):
|
||||||
seed = random.getrandbits(32)
|
seed = random.getrandbits(32)
|
||||||
body = """<!DOCTYPE html>
|
body = """<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head><title>Dev Server</title></head>
|
<head>
|
||||||
|
<title>Dev Server</title>
|
||||||
|
<script>
|
||||||
|
// Skip trying to log in
|
||||||
|
sessionStorage.setItem("id", "Hello from the development server")
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Dev Server</h1>
|
<h1>Dev Server</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
You need to provide the contest seed in the URL.
|
Pick a seed:
|
||||||
If you don't have a contest seed in mind,
|
|
||||||
why not try <a href="{seed}/">{seed}</a>?
|
|
||||||
</p>
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="{seed}/">{seed}</a>: a special seed I made just for you!</li>
|
||||||
|
<li><a href="random/">random</a>: will use a different seed every time you load a page (could be useful for debugging)</li>
|
||||||
|
<li>You can also hack your own seed into the URL, if you want to.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
If you are chaotic,
|
Puzzles can be generated from Python code: these puzzles can use a random number generator if they want.
|
||||||
you could even take your chances with a
|
The seed is used to create these random numbers.
|
||||||
<a href="random/">random seed</a> for every HTTP request.
|
</p>
|
||||||
This means generated files will get a different seed than the puzzle itself!
|
|
||||||
|
<p>
|
||||||
|
We like to make a new seed for every contest,
|
||||||
|
and re-use that seed whenever we regenerate a category during an event
|
||||||
|
(say to fix a bug).
|
||||||
|
By using the same seed,
|
||||||
|
we make sure that all the dynamically-generated puzzles have the same values
|
||||||
|
in any new packages we build.
|
||||||
</p>
|
</p>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -140,11 +178,7 @@ async def handle_static(request):
|
||||||
themes = request.app["theme_dir"]
|
themes = request.app["theme_dir"]
|
||||||
fn = request.match_info.get("filename")
|
fn = request.match_info.get("filename")
|
||||||
if not fn:
|
if not fn:
|
||||||
for fn in ("puzzle-list.html", "index.html"):
|
fn = "index.html"
|
||||||
path = themes.joinpath(fn)
|
|
||||||
if path.exists():
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
path = themes.joinpath(fn)
|
path = themes.joinpath(fn)
|
||||||
return web.FileResponse(path)
|
return web.FileResponse(path)
|
||||||
|
|
||||||
|
@ -182,7 +216,7 @@ if __name__ == '__main__':
|
||||||
app["puzzles_dir"] = pathlib.Path(args.puzzles)
|
app["puzzles_dir"] = pathlib.Path(args.puzzles)
|
||||||
app["theme_dir"] = pathlib.Path(args.theme)
|
app["theme_dir"] = pathlib.Path(args.theme)
|
||||||
app.router.add_route("GET", "/", handle_index)
|
app.router.add_route("GET", "/", handle_index)
|
||||||
app.router.add_route("GET", "/{seed}/puzzles.json", handle_puzzlelist)
|
app.router.add_route("*", "/{seed}/puzzles.json", handle_puzzlelist)
|
||||||
app.router.add_route("GET", "/{seed}/content/{category}/{points}/puzzle.json", handle_puzzle)
|
app.router.add_route("GET", "/{seed}/content/{category}/{points}/puzzle.json", handle_puzzle)
|
||||||
app.router.add_route("GET", "/{seed}/content/{category}/{points}/{filename}", handle_puzzlefile)
|
app.router.add_route("GET", "/{seed}/content/{category}/{points}/{filename}", handle_puzzlefile)
|
||||||
app.router.add_route("GET", "/{seed}/mothballer/{category}", handle_mothballer)
|
app.router.add_route("GET", "/{seed}/mothballer/{category}", handle_mothballer)
|
||||||
|
|
|
@ -175,6 +175,12 @@ func (ctx *Instance) answerHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *Instance) puzzlesHandler(w http.ResponseWriter, req *http.Request) {
|
func (ctx *Instance) puzzlesHandler(w http.ResponseWriter, req *http.Request) {
|
||||||
|
teamid := req.FormValue("id")
|
||||||
|
if _, err := ctx.TeamName(teamid); err != nil {
|
||||||
|
http.Error(w, "Unauthorized: must provide team ID", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Write(ctx.jPuzzleList)
|
w.Write(ctx.jPuzzleList)
|
||||||
|
@ -273,13 +279,7 @@ func (ctx *Instance) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
|
||||||
w: wOrig,
|
w: wOrig,
|
||||||
statusCode: new(int),
|
statusCode: new(int),
|
||||||
}
|
}
|
||||||
w.Header().Set("WWW-Authenticate", "Basic")
|
|
||||||
_, password, _ := r.BasicAuth()
|
|
||||||
if password != ctx.Password {
|
|
||||||
http.Error(w, "Authentication Required", 401)
|
|
||||||
} else {
|
|
||||||
ctx.mux.ServeHTTP(w, r)
|
ctx.mux.ServeHTTP(w, r)
|
||||||
}
|
|
||||||
log.Printf(
|
log.Printf(
|
||||||
"%s %s %s %d\n",
|
"%s %s %s %d\n",
|
||||||
r.RemoteAddr,
|
r.RemoteAddr,
|
||||||
|
|
|
@ -19,7 +19,6 @@ type Instance struct {
|
||||||
MothballDir string
|
MothballDir string
|
||||||
StateDir string
|
StateDir string
|
||||||
ResourcesDir string
|
ResourcesDir string
|
||||||
Password string
|
|
||||||
Categories map[string]*Mothball
|
Categories map[string]*Mothball
|
||||||
update chan bool
|
update chan bool
|
||||||
jPuzzleList []byte
|
jPuzzleList []byte
|
||||||
|
@ -27,13 +26,12 @@ type Instance struct {
|
||||||
mux *http.ServeMux
|
mux *http.ServeMux
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewInstance(base, mothballDir, stateDir, resourcesDir, password string) (*Instance, error) {
|
func NewInstance(base, mothballDir, stateDir, resourcesDir string) (*Instance, error) {
|
||||||
ctx := &Instance{
|
ctx := &Instance{
|
||||||
Base: strings.TrimRight(base, "/"),
|
Base: strings.TrimRight(base, "/"),
|
||||||
MothballDir: mothballDir,
|
MothballDir: mothballDir,
|
||||||
StateDir: stateDir,
|
StateDir: stateDir,
|
||||||
ResourcesDir: resourcesDir,
|
ResourcesDir: resourcesDir,
|
||||||
Password: password,
|
|
||||||
Categories: map[string]*Mothball{},
|
Categories: map[string]*Mothball{},
|
||||||
update: make(chan bool, 10),
|
update: make(chan bool, 10),
|
||||||
mux: http.NewServeMux(),
|
mux: http.NewServeMux(),
|
||||||
|
|
|
@ -37,11 +37,6 @@ func main() {
|
||||||
"/theme",
|
"/theme",
|
||||||
"Path to static theme resources (HTML, images, css, ...)",
|
"Path to static theme resources (HTML, images, css, ...)",
|
||||||
)
|
)
|
||||||
password := flag.String(
|
|
||||||
"password",
|
|
||||||
"sesame",
|
|
||||||
"Pass Word (in the 1920s sense) to view the site. Not a secure passphrase.",
|
|
||||||
)
|
|
||||||
maintenanceInterval := flag.Duration(
|
maintenanceInterval := flag.Duration(
|
||||||
"maint",
|
"maint",
|
||||||
20*time.Second,
|
20*time.Second,
|
||||||
|
@ -58,7 +53,7 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, err := NewInstance(*base, *mothballDir, *stateDir, *themeDir, *password)
|
ctx, err := NewInstance(*base, *mothballDir, *stateDir, *themeDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>MOTH</title>
|
<title>MOTH</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width">
|
<meta name="viewport" content="width=device-width">
|
||||||
<link rel="stylesheet" href="basic.css">
|
<link rel="stylesheet" href="basic.css">
|
||||||
<script src="moth.js"></script>
|
<script src="moth.js"></script>
|
||||||
|
@ -11,11 +12,11 @@
|
||||||
<section>
|
<section>
|
||||||
<div id="messages"></div>
|
<div id="messages"></div>
|
||||||
|
|
||||||
<div id="login">
|
<form id="login">
|
||||||
Team name: <input name="name">
|
Team name: <input name="name">
|
||||||
Team ID: <input name="id"> <br>
|
Team ID: <input name="id"> <br>
|
||||||
<button id="submit">Sign In</button>
|
<input type="submit" value="Sign In">
|
||||||
</div>
|
</form>
|
||||||
|
|
||||||
<div id="puzzles"></div>
|
<div id="puzzles"></div>
|
||||||
|
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="scoreboard.html">Scoreboard</a></li>
|
<li><a href="scoreboard.html">Scoreboard</a></li>
|
||||||
|
<li><a href="logout.html">Sign Out</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -15,7 +15,7 @@ sessionStorage.removeItem("id")
|
||||||
</section>
|
</section>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="index.html">login</a></li>
|
<li><a href="index.html">Sign In</a></li>
|
||||||
<li><a href="scoreboard.html">Scoreboard</a></li>
|
<li><a href="scoreboard.html">Scoreboard</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -64,6 +64,7 @@ function renderPuzzles(obj) {
|
||||||
i.appendChild(a)
|
i.appendChild(a)
|
||||||
a.textContent = points
|
a.textContent = points
|
||||||
a.href = "puzzle.html?cat=" + cat + "&points=" + points + "&pid=" + id
|
a.href = "puzzle.html?cat=" + cat + "&points=" + points + "&pid=" + id
|
||||||
|
a.target = "_blank"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,13 +77,10 @@ function renderPuzzles(obj) {
|
||||||
container.firstChild.remove()
|
container.firstChild.remove()
|
||||||
}
|
}
|
||||||
container.appendChild(puzzlesElement)
|
container.appendChild(puzzlesElement)
|
||||||
container.style.display = "none"
|
|
||||||
|
|
||||||
document.getElementById("login").style.display = "block"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function heartbeat(teamId) {
|
function heartbeat(teamId) {
|
||||||
rpc("puzzles.json", {teamid: teamId})
|
rpc("puzzles.json", {id: teamId})
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
if (resp.ok) {
|
if (resp.ok) {
|
||||||
resp.json()
|
resp.json()
|
||||||
|
@ -103,16 +101,20 @@ function showPuzzles(teamId) {
|
||||||
let spinner = document.createElement("span")
|
let spinner = document.createElement("span")
|
||||||
spinner.classList.add("spinner")
|
spinner.classList.add("spinner")
|
||||||
|
|
||||||
|
sessionStorage.setItem("id", teamId)
|
||||||
|
|
||||||
document.getElementById("login").style.display = "none"
|
document.getElementById("login").style.display = "none"
|
||||||
document.getElementById("puzzles").appendChild(spinner)
|
document.getElementById("puzzles").appendChild(spinner)
|
||||||
heartbeat(teamId)
|
heartbeat(teamId)
|
||||||
setInterval(e => { heartbeat(teamId) }, 40000)
|
setInterval(e => { heartbeat(teamId) }, 40000)
|
||||||
}
|
}
|
||||||
|
|
||||||
function login() {
|
function login(e) {
|
||||||
let name = document.querySelector("[name=name]").value
|
let name = document.querySelector("[name=name]").value
|
||||||
let id = document.querySelector("[name=id]").value
|
let id = document.querySelector("[name=id]").value
|
||||||
|
|
||||||
|
e.preventDefault()
|
||||||
|
|
||||||
rpc("register", {
|
rpc("register", {
|
||||||
name: name,
|
name: name,
|
||||||
id: id,
|
id: id,
|
||||||
|
@ -158,7 +160,13 @@ function toast(message, timeout=5000) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
document.getElementById("submit").addEventListener("click", login)
|
// Already signed in?
|
||||||
|
let id = sessionStorage.getItem("id")
|
||||||
|
if (id) {
|
||||||
|
showPuzzles(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById("login").addEventListener("submit", login)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
|
|
|
@ -94,7 +94,7 @@ if (document.readyState === "loading") {
|
||||||
<div id="devel"></div>
|
<div id="devel"></div>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="puzzle-list.html">Puzzles</a></li>
|
<li><a href="index.html">Puzzles</a></li>
|
||||||
<li><a href="scoreboard.html">Scoreboard</a></li>
|
<li><a href="scoreboard.html">Scoreboard</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
|
@ -128,7 +128,7 @@ if (document.readyState === "loading") {
|
||||||
</section>
|
</section>
|
||||||
<nav>
|
<nav>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="puzzle-list.html">Puzzles</a></li>
|
<li><a href="index.html">Puzzles</a></li>
|
||||||
<li><a href="scoreboard.html">Scoreboard</a></li>
|
<li><a href="scoreboard.html">Scoreboard</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
Loading…
Reference in New Issue