diff --git a/.gitignore b/.gitignore index 0e172ef..290881f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ bazel-bin bazel-out bazel-roleypoly bazel-testlogs -node_modules \ No newline at end of file +node_modules +.env \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index f3c14b1..f55b0ab 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,10 @@ { "go.inferGopath": false, - "editor.tabSize": 4, + "editor.tabSize": 2, "editor.insertSpaces": true, - "editor.formatOnSave": true + "editor.formatOnSave": true, + "bazel.buildifierFixOnFormat": true, + "[starlark]": { + "editor.tabSize": 4 + } } diff --git a/README.md b/README.md index 03b0d32..08c4ac9 100644 --- a/README.md +++ b/README.md @@ -10,53 +10,23 @@ Tame your Discord roles. ## Developing -This is a meta-package for all of the sub-repos under Roleypoly. Because this is a distributed system, you might not need to develop on all of these at once to get something done. *Docs in future.* +Roleypoly is a distributed system built with Go, React, Terraform, and Bazel. ### Prerequisites -- [Discord OAuth/Bot Application](https://discordapp.com/developers) -- Go 1.12+ w/ `GO111MODULES=on` -- Node 12+ -- Yarn -- [mkcert](https://github.com/FiloSottile/mkcert) for self-signing certs for `roleypoly.local *.roleypoly.local` -- /etc/hosts entry for the above +This app is known to work on Mac, Linux, and on WSL. Maintainer is typically using WSL. -Some parts of development can be done online or integrated into the online systems hosted on roleypoly.com or canary.roleypoly.com. +- **Required Tools** + - Bazel + - Python (required for Bazel) + - Docker + - Docker Compose + - mkcert +- **Optional Tools** + - _Not having these isn't going to stop you from working on the app. Bazel is magic._ + - ibazel + - Go 1.15+ + - Node 14+ + - Yarn -### Repos - -- [**Aoi 青い**](https://github.com/roleypoly/aoi) - - https://aoi.roleypoly.com - - admin & devops dashboard - - go, typescript, react -- [**Auth**](https://github.com/roleypoly/auth) - - authentication, authorization, and session server. - - go -- [**Discord**](https://github.com/roleypoly/discord) - - discord api client, and bot responder - - go -- [**Design**](https://github.com/roleypoly/design) - - https://ui.roleypoly.com - - branding, ui kit, storybooks - - typescript, react -- [**DevOps**](https://github.com/roleypoly/devops) - - docker wiring and other tooling -- [**Gripkit**](https://github.com/roleypoly/gripkit) - - gRPC wiring toolkit - - go -- [**Platform**](https://github.com/roleypoly/platform) - - main business logic, data & presentation layer - - go -- [**RPC**](https://github.com/roleypoly/rpc) - - protobuf definitions, grpc-go and grpc-web libraries -- [**UI**](https://github.com/roleypoly/ui) - - https://roleypoly.com, https://canary.roleypoly.com - - frontend component - - node, express, typescript, react, next.js - -### Other Repos - -- [Docker Hub](https://hub.docker.com/u/roleypoly) -- [npm](https://www.npmjs.com/org/roleypoly) -- [GitHub](https://github.com/roleypoly) -- [Original Repo](https://github.com/kayteh/roleypoly) +Setup a `.env` file copied from `.env.example`. diff --git a/deps.bzl b/deps.bzl index b5fd6e3..cc15f62 100644 --- a/deps.bzl +++ b/deps.bzl @@ -376,8 +376,8 @@ def go_repositories(): go_repository( name = "com_github_lampjaw_discordclient", importpath = "github.com/lampjaw/discordclient", - sum = "h1:jrRFvccla3/+7EMUrQxpdwy0Jy1O3b0IK/k3KqJKoec=", - version = "v0.0.0-20200914142129-e3b4f5747970", + sum = "h1:Y2o9fEOoAYjCw8IDyxUVaBq44AUbOLyPnYSPpM6Ef3M=", + version = "v0.0.0-20200923011548-6558fc9e89df", ) go_repository( name = "com_github_lib_pq", diff --git a/go.mod b/go.mod index 8fff80f..411c3fb 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/golang/protobuf v1.4.2 // indirect github.com/improbable-eng/grpc-web v0.13.0 github.com/joho/godotenv v1.3.0 - github.com/lampjaw/discordclient v0.0.0-20200914142129-e3b4f5747970 + github.com/lampjaw/discordclient v0.0.0-20200923011548-6558fc9e89df github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/rs/cors v1.7.0 // indirect diff --git a/go.sum b/go.sum index bcafc99..827a8ad 100644 --- a/go.sum +++ b/go.sum @@ -92,6 +92,8 @@ github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kayteh/discordclient v0.0.0-20200923010449-4b45cd745b2b h1:P/u6dpl89r9O7pDVkd4pCo57Bg/wfZD0VReSJh+JhqM= +github.com/kayteh/discordclient v0.0.0-20200923010449-4b45cd745b2b/go.mod h1:lOfqvGl1HcXws86Sczusw1DyV5d0KHPtTTtdjneekto= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -103,6 +105,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/lampjaw/discordclient v0.0.0-20200914142129-e3b4f5747970 h1:jrRFvccla3/+7EMUrQxpdwy0Jy1O3b0IK/k3KqJKoec= github.com/lampjaw/discordclient v0.0.0-20200914142129-e3b4f5747970/go.mod h1:lOfqvGl1HcXws86Sczusw1DyV5d0KHPtTTtdjneekto= +github.com/lampjaw/discordclient v0.0.0-20200923011548-6558fc9e89df h1:Y2o9fEOoAYjCw8IDyxUVaBq44AUbOLyPnYSPpM6Ef3M= +github.com/lampjaw/discordclient v0.0.0-20200923011548-6558fc9e89df/go.mod h1:lOfqvGl1HcXws86Sczusw1DyV5d0KHPtTTtdjneekto= github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg= github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= diff --git a/src/discord-bot/BUILD.bazel b/src/discord-bot/BUILD.bazel index fa374f0..21fe37f 100644 --- a/src/discord-bot/BUILD.bazel +++ b/src/discord-bot/BUILD.bazel @@ -3,11 +3,15 @@ load("@io_bazel_rules_docker//go:image.bzl", "go_image") go_library( name = "discord-bot_lib", - srcs = ["discordbot.go"], + srcs = [ + "discordbot.go", + "listener.go", + ], importpath = "github.com/roleypoly/roleypoly/src/discord-bot", visibility = ["//visibility:private"], deps = [ "//src/common/version", + "//src/discord-bot/internal/strings", "@com_github_bwmarrin_discordgo//:discordgo", "@com_github_joho_godotenv//autoload", "@com_github_lampjaw_discordclient//:discordclient", @@ -16,13 +20,13 @@ go_library( ) go_binary( - name = "discord-bot_bin", + name = "discord-bot", embed = [":discord-bot_lib"], visibility = ["//visibility:private"], ) go_image( - name = "discord-bot", + name = "image", embed = [":discord-bot_lib"], - visibility = ["//visibility:public"], + visibility = ["//visibility:private"], ) diff --git a/src/discord-bot/discordbot.go b/src/discord-bot/discordbot.go index 2f0ae8d..71eace4 100644 --- a/src/discord-bot/discordbot.go +++ b/src/discord-bot/discordbot.go @@ -3,6 +3,7 @@ package main import ( "os" "os/signal" + "regexp" "strings" "syscall" @@ -13,15 +14,21 @@ import ( "k8s.io/klog" ) +var ( + botToken = os.Getenv("DISCORD_BOT_TOKEN") + botClientID = os.Getenv("DISCORD_CLIENT_ID") + rootUsers = strings.Split(os.Getenv("ROOT_USERS"), ",") + allowedBots = strings.Split(os.Getenv("ALLOWED_BOTS"), ",") + appURL = os.Getenv("PUBLIC_URL") + + selfMention = regexp.MustCompile("<@!?" + botClientID + ">") +) + func main() { klog.Info(version.StartupInfo("discord-bot")) - botToken := os.Getenv("DISCORD_BOT_TOKEN") - botClientID := os.Getenv("DISCORD_CLIENT_ID") - rootUsers := strings.Split(os.Getenv("ROOT_USERS"), ",") - discordClient := discordclient.NewDiscordClient(botToken, rootUsers[0], botClientID) - discordClient.GatewayIntents = discordgo.IntentsNone + discordClient.GatewayIntents = discordgo.IntentsGuildMessages messageChannel, err := discordClient.Listen(-1) if err != nil { @@ -30,14 +37,21 @@ func main() { defer awaitExit() - go processMessages(messageChannel) + l := listener{ + client: discordClient, + } + + go l.processMessages(messageChannel) } -func processMessages(messageChannel <-chan discordclient.Message) { - for { - message := <-messageChannel - klog.Infoln("message: ", message.Message()) +func isBotAllowlisted(userID string) bool { + for _, id := range allowedBots { + if id == userID { + return true + } } + + return false } func awaitExit() { diff --git a/src/discord-bot/internal/strings/BUILD.bazel b/src/discord-bot/internal/strings/BUILD.bazel new file mode 100644 index 0000000..4269590 --- /dev/null +++ b/src/discord-bot/internal/strings/BUILD.bazel @@ -0,0 +1,11 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "strings", + srcs = [ + "renderer.go", + "strings.go", + ], + importpath = "github.com/roleypoly/roleypoly/src/discord-bot/internal/strings", + visibility = ["//src/discord-bot:__subpackages__"], +) diff --git a/src/discord-bot/internal/strings/renderer.go b/src/discord-bot/internal/strings/renderer.go new file mode 100644 index 0000000..9d6e565 --- /dev/null +++ b/src/discord-bot/internal/strings/renderer.go @@ -0,0 +1,13 @@ +package strings + +import ( + "bytes" + "text/template" +) + +// Render a template to string +func Render(tmpl *template.Template, data interface{}) string { + buffer := &bytes.Buffer{} + tmpl.Execute(buffer, data) + return buffer.String() +} diff --git a/src/discord-bot/internal/strings/strings.go b/src/discord-bot/internal/strings/strings.go new file mode 100644 index 0000000..8a9b7c9 --- /dev/null +++ b/src/discord-bot/internal/strings/strings.go @@ -0,0 +1,44 @@ +package strings + +import ( + "text/template" +) + +var ( + // MentionResponse is a template that requires strings AppURL and GuildID. + MentionResponse = template.Must( + template.New("MentionResponse").Parse( + `:beginner: Assign your roles here! {{ .AppURL }}/s/{{ .GuildID }}`, + ), + ) + + RootStats = template.Must( + template.New("RootStats").Parse(`🐈 +**People Stats** +<:blank:676216695375003650>🙎‍♀️ **Total Users:** {{ .Users }} +<:blank:676216695375003650>👨‍👩‍👦‍👦 **Total Guilds:** {{ .Guilds }} +<:blank:676216695375003650>🦺 **Total Roles:** {{ .Roles }} + +**Bot Stats** +<:blank:676216695375003650>🔩 **Total Shards:** {{ .Shards }} +<:blank:676216695375003650>⚙️ **Revision:** {{ .GitCommit }} ({{ .GitBranch }}) +<:blank:676216695375003650>⏰ **Built at** {{ .BuildDate }} +`, + ), + ) +) + +type MentionResponseData struct { + AppURL string + GuildID string +} + +type RootStatsData struct { + Users int + Guilds int + Roles int + Shards int + GitCommit string + GitBranch string + BuildDate string +} diff --git a/src/discord-bot/listener.go b/src/discord-bot/listener.go new file mode 100644 index 0000000..493b9a7 --- /dev/null +++ b/src/discord-bot/listener.go @@ -0,0 +1,53 @@ +package main + +import ( + "github.com/lampjaw/discordclient" + "k8s.io/klog" + + "github.com/roleypoly/roleypoly/src/discord-bot/internal/strings" +) + +type listener struct { + client *discordclient.DiscordClient +} + +func (l *listener) processMessages(messageChannel <-chan discordclient.Message) { + for { + go l.handle(<-messageChannel) + } +} + +func (l *listener) handle(message discordclient.Message) { + // Only if it's a message create + if message.Type() != discordclient.MessageTypeCreate { + return + } + + // Only if it's an allowed bot + if message.IsBot() && !isBotAllowlisted(message.UserID()) { + return + } + + // Only if it has the right mention + if !selfMention.MatchString(message.RawMessage()) { + return + } + + l.defaultResponse(message) +} + +func (l *listener) defaultResponse(message discordclient.Message) { + channel := message.Channel() + guild, err := message.ResolveGuildID() + if err != nil { + klog.Warning("failed to fetch guild, ", err) + } + + l.client.SendMessage( + channel, + strings.Render( + strings.MentionResponse, + strings.MentionResponseData{GuildID: guild, AppURL: appURL}, + ), + ) +} diff --git a/src/gripkit/gripkit.go b/src/gripkit/gripkit.go index 3a3d6ad..d7f4ccd 100644 --- a/src/gripkit/gripkit.go +++ b/src/gripkit/gripkit.go @@ -1,5 +1,5 @@ // Package gripkit provides wrappers and helpers for -package gripkit // import "github.com/roleypoly/gripkit" +package gripkit import ( "net/http"