package main import ( "fmt" "log" "strconv" "strings" "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/draftbot" ) 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 // Session open message SessionOpenMessage int64 // Pick message CurrentPack int PickDone []string PickWaiting []string PickMessage int64 PickNum int } 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 (BROKEN!)" 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) if err != nil { log.Printf("[WARN] Trying to send message to weird place: \"%s\". Dropped.\n", msg.RoomID) return } // For now, skip non-messages if msg.Type != room.MsgMessage { //TODO return } // Are we sending a message to a player in particular? if msg.Message.To != "" { chatid = tgi.usermap[msg.Message.To].ID } switch msg.Message.Type { case "draft-newpack": tgi.sessions[chatid].CurrentPack++ tgi.sessions[chatid].PickNum = 0 tgi.client.SendTextMessage(tg.ClientTextMessageData{ ChatID: chatid, Text: msg.Message.Message, }) tgi.sessions[chatid].PickMessage = 0 case "draft-newpick": tgi.sessions[chatid].PickNum++ tgi.sessions[chatid].PickDone = []string{} tgi.sessions[chatid].PickWaiting = playerNames(tgi.bot.Sessions[msg.RoomID].Players) if tgi.sessions[chatid].PickMessage != 0 { tgi.client.EditText(tg.ClientEditTextData{ ChatID: chatid, MessageID: tgi.sessions[chatid].PickMessage, Text: txtCurrentPick(tgi.sessions[chatid]), }) } else { msg, _ := tgi.client.SendTextMessage(tg.ClientTextMessageData{ ChatID: chatid, Text: txtCurrentPick(tgi.sessions[chatid]), }) tgi.sessions[chatid].PickMessage = msg.MessageID } case "card-picked": picked := msg.Message.Data.(struct { Player string }) player := "@" + picked.Player tgi.sessions[chatid].PickDone = append(tgi.sessions[chatid].PickDone, player) tgi.sessions[chatid].PickWaiting = removeItem(tgi.sessions[chatid].PickWaiting, player) tgi.client.EditText(tg.ClientEditTextData{ ChatID: chatid, MessageID: tgi.sessions[chatid].PickMessage, Text: txtCurrentPick(tgi.sessions[chatid]), }) case "draft-availablepicks": cards := msg.Message.Data.(draft.Pack) cardphotos := []tg.APIInputMediaPhoto{} 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.APIKeyboardButton{ Text: card.ID, }) } // If sending more than 10 cards, split in two messages if len(cardphotos) > 10 { half := len(cardphotos) / 2 tgi.client.SendAlbum(tg.ClientAlbumData{ ChatID: chatid, Silent: true, Media: cardphotos[:half], }) tgi.client.SendAlbum(tg.ClientAlbumData{ ChatID: chatid, Silent: true, Media: cardphotos[half:], }) } else { tgi.client.SendAlbum(tg.ClientAlbumData{ ChatID: chatid, Silent: true, Media: cardphotos, }) } tgi.client.SendTextMessage(tg.ClientTextMessageData{ ChatID: chatid, Text: "Choose your pick:", ReplyMarkup: &tg.APIReplyKeyboardMarkup{ Keyboard: rebalance(buttons), OneTime: true, Resize: true, }, }) case "session-open": opt := msg.Message.Data.(draftbot.SessionOptions) tgi.sessions[chatid].Options = opt msg, _ := tgi.client.SendTextMessage(tg.ClientTextMessageData{ ChatID: chatid, Text: txtDraftSessionCreated(opt, tgi.bot.Sessions[msg.RoomID].Players), }) tgi.sessions[chatid].SessionOpenMessage = msg.MessageID case "player-joined-session": tgi.client.EditText(tg.ClientEditTextData{ ChatID: chatid, MessageID: tgi.sessions[chatid].SessionOpenMessage, Text: txtDraftSessionCreated(tgi.sessions[chatid].Options, tgi.bot.Sessions[msg.RoomID].Players), }) case "draft-order": players := msg.Message.Data.([]string) tgi.client.SendTextMessage(tg.ClientTextMessageData{ ChatID: chatid, Text: txtDraftSessionOrder(players), }) case "draft-picks": picks := msg.Message.Data.([]string) tgi.client.SendTextMessage(tg.ClientTextMessageData{ ChatID: chatid, Text: txtYourPicks(picks), }) default: tgi.client.SendTextMessage(tg.ClientTextMessageData{ ChatID: chatid, Text: fmt.Sprintf("[%s] %s", msg.Message.Type, msg.Message.Message), }) } } 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] = &draftUser{ ID: user.UserID, Session: session, } tgi.bot.OnMessage(room.ServerMessage{ RoomID: session, Type: room.MsgMessage, Message: &room.Message{ From: user.Username, To: botname, Type: typ, Data: data, }, }) } 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, Type: room.MsgEvent, Event: &room.Event{ Type: room.EvtNewRoom, Room: &lobby.Room{ Id: chatid, Name: *chat.Title, Creator: author.Username, }, }, }) // 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) } func (tgi *tgInterface) StartDraft(chat *tg.APIChat, usr tg.APIUser) { chatid := strconv.FormatInt(chat.ChatID, 10) tgi.message(usr, chatid, "start", nil) } 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: perRow = 2 case len(buttons) < 10: perRow = 3 default: perRow = 4 } for i := range buttons { currentRow = append(currentRow, buttons[i]) if i%perRow == (perRow - 1) { out = append(out, currentRow) currentRow = []tg.APIKeyboardButton{} } } if len(currentRow) > 0 { out = append(out, currentRow) } return out } func removeItem(lst []string, item string) []string { for i := range lst { if lst[i] == item { return append(lst[:i], lst[i+1:]...) } } return lst }