mirror of
https://github.com/roleypoly/roleypoly.git
synced 2025-04-25 03:49:11 +00:00
feat: add discord-bot service
This commit is contained in:
parent
c40961b1b2
commit
5e3ceb4181
13 changed files with 184 additions and 66 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,3 +3,4 @@ bazel-out
|
||||||
bazel-roleypoly
|
bazel-roleypoly
|
||||||
bazel-testlogs
|
bazel-testlogs
|
||||||
node_modules
|
node_modules
|
||||||
|
.env
|
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
|
@ -1,6 +1,10 @@
|
||||||
{
|
{
|
||||||
"go.inferGopath": false,
|
"go.inferGopath": false,
|
||||||
"editor.tabSize": 4,
|
"editor.tabSize": 2,
|
||||||
"editor.insertSpaces": true,
|
"editor.insertSpaces": true,
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true,
|
||||||
|
"bazel.buildifierFixOnFormat": true,
|
||||||
|
"[starlark]": {
|
||||||
|
"editor.tabSize": 4
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
60
README.md
60
README.md
|
@ -10,53 +10,23 @@ Tame your Discord roles.
|
||||||
|
|
||||||
## Developing
|
## 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
|
### Prerequisites
|
||||||
|
|
||||||
- [Discord OAuth/Bot Application](https://discordapp.com/developers)
|
This app is known to work on Mac, Linux, and on WSL. Maintainer is typically using WSL.
|
||||||
- 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
|
|
||||||
|
|
||||||
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
|
Setup a `.env` file copied from `.env.example`.
|
||||||
|
|
||||||
- [**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)
|
|
||||||
|
|
4
deps.bzl
4
deps.bzl
|
@ -376,8 +376,8 @@ def go_repositories():
|
||||||
go_repository(
|
go_repository(
|
||||||
name = "com_github_lampjaw_discordclient",
|
name = "com_github_lampjaw_discordclient",
|
||||||
importpath = "github.com/lampjaw/discordclient",
|
importpath = "github.com/lampjaw/discordclient",
|
||||||
sum = "h1:jrRFvccla3/+7EMUrQxpdwy0Jy1O3b0IK/k3KqJKoec=",
|
sum = "h1:Y2o9fEOoAYjCw8IDyxUVaBq44AUbOLyPnYSPpM6Ef3M=",
|
||||||
version = "v0.0.0-20200914142129-e3b4f5747970",
|
version = "v0.0.0-20200923011548-6558fc9e89df",
|
||||||
)
|
)
|
||||||
go_repository(
|
go_repository(
|
||||||
name = "com_github_lib_pq",
|
name = "com_github_lib_pq",
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -9,7 +9,7 @@ require (
|
||||||
github.com/golang/protobuf v1.4.2 // indirect
|
github.com/golang/protobuf v1.4.2 // indirect
|
||||||
github.com/improbable-eng/grpc-web v0.13.0
|
github.com/improbable-eng/grpc-web v0.13.0
|
||||||
github.com/joho/godotenv v1.3.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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
|
||||||
github.com/rs/cors v1.7.0 // indirect
|
github.com/rs/cors v1.7.0 // indirect
|
||||||
|
|
4
go.sum
4
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/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/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/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.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
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=
|
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/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 h1:jrRFvccla3/+7EMUrQxpdwy0Jy1O3b0IK/k3KqJKoec=
|
||||||
github.com/lampjaw/discordclient v0.0.0-20200914142129-e3b4f5747970/go.mod h1:lOfqvGl1HcXws86Sczusw1DyV5d0KHPtTTtdjneekto=
|
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 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
|
||||||
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
|
|
@ -3,11 +3,15 @@ load("@io_bazel_rules_docker//go:image.bzl", "go_image")
|
||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
name = "discord-bot_lib",
|
name = "discord-bot_lib",
|
||||||
srcs = ["discordbot.go"],
|
srcs = [
|
||||||
|
"discordbot.go",
|
||||||
|
"listener.go",
|
||||||
|
],
|
||||||
importpath = "github.com/roleypoly/roleypoly/src/discord-bot",
|
importpath = "github.com/roleypoly/roleypoly/src/discord-bot",
|
||||||
visibility = ["//visibility:private"],
|
visibility = ["//visibility:private"],
|
||||||
deps = [
|
deps = [
|
||||||
"//src/common/version",
|
"//src/common/version",
|
||||||
|
"//src/discord-bot/internal/strings",
|
||||||
"@com_github_bwmarrin_discordgo//:discordgo",
|
"@com_github_bwmarrin_discordgo//:discordgo",
|
||||||
"@com_github_joho_godotenv//autoload",
|
"@com_github_joho_godotenv//autoload",
|
||||||
"@com_github_lampjaw_discordclient//:discordclient",
|
"@com_github_lampjaw_discordclient//:discordclient",
|
||||||
|
@ -16,13 +20,13 @@ go_library(
|
||||||
)
|
)
|
||||||
|
|
||||||
go_binary(
|
go_binary(
|
||||||
name = "discord-bot_bin",
|
name = "discord-bot",
|
||||||
embed = [":discord-bot_lib"],
|
embed = [":discord-bot_lib"],
|
||||||
visibility = ["//visibility:private"],
|
visibility = ["//visibility:private"],
|
||||||
)
|
)
|
||||||
|
|
||||||
go_image(
|
go_image(
|
||||||
name = "discord-bot",
|
name = "image",
|
||||||
embed = [":discord-bot_lib"],
|
embed = [":discord-bot_lib"],
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:private"],
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
@ -13,15 +14,21 @@ import (
|
||||||
"k8s.io/klog"
|
"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() {
|
func main() {
|
||||||
klog.Info(version.StartupInfo("discord-bot"))
|
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 := discordclient.NewDiscordClient(botToken, rootUsers[0], botClientID)
|
||||||
discordClient.GatewayIntents = discordgo.IntentsNone
|
discordClient.GatewayIntents = discordgo.IntentsGuildMessages
|
||||||
|
|
||||||
messageChannel, err := discordClient.Listen(-1)
|
messageChannel, err := discordClient.Listen(-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -30,14 +37,21 @@ func main() {
|
||||||
|
|
||||||
defer awaitExit()
|
defer awaitExit()
|
||||||
|
|
||||||
go processMessages(messageChannel)
|
l := listener{
|
||||||
|
client: discordClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
go l.processMessages(messageChannel)
|
||||||
}
|
}
|
||||||
|
|
||||||
func processMessages(messageChannel <-chan discordclient.Message) {
|
func isBotAllowlisted(userID string) bool {
|
||||||
for {
|
for _, id := range allowedBots {
|
||||||
message := <-messageChannel
|
if id == userID {
|
||||||
klog.Infoln("message: ", message.Message())
|
return true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func awaitExit() {
|
func awaitExit() {
|
||||||
|
|
11
src/discord-bot/internal/strings/BUILD.bazel
Normal file
11
src/discord-bot/internal/strings/BUILD.bazel
Normal file
|
@ -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__"],
|
||||||
|
)
|
13
src/discord-bot/internal/strings/renderer.go
Normal file
13
src/discord-bot/internal/strings/renderer.go
Normal file
|
@ -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()
|
||||||
|
}
|
44
src/discord-bot/internal/strings/strings.go
Normal file
44
src/discord-bot/internal/strings/strings.go
Normal file
|
@ -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
|
||||||
|
}
|
53
src/discord-bot/listener.go
Normal file
53
src/discord-bot/listener.go
Normal file
|
@ -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},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
// Package gripkit provides wrappers and helpers for
|
// Package gripkit provides wrappers and helpers for
|
||||||
package gripkit // import "github.com/roleypoly/gripkit"
|
package gripkit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
Loading…
Add table
Reference in a new issue