Add interactive draft creation dialog
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Hamcha 2019-08-23 15:39:52 +02:00
parent d9617ff1af
commit 6db72f69ac
Signed by: hamcha
GPG key ID: 44AD3571EB09A39E
4 changed files with 292 additions and 55 deletions

2
go.mod
View file

@ -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

14
go.sum
View file

@ -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=

24
main.go
View file

@ -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
}
}

View file

@ -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 {