package capturetheflag import ( "database/sql" "fmt" "log" "math/rand" "strings" "time" "github.com/jmoiron/sqlx" "github.com/velour/catbase/bot" "github.com/velour/catbase/bot/msg" ) const ( SECONDS_PER_SECOND = 1 SECONDS_PER_MINUTE = 60 SECONDS_PER_HOUR = SECONDS_PER_MINUTE * SECONDS_PER_MINUTE SECONDS_PER_DAY = SECONDS_PER_HOUR * 24 SECONDS_PER_YEAR = SECONDS_PER_DAY * 365 ) type CaptureTheFlagPlugin struct { Bot bot.Bot db *sqlx.DB } type User struct { UserId int64 `db:"id"` UserName string `db:"user"` } type Flag struct { FlagId int64 `db:"id"` FlagName string `db:"flag"` } type Possession struct { PossessionId int64 `db:"id"` UserId int64 `db:"user"` FlagId int64 `db:"flag"` Active bool `db:"active"` LastGrab int64 `db:"lastgrab"` PossessionDuration int64 `db:"duration"` } // NewDicePlugin creates a new DicePlugin with the Plugin interface func New(bot bot.Bot) *CaptureTheFlagPlugin { rand.Seed(time.Now().Unix()) if _, err := bot.DB().Exec(`create table if not exists ctf_user ( id integer primary key, user string );`); err != nil { log.Fatal(err) } if _, err := bot.DB().Exec(`create table if not exists ctf_flag ( id integer primary key, flag string );`); err != nil { log.Fatal(err) } if _, err := bot.DB().Exec(`create table if not exists ctf_possession ( id integer primary key, user integer, flag integer, active integer, lastgrab integer, duration integer );`); err != nil { log.Fatal(err) } return &CaptureTheFlagPlugin{ Bot: bot, db: bot.DB(), } } func (p *CaptureTheFlagPlugin) Message(message msg.Message) bool { if !message.Command { return false } lowercase := strings.ToLower(message.Body) tokens := strings.Fields(lowercase) numTokens := len(tokens) log.Println(lowercase) if numTokens >= 2 && tokens[0] == "capture" { log.Printf("%s trying to capture %s.", message.User.Name, strings.Join(tokens[1:], " ")) response, err := p.tryCapture(message.User.Name, strings.Join(tokens[1:], " ")) if err == nil { log.Print("Capture success.") log.Print(response) p.Bot.SendMessage(message.Channel, response) return true } else { log.Print("Capture failed.") } } return false } func (p *CaptureTheFlagPlugin) Help(channel string, parts []string) { p.Bot.SendMessage(channel, "There are flags. Capture them.") } func (p *CaptureTheFlagPlugin) Event(kind string, message msg.Message) bool { return false } func (p *CaptureTheFlagPlugin) BotMessage(message msg.Message) bool { return false } func (p *CaptureTheFlagPlugin) RegisterWeb() *string { return nil } func (p *CaptureTheFlagPlugin) createUser(username string) (*User, error) { res, err := p.db.Exec(`insert into ctf_user (user) values (?);`, username) if err != nil { log.Print(err) return nil, err } id, err := res.LastInsertId() if err != nil { log.Print(err) return nil, err } return &User{ UserId: id, UserName: username, }, nil } func (p *CaptureTheFlagPlugin) getOrCreateUser(username string) (*User, bool, error) { var usr User err := p.db.QueryRowx(`select * from ctf_user where user = ? LIMIT 1;`, username).StructScan(&usr) if err != nil { if err == sql.ErrNoRows { usrPtr, err := p.createUser(username) return usrPtr, true, err } log.Print(err) return nil, false, err } return &usr, false, nil } func (p *CaptureTheFlagPlugin) getUserById(id int64) (*User, error) { var usr User err := p.db.QueryRowx(`select * from ctf_user where id = ? LIMIT 1;`, id).StructScan(&usr) if err != nil { log.Print(err) return nil, err } return &usr, nil } func (p *CaptureTheFlagPlugin) createFlag(flagName string) (*Flag, error) { res, err := p.db.Exec(`insert into ctf_flag (flag) values (?);`, flagName) if err != nil { log.Print(err) return nil, err } id, err := res.LastInsertId() if err != nil { log.Print(err) return nil, err } return &Flag{ FlagId: id, FlagName: flagName, }, nil } func (p *CaptureTheFlagPlugin) getOrCreateFlag(flagName string) (*Flag, bool, error) { var flag Flag err := p.db.QueryRowx(`select * from ctf_flag where flag = ? LIMIT 1;`, flagName).StructScan(&flag) if err != nil { if err == sql.ErrNoRows { flagPtr, err := p.createFlag(flagName) return flagPtr, true, err } log.Print(err) return nil, false, err } return &flag, false, nil } func (p *CaptureTheFlagPlugin) transferActivePossession(pos *Possession, usr *User, flag *Flag) (string, error) { extraTime := time.Now().Unix() - pos.LastGrab newTime := extraTime + pos.PossessionDuration _, err := p.db.Exec(`update ctf_possession set active = 0, duration = ? where id = ?;`, newTime, pos.PossessionId) if err != nil { log.Print(err) return "", err } last, err := p.getUserById(pos.UserId) if err != nil { log.Print(err) return "Flag possession dropped! (failed db query)", err } myPos, err := p.getPossession(usr, flag) if err != nil { log.Print(err) return "Flag possession dropped! (failed db query)", err } if myPos == nil { err = p.createFirstPossession(usr, flag) if err != nil { log.Print(err) return "Flag possession dropped! (failed db query)", err } } else { _, err = p.db.Exec(`update ctf_possession set active = 1, lastgrab = ? where id = ?;`, time.Now().Unix(), myPos.PossessionId) if err != nil { log.Print(err) return "Flag possession dropped! (failed db query)", err } } return fmt.Sprintf("%s now has '%s'. %s had it last for %d seconds (total %d seconds).", usr.UserName, flag.FlagName, last.UserName, sexyTime(extraTime), sexyTime(newTime)), nil } func (p *CaptureTheFlagPlugin) createFirstPossession(usr *User, flag *Flag) error { _, err := p.db.Exec(`insert into ctf_possession (user, flag, active, lastgrab, duration) values (?, ?, ?, ?, ?);`, usr.UserId, flag.FlagId, true, time.Now().Unix(), 0) if err != nil { log.Print(err) } return err } func (p *CaptureTheFlagPlugin) getActivePossession(flag *Flag) (*Possession, error) { var pos Possession err := p.db.QueryRowx(`select * from ctf_possession where active = 1 and flag = ? LIMIT 1;`, flag.FlagId).StructScan(&pos) if err != nil { if err == sql.ErrNoRows { return nil, nil } log.Print(err) return nil, err } return &pos, nil } func (p *CaptureTheFlagPlugin) getPossession(usr *User, flag *Flag) (*Possession, error) { var pos Possession err := p.db.QueryRowx(`select * from ctf_possession where user = ? and flag = ? LIMIT 1;`, usr.UserId, flag.FlagId).StructScan(&pos) if err != nil { if err == sql.ErrNoRows { return nil, nil } log.Print(err) return nil, err } return &pos, nil } func (p *CaptureTheFlagPlugin) tryCapture(username, what string) (string, error) { usr, _, err := p.getOrCreateUser(username) if err != nil { log.Print(err) return "", err } flag, newFlag, err := p.getOrCreateFlag(what) if err != nil { log.Print(err) return "", err } if !newFlag { pos, err := p.getActivePossession(flag) if err != nil { log.Print(err) return "", err } if pos == nil { err = p.createFirstPossession(usr, flag) if err != nil { log.Print(err) return "", err } return fmt.Sprintf("You now have '%s'", what), nil } else if pos.UserId == usr.UserId { return fmt.Sprintf("You already have '%s'", what), nil } response, err := p.transferActivePossession(pos, usr, flag) if err != nil { log.Print(err) return "", err } return response, nil } else { err = p.createFirstPossession(usr, flag) if err != nil { log.Print(err) return "", err } return fmt.Sprintf("You now have '%s'", what), nil } } func sexyTime(seconds int64) string { timeString := "" units := []int64{SECONDS_PER_YEAR, SECONDS_PER_DAY, SECONDS_PER_HOUR, SECONDS_PER_MINUTE, SECONDS_PER_SECOND} labels := []string{"year", "day", "hour", "minute", "second"} for i, unit := range units { howMany := seconds / unit if howMany > 0 { seconds -= howMany * unit label := labels[i] if howMany > 1 { label += "s" } timeString = fmt.Sprintf("%s %d %s", timeString, howMany, label) } } return strings.TrimSpace(timeString) }