diff --git a/.drone.yml b/.drone.yml index c81b376..401caf9 100644 --- a/.drone.yml +++ b/.drone.yml @@ -7,13 +7,13 @@ trigger: steps: - name: build pull: always - image: golang:1.15 + image: golang:1.16 commands: - make test - - make build-all + - make build - name: check pull: always - image: golang:1.15 + image: golang:1.16 commands: - make vet @@ -24,19 +24,19 @@ trigger: event: - push branch: - - master + - main steps: - name: build pull: always - image: golang:1.15 + image: golang:1.16 commands: - - make build-all + - make build - name: gitea-release pull: always image: jolheiser/drone-gitea-main:latest settings: token: from_secret: gitea_token - base_url: https://git.etztech.xyz + base: https://git.etztech.xyz files: - "sedbot" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0e03ca1..e1c8229 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ # GoLand .idea/ -# Generated -config/config_default.go - # sedbot -sedbot* \ No newline at end of file +sedbot* +!config/sedbot.example.toml \ No newline at end of file diff --git a/Makefile b/Makefile index 970187d..339839a 100644 --- a/Makefile +++ b/Makefile @@ -8,10 +8,6 @@ fmt: imp: imp -w -.PHONY: generate -generate: - $(GO) generate ./... - .PHONY: test test: $(GO) test -race ./... @@ -24,8 +20,5 @@ vet: build: $(GO) build -.PHONY: build-all -build-all: generate build - .PHONY: check check: generate imp fmt test vet build \ No newline at end of file diff --git a/config/config.go b/config/config.go index 1198b1a..a1bd19e 100644 --- a/config/config.go +++ b/config/config.go @@ -1,13 +1,15 @@ package config import ( + _ "embed" "io/ioutil" "os" - "github.com/BurntSushi/toml" + "github.com/pelletier/go-toml" ) -var defaultConfig = []byte("") +//go:embed sedbot.example.toml +var defaultConfig []byte type Config struct { Token string `toml:"token"` @@ -20,14 +22,26 @@ type Config struct { Address string `toml:"address"` Port int `toml:"port"` } `toml:"server"` - DBPath string `toml:"db_path"` - MCPath string `toml:"mc_path"` + DBPath string `toml:"db_path"` + MCPath string `toml:"mc_path"` + ImgurClientID string `toml:"imgur_client_id"` + ServerAPI struct { + Endpoint string `toml:"endpoint"` + Token string `toml:"token"` + } `toml:"serverapi"` + Twitter struct { + ConsumerKey string `toml:"consumer_key"` + ConsumerSecret string `toml:"consumer_secret"` + AccessToken string `toml:"access_token"` + AccessSecret string `toml:"access_secret"` + } `toml:"twitter"` StaffRoles []string `toml:"staff_roles"` Echoes []Echo `toml:"echoes"` MessageRoles []MessageRole `toml:"message_roles"` RegisterRole string `toml:"register_role"` RegisteredChannel string `toml:"registered_channel"` + LeaveChannel string `toml:"leave_channel"` FiredRole string `toml:"fired_role"` MemeRate string `toml:"meme_rate"` Insult struct { @@ -76,10 +90,15 @@ func Load(configPath string) (*Config, error) { } } - var cfg *Config - if err = toml.Unmarshal(configContent, &cfg); err != nil { + var cfg Config + tree, err := toml.LoadBytes(configContent) + if err != nil { return nil, err } - return cfg, nil + if err := tree.Unmarshal(&cfg); err != nil { + return nil, err + } + + return &cfg, nil } diff --git a/sedbot.example.toml b/config/sedbot.example.toml similarity index 79% rename from sedbot.example.toml rename to config/sedbot.example.toml index 8297124..4af6fcc 100644 --- a/sedbot.example.toml +++ b/config/sedbot.example.toml @@ -19,12 +19,36 @@ register_role = "0" # registered_channel is the channel to message to welcome the newly registered user registered_channel = "0" +# leave_channel is the channel to post leave messages to +leave_channel = "0" + # staff_roles are for staff commands staff_roles = [] # meme_rate is the rate limit for memes meme_rate = "0" +# Imgur Client ID +imgur_client_id = "" + +# ServerAPI options +[serverapi] +# API endpoint +endpoint = "" +# Auth token +token = "" + +# Twitter options +[twitter] +# Consumer Key +consumer_key = "" +# Consumer Secret +consumer_secret = "" +# Access Token +access_token = "" +# Access Secret +access_secret = "" + # Server options [server] # connection address diff --git a/database/database.go b/database/database.go index 8f3c9ac..663312e 100644 --- a/database/database.go +++ b/database/database.go @@ -1,16 +1,16 @@ package database import ( - "strconv" - "go.etcd.io/bbolt" ) var ( firedBucket = []byte("fired") + unbanBucket = []byte("unban") buckets = [][]byte{ firedBucket, + unbanBucket, } ) @@ -35,26 +35,3 @@ func Load(dbPath string) (*Database, error) { db: db, }, nil } - -func (db *Database) CheckPing(roleID string) int { - roleIDByte := []byte(roleID) - var idx int - _ = db.db.View(func(tx *bbolt.Tx) error { - num := tx.Bucket(firedBucket).Get(roleIDByte) - if num != nil { - if i, err := strconv.Atoi(string(num)); err == nil { - idx = i - } - } - return nil - }) - return idx -} - -func (db *Database) IncrementPing(roleID string) error { - roleIDByte := []byte(roleID) - return db.db.Update(func(tx *bbolt.Tx) error { - idx := db.CheckPing(roleID) - return tx.Bucket(firedBucket).Put(roleIDByte, []byte(strconv.Itoa(idx+1))) - }) -} diff --git a/database/ping.go b/database/ping.go new file mode 100644 index 0000000..7f0738d --- /dev/null +++ b/database/ping.go @@ -0,0 +1,30 @@ +package database + +import ( + "strconv" + + "go.etcd.io/bbolt" +) + +func (db *Database) CheckPing(roleID string) int { + roleIDByte := []byte(roleID) + var idx int + _ = db.db.View(func(tx *bbolt.Tx) error { + num := tx.Bucket(firedBucket).Get(roleIDByte) + if num != nil { + if i, err := strconv.Atoi(string(num)); err == nil { + idx = i + } + } + return nil + }) + return idx +} + +func (db *Database) IncrementPing(roleID string) error { + roleIDByte := []byte(roleID) + return db.db.Update(func(tx *bbolt.Tx) error { + idx := db.CheckPing(roleID) + return tx.Bucket(firedBucket).Put(roleIDByte, []byte(strconv.Itoa(idx+1))) + }) +} diff --git a/database/unban.go b/database/unban.go new file mode 100644 index 0000000..ce7c205 --- /dev/null +++ b/database/unban.go @@ -0,0 +1,49 @@ +package database + +import ( + "strconv" + "time" + + "go.etcd.io/bbolt" +) + +type UnbanRecord struct { + Username string + Expiration time.Time +} + +func (u UnbanRecord) ExpirationString() string { + return u.Expiration.Format("01/02/2006 at 03:04:05 pm MST") +} + +func (db *Database) ListUnbans() []UnbanRecord { + unbans := make([]UnbanRecord, 0) + _ = db.db.View(func(tx *bbolt.Tx) error { + return tx.Bucket(unbanBucket).ForEach(func(username, unix []byte) error { + unixNum, err := strconv.ParseInt(string(unix), 10, 64) + if err != nil { + return err + } + unbans = append(unbans, UnbanRecord{ + Username: string(username), + Expiration: time.Unix(unixNum, 0), + }) + return nil + }) + }) + return unbans +} + +func (db *Database) AddUnban(record UnbanRecord) error { + usernameByte := []byte(record.Username) + return db.db.Update(func(tx *bbolt.Tx) error { + return tx.Bucket(unbanBucket).Put(usernameByte, []byte(strconv.FormatInt(record.Expiration.Unix(), 10))) + }) +} + +func (db *Database) RemoveUnban(username string) error { + usernameByte := []byte(username) + return db.db.Update(func(tx *bbolt.Tx) error { + return tx.Bucket(unbanBucket).Delete(usernameByte) + }) +} diff --git a/discord/ban.go b/discord/ban.go new file mode 100644 index 0000000..1180784 --- /dev/null +++ b/discord/ban.go @@ -0,0 +1,54 @@ +package discord + +import ( + "fmt" + "net/http" + "strings" + "time" + + "go.etztech.xyz/go-serverapi" +) + +func init() { + commands = append(commands, &command{ + staffOnly: true, + name: "ban", + validate: func(cmd commandInit) bool { + return isStaff(cmd.message.Member.Roles, cmd.config.StaffRoles) + }, + run: func(cmd commandInit) (string, error) { + args := strings.Fields(cmd.message.Content) + + if len(args) < 2 { + return "This command requires an in-game username.", nil + } + target := args[1] + reason := "You have been banned. Appeal at https://birbmc.com/appeal" + if len(args) >= 3 { + reason = fmt.Sprintf("%s. Appeal at https://birbmc.com/appeal", strings.Join(args[2:], " ")) + } + + ban := serverapi.Ban{ + Kick: serverapi.Kick{ + Unban: serverapi.Unban{ + Target: target, + }, + Reason: reason, + }, + Source: "Discord", + Created: time.Now().Unix(), + } + + status, err := cmd.sapiClient.Ban(ban) + if err != nil { + return "", err + } + if status != http.StatusOK { + return fmt.Sprintf("ServerAPI returned status %d when trying to ban %s", status, target), nil + } + + return fmt.Sprintf("%s was banned by %s", target, cmd.message.Author.Username), nil + }, + help: "Ban a player", + }) +} diff --git a/discord/birb.go b/discord/birb.go new file mode 100644 index 0000000..91642c3 --- /dev/null +++ b/discord/birb.go @@ -0,0 +1,47 @@ +package discord + +import ( + "context" + "fmt" + "strconv" + "strings" + + "go.etztech.xyz/falseknees" +) + +func init() { + commands = append(commands, &command{ + name: "birb", + validate: func(cmd commandInit) bool { + return true + }, + run: func(cmd commandInit) (string, error) { + if !memeRateLimit.Try() { + return "", nil + } + + client := falseknees.New() + + var comic *falseknees.Comic + var err error + args := strings.Fields(cmd.message.Content) + if len(args) < 2 { + comic, err = client.Random(context.Background()) + } else if strings.EqualFold(args[1], "new") { + comic, err = client.Current(context.Background()) + } else { + comicNum, err := strconv.Atoi(args[1]) + if err != nil { + return "", err + } + comic, err = client.Comic(context.Background(), comicNum) + } + if err != nil { + return "", err + } + + return fmt.Sprintf("%d: %s\n%s", comic.Num, comic.Title, comic.Img), nil + }, + help: "Get a FalseKnees comic", + }) +} diff --git a/discord/broadcast.go b/discord/broadcast.go new file mode 100644 index 0000000..18de5d1 --- /dev/null +++ b/discord/broadcast.go @@ -0,0 +1,42 @@ +package discord + +import ( + "fmt" + "net/http" + "strings" + + "go.etztech.xyz/go-serverapi" +) + +func init() { + commands = append(commands, &command{ + staffOnly: true, + name: "broadcast", + validate: func(cmd commandInit) bool { + return isStaff(cmd.message.Member.Roles, cmd.config.StaffRoles) + }, + run: func(cmd commandInit) (string, error) { + args := strings.Fields(cmd.message.Content) + + if len(args) < 2 { + return "This command requires a message to broadcast", nil + } + + message := strings.Join(args[1:], " ") + broadcast := serverapi.Broadcast{ + From: cmd.message.Author.Username, + Message: message, + } + status, err := cmd.sapiClient.Broadcast(broadcast) + if err != nil { + return "", err + } + if status != http.StatusOK { + return fmt.Sprintf("ServerAPI returned status %d when trying to broadcast.", status), nil + } + + return "Broadcast sent!", nil + }, + help: "Send an in-game broadcast", + }) +} diff --git a/discord/clear.go b/discord/clear.go index d549868..f75c676 100644 --- a/discord/clear.go +++ b/discord/clear.go @@ -8,7 +8,8 @@ import ( const clearMax = 20 func init() { - commands["clear"] = command{ + commands = append(commands, &command{ + name: "clear", validate: func(cmd commandInit) bool { return isStaff(cmd.message.Member.Roles, cmd.config.StaffRoles) }, @@ -24,7 +25,7 @@ func init() { if userID != "" { limitArg = 2 if len(args) < 2 { - return "This command takes needs two arguments with a mention", nil + return "This command takes two arguments with a mention", nil } } else if len(args) < 1 { return "This command takes one argument without a mention", nil @@ -64,5 +65,5 @@ func init() { return "", cmd.session.ChannelMessagesBulkDelete(cmd.message.ChannelID, batch) }, help: "Clear messages", - } + }) } diff --git a/discord/compliment.go b/discord/compliment.go index 3df9afa..f26066d 100644 --- a/discord/compliment.go +++ b/discord/compliment.go @@ -6,7 +6,8 @@ import ( ) func init() { - commands["compliment"] = command{ + commands = append(commands, &command{ + name: "compliment", validate: func(cmd commandInit) bool { return true }, @@ -38,5 +39,5 @@ func init() { return "", nil }, help: "Compliment someone!", - } + }) } diff --git a/discord/dad.go b/discord/dad.go index 7744e99..a9fb940 100644 --- a/discord/dad.go +++ b/discord/dad.go @@ -18,7 +18,8 @@ type dadJoke struct { } func init() { - commands["dad"] = command{ + commands = append(commands, &command{ + name: "dad", validate: func(cmd commandInit) bool { return true }, @@ -60,5 +61,5 @@ func init() { return dj.Joke, nil }, help: "Get a random Dad joke", - } + }) } diff --git a/discord/discord.go b/discord/discord.go index 3a3e5ea..5b0c533 100644 --- a/discord/discord.go +++ b/discord/discord.go @@ -1,34 +1,49 @@ package discord import ( + "fmt" "strings" "time" "go.etztech.xyz/sedbot/config" "go.etztech.xyz/sedbot/database" + "go.etztech.xyz/sedbot/imgur" "github.com/bwmarrin/discordgo" + "github.com/dghubble/go-twitter/twitter" + "github.com/dghubble/oauth1" + "go.etztech.xyz/go-serverapi" "go.jolheiser.com/beaver" ) // Register commands to this map var ( - commands = make(map[string]command) + commands = make([]*command, 0) + commandMap = make(map[string]*command) messageRoleMap = make(map[string]map[string]string) memeRateLimit *rateLimit + embedColor = 0x007D96 ) type commandInit struct { - session *discordgo.Session - message *discordgo.Message - config *config.Config - database *database.Database + session *discordgo.Session + message *discordgo.Message + config *config.Config + database *database.Database + sapiClient *serverapi.Client + twitterClient *twitter.Client } type command struct { + staffOnly bool + deleteInvocation bool + echo bool + // TODO Does this really need to exist separately? validate func(cmd commandInit) bool run func(cmd commandInit) (string, error) + name string + aliases []string help string } @@ -38,6 +53,28 @@ func Bot(cfg *config.Config, db *database.Database) (*discordgo.Session, error) return nil, err } + // Init Jupiter images + rand.Seed(time.Now().UnixNano()) + if err := imgur.Init(cfg.ImgurClientID); err != nil { + return nil, err + } + + // Init ServerAPI + sapi := serverapi.NewClient(cfg.ServerAPI.Endpoint, serverapi.WithToken(cfg.ServerAPI.Token)) + + // Init Twitter + twitterConfig := oauth1.NewConfig(cfg.Twitter.ConsumerKey, cfg.Twitter.ConsumerSecret) + twitterToken := oauth1.NewToken(cfg.Twitter.AccessToken, cfg.Twitter.AccessSecret) + twitterHttpClient := twitterConfig.Client(oauth1.NoContext, twitterToken) + twitterClient := twitter.NewClient(twitterHttpClient) + + // Init Unban Schedule + sched := &unbanSchedule{ + db: db, + sapi: sapi, + } + go sched.Run() + for _, messageRole := range cfg.MessageRoles { if messageRoleMap[messageRole.MessageID] == nil { messageRoleMap[messageRole.MessageID] = make(map[string]string) @@ -46,9 +83,22 @@ func Bot(cfg *config.Config, db *database.Database) (*discordgo.Session, error) messageRoleMap[messageRole.MessageID][messageRole.Emoji] = messageRole.RoleID } + // Init commandMap Echo(cfg) + for _, c := range commands { + if c.name == "" { + beaver.Errorf("command is missing a name: %s", c.help) + continue + } + commandMap[c.name] = c + for _, a := range c.aliases { + commandMap[a] = c + } + } + bot.AddHandler(readyHandler()) - bot.AddHandler(commandHandler(cfg, db)) + bot.AddHandler(leaveHandler(cfg)) + bot.AddHandler(commandHandler(cfg, db, sapi, twitterClient)) bot.AddHandler(messageHandler(cfg, db)) bot.AddHandler(reactionAddHandler()) bot.AddHandler(reactionRemoveHandler()) @@ -60,6 +110,9 @@ func Bot(cfg *config.Config, db *database.Database) (*discordgo.Session, error) } memeRateLimit = NewRateLimit(d) + // Intents + bot.Identify.Intents = discordgo.MakeIntent(discordgo.IntentsAll) + return bot, nil } @@ -88,6 +141,7 @@ func sendMessage(s *discordgo.Session, channelID, content string, scrub bool) *d } func sendEmbed(s *discordgo.Session, channelID string, embed *discordgo.MessageEmbed) *discordgo.Message { + embed.Color = embedColor msg, err := s.ChannelMessageSendEmbed(channelID, embed) if err != nil { beaver.Errorf("could not send embed: %v", err) @@ -113,7 +167,7 @@ func readyHandler() func(s *discordgo.Session, m *discordgo.Ready) { } } -func commandHandler(cfg *config.Config, db *database.Database) func(s *discordgo.Session, m *discordgo.MessageCreate) { +func commandHandler(cfg *config.Config, db *database.Database, sapi *serverapi.Client, twitterClient *twitter.Client) func(s *discordgo.Session, m *discordgo.MessageCreate) { return func(s *discordgo.Session, m *discordgo.MessageCreate) { // Ignore bots if m.Author.Bot { @@ -133,21 +187,32 @@ func commandHandler(cfg *config.Config, db *database.Database) func(s *discordgo cmdArg := strings.ToLower(args[0]) - cmd, ok := commands[cmdArg] + cmd, ok := commandMap[cmdArg] if !ok { return } + if cmd.staffOnly && !isStaff(m.Member.Roles, cfg.StaffRoles) { + return + } + cmdInit := commandInit{ - session: s, - message: m.Message, - config: cfg, - database: db, + session: s, + message: m.Message, + config: cfg, + database: db, + sapiClient: sapi, + twitterClient: twitterClient, } if !cmd.validate(cmdInit) { sendMessage(s, m.ChannelID, "You cannot run this command.", false) return } + if cmd.deleteInvocation { + if err := s.ChannelMessageDelete(m.Message.ChannelID, m.Message.ID); err != nil { + beaver.Warnf("could not remove invocation for %s: %v", m.Content, err) + } + } feedback, err := cmd.run(cmdInit) if err != nil { feedback = "Internal error" @@ -204,3 +269,9 @@ func reactionHandler(add bool, s *discordgo.Session, m *discordgo.MessageReactio } } } + +func leaveHandler(cfg *config.Config) func(s *discordgo.Session, m *discordgo.GuildMemberRemove) { + return func(s *discordgo.Session, m *discordgo.GuildMemberRemove) { + sendMessage(s, cfg.LeaveChannel, fmt.Sprintf("%s (%s) left the server. :sob:", m.Mention(), m.User.String()), true) + } +} diff --git a/discord/echo.go b/discord/echo.go index b450a02..9ba244b 100644 --- a/discord/echo.go +++ b/discord/echo.go @@ -5,44 +5,57 @@ import ( "strings" "go.etztech.xyz/sedbot/config" + + "github.com/bwmarrin/discordgo" ) func Echo(cfg *config.Config) { - echoes := make([]string, 0) for _, e := range cfg.Echoes { - echo := e - commands[echo.Name] = command{ + commands = append(commands, &command{ + name: e.Name, + aliases: e.Aliases, + echo: true, validate: func(cmd commandInit) bool { return true }, run: func(cmd commandInit) (string, error) { - return echo.Message, nil + return e.Message, nil }, - help: echo.Help, - } - for _, a := range echo.Aliases { - alias := a - commands[alias] = command{ - validate: func(cmd commandInit) bool { - return true - }, - run: func(cmd commandInit) (string, error) { - return echo.Message, nil - }, - help: echo.Help, - } - } - combined := append([]string{echo.Name}, echo.Aliases...) - echoes = append(echoes, fmt.Sprintf("**%s**: %s", strings.Join(combined, ", "), echo.Help)) + help: e.Help, + }) } - commands["echoes"] = command{ + commands = append(commands, &command{ + deleteInvocation: true, + name: "echoes", validate: func(cmd commandInit) bool { return true }, run: func(cmd commandInit) (string, error) { - return strings.Join(echoes, "\n"), nil + embed := &discordgo.MessageEmbed{ + Title: "Echo Commands", + Fields: make([]*discordgo.MessageEmbedField, len(cfg.Echoes)), + } + for i, echo := range cfg.Echoes { + name := echo.Name + if len(echo.Aliases) > 0 { + name += fmt.Sprintf(" (%s)", strings.Join(echo.Aliases, ", ")) + } + embed.Fields[i] = &discordgo.MessageEmbedField{ + Name: name, + Value: echo.Help, + Inline: true, + } + } + + channel, err := cmd.session.UserChannelCreate(cmd.message.Author.ID) + if err != nil { + return "", err + } + + sendEmbed(cmd.session, channel.ID, embed) + return "", nil }, help: "Get all dynamic messages", - } + }) } diff --git a/discord/fired.go b/discord/fired.go index 7247018..6c5c5b8 100644 --- a/discord/fired.go +++ b/discord/fired.go @@ -5,7 +5,8 @@ import ( ) func init() { - commands["fired"] = command{ + commands = append(commands, &command{ + name: "fired", validate: func(cmd commandInit) bool { return true }, @@ -14,5 +15,5 @@ func init() { cmd.database.CheckPing(cmd.config.FiredRole)), nil }, help: "Check how many times Carolyn has been fired.", - } + }) } diff --git a/discord/help.go b/discord/help.go index cc7170a..2587ef6 100644 --- a/discord/help.go +++ b/discord/help.go @@ -3,39 +3,86 @@ package discord import ( "fmt" "strings" + + "github.com/bwmarrin/discordgo" ) func init() { - commands["help"] = command{ + commands = append(commands, &command{ + deleteInvocation: true, + name: "help", validate: func(cmd commandInit) bool { return true }, run: func(cmd commandInit) (string, error) { args := strings.Fields(cmd.message.Content)[1:] - var resp string + var resp *discordgo.MessageEmbed if len(args) == 1 { - resp = singleHelp(args[0]) + resp = singleHelp(cmd, args[0]) } else { - resp = allHelp() + resp = allHelp(cmd) } - return resp, nil + + channel, err := cmd.session.UserChannelCreate(cmd.message.Author.ID) + if err != nil { + return "", err + } + + sendEmbed(cmd.session, channel.ID, resp) + return "", nil }, help: "HELP! HEEEEEEEEEELP!", - } + }) } -func singleHelp(cmd string) string { - if c, ok := commands[cmd]; ok { - return fmt.Sprintf("%s: %s", cmd, c.help) +func singleHelp(cmd commandInit, arg string) *discordgo.MessageEmbed { + embed := &discordgo.MessageEmbed{ + Title: "Unkown Command", + Color: 0x007D96, } - return "Unknown command" + c, ok := commandMap[arg] + if !ok { + return embed + } + + if c.staffOnly && !isStaff(cmd.message.Member.Roles, cmd.config.StaffRoles) { + return embed + } + + embed.Title = c.name + embed.Description = c.help + return embed } -func allHelp() string { - helps := make([]string, 0) - for n, c := range commands { - helps = append(helps, fmt.Sprintf("%s: %s", n, c.help)) +func allHelp(cmd commandInit) *discordgo.MessageEmbed { + staff := isStaff(cmd.message.Member.Roles, cmd.config.StaffRoles) + embed := &discordgo.MessageEmbed{ + Title: "SedBot Help", + Fields: make([]*discordgo.MessageEmbedField, 0), } - return strings.Join(helps, "\n") + if staff { + embed.Description = "Commands with an asterisk (*) are staff-only" + } + for _, c := range commands { + if c.echo { + continue + } + + cmdName := c.name + if len(c.aliases) > 0 { + cmdName += fmt.Sprintf(" (%s)", strings.Join(c.aliases, ", ")) + } + if c.staffOnly { + cmdName = fmt.Sprintf("*%s", cmdName) + } + if !c.staffOnly || staff { + embed.Fields = append(embed.Fields, &discordgo.MessageEmbedField{ + Name: cmdName, + Value: c.help, + Inline: true, + }) + } + } + return embed } diff --git a/discord/history.go b/discord/history.go index 3b5c26f..baa609a 100644 --- a/discord/history.go +++ b/discord/history.go @@ -12,7 +12,9 @@ import ( ) func init() { - cmd := command{ + commands = append(commands, &command{ + name: "history", + aliases: []string{"names"}, validate: func(cmd commandInit) bool { return true }, @@ -79,8 +81,5 @@ func init() { return "", nil }, help: "Minecraft name history", - } - - commands["history"] = cmd - commands["names"] = cmd + }) } diff --git a/discord/inspire.go b/discord/inspire.go index 2e89db9..e4238ae 100644 --- a/discord/inspire.go +++ b/discord/inspire.go @@ -3,7 +3,8 @@ package discord import "go.etztech.xyz/inspiro" func init() { - commands["inspire"] = command{ + commands = append(commands, &command{ + name: "inspire", validate: func(cmd commandInit) bool { return true }, @@ -22,5 +23,5 @@ func init() { return img, nil }, help: "Get inspired!", - } + }) } diff --git a/discord/insult.go b/discord/insult.go index ad85472..09ddce1 100644 --- a/discord/insult.go +++ b/discord/insult.go @@ -6,7 +6,8 @@ import ( ) func init() { - commands["insult"] = command{ + commands = append(commands, &command{ + name: "insult", validate: func(cmd commandInit) bool { return true }, @@ -39,5 +40,5 @@ func init() { return "", nil }, help: "Insult someone!", - } + }) } diff --git a/discord/jupiter.go b/discord/jupiter.go new file mode 100644 index 0000000..1fecc3e --- /dev/null +++ b/discord/jupiter.go @@ -0,0 +1,21 @@ +package discord + +import "go.etztech.xyz/sedbot/imgur" + +func init() { + commands = append(commands, &command{ + name: "jupiter", + aliases: []string{"jup", "jupjup"}, + validate: func(cmd commandInit) bool { + return true + }, + run: func(cmd commandInit) (string, error) { + if !memeRateLimit.Try() { + return "", nil + } + img := imgur.Images[rand.Intn(len(imgur.Images))-1] + return img.Link, nil + }, + help: "Get a Jupiter image", + }) +} diff --git a/discord/register.go b/discord/register.go index f5b3289..9901d3a 100644 --- a/discord/register.go +++ b/discord/register.go @@ -16,7 +16,8 @@ import ( const bannedPlayersFile = "banned-players.json" func init() { - commands["register"] = command{ + commands = append(commands, &command{ + name: "register", validate: func(cmd commandInit) bool { return len(cmd.message.Member.Roles) == 0 }, @@ -58,7 +59,7 @@ func init() { nickname = player.Username if len(apps) == 0 { - apps, err = models.Application(models.NewDjangoBuilder().Eq(django.ApplicationID, player.ApplicationID)) + apps, err = models.Application(models.NewDjangoBuilder().Exact(django.ApplicationID, player.ApplicationID)) if err != nil { return "Something went wrong, please contact staff", nil } @@ -94,7 +95,7 @@ func init() { return "", nil }, help: "Register yourself with the Discord", - } + }) } type Ban struct { diff --git a/discord/status.go b/discord/status.go index d7d3e73..e2afe3a 100644 --- a/discord/status.go +++ b/discord/status.go @@ -9,7 +9,9 @@ import ( ) func init() { - cmd := command{ + commands = append(commands, &command{ + name: "status", + aliases: []string{"version", "online"}, validate: func(cmd commandInit) bool { return true }, @@ -22,7 +24,6 @@ func init() { } embed := &discordgo.MessageEmbed{ - Color: 0x007D96, Title: fmt.Sprintf("Server Status for `%s`", cmd.config.Server.Address), Description: q.MOTD, Fields: []*discordgo.MessageEmbedField{ @@ -32,12 +33,12 @@ func init() { Inline: true, }, { - Name: "Player's Online", + Name: "~~Players~~ Birbs Online", Value: fmt.Sprintf("%d / %d", q.CurrentPlayers, q.MaxPlayers), Inline: true, }, { - Name: "Players", + Name: "~~Players~~ Birbs", Value: strings.Join(q.Players, ", "), }, }, @@ -46,9 +47,5 @@ func init() { return "", nil }, help: "Get the server status", - } - - commands["status"] = cmd - commands["version"] = cmd - commands["online"] = cmd + }) } diff --git a/discord/tweet.go b/discord/tweet.go new file mode 100644 index 0000000..756fedd --- /dev/null +++ b/discord/tweet.go @@ -0,0 +1,35 @@ +package discord + +import ( + "fmt" + "strings" +) + +func init() { + commands = append(commands, &command{ + staffOnly: true, + name: "tweet", + validate: func(cmd commandInit) bool { + return isStaff(cmd.message.Member.Roles, cmd.config.StaffRoles) + }, + run: func(cmd commandInit) (string, error) { + args := strings.Fields(cmd.message.Content) + + if len(args) < 2 { + return "This command requires a message to tweet", nil + } + + message := strings.Join(args[1:], " ") + tweet, resp, err := cmd.twitterClient.Statuses.Update(message, nil) + if err != nil { + return "", err + } + if resp.StatusCode%100 != 2 { + + } + + return fmt.Sprintf("https://twitter.com/%d/status/%d", tweet.User.ID, tweet.ID), nil + }, + help: "Send a tweet from the BirbMC Twitter", + }) +} diff --git a/discord/unban.go b/discord/unban.go new file mode 100644 index 0000000..b699acd --- /dev/null +++ b/discord/unban.go @@ -0,0 +1,41 @@ +package discord + +import ( + "fmt" + "strconv" + "strings" + "time" + + "go.etztech.xyz/sedbot/database" +) + +func init() { + commands = append(commands, &command{ + staffOnly: true, + name: "unban", + validate: func(cmd commandInit) bool { + return isStaff(cmd.message.Member.Roles, cmd.config.StaffRoles) + }, + run: func(cmd commandInit) (string, error) { + args := strings.Fields(cmd.message.Content) + + if len(args) < 3 { + return "This command requires an in-game username and number of days (use 0 days to unban on next schedule).", nil + } + days, err := strconv.Atoi(args[2]) + if err != nil { + return "number of days must be an integer", nil + } + target := args[1] + expiration := time.Now().Add(time.Hour * 24 * time.Duration(days)) + record := database.UnbanRecord{ + Username: target, + Expiration: expiration, + } + + return fmt.Sprintf("%s will be unbanned on %s", target, record.ExpirationString()), + cmd.database.AddUnban(record) + }, + help: "Unban a player", + }) +} diff --git a/discord/unbans.go b/discord/unbans.go new file mode 100644 index 0000000..d2d8742 --- /dev/null +++ b/discord/unbans.go @@ -0,0 +1,36 @@ +package discord + +import ( + "github.com/bwmarrin/discordgo" +) + +func init() { + commands = append(commands, &command{ + staffOnly: true, + name: "unbans", + validate: func(cmd commandInit) bool { + return isStaff(cmd.message.Member.Roles, cmd.config.StaffRoles) + }, + run: func(cmd commandInit) (string, error) { + unbans := cmd.database.ListUnbans() + if len(unbans) == 0 { + return "There are no pending unbans", nil + } + + embed := &discordgo.MessageEmbed{ + Fields: make([]*discordgo.MessageEmbedField, len(unbans)), + } + for i, record := range cmd.database.ListUnbans() { + embed.Fields[i] = &discordgo.MessageEmbedField{ + Name: record.Username, + Value: record.ExpirationString(), + Inline: true, + } + } + + sendEmbed(cmd.session, cmd.message.ChannelID, embed) + return "", nil + }, + help: "Check the unban scheduler", + }) +} diff --git a/discord/utils.go b/discord/utils.go index 7406bbb..22b60cc 100644 --- a/discord/utils.go +++ b/discord/utils.go @@ -2,7 +2,13 @@ package discord import ( r "math/rand" + "net/http" "time" + + "go.etztech.xyz/sedbot/database" + + "go.etztech.xyz/go-serverapi" + "go.jolheiser.com/beaver" ) type rateLimit struct { @@ -38,3 +44,40 @@ func random(list []string) string { idx := rand.Intn(size) return list[idx] } + +type unbanSchedule struct { + db *database.Database + sapi *serverapi.Client +} + +func (u *unbanSchedule) Run() { + u.check() + ticker := time.NewTicker(time.Minute * 5) + for { + <-ticker.C + u.check() + } +} + +func (u *unbanSchedule) check() { + beaver.Debug("Running unban schedule") + now := time.Now() + for _, record := range u.db.ListUnbans() { + if now.After(record.Expiration) { + beaver.Infof("Unbanning %s", record.Username) + unban := serverapi.Unban{ + Target: record.Username, + } + status, err := u.sapi.Unban(unban) + if err != nil { + beaver.Error(err) + } + if status != http.StatusOK { + beaver.Errorf("ServerAPI returned status %d when trying to ban %s", status, record.Username) + } + if err := u.db.RemoveUnban(record.Username); err != nil { + beaver.Errorf("could not remove unban for %s in database", record.Username) + } + } + } +} diff --git a/discord/welcome.go b/discord/welcome.go index e4380b2..785aa5c 100644 --- a/discord/welcome.go +++ b/discord/welcome.go @@ -7,15 +7,13 @@ import ( ) func init() { - commands["welcome"] = command{ + commands = append(commands, &command{ + deleteInvocation: true, + name: "welcome", validate: func(cmd commandInit) bool { return isStaff(cmd.message.Member.Roles, cmd.config.StaffRoles) }, run: func(cmd commandInit) (string, error) { - if err := cmd.session.ChannelMessageDelete(cmd.message.ChannelID, cmd.message.ID); err != nil { - return "", err - } - orphans := make([]*discordgo.Member, 0) members, err := cmd.session.GuildMembers(cmd.message.GuildID, "", 1000) @@ -49,5 +47,5 @@ func init() { return "", nil }, help: "Get a list of people with no roles", - } + }) } diff --git a/discord/xkcd.go b/discord/xkcd.go new file mode 100644 index 0000000..b498a76 --- /dev/null +++ b/discord/xkcd.go @@ -0,0 +1,45 @@ +package discord + +import ( + "context" + "fmt" + "strconv" + "strings" + + "go.jolheiser.com/xkcd" +) + +func init() { + commands = append(commands, &command{ + name: "xkcd", + validate: func(cmd commandInit) bool { + return true + }, + run: func(cmd commandInit) (string, error) { + if !memeRateLimit.Try() { + return "", nil + } + + client := xkcd.New() + + var comic *xkcd.Comic + var err error + args := strings.Fields(cmd.message.Content) + if len(args) < 2 { + comic, err = client.Current(context.Background()) + } else { + comicNum, err := strconv.Atoi(args[1]) + if err != nil { + return "", err + } + comic, err = client.Comic(context.Background(), comicNum) + } + if err != nil { + return "", err + } + + return fmt.Sprintf("%d: %s\n%s\n%s", comic.Num, comic.SafeTitle, comic.Alt, comic.Img), nil + }, + help: "Get an xkcd comic", + }) +} diff --git a/go.mod b/go.mod index 69cbd96..f90ef43 100644 --- a/go.mod +++ b/go.mod @@ -1,16 +1,21 @@ module go.etztech.xyz/sedbot -go 1.14 +go 1.16 require ( - github.com/BurntSushi/toml v0.3.1 - github.com/bwmarrin/discordgo v0.21.1 + github.com/bwmarrin/discordgo v0.22.0 + github.com/dghubble/go-twitter v0.0.0-20201011215211-4b180d0cc78d + github.com/dghubble/oauth1 v0.7.0 github.com/gorilla/websocket v1.4.2 // indirect + github.com/pelletier/go-toml v1.8.1 go.etcd.io/bbolt v1.3.4 - go.etztech.xyz/go-mcm v1.3.0 + go.etztech.xyz/falseknees v0.0.1 + go.etztech.xyz/go-mcm v1.3.1 + go.etztech.xyz/go-serverapi v0.0.3 go.etztech.xyz/inspiro v0.0.0-20200606185551-edfdf9da2359 go.jolheiser.com/beaver v1.0.2 - go.jolheiser.com/gojang v0.0.2 - golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect - golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae // indirect + go.jolheiser.com/gojang v0.0.3 + go.jolheiser.com/xkcd v0.0.1 + golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect + golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5 // indirect ) diff --git a/go.sum b/go.sum index 2e1439d..7ba0f54 100644 --- a/go.sum +++ b/go.sum @@ -1,33 +1,55 @@ -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/bwmarrin/discordgo v0.21.1 h1:UI2PWwzvn5IFuscYcDc6QB/duhs9MUIjQ4HclcIZisc= -github.com/bwmarrin/discordgo v0.21.1/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/bwmarrin/discordgo v0.22.0 h1:uBxY1HmlVCsW1IuaPjpCGT6A2DBwRn0nvOguQIxDdFM= +github.com/bwmarrin/discordgo v0.22.0/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M= +github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY= +github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dghubble/go-twitter v0.0.0-20201011215211-4b180d0cc78d h1:sBKr0A8iQ1qAOozedZ8Aox+Jpv+TeP1Qv7dcQyW8V+M= +github.com/dghubble/go-twitter v0.0.0-20201011215211-4b180d0cc78d/go.mod h1:xfg4uS5LEzOj8PgZV7SQYRHbG7jPUnelEiaAVJxmhJE= +github.com/dghubble/oauth1 v0.7.0 h1:AlpZdbRiJM4XGHIlQ8BuJ/wlpGwFEJNnB4Mc+78tA/w= +github.com/dghubble/oauth1 v0.7.0/go.mod h1:8pFdfPkv/jr8mkChVbNVuJ0suiHe278BtWI4Tk1ujxk= +github.com/dghubble/sling v1.3.0 h1:pZHjCJq4zJvc6qVQ5wN1jo5oNZlNE0+8T/h0XeXBUKU= +github.com/dghubble/sling v1.3.0/go.mod h1:XXShWaBWKzNLhu2OxikSNFrlsvowtz4kyRuXUG7oQKY= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etztech.xyz/go-mcm v1.3.0 h1:ULhLEHRJZYh+hIgHfp7IuqpOn6H7kkrVxnnfzyZLQM0= -go.etztech.xyz/go-mcm v1.3.0/go.mod h1:Hz2YULB3sN/aQA8cPSm2d6LM3E3qTMspzfRCIAD/1dc= +go.etztech.xyz/falseknees v0.0.1 h1:XjlBqMyBUH5b7e/9oedEwZ9pnz3sqMhXQv6vnQocxeM= +go.etztech.xyz/falseknees v0.0.1/go.mod h1:Acn1AwrvAArQEqhMBDlak5BvCZ3jgV8vdL8Pe5ZldRE= +go.etztech.xyz/go-mcm v1.3.1 h1:RLdOQrMgw0eP7bsfRRbXLW8c5/RaJ5Mg7i/ESFaUwvY= +go.etztech.xyz/go-mcm v1.3.1/go.mod h1:Hz2YULB3sN/aQA8cPSm2d6LM3E3qTMspzfRCIAD/1dc= +go.etztech.xyz/go-serverapi v0.0.3 h1:h2Zww0x5E61cH4jXB97x3oUnbryEuLkuq+RTgIi5/U4= +go.etztech.xyz/go-serverapi v0.0.3/go.mod h1:tq4J5zxVnAwzOiv79iLUzpfNAd7IoNirOfb0gt3/IEY= go.etztech.xyz/inspiro v0.0.0-20200606185551-edfdf9da2359 h1:j/ZeoAj185wHfCSYD52Kt/69i3Bzk1MXN4Qh1yP6+P4= go.etztech.xyz/inspiro v0.0.0-20200606185551-edfdf9da2359/go.mod h1:+fC1WzJm/oS4UEgqr1jPouWerxBys52lTTDA94/5bf8= go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk= go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g= -go.jolheiser.com/gojang v0.0.2 h1:CvQETKT9sFfvuDeYVUkiR0Jh7xIs7Cayi0rZuDrXoZg= -go.jolheiser.com/gojang v0.0.2/go.mod h1:hUBULFDoampNM97E1IaYUhkLBJ30sb7iGsoFOdDU76I= -golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16 h1:y6ce7gCWtnH+m3dCjzQ1PCuwl28DDIc3VNnvY29DlIA= +go.jolheiser.com/gojang v0.0.3 h1:EWDLMo6X3f67DK2p/mSB680H43t8SDrDYNZnXvY7PLg= +go.jolheiser.com/gojang v0.0.3/go.mod h1:hUBULFDoampNM97E1IaYUhkLBJ30sb7iGsoFOdDU76I= +go.jolheiser.com/gql v0.0.1 h1:y3LGHcJUZI9otTCcMn8TVdF3aEzNX0FW6m0YUamlLto= +go.jolheiser.com/gql v0.0.1/go.mod h1:74eYqVRIxsOFxtVl0RYGKNyYQgJYQaxOCgar7LP71Hw= +go.jolheiser.com/xkcd v0.0.1 h1:pRNY2BXxUS+NMtKlm/ENumOr7g3k4VWY/QoDri9YSNU= +go.jolheiser.com/xkcd v0.0.1/go.mod h1:AzWPrZToCLfpazsZBkeu/nPuIvurMqfCOptiydz7Dvk= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5 h1:iCaAy5bMeEvwANu3YnJfWwI0kWAGkEa2RXPdweI/ysk= +golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/imgur/imgur.go b/imgur/imgur.go new file mode 100644 index 0000000..b7b6378 --- /dev/null +++ b/imgur/imgur.go @@ -0,0 +1,55 @@ +package imgur + +import ( + "encoding/json" + "fmt" + "net/http" +) + +// TODO Make a client for this in a separate module + +var Images []*Image + +type Response struct { + Images []*Image `json:"data"` + Success bool `json:"success"` + Status int `json:"status"` +} + +type Image struct { + ID string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Datetime int `json:"datetime"` + Type string `json:"type"` + Animated bool `json:"animated"` + Width int `json:"width"` + Height int `json:"height"` + Size int `json:"size"` + Views int `json:"views"` + Bandwidth int `json:"bandwidth"` + Deletehash string `json:"deletehash"` + Section string `json:"section"` + Link string `json:"link"` +} + +func Init(clientID string) error { + + req, err := http.NewRequest(http.MethodGet, "https://api.imgur.com/3/album/TaJVIdQ/images", nil) + if err != nil { + return err + } + req.Header.Set("Authorization", fmt.Sprintf("Client-ID %s", clientID)) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + + var imgurResp Response + if err := json.NewDecoder(resp.Body).Decode(&imgurResp); err != nil { + return err + } + Images = imgurResp.Images + return resp.Body.Close() +} diff --git a/main.go b/main.go index 442ec31..9be634f 100644 --- a/main.go +++ b/main.go @@ -1,5 +1,3 @@ -//go:generate go run sedbot.example.go - package main import ( diff --git a/sedbot.example.go b/sedbot.example.go deleted file mode 100644 index 10abce6..0000000 --- a/sedbot.example.go +++ /dev/null @@ -1,35 +0,0 @@ -// +build ignore - -package main - -import ( - "fmt" - "io/ioutil" - "os" -) - -const ( - exampleFile = "sedbot.example.toml" - writeFile = "config/config_default.go" - tmpl = `package config - -func init() { - defaultConfig = []byte(` + "`" + `%s` + "`" + `) -} -` -) - -func main() { - bytes, err := ioutil.ReadFile(exampleFile) - if err != nil { - fmt.Printf("Could not read from %s. Are you in the root directory of the project?", exampleFile) - os.Exit(1) - } - - data := fmt.Sprintf(tmpl, string(bytes)) - - if err := ioutil.WriteFile(writeFile, []byte(data), os.ModePerm); err != nil { - fmt.Printf("Could not write to %s.", writeFile) - os.Exit(1) - } -}