tplayer.go - scoreboard - Interactive scoreboard for CTF-like games
 (HTM) git clone git://git.z3bra.org/scoreboard.git
 (DIR) Log
 (DIR) Files
 (DIR) Refs
       ---
       tplayer.go (4301B)
       ---
            1 // Copyright 2016 The Tcell Authors
            2 //
            3 // Licensed under the Apache License, Version 2.0 (the "License");
            4 // you may not use file except in compliance with the License.
            5 // You may obtain a copy of the license at
            6 //
            7 //    http://www.apache.org/licenses/LICENSE-2.0
            8 //
            9 // Unless required by applicable law or agreed to in writing, software
           10 // distributed under the License is distributed on an "AS IS" BASIS,
           11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
           12 // See the License for the specific language governing permissions and
           13 // limitations under the License.
           14 
           15 package main
           16 
           17 import (
           18         "crypto/rand"
           19         "database/sql"
           20         "encoding/base32"
           21         "errors"
           22         "fmt"
           23         "strings"
           24         "time"
           25         "golang.org/x/crypto/scrypt"
           26         "github.com/dustin/go-humanize"
           27 
           28         _ "modernc.org/sqlite"
           29 )
           30 
           31 type Player struct {
           32         db *sql.DB
           33         id int
           34         token string
           35         name string
           36         flags []Flag
           37         score int
           38         ts int64
           39 }
           40 
           41 /* Randomize a buffer of a given length */
           42 func randbuf(length int64) []byte {
           43         b := make([]byte, length)
           44         _, err := rand.Read(b)
           45         if err != nil {
           46                 panic(err)
           47         }
           48         return b
           49 }
           50 
           51 /* Generate a random base32 token using the provided input as a salt */
           52 func mktoken(key []byte, salt string) (string, string, error) {
           53         salt32 := base32.StdEncoding.EncodeToString([]byte(salt))
           54 
           55         token := key
           56         token = append(token, []byte(salt)...)
           57 
           58         dk, err := scrypt.Key(key, []byte(salt32), 1<<15, 8, 1, 32)
           59         if err != nil {
           60                 return "", "", err
           61         }
           62         hash32 := base32.StdEncoding.EncodeToString(dk)
           63         token32 := base32.StdEncoding.EncodeToString(token)
           64 
           65         return token32, hash32, nil
           66 }
           67 
           68 func hashtoken(token string) (string, error) {
           69         var err error
           70 
           71         blob, err := base32.StdEncoding.DecodeString(token)
           72         if err != nil {
           73                 return "", err
           74         }
           75 
           76         key := blob[:12]
           77         name := string(blob[12:])
           78 
           79         salt := base32.StdEncoding.EncodeToString([]byte(name))
           80 
           81         dk, err := scrypt.Key(key, []byte(salt), 1<<15, 8, 1, 32)
           82         if err != nil {
           83                 return "", err
           84         }
           85         hash := base32.StdEncoding.EncodeToString(dk)
           86 
           87         return hash, nil
           88 }
           89 
           90 /* Register a user in the database */
           91 func (p *Player) Register() error {
           92         var hash string
           93         var err error
           94 
           95         p.ts = time.Now().Unix()
           96         p.token, hash, err = mktoken(randbuf(12), p.name)
           97         if err != nil {
           98                 return err
           99         }
          100 
          101         return db_add_player(p.db, *p, hash)
          102 }
          103 
          104 func (p *Player) Fetch() error {
          105         var err error
          106         var hash string
          107 
          108         if (p.token != "") {
          109                 hash, err = hashtoken(p.token)
          110         }
          111 
          112         /* Fill player struct with basic info */
          113         p, err = db_get_user(p.db, p, hash)
          114         if errors.Is(err, sql.ErrNoRows) {
          115                 return errors.New("Player not found")
          116         }
          117 
          118         return nil
          119 }
          120 
          121 func (p *Player) Rank() int {
          122         rank, err := db_get_user_rank(p.db, p.name)
          123         if err != nil {
          124                 return -1
          125         }
          126 
          127         return rank
          128 }
          129 
          130 func (p *Player) FlagStr() string {
          131         return fmt.Sprintf("%2d/%d", len(p.flags), len(scoreboard.flag_ref))
          132 }
          133 
          134 func (p *Player) FlagsStr() string {
          135         var flaglist strings.Builder
          136 
          137         flaglist.WriteString("[-::-]")
          138         for _, f := range p.flags {
          139                 flaglist.WriteString(fmt.Sprintf("%s %s\n", f.badge, f.value))
          140         }
          141 
          142         var hasflag = func(a []Flag, f Flag) bool {
          143                 for _, r := range a {
          144                         if f.value == r.value {
          145                                 return true
          146                         }
          147                 }
          148                 return false
          149         }
          150         for _, r := range scoreboard.flag_ref {
          151                 if !hasflag(p.flags, r) {
          152                         flaglist.WriteString(fmt.Sprintf("[::d]%s %64s[::-]\n", r.badge, ""))
          153                 }
          154         }
          155 
          156         return flaglist.String();
          157 }
          158 
          159 func (p *Player) BadgeStr() string {
          160         var badges strings.Builder
          161 
          162         badges.WriteString("[-::-]")
          163         for n, f := range p.flags {
          164                 if n > 0 && n % 8 == 0 {
          165                         badges.WriteString("\n")
          166                 }
          167                 badges.WriteString(fmt.Sprintf(`%s`, f.badge))
          168         }
          169 
          170         return badges.String();
          171 }
          172 
          173 func (p *Player) RankStr() string {
          174         return humanize.Ordinal(p.Rank() + 1)
          175 }
          176 
          177 func (p *Player) Exists() bool {
          178         forbidden := []string{"KKK"}
          179         for i := 0; i<len(forbidden); i++ {
          180                 if p.name == forbidden[i] {
          181                         return true
          182                 }
          183         }
          184 
          185         _, err := db_get_user(p.db, p, "")
          186 
          187         return !errors.Is(err, sql.ErrNoRows)
          188 }
          189 
          190 func (p *Player) HasFlag(flag Flag) bool {
          191         for _, f := range p.flags {
          192                 if f.value == flag.value {
          193                         return true
          194                 }
          195         }
          196         return false
          197 }
          198 
          199 func (p *Player) Submit(flag Flag) error {
          200         var err error
          201 
          202         if p.HasFlag(flag) {
          203                 return errors.New("Flag already submitted")
          204         } else {
          205                 p.ts = time.Now().Unix()
          206                 err = db_add_player_flag(p.db, *p, flag)
          207                 if err != nil {
          208                         return err
          209                 }
          210         }
          211 
          212         p.flags = append(p.flags, flag)
          213 
          214         return nil
          215 }
          216