commit - /dev/null
commit + 82e5b12a48c5af20503943aa43cfdf3c26dcdfdc
blob - /dev/null
blob + 3a46bd7d55f89caf5b0178268117e8856e6b9dc7 (mode 644)
--- /dev/null
+++ go.mod
+module github.com/Witcher01/discord_sr
+
+go 1.13
+
+require (
+ github.com/Witcher01/srapi v0.0.0-20210305194725-c19b54f7ba71
+ github.com/akamensky/argparse v1.2.2
+ github.com/bwmarrin/discordgo v0.23.2
+)
blob - /dev/null
blob + 235c3c79462c988b480c9f1e6b330505c957c02b (mode 644)
--- /dev/null
+++ go.sum
+github.com/Witcher01/srapi v0.0.0-20210305194725-c19b54f7ba71 h1:mP7nJhpQpSYKgVlzhfb1kcO1H9LZzzRWC6Fczu8RgSM=
+github.com/Witcher01/srapi v0.0.0-20210305194725-c19b54f7ba71/go.mod h1:zkV6ShyNJYwzWIrB+MlXnPtDB81XlaYh208VeUw+lHo=
+github.com/akamensky/argparse v1.2.2 h1:P17T0ZjlUNJuWTPPJ2A5dM1wxarHgHqfYH+AZTo2xQA=
+github.com/akamensky/argparse v1.2.2/go.mod h1:S5kwC7IuDcEr5VeXtGPRVZ5o/FdhcMlQz4IZQuw64xA=
+github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4=
+github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
+github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA=
+golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
blob - /dev/null
blob + 35ed6b8e761e680d734a398d5da016c751d766bc (mode 644)
--- /dev/null
+++ main.go
+package main
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "os/signal"
+ "regexp"
+ "strings"
+ "syscall"
+
+ "github.com/Witcher01/discord_sr/srcom"
+ "github.com/bwmarrin/discordgo"
+
+ "github.com/akamensky/argparse"
+)
+
+type commandArgs struct {
+ game_id *string
+ category *string
+ amount *int
+ platform *string
+}
+
+func main() {
+ parser := argparse.NewParser("discord_sr", "A bot for discord that returns simple queries for speedrun.com")
+
+ token := parser.String("t", "token", &argparse.Options{Required: true, Help: "Authentication token to use for the bot"})
+ err := parser.Parse(os.Args)
+ if err != nil {
+ fmt.Println(parser.Usage(err))
+ return
+ }
+
+ discord, err := discordgo.New("Bot " + *token)
+
+ // Cleanly close down the Discord session on program end
+ defer discord.Close()
+
+ if err != nil {
+ fmt.Println("error creating bot,", err)
+ return
+ }
+
+ discord.AddHandler(messageCreate);
+
+ discord.Identify.Intents = discordgo.IntentsGuildMessages
+
+ err = discord.Open()
+
+ if err != nil {
+ fmt.Println("error opening connection,", err)
+ return
+ }
+
+ // Wait here until CTRL-C or other term signal is received
+ fmt.Println("Bot is now running. Press CTRL-C to exit.")
+ sc := make(chan os.Signal, 1)
+ signal.Notify(sc, syscall.SIGINT, syscall.SIGTERM, os.Interrupt, os.Kill)
+ <-sc
+}
+
+func messageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
+ if m.Author.ID == s.State.User.ID {
+ return
+ }
+
+ if m.Content == "*ping" {
+ s.ChannelMessageSend(m.ChannelID, "Pong!")
+ return
+ }
+
+ if m.Content == "*pong" {
+ s.ChannelMessageSend(m.ChannelID, "Ping!")
+ return
+ }
+
+ if strings.HasPrefix(m.Content, "*records") {
+ args, err := leaderboardArgsParser(m.Content, "records", "Return the name of the top runners for a specified game")
+ if err != nil {
+ s.ChannelMessageSend(m.ChannelID, err.Error())
+ return
+ }
+
+ top, err := srcom.GetTopRunnersByGame(*args.game_id, *args.category, *args.amount, *args.platform)
+ if err != nil {
+ s.ChannelMessageSend(m.ChannelID, err.Error())
+ return
+ }
+
+ s.ChannelMessageSend(m.ChannelID, top)
+ return
+ }
+
+ if strings.HasPrefix(m.Content, "*top") {
+ args, err := leaderboardArgsParser(m.Content, "top", "Return the top runs of a game")
+ if err != nil {
+ s.ChannelMessageSend(m.ChannelID, err.Error())
+ return
+ }
+
+ top, err := srcom.GetTopByGame(*args.game_id, *args.category, *args.amount, *args.platform)
+ if err != nil {
+ s.ChannelMessageSend(m.ChannelID, err.Error())
+ return
+ }
+
+ s.ChannelMessageSend(m.ChannelID, top)
+ return
+ }
+
+ if strings.HasPrefix(m.Content, "*wr") {
+ args, err := leaderboardArgsParser(m.Content, "wr", "Return the wr of the specified game")
+ if err != nil {
+ s.ChannelMessageSend(m.ChannelID, err.Error())
+ return
+ }
+
+ wr, err := srcom.GetWRByGame(*args.game_id, *args.category, *args.platform)
+ if err != nil {
+ s.ChannelMessageSend(m.ChannelID, err.Error())
+ }
+
+ s.ChannelMessageSend(m.ChannelID, wr)
+ return
+ }
+}
+
+func leaderboardArgsParser(input string, name string, desc string) (*commandArgs, error) {
+ parser := argparse.NewParser(name, desc)
+ gameid := parser.String("i", "game-id", &argparse.Options{Required: true, Help: "The game id found in the URL of the leaderboard"})
+ amount := parser.Int("a", "amount", &argparse.Options{Required: false, Help: "The amount of records to be returned", Default: 3})
+ platform := parser.String("p", "platform", &argparse.Options{Required: false, Help: "The platform for which the leaderboard should be returned", Default: *new(string)})
+ category := parser.String("c", "category", &argparse.Options{Required: false, Help: "The category for which the leaderboard should be returned", Default: *new(string)})
+
+ r := regexp.MustCompile(`[^\s"']+|"([^"]*)"|'([^']*)`)
+ split := r.FindAllString(input, -1)
+ err := parser.Parse(split)
+ if err != nil {
+ return nil, errors.New(parser.Usage(err))
+ }
+
+ // trim quotation marks from category to allow category names with spaces
+ *category = strings.Trim(*category, "\"")
+
+ return &commandArgs{gameid, category, amount, platform}, nil
+}
blob - /dev/null
blob + 71869a2d547c2ffcdac31e405290a2f34a5d4ffa (mode 644)
--- /dev/null
+++ srcom/srcom.go
+package srcom
+
+import (
+ "errors"
+ "strings"
+
+ "github.com/Witcher01/srapi"
+)
+
+func GetTopRunnersByGame(id string, category string, amount int, platform string) (string, error) {
+ collection, err := getLeaderboard(id, category, amount, platform)
+ if err != nil {
+ return "", err
+ }
+
+ var players_sb strings.Builder
+ for _, run := range collection.Runs {
+ players, err := run.Run.Players()
+ if err != nil {
+ return "", err
+ }
+
+ for _, player := range players.Players() {
+ players_sb.WriteString(player.Name())
+ players_sb.WriteString(", ")
+ }
+ }
+ ret := players_sb.String()
+ ret = strings.TrimSuffix(ret, ", ")
+
+ return ret, nil
+}
+
+func GetTopByGame(id string, category string, amount int, platform string) (string, error) {
+ collection, err := getLeaderboard(id, category, amount, platform)
+ if err != nil {
+ return "", err
+ }
+
+ var pbs strings.Builder
+ for _, run := range collection.Runs {
+ time, _err := getFormattedPlayerTime(run)
+ if err != nil {
+ return "", _err
+ }
+
+ pbs.WriteString(time)
+ pbs.WriteRune('\n')
+ }
+
+ return pbs.String(), nil
+}
+
+func GetWRByGame(id string, category string, platform string) (string, error) {
+ collection, err := getLeaderboard(id, category, 1, platform)
+ if err != nil {
+ return "", err
+ }
+
+ wr := collection.Runs[0]
+
+ time, _err := getFormattedPlayerTime(wr)
+ if _err != nil {
+ return "", err
+ }
+
+ return time, nil
+}
+
+func getFormattedTime(run srapi.RankedRun) string {
+ times := run.Run.Times
+ var time string
+ if times.IngameTime.String() != "0s" {
+ time = run.Run.Times.IngameTime.Format() + " (IGT)"
+ } else if times.RealtimeWithoutLoads.String() != "0s" {
+ time = run.Run.Times.RealtimeWithoutLoads.Format() + " (RTA/NL)"
+ } else {
+ time = run.Run.Times.Realtime.Format() + " (RTA)"
+ }
+
+ return time
+}
+
+func getFormattedPlayerTime(run srapi.RankedRun) (string, error) {
+ players, err := run.Run.Players()
+ if err != nil {
+ return "", err
+ }
+
+ var sb strings.Builder
+ for _, player := range players.Players() {
+ sb.WriteString(player.Name())
+ sb.WriteString(", ");
+ }
+ sb_string := sb.String()
+ sb_string = strings.TrimSuffix(sb_string, ", ")
+
+ time := getFormattedTime(run)
+
+ sb.Reset()
+ sb.WriteString(sb_string)
+ sb.WriteString(": ")
+ sb.WriteString(time)
+
+ return sb.String(), nil
+}
+
+func getDefaultGameLeaderboard(id string, amount int, platform string) (*srapi.Leaderboard, error) {
+ game, err := srapi.GameByID(id, srapi.NoEmbeds)
+ if err != nil {
+ return nil, err
+ }
+
+ filter := &srapi.LeaderboardOptions{Top: amount, Platform: platform}
+
+ collection, err := game.PrimaryLeaderboard(filter, srapi.NoEmbeds)
+ if err != nil {
+ return nil, err
+ }
+
+ return collection, nil
+}
+
+func getGameLeaderboardByCategory(id string, category string, amount int, platform string) (*srapi.Leaderboard, error) {
+ embeds := srapi.NoEmbeds
+ if category != "" {
+ embeds = category
+ }
+
+ game, err := srapi.GameByID(id, embeds)
+ if err != nil {
+ return nil, err
+ }
+
+ cats, err := game.Categories(nil, nil, srapi.NoEmbeds)
+ if err != nil {
+ return nil, err
+ }
+
+ var cat *srapi.Category = nil
+ cats.Walk(func(c *srapi.Category) bool {
+ if (strings.ToLower(c.Name) == strings.ToLower(category)) {
+ cat = c
+ return false
+ }
+ return true
+ });
+
+ if cat == nil {
+ return nil, errors.New("No category with that name found.")
+ }
+
+ filter_options := &srapi.LeaderboardOptions{Top: amount, Platform: platform}
+
+ collection, err := cat.PrimaryLeaderboard(filter_options, srapi.NoEmbeds)
+ if err != nil {
+ return nil, err
+ }
+
+ return collection, nil
+}
+
+func getLeaderboard(id string, category string, amount int, platform string) (*srapi.Leaderboard, error) {
+ if category != "" {
+ return getGameLeaderboardByCategory(id, category, amount, platform)
+ }
+
+ return getDefaultGameLeaderboard(id, amount, platform)
+}