package main import ( "crypto/rand" "encoding/hex" "encoding/json" "fmt" "math" "regexp" "sort" "strconv" "time" "gonum.org/v1/plot" "gonum.org/v1/plot/plotter" "gonum.org/v1/plot/plotutil" "gonum.org/v1/plot/vg" tb "gopkg.in/tucnak/telebot.v2" ) type Bot struct { bot *tb.Bot Config *TelegramConfig } type BotActionEntry struct { Action string CompanyID uint8 UserID int Time time.Time } var botActionMap map[string]*BotActionEntry func (b *Bot) Start() { var err error b.bot, err = tb.NewBot(tb.Settings{ Token: b.Config.Token, URL: b.Config.URL, Poller: &tb.LongPoller{Timeout: 10 * time.Second}, }) failError(err, "Bot.Start() : registering bot") botActionMap = make(map[string]*BotActionEntry) b.BotHandlers() } func (b *Bot) SendUser(id int64, msg string) { u := tb.User{ ID: int(id), } _, err := b.bot.Send(&u, msg) logErrorDebug(err, "Bot.SendUser()") } func (b *Bot) SendChat(chatID int64, text string) { opt := tb.SendOptions{ ParseMode: tb.ModeDefault, } ch := tb.Chat{ ID: chatID, } _, err := b.bot.Send(&ch, text, &opt) logErrorDebug(err, "Bot.SendChat()") } func (b *Bot) SendChatImage(chatID int64, image string) { opt := tb.SendOptions{ ParseMode: tb.ModeDefault, } ch := tb.Chat{ ID: chatID, } photo := &tb.Photo{File: tb.FromDisk(image)} _, err := b.bot.Send(&ch, photo, &opt) logErrorDebug(err, "Bot.SendChatImage()") } func (b *Bot) BotHandlers() { b.bot.Handle("/pause", botPause) b.bot.Handle("/unpause", botUnpause) b.bot.Handle("/register", botRegister) b.bot.Handle("/deregister", botDeregister) b.bot.Handle("/delete", botDelete) b.bot.Handle("/companies", botCompanies) b.bot.Handle("/clients", botClients) b.bot.Handle("/passwd", botPasswd) b.bot.Handle("/say", botSay) b.bot.Handle("/help", botHelp) b.bot.Handle("/version", botVersion) b.bot.Handle("/save", botSave) b.bot.Handle("/reset", botReset) b.bot.Handle("/ready", botReady) b.bot.Handle("/start", botStart) b.bot.Handle("/players", botPlayers) b.bot.Handle("/give", botGive) b.bot.Handle("/take", botTake) b.bot.Handle("/transfer", botTransfer) b.bot.Handle("/value", botGraphValue) b.bot.Handle("/value_delta", botGraphValueDelta) b.bot.Handle("/planes", botGraphPlanes) b.bot.Handle(tb.OnPhoto, botPhoto) b.bot.Handle(tb.OnChannelPost, botChannelPost) b.bot.Handle(tb.OnQuery, botQuery) b.bot.Handle(tb.OnText, botText) b.bot.Handle(tb.OnDocument, botDocument) go func() { time.Sleep(time.Second) b.SendUser(b.Config.AdminID, fmt.Sprintf("Started (%s)", version)) }() b.bot.Start() } func botPause(m *tb.Message) { for userID, cc := range cfg.Clients { if userID == m.Sender.ID { if co, ok := srv.Status.Companies[cc.CompanyID]; ok { if clt, ok2 := srv.Status.Clients[co.ClientID]; ok2 { clt.Paused = true if !srv.Status.Paused { srv.Pause() } else { bot.SendChat(bot.Config.ChatID, "Game already paused.") } return } } } } bot.SendChat(bot.Config.ChatID, "You are not playing and cannot pause the game.") return } func botUnpause(m *tb.Message) { PrintText(m) for userID, cc := range cfg.Clients { if userID == m.Sender.ID { if co, ok := srv.Status.Companies[cc.CompanyID]; ok { if clt, ok2 := srv.Status.Clients[co.ClientID]; ok2 { clt.Paused = false if !srv.NeedPause() { srv.Unpause() } else { bot.SendChat(bot.Config.ChatID, fmt.Sprintf("Cannot unpause : %s", srv.NeedPauseReason())) } return } } } } bot.SendChat(bot.Config.ChatID, "You are not playing and cannot unpause the game.") return } func botDelete(m *tb.Message) { r := regexp.MustCompile("\\/delete (?P[0-9]+)") id := uint8(255) if r.MatchString(m.Text) { ID64, _ := strconv.ParseInt(r.ReplaceAllString(m.Text, "${CompanyID}"), 10, 64) if m.Sender.ID == int(bot.Config.AdminID) { id = uint8(ID64) } else if cc, ok := cfg.Clients[m.Sender.ID]; ok && cc.CompanyID == uint8(ID64) { id = cc.CompanyID } else { bot.SendChat(m.Chat.ID, "Not authorized to delete") return } } else if cc, ok := cfg.Clients[m.Sender.ID]; ok { id = cc.CompanyID } else { bot.SendChat(m.Chat.ID, "User not registered") return } if id == 255 { bot.SendChat(m.Chat.ID, "No company registered") return } if co, ok := srv.Status.Companies[id]; ok { b := make([]byte, 8) _, err := rand.Read(b) logErrorDebug(err, "botDelete : rand.Read") if err != nil { bot.SendChat(m.Chat.ID, "internal error") return } c := hex.EncodeToString(b) d := &BotActionEntry{ Action: "DeleteCompany", CompanyID: id, UserID: m.Sender.ID, Time: time.Now(), } botActionMap[c] = d bot.SendChat(m.Chat.ID, fmt.Sprintf("Press /delete_%s to delete '%s'", c, co.Name)) } else { bot.SendChat(m.Chat.ID, "Company doesn't exist") } return } func botActuallyDelete(m *tb.Message) { r := regexp.MustCompile("^\\/delete_(?P[a-f0-9]{16})$") ref := r.ReplaceAllString(m.Text, "${Ref}") a, ok := botActionMap[ref] if !ok { bot.SendChat(m.Chat.ID, "No corresponding request.") return } if a.Action != "DeleteCompany" { bot.SendChat(m.Chat.ID, "Not a delete request.") return } if a.UserID != m.Sender.ID { bot.SendChat(m.Chat.ID, "Requesting user has to confirm himself.") return } if time.Now().Sub(a.Time) > time.Minute { bot.SendChat(m.Chat.ID, "Request expired.") } else { srv.DeleteCompany(a.CompanyID) bot.SendChat(m.Chat.ID, "Company deleted.") } delete(botActionMap, ref) return } func botCompanies(m *tb.Message) { str := "Companies :" for k, v := range srv.Status.Companies { str = str + "\r\n" + fmt.Sprintf(" - %s (%d - %t)", v.Name, k, v.Protected) } bot.SendChat(m.Chat.ID, str) } func botClients(m *tb.Message) { str := "Clients :" for k, v := range srv.Status.Clients { str = str + "\r\n" + fmt.Sprintf(" - %s (%d) : company #%d", v.Name, k, v.CompanyID) } bot.SendChat(m.Chat.ID, str) } func botReset(m *tb.Message) { if m.Sender.ID != int(cfg.Telegram.AdminID) { bot.SendChat(m.Chat.ID, "Only the admin can use this command.") return } if !cfg.Game.Started { bot.SendChat(m.Chat.ID, "Game is not started.") return } b := make([]byte, 8) _, err := rand.Read(b) logErrorDebug(err, "botReset : rand.Read") if err != nil { bot.SendChat(m.Chat.ID, "internal error") return } c := hex.EncodeToString(b) d := &BotActionEntry{ Action: "ResetGame", CompanyID: 0, UserID: m.Sender.ID, Time: time.Now(), } botActionMap[c] = d bot.SendChat(m.Chat.ID, fmt.Sprintf("Press /reset_%s to reset the game", c)) return } func botActuallyReset(m *tb.Message) { r := regexp.MustCompile("^\\/reset_(?P[a-f0-9]{16})$") ref := r.ReplaceAllString(m.Text, "${Ref}") a, ok := botActionMap[ref] if !ok { bot.SendChat(m.Chat.ID, "No corresponding request.") return } if a.Action != "ResetGame" { bot.SendChat(m.Chat.ID, "Not a reset request.") return } if a.UserID != m.Sender.ID { bot.SendChat(m.Chat.ID, "Requesting user has to confirm himself.") return } if time.Now().Sub(a.Time) > time.Minute { bot.SendChat(m.Chat.ID, "Request expired.") } else { cfg.Save("backup." + *configFlag) cfg.Game.Started = false cfg.Stats = make(map[uint8]map[string]*Stat) for _, cc := range cfg.Clients { cc.Ready = false cc.CompanyID = 255 cc.Online = false cc.TimeLeft = 0 } bot.SendChat(m.Chat.ID, "Game resetted.") } delete(botActionMap, ref) return } func botStart(m *tb.Message) { if cfg.Game.Started { bot.SendChat(m.Chat.ID, "Game already started.") return } actuallyReady := true for _, cc := range cfg.Clients { if !cc.Ready { actuallyReady = false } } if m.Sender.ID != int(cfg.Telegram.AdminID) && !actuallyReady { bot.SendChat(m.Chat.ID, "Not all players are ready. Only the admin can force the start.") } b := make([]byte, 8) _, err := rand.Read(b) logErrorDebug(err, "botStart : rand.Read") if err != nil { bot.SendChat(m.Chat.ID, "internal error") return } c := hex.EncodeToString(b) d := &BotActionEntry{ Action: "StartGame", CompanyID: 0, UserID: m.Sender.ID, Time: time.Now(), } botActionMap[c] = d bot.SendChat(m.Chat.ID, fmt.Sprintf("Press /start_%s to actually start game", c)) return } func botActuallyStart(m *tb.Message) { r := regexp.MustCompile("^\\/start_(?P[a-f0-9]{16})$") ref := r.ReplaceAllString(m.Text, "${Ref}") a, ok := botActionMap[ref] if !ok { bot.SendChat(m.Chat.ID, "No corresponding request.") return } if a.Action != "StartGame" { bot.SendChat(m.Chat.ID, "Not a game start request.") return } if a.UserID != m.Sender.ID { bot.SendChat(m.Chat.ID, "Requesting user has to confirm himself.") return } if time.Now().Sub(a.Time) > time.Minute { bot.SendChat(m.Chat.ID, "Request expired.") } else { cfg.Game.Started = true cfg.Game.StartDate = time.Now() for _, cc := range cfg.Clients { cc.Ready = true cc.TimeLeft = cfg.Game.StartingAllotment } bot.SendChat(m.Chat.ID, "Game started !") } delete(botActionMap, ref) return } func botReady(m *tb.Message) { if cfg.Game.Started { bot.SendChat(m.Chat.ID, "Game is already started.") return } cc, ok := cfg.Clients[m.Sender.ID] if !ok { bot.SendChat(m.Chat.ID, "Player not registered") return } ready := 0 waiting := 0 for _, cc2 := range cfg.Clients { if cc2.Ready { ready++ } else { waiting++ } } if cc.Ready { cc.Ready = false ready-- waiting++ bot.SendChat(m.Chat.ID, fmt.Sprintf("Player not ready anymore. Only %d players ready now.", ready)) } else { cc.Ready = true ready++ waiting-- bot.SendChat(m.Chat.ID, fmt.Sprintf("Player is now ready. Still waiting for %d players.", waiting)) } return } func botPlayers(m *tb.Message) { if cfg.Game.Started { botPlayersStarted(m) } else { botPlayersWaiting(m) } return } func botPlayersStarted(m *tb.Message) { d1 := time.Now().Sub(cfg.Game.StartDate) days := int(time.Duration(d1.Hours()) / 24) d2 := time.Duration(days+1)*(time.Hour)*24 - d1 str := fmt.Sprintf("Update in %s\r\n", d2.Round(time.Second)) online := "" for _, cc := range cfg.Clients { if cc.Online { if co, ok := srv.Status.Companies[cc.CompanyID]; ok { online = online + fmt.Sprintf(" - %s (%s) : %s", cc.Username, cc.TimeLeft.Round(time.Second), co.Name) + "\r\n" } else { online = online + fmt.Sprintf(" - %s (%s) : none", cc.Username, cc.TimeLeft.Round(time.Second)) + "\r\n" } } } offline := "" for _, cc := range cfg.Clients { if co, ok := srv.Status.Companies[cc.CompanyID]; ok && !cc.Online { offline = offline + fmt.Sprintf(" - %s (%s) : %s", cc.Username, cc.TimeLeft.Round(time.Second), co.Name) + "\r\n" } else { offline = offline + fmt.Sprintf(" - %s (%s) : none", cc.Username, cc.TimeLeft.Round(time.Second)) + "\r\n" } } if len(online) > 0 { str = str + "Players online :\r\n" + online } if len(offline) > 0 { str = str + "Players offline :\r\n" + offline } bot.SendChat(m.Chat.ID, str) } func botPlayersWaiting(m *tb.Message) { msg := "Player status :\r\n" ready := "" waiting := "" for _, cc := range cfg.Clients { if cc.Ready { ready = ready + fmt.Sprintf(" - %s", cc.Username) + "\r\n" } else { waiting = waiting + fmt.Sprintf(" - %s", cc.Username) + "\r\n" } } if len(ready) > 0 { msg = msg + "Players ready :\r\n" + ready } if len(waiting) > 0 { msg = msg + "Waiting for :\r\n" + waiting } bot.SendChat(m.Chat.ID, msg) } func botGive(m *tb.Message) { if m.Sender.ID != int(cfg.Telegram.AdminID) { bot.SendChat(m.Chat.ID, "Only the admin can use this command.") return } r := regexp.MustCompile("^\\/give @(?P[a-zA-Z0-9]+) (?P[a-z0-9]+)") if !r.MatchString(m.Text) { bot.SendChat(m.Chat.ID, "Wrong format.") return } uStr := r.ReplaceAllString(m.Text, "${Username}") var u int for ccID, cc := range cfg.Clients { if cc.Username == uStr { u = ccID } } if u == 0 { bot.SendChat(m.Chat.ID, "No such user found.") return } dStr := r.ReplaceAllString(m.Text, "${Duration}") d, err := time.ParseDuration(dStr) logErrorDebug(err, "botGive : time.ParseDuration") if err != nil { bot.SendChat(m.Chat.ID, "Cannot parse duration.") } cc := cfg.Clients[u] cc.TimeLeft += d bot.SendChat(m.Chat.ID, fmt.Sprintf("@%s now has %s left.", uStr, cc.TimeLeft.Round(time.Second))) } func botVersion(m *tb.Message) { bot.SendChat(m.Chat.ID, version) return } func botSave(m *tb.Message) { if m.Sender.ID != int(cfg.Telegram.AdminID) { bot.SendChat(m.Chat.ID, "Only the admin can use this command.") return } r := regexp.MustCompile("^\\/save (?P[a-zA-Z0-9._\\/]+)") if !r.MatchString(m.Text) { bot.SendChat(m.Chat.ID, "Wrong usage.") return } filename := r.ReplaceAllString(m.Text, "${Filename}") err := cfg.Save(filename + ".json") logErrorDebug(err, "botSave : Config.Save(%s)", filename+".json") if err != nil { bot.SendChat(m.Chat.ID, fmt.Sprintf("Error : %s", err)) } else { bot.SendChat(m.Chat.ID, "Saved.") } return } func botTransfer(m *tb.Message) { r := regexp.MustCompile("^\\/transfer @(?P[a-zA-Z0-9]+) (?P[a-z0-9]+)") if !r.MatchString(m.Text) { bot.SendChat(m.Chat.ID, "Wrong usage.") return } ccFrom, ok := cfg.Clients[m.Sender.ID] if !ok { bot.SendChat(m.Chat.ID, "Client not registered.") return } dStr := r.ReplaceAllString(m.Text, "${Duration}") d, err := time.ParseDuration(dStr) logErrorDebug(err, "botTransfer : time.ParseDuration") if err != nil { bot.SendChat(m.Chat.ID, "Cannot parse duration.") return } if ccFrom.TimeLeft < d { bot.SendChat(m.Chat.ID, "Not enough time left for user.") return } uStr := r.ReplaceAllString(m.Text, "${Username}") var toID int for ccID, cc := range cfg.Clients { if cc.Username == uStr { toID = ccID } } if toID == 0 { bot.SendChat(m.Chat.ID, "No such user found.") return } ccTo := cfg.Clients[toID] ccTo.TimeLeft += d ccFrom.TimeLeft -= d bot.SendChat(m.Chat.ID, fmt.Sprintf("@%s now has %s left.\r\n@%s has %s left.", uStr, ccTo.TimeLeft.Round(time.Second), ccFrom.Username, ccFrom.TimeLeft.Round(time.Second))) } func botTake(m *tb.Message) { if m.Sender.ID != int(cfg.Telegram.AdminID) { bot.SendChat(m.Chat.ID, "Only the admin can use this command.") return } r := regexp.MustCompile("^\\/take @(?P[a-zA-Z0-9]+) (?P[a-z0-9]+)") if !r.MatchString(m.Text) { bot.SendChat(m.Chat.ID, "Wrong format.") return } uStr := r.ReplaceAllString(m.Text, "${Username}") var u int for ccID, cc := range cfg.Clients { if cc.Username == uStr { u = ccID } } if u == 0 { bot.SendChat(m.Chat.ID, "No such user found.") return } dStr := r.ReplaceAllString(m.Text, "${Duration}") d, err := time.ParseDuration(dStr) logErrorDebug(err, "botGive : time.ParseDuration") if err != nil { bot.SendChat(m.Chat.ID, "Cannot parse duration.") return } cc := cfg.Clients[u] cc.TimeLeft -= d bot.SendChat(m.Chat.ID, fmt.Sprintf("@%s now has %s left.", uStr, cc.TimeLeft.Round(time.Second))) } func botHelp(m *tb.Message) { msg := "" msg = fmt.Sprintf("%s/pause - pause the game\r\n", msg) msg = fmt.Sprintf("%s/unpause - unpause the game\r\n", msg) msg = fmt.Sprintf("%s/register - register company\r\n", msg) msg = fmt.Sprintf("%s/deregister - deregister company\r\n", msg) msg = fmt.Sprintf("%s/delete - delete company\r\n", msg) msg = fmt.Sprintf("%s/companies - list companies\r\n", msg) msg = fmt.Sprintf("%s/clients - list clients\r\n", msg) msg = fmt.Sprintf("%s/give - give time to player\r\n", msg) msg = fmt.Sprintf("%s/take - take time from player\r\n", msg) msg = fmt.Sprintf("%s/transfer - transfer time between players\r\n", msg) msg = fmt.Sprintf("%s/passwd - change passwd\r\n", msg) msg = fmt.Sprintf("%s/say - send msg to the game\r\n", msg) msg = fmt.Sprintf("%s/reset - reset the game\r\n", msg) msg = fmt.Sprintf("%s/start - start the game\r\n", msg) msg = fmt.Sprintf("%s/ready - set player as ready\r\n", msg) msg = fmt.Sprintf("%s/players - list players\r\n", msg) msg = fmt.Sprintf("%s/version - version\r\n", msg) msg = fmt.Sprintf("%s/help - this\r\n", msg) bot.SendChat(m.Chat.ID, msg) return } func botSay(m *tb.Message) { r := regexp.MustCompile("^\\/say( )+(?P.+)$") if !r.MatchString(m.Text) { bot.SendChat(m.Chat.ID, "No message provided") return } msg := r.ReplaceAllString(m.Text, "${Message}") px := PacketAdminRCon{ Packet: Packet{PType: AdminPacketAdminRCon}, Command: fmt.Sprintf("say \"@%s says : %s\"", m.Sender.Username, msg), } srv.Send(px.Bytes()) return } func botPasswd(m *tb.Message) { cc, ok := cfg.Clients[m.Sender.ID] if !ok { bot.SendChat(m.Chat.ID, "User not registered.") return } if cc.CompanyID == 255 { bot.SendChat(m.Chat.ID, "No company registered.") return } co, ok := srv.Status.Companies[cc.CompanyID] if !ok { bot.SendChat(m.Chat.ID, "Registered company doesn't exist.") cc.CompanyID = 255 return } r := regexp.MustCompile("^\\/passwd( )+(?P[^ ]+)$") if !r.MatchString(m.Text) { bot.SendChat(m.Chat.ID, "No passwd provided") return } // we have a parameter cc.Passwd = r.ReplaceAllString(m.Text, "${Passwd}") err := bot.bot.Delete(m) logErrorDebug(err, "botPasswd : Delete") srv.SetPasswd(co.CompanyExtlID, cc.Passwd) bot.SendUser(int64(m.Sender.ID), fmt.Sprintf("Passwd set to \"%s\"", cc.Passwd)) return } func botDeregister(m *tb.Message) { r := regexp.MustCompile("^\\/deregister @(?P[a-zA-Z0-9]+)$") if r.MatchString(m.Text) { if m.Sender.ID != int(cfg.Telegram.AdminID) { bot.SendChat(m.Chat.ID, "Not admin.") return } uStr := r.ReplaceAllString(m.Text, "${Username}") var uID int for ccID, cc := range cfg.Clients { if cc.Username == uStr { uID = ccID } } if uID == 0 { bot.SendChat(m.Chat.ID, "No such user found.") return } cc := cfg.Clients[uID] if cc.CompanyID != 255 { for coID, co := range srv.Status.Companies { if coID == cc.CompanyID { cc.CompanyID = 255 bot.SendChat(m.Chat.ID, fmt.Sprintf("Deregistered from %s. %s playable left.", co.Name, cc.TimeLeft.Round(time.Second))) return } } logInfoAlert("botRegister : %s : no such CompanyID : %d", cc.Username, cc.CompanyID) cc.CompanyID = 255 bot.SendChat(m.Chat.ID, fmt.Sprintf("Registered company didn't exist anymore. %s playable left.", cc.TimeLeft.Round(time.Second))) return } return } cc, ok := cfg.Clients[m.Sender.ID] if !ok { cc = &ClientConfig{ UserID: m.Sender.ID, Username: m.Sender.Username, CompanyID: 255, TimeLeft: 0, } cfg.Clients[m.Sender.ID] = cc bot.SendChat(m.Chat.ID, "User isn't registered.") return } if cc.CompanyID != 255 { for coID, co := range srv.Status.Companies { if coID == cc.CompanyID { cc.CompanyID = 255 bot.SendChat(m.Chat.ID, fmt.Sprintf("Deregistered from %s. %s playable left.", co.Name, cc.TimeLeft.Round(time.Second))) return } } logInfoAlert("botRegister : %s : no such CompanyID : %d", cc.Username, cc.CompanyID) cc.CompanyID = 255 bot.SendChat(m.Chat.ID, fmt.Sprintf("Registered company didn't exist anymore. %s playable left.", cc.TimeLeft.Round(time.Second))) return } return } func botRegister(m *tb.Message) { cc, ok := cfg.Clients[m.Sender.ID] if !ok { cc = &ClientConfig{ UserID: m.Sender.ID, Username: m.Sender.Username, CompanyID: 255, TimeLeft: 0, Ready: cfg.Game.Started, } cfg.Clients[m.Sender.ID] = cc } else { if cc.CompanyID != 255 { for coID, co := range srv.Status.Companies { if coID == cc.CompanyID { bot.SendChat(m.Chat.ID, fmt.Sprintf("Already registered %s. Please /deregister first.", co.Name)) return } } logInfoAlert("botRegister : %s : no such CompanyID : %d", cc.Username, cc.CompanyID) cc.CompanyID = 255 } } coList := make(map[uint8]struct{}) for coID, _ := range srv.Status.Companies { coList[coID] = struct{}{} } for _, c := range cfg.Clients { if c.CompanyID != 255 { if _, ok := coList[c.CompanyID]; !ok { logInfoAlert("botRegister : %s : no such CompanyID : %d", c.Username, c.CompanyID) c.CompanyID = 255 } else { delete(coList, c.CompanyID) } } } if len(coList) == 0 { bot.SendChat(m.Chat.ID, "No company to register") return } r := regexp.MustCompile("^\\/register( )+(?P[a-zA-Z\\.\\-0-9 ]+)$") if r.MatchString(m.Text) { // we have a parameter coName := r.ReplaceAllString(m.Text, "${Company}") for coID, co := range srv.Status.Companies { if co.Name == coName { for _, c := range cfg.Clients { if c.CompanyID == coID { bot.SendChat(m.Chat.ID, fmt.Sprintf("Company %s is already registered to @%s", coName, c.Username)) return } } cc.CompanyID = coID if cc.TimeLeft == 0 { days := int(time.Now().Sub(cfg.Game.StartDate).Hours() / 24) cc.TimeLeft = cfg.Game.StartingAllotment + cfg.Game.DailyAllotment*time.Duration(days) } bot.SendChat(m.Chat.ID, fmt.Sprintf("@%s registered %s (with %s playable)", cc.Username, srv.Status.Companies[cc.CompanyID].Name, cc.TimeLeft)) return } } bot.SendChat(m.Chat.ID, fmt.Sprintf("Can't find company %s", coName)) return } if len(coList) == 1 { for id, _ := range coList { cc.CompanyID = id if cc.TimeLeft == 0 && cfg.Game.Started { days := int(time.Now().Sub(cfg.Game.StartDate).Hours() / 24) cc.TimeLeft = cfg.Game.StartingAllotment + cfg.Game.DailyAllotment*time.Duration(days) bot.SendChat(m.Chat.ID, fmt.Sprintf("@%s registered %s (with %s playable)", cc.Username, srv.Status.Companies[cc.CompanyID].Name, cc.TimeLeft.Round(time.Second))) } else { bot.SendChat(m.Chat.ID, fmt.Sprintf("@%s registered %s", cc.Username, srv.Status.Companies[cc.CompanyID].Name)) } return } } bot.SendChat(m.Chat.ID, "More than one company unregistered. Wait for bot update (poke @tiennou)") return } func botGraphValue(m *tb.Message) { var ( maxVal float64 unitFactor float64 unitName string ) for coID, dStats := range cfg.Stats { if cfg.CompanyIsRegistered(coID) { for _, stat := range dStats { valueFloat := float64(stat.CompanyValueLastQuarter) if math.Abs(valueFloat) > maxVal { maxVal = math.Abs(valueFloat) } } } } if maxVal > 1000000000 { unitFactor = 1000000000 unitName = "billion" } else if maxVal > 1000000 { unitFactor = 1000000 unitName = "million" } else { unitFactor = 1 unitName = "" } var vals map[uint8]plotter.XYs vals = make(map[uint8]plotter.XYs) for coID, dStats := range cfg.Stats { if cfg.CompanyIsRegistered(coID) { vals[coID] = make(plotter.XYs, 0) for dStr, stat := range dStats { d, err := time.Parse("20060102", dStr) logErrorDebug(err, "botGraphValue : time.Parse") if err != nil { bot.SendChat(m.Chat.ID, fmt.Sprintf("time.Parse : %s", err)) return } dateFloat := float64(d.Year()) + float64(d.Month()-1)/12 valueFloat := float64(stat.CompanyValueLastQuarter) pt := plotter.XY{ X: dateFloat, Y: valueFloat / unitFactor, } vals[coID] = append(vals[coID], pt) } sort.Slice(vals[coID], func(i, j int) bool { return vals[coID][i].X < vals[coID][j].X }) } } p := plot.New() p.Title.Text = "Company Values" p.X.Label.Text = "Year" if unitName != "" { p.Y.Label.Text = fmt.Sprintf("Value (%s)", unitName) } else { p.Y.Label.Text = "Value" } i := 0 for coID, xys := range vals { cc := cfg.GetCompanyClient(coID) l, s, err := plotter.NewLinePoints(xys) logErrorDebug(err, "botGraphValue : plotter.NewLinePoints") if err != nil { bot.SendChat(m.Chat.ID, fmt.Sprintf("plotter.NewLinePoints : %s", err)) return } l.Color = plotutil.Color(i) l.Dashes = plotutil.Dashes(2) s.Color = plotutil.Color(i) s.Shape = plotutil.Shape(0) p.Add(l) p.Add(s) p.Legend.Add(cc.Username, l, s) i++ } err := p.Save(6*vg.Inch, 4*vg.Inch, "/app/data/points.png") logErrorDebug(err, "botGraphValue : plot.Save") if err != nil { bot.SendChat(m.Chat.ID, fmt.Sprintf("plot.Save : %s", err)) return } bot.SendChatImage(m.Chat.ID, "/app/data/points.png") return } func botGraphValueDelta(m *tb.Message) { var ( maxVal float64 unitFactor float64 unitName string ) var vals map[uint8]plotter.XYs vals = make(map[uint8]plotter.XYs) for coID, dStats := range cfg.Stats { if cfg.CompanyIsRegistered(coID) { vals[coID] = make(plotter.XYs, 0) for dStr, stat := range dStats { d, err := time.Parse("20060102", dStr) logErrorDebug(err, "botGraphValueDelta : time.Parse") if err != nil { bot.SendChat(m.Chat.ID, fmt.Sprintf("time.Parse : %s", err)) return } dateFloat := float64(d.Year()) + float64(d.Month()-1)/12 valueFloat := float64(stat.CompanyValueLastQuarter) pt := plotter.XY{ X: dateFloat, Y: valueFloat / unitFactor, } vals[coID] = append(vals[coID], pt) } sort.Slice(vals[coID], func(i, j int) bool { return vals[coID][i].X < vals[coID][j].X }) } } for coID, v := range vals { for i := len(vals) - 1; i > 0; i-- { v[i].Y = v[i].Y - v[i-1].Y if math.Abs(v[i].Y) > maxVal { maxVal = math.Abs(v[i].Y) } } vals[coID] = v[1:] } if maxVal > 1000000000 { unitFactor = 1000000000 unitName = "billion" } else if maxVal > 1000000 { unitFactor = 1000000 unitName = "million" } else { unitFactor = 1 unitName = "" } logInfoDebug("botGraphValueDelta : unit set to %s (factor : %f)", unitName, unitFactor) for coID, v := range vals { for i := 1; i < len(vals)-1; i++ { v[i].Y = v[i].Y / unitFactor if math.IsNaN(v[i].Y) { logInfoDebug("botGraphValueDelta : NaN : %d / %f", coID, v[i].X) } } vals[coID] = v } p := plot.New() p.Title.Text = "Company Values (delta)" p.X.Label.Text = "Year" if unitName != "" { p.Y.Label.Text = fmt.Sprintf("Variation (%s)", unitName) } else { p.Y.Label.Text = "Variation" } i := 0 for coID, xys := range vals { cc := cfg.GetCompanyClient(coID) l, s, err := plotter.NewLinePoints(xys) logErrorDebug(err, "botGraphValueDelta : plotter.NewLinePoints") if err != nil { bot.SendChat(m.Chat.ID, fmt.Sprintf("plotter.NewLinePoints : %s", err)) return } l.Color = plotutil.Color(i) l.Dashes = plotutil.Dashes(2) s.Color = plotutil.Color(i) s.Shape = plotutil.Shape(0) p.Add(l) p.Add(s) p.Legend.Add(cc.Username, l, s) i++ } err := p.Save(6*vg.Inch, 4*vg.Inch, "/app/data/points.png") logErrorDebug(err, "botGraphValueDelta : plot.Save") if err != nil { bot.SendChat(m.Chat.ID, fmt.Sprintf("plot.Save : %s", err)) return } bot.SendChatImage(m.Chat.ID, "/app/data/points.png") return } func botGraphPlanes(m *tb.Message) { var planes, airports map[uint8]plotter.XYs planes = make(map[uint8]plotter.XYs) airports = make(map[uint8]plotter.XYs) for coID, dStats := range cfg.Stats { if cfg.CompanyIsRegistered(coID) { planes[coID] = make(plotter.XYs, 0) for dStr, stat := range dStats { d, err := time.Parse("20060102", dStr) logErrorDebug(err, "botGraphPlanes : time.Parse") if err != nil { bot.SendChat(m.Chat.ID, fmt.Sprintf("time.Parse : %s", err)) return } dateFloat := float64(d.Year()) + float64(d.Month()-1)/12 pt := plotter.XY{ X: dateFloat, Y: float64(stat.Planes), } planes[coID] = append(planes[coID], pt) pt = plotter.XY{ X: dateFloat, Y: float64(stat.Airports), } airports[coID] = append(airports[coID], pt) } sort.Slice(planes[coID], func(i, j int) bool { return planes[coID][i].X < planes[coID][j].X }) sort.Slice(airports[coID], func(i, j int) bool { return airports[coID][i].X < airports[coID][j].X }) } } p := plot.New() p.Title.Text = "Planes summary" p.X.Label.Text = "Year" p.Y.Label.Text = "Planes" i := 0 for coID, xys := range planes { cc := cfg.GetCompanyClient(coID) l, s, err := plotter.NewLinePoints(xys) logErrorDebug(err, "botGraphPlanes : plotter.NewLinePoints") if err != nil { bot.SendChat(m.Chat.ID, fmt.Sprintf("plotter.NewLinePoints : %s", err)) return } l.Color = plotutil.Color(i) l.Dashes = plotutil.Dashes(2) s.Color = plotutil.Color(i) s.Shape = plotutil.Shape(0) p.Add(l) p.Add(s) p.Legend.Add(cc.Username, l, s) i++ } err := p.Save(6*vg.Inch, 4*vg.Inch, "/app/data/points.png") logErrorDebug(err, "botGraphPlanes : plot.Save") if err != nil { bot.SendChat(m.Chat.ID, fmt.Sprintf("plot.Save : %s", err)) return } bot.SendChatImage(m.Chat.ID, "/app/data/points.png") return } func PrintText(m *tb.Message) { logInfoDebug("[%d] %s(%d) | %s(%d) : %s\n", m.ID, m.Chat.Title, m.Chat.ID, m.Sender.Username, m.Sender.ID, m.Text) return } func botPhoto(m *tb.Message) { logInfoDebug("botPhoto :", m.Text) // photos only } func botDocument(m *tb.Message) { logInfoDebug("botDocument : %s (%d bytes)\n", m.Document.FileName, m.Document.File.FileSize) // documents only } func botChannelPost(m *tb.Message) { PrintText(m) b, _ := json.Marshal(m) logInfoDebug("botChannelPost : %s\n", string(b)) // channel posts only } func botQuery(q *tb.Query) { logInfoDebug("botQuery") // incoming inline queries } func botText(m *tb.Message) { r := regexp.MustCompile("^\\/delete_(?P[a-f0-9]{16})$") if r.MatchString(m.Text) { botActuallyDelete(m) return } r = regexp.MustCompile("^\\/reset_(?P[a-f0-9]{16})$") if r.MatchString(m.Text) { botActuallyReset(m) return } r = regexp.MustCompile("^\\/start_(?P[a-f0-9]{16})$") if r.MatchString(m.Text) { botActuallyStart(m) return } PrintText(m) }