diff --git a/assets/allegiance/guardians.png b/assets/allegiance/guardians.png new file mode 100644 index 0000000..128c05f Binary files /dev/null and b/assets/allegiance/guardians.png differ diff --git a/assets/allegiance/guardians.svg b/assets/allegiance/guardians.svg deleted file mode 100644 index 0841307..0000000 --- a/assets/allegiance/guardians.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/allegiance/servants.png b/assets/allegiance/servants.png new file mode 100644 index 0000000..a3d13be Binary files /dev/null and b/assets/allegiance/servants.png differ diff --git a/assets/allegiance/servants.svg b/assets/allegiance/servants.svg deleted file mode 100644 index 141e440..0000000 --- a/assets/allegiance/servants.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/bot/bot.go b/bot/bot.go index 93c05b7..538cdd0 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -18,6 +18,7 @@ import ( // List of Sea of Thieves API endpoints const ( APIURLSoTAchievements = "https://www.seaofthieves.com/api/profilev2/achievements" + APIURLSoTAllegiance = "https://www.seaofthieves.com/api/profilev2" APIURLSoTSeasons = "https://www.seaofthieves.com/api/profilev2/seasons-progress" APIURLSoTUserBalance = "https://www.seaofthieves.com/api/profilev2/balance" APIURLSoTUserOverview = "https://www.seaofthieves.com/api/profilev2/overview" @@ -31,6 +32,7 @@ const ( ErrFailedHTTPClient = "failed to generate new HTTP client: %s" ErrFailedRetrieveUserStatsDB = "failed retrieve user status from DB: %s" ErrFailedGuildLookupDB = "failed to look up guild in database: %s" + ErrFailedStringConvert = "failed to convert string to int: %s" ) // Bot represents the bot instance diff --git a/bot/sc_handler_sot_allegiance.go b/bot/sc_handler_sot_allegiance.go new file mode 100644 index 0000000..a91ad45 --- /dev/null +++ b/bot/sc_handler_sot_allegiance.go @@ -0,0 +1,174 @@ +package bot + +import ( + "encoding/json" + "fmt" + "regexp" + "strconv" + "strings" + + "github.com/bwmarrin/discordgo" + "golang.org/x/text/language" + "golang.org/x/text/message" +) + +// SoTAllegianceJSON is the nested struct from the Sea of Thieves event hub response +type SoTAllegianceJSON struct { + Stats []struct { + Name string `json:"name"` + Value string `json:"value"` + } `json:"stats"` +} + +// SoTAllegiance is the struct that represents the parsed data from the API endpoint +type SoTAllegiance struct { + Allegiance string + ShipsSunk int64 + MaxStreak int64 + TotalGold int64 +} + +// SlashCmdSoTAllegiance handles the /allegiance slash command +func (b *Bot) SlashCmdSoTAllegiance(s *discordgo.Session, i *discordgo.InteractionCreate) error { + eo := i.ApplicationCommandData().Options + if len(eo) <= 0 { + return fmt.Errorf("no option given") + } + rc, ok := eo[0].Value.(string) + if !ok { + return fmt.Errorf("provided option value is not a string") + } + + re, err := regexp.Compile(`^(?i:guardians|servants)$`) + if err != nil { + return err + } + ala := re.FindStringSubmatch(rc) + if len(ala) != 1 { + return fmt.Errorf("failed to parse value string") + } + al := ala[0] + + r, err := b.NewRequester(i.Interaction) + if err != nil { + return err + } + + a, err := b.SoTGetAllegiance(r, al) + if err != nil { + return err + } + + p := message.NewPrinter(language.German) + var ef []*discordgo.MessageEmbedField + ef = append(ef, &discordgo.MessageEmbedField{ + Name: "Ships Sunk", + Value: fmt.Sprintf("%s **%d** Total", IconShip, a.ShipsSunk), + Inline: true, + }) + ef = append(ef, &discordgo.MessageEmbedField{ + Name: "Highest Streak", + Value: fmt.Sprintf("%s **%d** Ships", IconGauge, a.MaxStreak), + Inline: true, + }) + ef = append(ef, &discordgo.MessageEmbedField{ + Name: "Highest Hourglass Value", + Value: fmt.Sprintf("%s **%s** Gold", IconGold, p.Sprintf("%d", a.TotalGold)), + Inline: true, + }) + + e := []*discordgo.MessageEmbed{ + { + Title: fmt.Sprintf("Your current allegiance values for the **%s**:", a.Allegiance), + Thumbnail: &discordgo.MessageEmbedThumbnail{ + URL: fmt.Sprintf("%s/allegiance/%s.png", AssetsBaseURL, al), + }, + Type: discordgo.EmbedTypeRich, + Fields: ef, + }, + } + if _, err := s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{Embeds: &e}); err != nil { + return err + } + + return nil +} + +// SoTGetAllegiance returns the parsed API response from the Sea of Thieves allegiance API +func (b *Bot) SoTGetAllegiance(rq *Requester, at string) (SoTAllegiance, error) { + var a SoTAllegiance + var al SoTAllegianceJSON + hc, err := NewHTTPClient() + if err != nil { + return a, fmt.Errorf(ErrFailedHTTPClient, err) + } + c, err := rq.GetSoTRATCookie() + if err != nil { + return a, err + } + + urlfmt := "%s/%s" + var url string + switch strings.ToLower(at) { + case "guardians": + url = fmt.Sprintf(urlfmt, APIURLSoTAllegiance, "piratelord") + case "servants": + url = fmt.Sprintf(urlfmt, APIURLSoTAllegiance, "flameheart") + default: + return a, fmt.Errorf("unknown allegiance given") + } + + r, err := hc.HTTPReq(url, ReqMethodGet, nil) + if err != nil { + return a, err + } + r.SetSOTRequest(c) + rd, _, err := hc.Fetch(r) + if err != nil { + return a, err + } + + if err := json.Unmarshal(rd, &al); err != nil { + return a, err + } + + switch strings.ToLower(at) { + case "guardians": + for _, d := range al.Stats { + v, err := strconv.ParseInt(d.Value, 10, 64) + if err != nil { + return a, fmt.Errorf(ErrFailedStringConvert, d.Value) + } + switch d.Name { + case "FactionG_Ships_Sunk": + a.ShipsSunk = v + case "PirateLord_MaxStreak": + a.MaxStreak = v + case "FactionG_SandsOfFate_TotalGold": + a.TotalGold = v + } + a.Allegiance = "Guardians of Fortune" + } + case "servants": + for _, d := range al.Stats { + if d.Value == "" { + d.Value = "0" + } + v, err := strconv.ParseInt(d.Value, 10, 64) + if err != nil { + return a, fmt.Errorf(ErrFailedStringConvert, d.Value) + } + switch d.Name { + case "FactionB_Ships_Sunk": + a.ShipsSunk = v + case "Flameheart_MaxStreak": + a.MaxStreak = v + case "FactionB_SandsOfFate_TotalGold": + a.TotalGold = v + } + } + a.Allegiance = "Servants of the Flame" + } + + return a, nil +} diff --git a/bot/slashcmd.go b/bot/slashcmd.go index 2bfb705..a0fb312 100644 --- a/bot/slashcmd.go +++ b/bot/slashcmd.go @@ -181,6 +181,47 @@ func (b *Bot) getSlashCommands() []*discordgo.ApplicationCommand { }, }, }, + + /* + // reputation provides the current emissary reputation value in the different factions + { + Name: "reputation", + Description: "Returns your current reputation value in the different emissary factions", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "emissary-faction", + Description: "Name of the emissary faction", + Required: true, + Choices: []*discordgo.ApplicationCommandOptionChoice{ + {Name: "Athena's Fortune", Value: "athena"}, + {Name: "Gold Hoarder", Value: "hoarder"}, + {Name: "Merchant Alliance", Value: "merchant"}, + {Name: "Order of Souls", Value: "order"}, + {Name: "Reaper's Bone", Value: "reaper"}, + }, + }, + }, + }, + */ + + // allegiance provides the current allegiance values in the different factions + { + Name: "allegiance", + Description: "Returns your current allegiance values in the different allegiance factions", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "allegiance-faction", + Description: "Name of the allegiance faction", + Required: true, + Choices: []*discordgo.ApplicationCommandOptionChoice{ + {Name: "Servants of the Flame", Value: "servants"}, + {Name: "Guardians of Fortune", Value: "guardians"}, + }, + }, + }, + }, } } @@ -322,6 +363,7 @@ func (b *Bot) SlashCommandHandler(s *discordgo.Session, i *discordgo.Interaction "compare": b.SlashCmdSoTCompare, "dailydeeds": b.SlashCmdSoTDailyDeeds, "ledger": b.SlashCmdSoTLedger, + "allegiance": b.SlashCmdSoTAllegiance, } // Define list of slash commands that should use ephemeral messages diff --git a/bot/version.go b/bot/version.go index 1ae3c27..0a67788 100644 --- a/bot/version.go +++ b/bot/version.go @@ -1,3 +1,3 @@ package bot -const Version = "0.2.4" +const Version = "0.2.5"