Commit Diff


commit - /dev/null
commit + 82e5b12a48c5af20503943aa43cfdf3c26dcdfdc
blob - /dev/null
blob + 3a46bd7d55f89caf5b0178268117e8856e6b9dc7 (mode 644)
--- /dev/null
+++ go.mod
@@ -0,0 +1,9 @@
+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
@@ -0,0 +1,10 @@
+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
@@ -0,0 +1,147 @@
+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
@@ -0,0 +1,169 @@
+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)
+}