diff --git a/go.mod b/go.mod index 6d000e2..62b6e97 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module git.fromouter.space/mcg/mlpdraftbot go 1.12 require ( - git.fromouter.space/hamcha/tg v0.0.0-20181213132350-d7698a3c50ea + git.fromouter.space/hamcha/tg v0.0.2 git.fromouter.space/mcg/cardgage v0.0.4 git.fromouter.space/mcg/draft v0.0.6 git.fromouter.space/mcg/mlp-server-tools/draftbot v0.0.0-20190822092843-7fffdf24f1e7 diff --git a/go.sum b/go.sum index ead7780..041e5c7 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,12 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= git.fromouter.space/Artificiale/moa v0.0.1-p2 h1:KhoRQeYCFIpHZEucrXz142O5zfSsyExyhuPSazCrt6I= git.fromouter.space/Artificiale/moa v0.0.1-p2/go.mod h1:dHYul6vVMwDCzre18AFs6NmI22yeI7AE0iQC1jFEQi0= -git.fromouter.space/hamcha/tg v0.0.0-20181213132350-d7698a3c50ea h1:2VrIQZEHaUGjf9Rqsli+S+/ZReLpodPWTklVUqQPCX0= -git.fromouter.space/hamcha/tg v0.0.0-20181213132350-d7698a3c50ea/go.mod h1:j0xF3DL3Y+HWAMzaFQlJxl9bLOKmFNEZNemg19vC+Cc= -git.fromouter.space/mcg/cardgage v0.0.2 h1:u3Wz+UJx0wEix7vlqMlDX9Kg8nplCy8A0+nIKdNNPp0= -git.fromouter.space/mcg/cardgage v0.0.2/go.mod h1:vCmJ9HRdRGSWg2YQW9oNG7geYACdgWYmzL+zZdrsYhQ= +git.fromouter.space/hamcha/tg v0.0.2 h1:Jp4fqXCDeqxvLOxH32bZ0h/2KJF6MIDDz7X9fmZqQdg= +git.fromouter.space/hamcha/tg v0.0.2/go.mod h1:63tLaopVQkIt7kotqIoYTGmV2Df+Mo4AOXSpk62qRLM= git.fromouter.space/mcg/cardgage v0.0.4 h1:LHMUeNMh0QiMkM3TgsLe9l5sDmanQrej6UiWSVTb67c= git.fromouter.space/mcg/cardgage v0.0.4/go.mod h1:vCmJ9HRdRGSWg2YQW9oNG7geYACdgWYmzL+zZdrsYhQ= -git.fromouter.space/mcg/cardgage v0.0.5 h1:aUTE1svuXK6osE7XsaVBOkr3Hl05gP8JrlXkx69QFBY= -git.fromouter.space/mcg/draft v0.0.2 h1:OT1uztgfCZnZCOg3uOtjQKH2WdwRAxNdYr4KXklVgXY= -git.fromouter.space/mcg/draft v0.0.2/go.mod h1:QQmDm9FgAZL3b2/pIDd4Eo608SxMiCQQe5vIybe/CDY= git.fromouter.space/mcg/draft v0.0.6 h1:eS9cjDXtWBKRgwIcioTQlYpAZt7iHsNSvo9fr9ZPSlo= git.fromouter.space/mcg/draft v0.0.6/go.mod h1:QQmDm9FgAZL3b2/pIDd4Eo608SxMiCQQe5vIybe/CDY= -git.fromouter.space/mcg/mlp-server-tools/draftbot v0.0.0-20190627121736-1eef3f120119 h1:rJFh1QfuSqQ690Q7z5/Zo1vHJys50kTBPR6UpP0uEso= -git.fromouter.space/mcg/mlp-server-tools/draftbot v0.0.0-20190627121736-1eef3f120119/go.mod h1:CQ8ajk7NpLVTTsx5Euqdg68lQswfcgn62QvUlAFM9hQ= git.fromouter.space/mcg/mlp-server-tools/draftbot v0.0.0-20190822092843-7fffdf24f1e7 h1:rPNqqjFHat+Mhj3zak0lzJePUrJyoWMzDTKckwQQ5U0= git.fromouter.space/mcg/mlp-server-tools/draftbot v0.0.0-20190822092843-7fffdf24f1e7/go.mod h1:MayXMb++X1rIDgn6ZbIASRqhu0ZOkmCbaSLhVXHboR0= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= @@ -67,9 +60,11 @@ github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80n github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-redis/redis v6.15.2+incompatible h1:9SpNVG76gr6InJGxoZ6IuuxaCOQwDAhzyXg+Bs+0Sb4= github.com/go-redis/redis v6.15.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -136,6 +131,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= diff --git a/main.go b/main.go index 9787b74..268fd43 100644 --- a/main.go +++ b/main.go @@ -67,8 +67,9 @@ func main() { api = tg.MakeAPIClient(token) api.SetWebhook(webhookURL) tgapi = &tgInterface{ - client: api, - usermap: make(map[string]int64), + client: api, + usermap: make(map[string]*draftUser), + sessions: make(map[int64]*draftSession), } bot := draftbot.NewDraftBot(tgapi, botname) bot.Logger = logger @@ -82,23 +83,8 @@ func webhook(update tg.APIUpdate) { if msg.Chat == nil { return } - switch msg.Chat.Type { - case tg.ChatTypeGroup, tg.ChatTypeSupergroup: - if isCommand(msg, "newdraft") { - tgapi.NewDraft(msg.Chat, msg.User) - return - } - if isCommand(msg, "join") { - tgapi.JoinDraft(msg.Chat, msg.User) - return - } - if isCommand(msg, "startdraft") { - tgapi.StartDraft(msg.Chat, msg.User) - return - } - case tg.ChatTypePrivate: - //TODO - } + tgapi.OnMessage(msg) + return } } diff --git a/tginterface.go b/tginterface.go index 6661b43..27f611c 100644 --- a/tginterface.go +++ b/tginterface.go @@ -6,22 +6,62 @@ import ( "strconv" "strings" - "git.fromouter.space/mcg/draft" - "git.fromouter.space/mcg/draft/mlp" + "git.fromouter.space/mcg/draft" + "git.fromouter.space/hamcha/tg" lobby "git.fromouter.space/mcg/cardgage/lobby/proto" room "git.fromouter.space/mcg/cardgage/room/api" "git.fromouter.space/mcg/mlp-server-tools/draftbot" ) -type tgInterface struct { - client *tg.Telegram - bot *draftbot.DraftBot - usermap map[string]int64 +type draftUser struct { + ID int64 + Session string } +const ( + positionEven = "Evenly spaced" + positionRandom = "At random" +) + +const ( + stepPlayerCount = iota + stepSpacing = iota + stepDraftType = iota + stepDraftInfo = iota + //stepPackCount = iota + //stepExtraPackCount = iota // Only for I8PCube + stepDone = iota +) + +type draftSession struct { + Step int + Options draftbot.SessionOptions +} + +type tgInterface struct { + client *tg.Telegram + bot *draftbot.DraftBot + usermap map[string]*draftUser + sessions map[int64]*draftSession +} + +// Draft types +const ( + DTSetDraft = "Set draft" + DTBlockDraft = "Block draft" + DTCubeDraft = "Cube draft" + DTI8PCube = "Seeded cube" +) + +const ( + blockPremiere = "Premiere" + blockOdyssey = "Odyssey" + blockDefenders = "Defenders" +) + func (tgi *tgInterface) Send(msg room.BotMessage) { // Get room ID chatid, err := strconv.ParseInt(msg.RoomID, 10, 64) @@ -38,22 +78,21 @@ func (tgi *tgInterface) Send(msg room.BotMessage) { // Are we sending a message to a player in particular? if msg.Message.To != "" { - chatid = tgi.usermap[msg.Message.To] + chatid = tgi.usermap[msg.Message.To].ID } switch msg.Message.Type { case "draft-newpick": cards := msg.Message.Data.(draft.Pack) cardphotos := []tg.APIInputMediaPhoto{} - buttons := []tg.APIInlineKeyboardButton{} + buttons := []tg.APIKeyboardButton{} for _, card := range cards { cardphotos = append(cardphotos, tg.APIInputMediaPhoto{ Type: "photo", Media: fmt.Sprintf("http://ponyhead.com/img/cards/%s.jpg", card.ID), }) - buttons = append(buttons, tg.APIInlineKeyboardButton{ - Text: strings.ToUpper(card.ID), - CallbackData: fmt.Sprintf("PICK %s %s %s", msg.RoomID, msg.Message.To, card), + buttons = append(buttons, tg.APIKeyboardButton{ + Text: card.ID, }) } // If sending more than 10 cards, split in two messages @@ -79,8 +118,10 @@ func (tgi *tgInterface) Send(msg room.BotMessage) { tgi.client.SendTextMessage(tg.ClientTextMessageData{ ChatID: chatid, Text: "Choose your pick:", - ReplyMarkup: &tg.APIInlineKeyboardMarkup{ - InlineKeyboard: rebalance(buttons), + ReplyMarkup: &tg.APIReplyKeyboardMarkup{ + Keyboard: rebalance(buttons), + OneTime: true, + Resize: true, }, }) default: @@ -91,12 +132,45 @@ func (tgi *tgInterface) Send(msg room.BotMessage) { } } +func (tgi *tgInterface) OnMessage(msg tg.APIMessage) { + switch msg.Chat.Type { + case tg.ChatTypeGroup, tg.ChatTypeSupergroup: + if msg.Text != nil { + if isCommand(msg, "newdraft") { + tgi.NewDraft(msg) + return + } + if isCommand(msg, "join") { + tgi.JoinDraft(msg.Chat, msg.User) + return + } + if isCommand(msg, "startdraft") { + tgi.StartDraft(msg.Chat, msg.User) + return + } + if session, ok := tgi.sessions[msg.Chat.ChatID]; ok && session.Step != stepDone { + tgi.nextCreateDraftStep(msg) + return + } + } + case tg.ChatTypePrivate: + if user, ok := tgi.usermap[msg.User.Username]; ok && user.Session != "" { + if msg.Text != nil && (*msg.Text)[0] != '/' { + tgi.PickCard(msg.User, *msg.Text) + } + } + } +} + func (tgi *tgInterface) Bind(bot *draftbot.DraftBot) { tgi.bot = bot } func (tgi *tgInterface) message(user tg.APIUser, session string, typ string, data interface{}) { - tgi.usermap[user.Username] = user.UserID + tgi.usermap[user.Username] = &draftUser{ + ID: user.UserID, + Session: session, + } tgi.bot.OnMessage(room.ServerMessage{ RoomID: session, Type: room.MsgMessage, @@ -109,7 +183,9 @@ func (tgi *tgInterface) message(user tg.APIUser, session string, typ string, dat }) } -func (tgi *tgInterface) NewDraft(chat *tg.APIChat, author tg.APIUser) { +func (tgi *tgInterface) NewDraft(msg tg.APIMessage) { + chat := msg.Chat + author := msg.User chatid := strconv.FormatInt(chat.ChatID, 10) tgi.bot.OnMessage(room.ServerMessage{ RoomID: chatid, @@ -124,17 +200,192 @@ func (tgi *tgInterface) NewDraft(chat *tg.APIChat, author tg.APIUser) { }, }) - tgi.message(author, chatid, "create", draftbot.SessionOptions{ - Players: 8, - Options: draftbot.DraftOptions{ - Type: draftbot.DraftSet, - Positioning: draftbot.PosEven, - Set: mlp.SetAbsoluteDiscord, - PackCount: 4, + // Open interactive dialog + tgi.sessions[chat.ChatID] = &draftSession{ + Step: stepPlayerCount, + Options: draftbot.SessionOptions{ + Options: draftbot.DraftOptions{ + // For now let's make these non-modifiable + PackCount: 4, + PackSize: 12, + MainCount: 4, + ProblemCount: 1, + }, }, + } + tgi.client.SendTextMessage(tg.ClientTextMessageData{ + ChatID: msg.Chat.ChatID, + Text: "How many players will be partecipating in the draft?", + ReplyID: &msg.MessageID, }) } +func (tgi *tgInterface) nextCreateDraftStep(msg tg.APIMessage) { + session, ok := tgi.sessions[msg.Chat.ChatID] + if !ok { + return + } + + switch session.Step { + case stepPlayerCount: + n, err := strconv.Atoi(*msg.Text) + if err != nil { + tgi.client.SendTextMessage(tg.ClientTextMessageData{ + ChatID: msg.Chat.ChatID, + Text: "That's not a valid number!", + ReplyID: &msg.MessageID, + }) + return + } + if n < 1 || n > 8 { + tgi.client.SendTextMessage(tg.ClientTextMessageData{ + ChatID: msg.Chat.ChatID, + Text: "Please choose a reasonable number. (1-8)", + ReplyID: &msg.MessageID, + }) + return + } + session.Options.Players = n + tgi.client.SendTextMessage(tg.ClientTextMessageData{ + ChatID: msg.Chat.ChatID, + Text: "How should player be positioned in the pod (assuming bots)?", + ReplyID: &msg.MessageID, + ReplyMarkup: tg.APIReplyKeyboardMarkup{ + Selective: true, + OneTime: true, + Keyboard: rebalance([]tg.APIKeyboardButton{ + {Text: positionEven}, + {Text: positionRandom}, + }), + }, + }) + session.Step = stepSpacing + case stepSpacing: + switch *msg.Text { + case positionEven: + session.Options.Options.Positioning = draftbot.PosEven + case positionRandom: + session.Options.Options.Positioning = draftbot.PosRandom + default: + tgi.client.SendTextMessage(tg.ClientTextMessageData{ + ChatID: msg.Chat.ChatID, + Text: "Sorry, valid choices are: " + strings.Join([]string{positionEven, positionRandom}, ", "), + ReplyID: &msg.MessageID, + ReplyMarkup: tg.APIReplyKeyboardMarkup{ + Selective: true, + OneTime: true, + Keyboard: rebalance([]tg.APIKeyboardButton{ + {Text: positionEven}, + {Text: positionRandom}, + }), + }, + }) + return + } + tgi.client.SendTextMessage(tg.ClientTextMessageData{ + ChatID: msg.Chat.ChatID, + Text: "What type of draft do you want?", + ReplyID: &msg.MessageID, + ReplyMarkup: tg.APIReplyKeyboardMarkup{ + Selective: true, + OneTime: true, + Keyboard: rebalance([]tg.APIKeyboardButton{ + {Text: DTSetDraft}, + {Text: DTBlockDraft}, + {Text: DTCubeDraft}, + {Text: DTI8PCube}, + }), + }, + }) + session.Step = stepDraftType + case stepDraftType: + switch *msg.Text { + case DTSetDraft: + session.Options.Options.Type = draftbot.DraftSet + tgi.client.SendTextMessage(tg.ClientTextMessageData{ + ChatID: msg.Chat.ChatID, + Text: "What set do you want to draft?", + ReplyID: &msg.MessageID, + ReplyMarkup: tg.APIReplyKeyboardMarkup{ + Selective: true, + OneTime: true, + Keyboard: rebalance([]tg.APIKeyboardButton{ + {Text: string(mlp.SetPremiere)}, + {Text: string(mlp.SetCanterlotNights)}, + {Text: string(mlp.SetCrystalGames)}, + {Text: string(mlp.SetAbsoluteDiscord)}, + {Text: string(mlp.SetEquestrialOdysseys)}, + {Text: string(mlp.SetHighMagic)}, + {Text: string(mlp.SetMarksInTime)}, + {Text: string(mlp.SetDefendersOfEquestria)}, + {Text: string(mlp.SetSeaquestriaBeyond)}, + {Text: string(mlp.SetFriendsForever)}, + }), + }, + }) + session.Step = stepDraftInfo + case DTBlockDraft: + session.Options.Options.Type = draftbot.DraftBlock + tgi.client.SendTextMessage(tg.ClientTextMessageData{ + ChatID: msg.Chat.ChatID, + Text: "What block do you want to draft?", + ReplyID: &msg.MessageID, + ReplyMarkup: tg.APIReplyKeyboardMarkup{ + Selective: true, + OneTime: true, + Keyboard: rebalance([]tg.APIKeyboardButton{ + {Text: blockPremiere}, + {Text: blockOdyssey}, + {Text: blockDefenders}, + }), + }, + }) + session.Step = stepDraftInfo + case DTCubeDraft: + session.Options.Options.Type = draftbot.DraftCube + tgi.client.SendTextMessage(tg.ClientTextMessageData{ + ChatID: msg.Chat.ChatID, + Text: "Write the URL of a file containing a line-separated list of cards in the cube", + ReplyID: &msg.MessageID, + }) + session.Step = stepDraftInfo + case DTI8PCube: + session.Options.Options.Type = draftbot.DraftI8PCube + tgi.client.SendTextMessage(tg.ClientTextMessageData{ + ChatID: msg.Chat.ChatID, + Text: "Write the URL of a I8P-JSON cube", + ReplyID: &msg.MessageID, + }) + session.Step = stepDraftInfo + default: + tgi.client.SendTextMessage(tg.ClientTextMessageData{ + ChatID: msg.Chat.ChatID, + Text: "Sorry, valid choices are: " + strings.Join([]string{DTSetDraft, DTBlockDraft, DTCubeDraft, DTI8PCube}, " "), + ReplyID: &msg.MessageID, + }) + return + } + case stepDraftInfo: + switch session.Options.Options.Type { + case draftbot.DraftSet: + session.Options.Options.Set = mlp.SetID(*msg.Text) + case draftbot.DraftBlock: + session.Options.Options.Block = mlp.BlockID(*msg.Text) + case draftbot.DraftCube, draftbot.DraftI8PCube: + // Check for a valid URL + session.Options.Options.CubeURL = *msg.Text + } + chatid := strconv.FormatInt(msg.Chat.ChatID, 10) + tgi.message(msg.User, chatid, "create", session.Options) + session.Step = stepDone + } +} + +func (tgi *tgInterface) OpenDraft(chat *tg.APIChat, author tg.APIUser, options draftbot.SessionOptions) { + chatid := strconv.FormatInt(chat.ChatID, 10) + tgi.message(author, chatid, "create", options) +} + func (tgi *tgInterface) JoinDraft(chat *tg.APIChat, usr tg.APIUser) { chatid := strconv.FormatInt(chat.ChatID, 10) tgi.message(usr, chatid, "join", nil) @@ -145,9 +396,13 @@ func (tgi *tgInterface) StartDraft(chat *tg.APIChat, usr tg.APIUser) { tgi.message(usr, chatid, "start", nil) } -func rebalance(buttons []tg.APIInlineKeyboardButton) [][]tg.APIInlineKeyboardButton { - out := [][]tg.APIInlineKeyboardButton{} - currentRow := []tg.APIInlineKeyboardButton{} +func (tgi *tgInterface) PickCard(usr tg.APIUser, cardid string) { + tgi.message(usr, tgi.usermap[usr.Username].Session, "pick", cardid) +} + +func rebalance(buttons []tg.APIKeyboardButton) [][]tg.APIKeyboardButton { + out := [][]tg.APIKeyboardButton{} + currentRow := []tg.APIKeyboardButton{} var perRow int switch { case len(buttons) < 5: @@ -161,7 +416,7 @@ func rebalance(buttons []tg.APIInlineKeyboardButton) [][]tg.APIInlineKeyboardBut currentRow = append(currentRow, buttons[i]) if i%perRow == (perRow - 1) { out = append(out, currentRow) - currentRow = []tg.APIInlineKeyboardButton{} + currentRow = []tg.APIKeyboardButton{} } } if len(currentRow) > 0 {