commit b317dfddf48533aebf309269a19eff5e789a01b2 Author: Michael Zhang Date: Mon Jul 19 17:06:54 2021 -0500 trying out discord slash commands diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7f7fade --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/pepster +/config.toml +/test.db \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b590908 --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ +SOURCES := $(shell find . -name "*.go" -print) +BIN := pepster + +$(BIN): $(SOURCES) + go build -o $@ diff --git a/commandLink.go b/commandLink.go new file mode 100644 index 0000000..69dafca --- /dev/null +++ b/commandLink.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + + "github.com/bwmarrin/discordgo" + "gorm.io/gorm/clause" + + "pepster/models" +) + +func (p *Pepster) commandLink() Command { + return Command{ + Name: "link", + Description: "Links your osu! account to your Discord account.", + Options: []*CommandOption{ + &CommandOption{ + Type: CommandOptionString, + Name: "username", + Description: "Your osu! username", + Required: true, + }, + }, + Handler: func(s *discordgo.Session, i *discordgo.InteractionCreate) { + var user *discordgo.User + if i.User != nil { + user = i.User + } else if i.Member != nil { + user = i.Member.User + } + + dbUser := models.User{ + DiscordID: user.ID, + OsuID: 2688103, + } + + p.db.Clauses(clause.OnConflict{ + Columns: []clause.Column{{Name: "discord_id"}}, + UpdateAll: true, + }).Create(&dbUser) + fmt.Println("Updated", dbUser) + + p.updateStatus <- true + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf(":thumbsup: Linked osu! user **%s** to **%s**", "IOException", user.String()), + }, + }) + }, + } +} diff --git a/commands.go b/commands.go new file mode 100644 index 0000000..4910f77 --- /dev/null +++ b/commands.go @@ -0,0 +1,97 @@ +package main + +import ( + "log" + + "github.com/bwmarrin/discordgo" +) + +const ( + DEV_GUILD_ID = "665066008507187220" +) + +type Command struct { + Name string + Description string + Options []*CommandOption + Handler CommandHandler +} + +type CommandHandler = func(*discordgo.Session, *discordgo.InteractionCreate) + +type CommandOption struct { + Type CommandOptionType + Name string + Description string + Required bool +} + +type CommandOptionType uint8 + +const ( + CommandOptionSubCommand CommandOptionType = 1 + CommandOptionSubCommandGroup CommandOptionType = 2 + CommandOptionString CommandOptionType = 3 + CommandOptionInteger CommandOptionType = 4 + CommandOptionBoolean CommandOptionType = 5 + CommandOptionUser CommandOptionType = 6 + CommandOptionChannel CommandOptionType = 7 + CommandOptionRole CommandOptionType = 8 + CommandOptionMentionable CommandOptionType = 9 +) + +func guildID(global bool) string { + if global { + return "" + } else { + return DEV_GUILD_ID + } +} + +func (p *Pepster) registerCommands(commands []Command, global bool) { + handlers := make(map[string]CommandHandler, len(commands)) + + for _, command := range commands { + convertedOptions := make([]*discordgo.ApplicationCommandOption, len(command.Options)) + for i, option := range command.Options { + convertedOptions[i] = &discordgo.ApplicationCommandOption{ + Name: option.Name, + Description: option.Description, + Type: discordgo.ApplicationCommandOptionType(option.Type), + Required: option.Required, + } + } + + _, err := p.discord.ApplicationCommandCreate(p.config.AppID, guildID(global), &discordgo.ApplicationCommand{ + Name: command.Name, + Description: command.Description, + Options: convertedOptions, + }) + + if err != nil { + log.Println("could not register command", command.Name, ":", err) + continue + } + + handlers[command.Name] = command.Handler + } + + p.discord.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { + if handler, ok := handlers[i.ApplicationCommandData().Name]; ok { + handler(s, i) + } + }) +} + +func (p *Pepster) unregisterAllCommands(global bool) { + commands, _ := p.discord.ApplicationCommands(p.config.AppID, guildID(global)) + for _, command := range commands { + p.discord.ApplicationCommandDelete(p.config.AppID, guildID(global), command.ID) + } +} + +func (p *Pepster) commandsList() []Command { + return []Command{ + p.commandLink(), + } +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..37b5e23 --- /dev/null +++ b/config.go @@ -0,0 +1,7 @@ +package main + +type Config struct { + Debug bool `toml:"debug,omitempty"` + AppID string `toml:"app_id"` + BotToken string `toml:"bot_token"` +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..85e7949 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module pepster + +go 1.16 + +require ( + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/bwmarrin/discordgo v0.23.3-0.20210627161652-421e14965030 // indirect + github.com/mattn/go-sqlite3 v1.14.8 // indirect + gorm.io/driver/sqlite v1.1.4 // indirect + gorm.io/gorm v1.21.12 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f304e9c --- /dev/null +++ b/go.sum @@ -0,0 +1,27 @@ +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.23.3-0.20210627161652-421e14965030 h1:JgAlPJlCqsrUqXcClndnvF6LOjV528hpU/l5YzN++PA= +github.com/bwmarrin/discordgo v0.23.3-0.20210627161652-421e14965030/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= +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/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= +github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU= +github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM= +gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw= +gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.21.12 h1:3fQM0Eiz7jcJEhPggHEpoYnsGZqynMzverL77DV40RM= +gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= diff --git a/main.go b/main.go new file mode 100644 index 0000000..0cbb688 --- /dev/null +++ b/main.go @@ -0,0 +1,73 @@ +package main + +import ( + "flag" + "io/ioutil" + "log" + "os" + "os/signal" + + "github.com/BurntSushi/toml" + "github.com/bwmarrin/discordgo" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + + "pepster/models" +) + +func main() { + var err error + + // open config file + configPath := flag.String("config", "config.toml", "Path to the config file (defaults to config.toml)") + flag.Parse() + + var config Config + configData, err := ioutil.ReadFile(*configPath) + if err != nil { + log.Fatal("Failed to read config file", err) + } + + if err = toml.Unmarshal(configData, &config); err != nil { + log.Fatal("Invalid toml in config file", err) + } + + // open database + db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{}) + if err != nil { + panic("failed to connect database") + } + + // migrate the schema + models.AutoMigrate(db) + + // connect to discord + discord, err := discordgo.New("Bot " + config.BotToken) + if err != nil { + log.Fatal("Could not connect to discord", err) + } + if err = discord.Open(); err != nil { + log.Fatal("Could not connect to discord", err) + } + + pepster := &Pepster{ + config: &config, + db: db, + discord: discord, + updateStatus: make(chan bool, 5), + } + pepster.Init() + pepster.updateStatus <- true + defer pepster.Close() + + // registering command handlers + pepster.registerCommands(pepster.commandsList(), false) + + // graceful shutdown handler + stop := make(chan os.Signal) + signal.Notify(stop, os.Interrupt) + <-stop + log.Println("Gracefully shutting down...") + + // deferred close +} diff --git a/models/automigrate.go b/models/automigrate.go new file mode 100644 index 0000000..fcb0682 --- /dev/null +++ b/models/automigrate.go @@ -0,0 +1,7 @@ +package models + +import "gorm.io/gorm" + +func AutoMigrate(db *gorm.DB) { + db.AutoMigrate(&User{}) +} diff --git a/models/user.go b/models/user.go new file mode 100644 index 0000000..55b9c61 --- /dev/null +++ b/models/user.go @@ -0,0 +1,9 @@ +package models + +import "gorm.io/gorm" + +type User struct { + gorm.Model + DiscordID string `gorm:"column:discord_id;unique"` + OsuID int `gorm:"column:osu_id"` +} diff --git a/pepster.go b/pepster.go new file mode 100644 index 0000000..ba3407b --- /dev/null +++ b/pepster.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + + "github.com/bwmarrin/discordgo" + "gorm.io/gorm" +) + +type Pepster struct { + config *Config + db *gorm.DB + discord *discordgo.Session + + updateStatus chan bool +} + +func (p *Pepster) Init() { + go (func() { + fmt.Println("listening") + for _ = range p.updateStatus { + _ = p.discord.UpdateStatusComplex(discordgo.UpdateStatusData{ + AFK: false, + Status: "hellosu", + }) + fmt.Println("lol Update") + } + })() +} + +func (p *Pepster) Close() { + p.unregisterAllCommands(false) + p.discord.Close() +}