tImplement HTML score page generation - scoreboard - Interactive scoreboard for CTF-like games
(HTM) git clone git://git.z3bra.org/scoreboard.git
(DIR) Log
(DIR) Files
(DIR) Refs
---
(DIR) commit 528d94f22d463e41294bd92095b4953f52dc1fdd
(DIR) parent e060760bef0f2e589d993fa17bbf11cc6762f3dc
(HTM) Author: Willy Goiffon <dev@z3bra.org>
Date: Wed, 7 Dec 2022 12:45:04 +0100
Implement HTML score page generation
Diffstat:
M db.go | 4 ++--
A html.go | 97 ++++++++++++++++++++++++++++++
M main.go | 16 +++++++++++-----
M player.go | 15 ++++++++++-----
M playerbox.go | 1 +
5 files changed, 121 insertions(+), 12 deletions(-)
---
(DIR) diff --git a/db.go b/db.go
t@@ -104,7 +104,7 @@ func db_id(db *sql.DB, nick string) bool {
func db_ranked_players(db *sql.DB, offset, limit int) ([]Player, error) {
query := `SELECT
- name,flag,score
+ name,flag,score,ts
FROM score
ORDER BY
score DESC,
t@@ -122,7 +122,7 @@ func db_ranked_players(db *sql.DB, offset, limit int) ([]Player, error) {
players := make([]Player, 0)
for rows.Next() {
var p Player
- err := rows.Scan(&p.name, &p.flag, &p.score)
+ err := rows.Scan(&p.name, &p.flag, &p.score, &p.ts)
if err != nil {
return nil, err
}
(DIR) diff --git a/html.go b/html.go
t@@ -0,0 +1,97 @@
+package main
+
+import (
+ "os"
+ "fmt"
+ "html/template"
+ "github.com/dustin/go-humanize"
+)
+
+type Boardline struct {
+ Name string
+ Rank string
+ Flag string
+ Score string
+}
+
+type Template struct {
+ Players []Boardline
+ Placeholders []Boardline
+}
+
+var html string = `
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <meta name="author" content="z3bra">
+ <meta name="viewport" content="width=device-width">
+ <link rel="stylesheet" type="text/css" href="/arcade.css">
+ <link rel="stylesheet" type="text/css" href="/glitch.css">
+ <link rel="stylesheet" type="text/css" href="/board.css">
+ <link rel="icon" type="image/ico" href="/favicon.ico" />
+ <title>High scores</title>
+</head>
+<body>
+<h1 class="title glitch" data-text="CYB3R HUNT">CYB3R HUNT</h1>
+<h2>HIGH SCORES</h2>
+<div class="board">
+<table>
+ <thead><tr><th>RANK</th><th>NAME</th><th>FLAGS</th><th>SCORE</th></tr></thead>
+ <tbody>
+{{range .Players}} <tr><td>{{.Rank}}<td>{{.Name}}</td><td>{{.Flag}}</td><td>{{.Score}}</td></tr>
+{{end}}
+{{range .Placeholders}} <tr class='unset'><td>{{.Rank}}<td>{{.Name}}</td><td>{{.Flag}}</td><td>{{.Score}}</td></tr>
+{{end}}
+ </tbody>
+</table>
+</div>
+</body>
+</html>
+`
+
+func (a *Application) GenerateHTML() {
+ players, err := db_ranked_players(a.db, 0, -1)
+ if err != nil {
+ panic(err)
+ }
+
+ data := Template{}
+ for i:=0; i<len(players); i++ {
+ players[i].db = a.db
+ data.Players = append(data.Players, Boardline{
+ Name: players[i].name,
+ Rank: players[i].RankStr(),
+ Flag: players[i].FlagStr(),
+ Score: fmt.Sprintf("%d", players[i].score),
+ })
+ }
+
+ /* fill with placeholder data */
+ if len(data.Players) < 10 {
+ for i:=len(data.Players); i<10; i++ {
+ data.Placeholders = append(data.Placeholders, Boardline{
+ Name: "AAA",
+ Rank: humanize.Ordinal(i+1),
+ Flag: ".....",
+ Score: "00000",
+ })
+ }
+ }
+
+ tmpl, err := template.New("board").Parse(html)
+ if err != nil {
+ panic(err)
+ }
+
+ f, err := os.OpenFile(a.html, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
+ if err != nil {
+ panic(err)
+ }
+ defer f.Close()
+
+ err = tmpl.Execute(f, data)
+ if err != nil {
+ panic(err)
+ }
+}
(DIR) diff --git a/main.go b/main.go
t@@ -31,12 +31,11 @@ import (
const (
BOARD_WIDTH int = 26
BOARD_HEIGHT int = 15
- DB string = "leaderboard.db"
+ HTML string = "score.html"
+ DB string = "score.db"
TOKEN_REMINDER string = `TOKEN FOR %s: %s
-
This token will be requested when you submit flag #%d.
Save it carefully, and do not share it with anyone.
-
Good bye hunter. Good luck.
`
)
t@@ -45,6 +44,7 @@ type Application struct {
flag int
db *sql.DB
app *tview.Application
+ html string
pages *tview.Pages
frame *tview.Grid
board *tview.Flex
t@@ -122,6 +122,7 @@ func pageToken() tview.Primitive {
}
cyboard.HighlightBoard(cyboard.player.ScoreRank())
cyboard.pages.SwitchToPage("board")
+ cyboard.GenerateHTML()
})
return center(40, 1, input)
t@@ -137,6 +138,11 @@ func pageBoard() tview.Primitive {
func main() {
var err error
+ html := flag.String("o", HTML, "Output HTML file")
+ db := flag.String("d", DB, "Database file")
+
+ flag.Parse()
+
// Override default borders
tview.Borders.HorizontalFocus = tview.BoxDrawingsLightHorizontal
tview.Borders.VerticalFocus = tview.BoxDrawingsLightVertical
t@@ -152,13 +158,14 @@ func main() {
tview.Styles.GraphicsColor = tcell.ColorDefault
tview.Styles.PrimaryTextColor = tcell.ColorDefault
- cyboard.db, err = db_init(DB)
+ cyboard.db, err = db_init(*db)
if err != nil {
panic(err)
}
defer cyboard.db.Close()
cyboard.flag = 0
+ cyboard.html = *html
cyboard.app = tview.NewApplication()
cyboard.pages = tview.NewPages()
cyboard.frame = tview.NewGrid()
t@@ -170,7 +177,6 @@ func main() {
cyboard.pages.AddPage("token", pageToken(), true, false)
cyboard.pages.AddPage("board", pageBoard(), true, false)
- flag.Parse()
args := flag.Args()
if len(args) > 1 || (len(args) == 1 && args[0] == "help") {
(DIR) diff --git a/player.go b/player.go
t@@ -49,8 +49,6 @@ func tokenize(name string) (string, string, error) {
key := randbuf(12)
salt := base32.StdEncoding.EncodeToString([]byte(name))
- //token := []byte(name)
- //token = append(token, key...)
token := key
token = append(token, []byte(name)...)
t@@ -104,10 +102,10 @@ func (p *Player) ScoreRank() int {
count(*)
FROM score
WHERE
- score >= ? OR (score == ? AND ts <= ?)
+ name != ? AND (score > ? OR (score == ? AND ts <= ?))
;`
- row := p.db.QueryRow(query, p.score, p.score, p.ts)
+ row := p.db.QueryRow(query, p.name, p.score, p.score, p.ts)
row.Scan(&count)
return count
}
t@@ -139,6 +137,10 @@ func (p *Player) FlagStr() string {
return fmt.Sprintf("%s", str)
}
+func (p *Player) RankStr() string {
+ return humanize.Ordinal(p.ScoreRank() + 1)
+}
+
func (p *Player) Exists() bool {
forbidden := []string{"KKK"}
for i := 0; i<len(forbidden); i++ {
t@@ -177,7 +179,10 @@ func (p *Player) Submit(flag int) error {
p.score += 10 * flag
}
- p.Update()
+ err := p.Update()
+ if err != nil {
+ return err
+ }
return nil
}
(DIR) diff --git a/playerbox.go b/playerbox.go
t@@ -76,6 +76,7 @@ func PlayerBoxName(p *Player) *tview.TextView {
cyboard.Fatal(err)
}
cyboard.HighlightBoard(p.ScoreRank())
+ cyboard.GenerateHTML()
} else {
cyboard.Popup("NOPE", "Player name unavailable\nPlease pick another one")
}