feat: add discord-bot service

This commit is contained in:
41666 2020-09-22 21:21:11 -04:00
parent c40961b1b2
commit 5e3ceb4181
13 changed files with 184 additions and 66 deletions

3
.gitignore vendored
View file

@ -2,4 +2,5 @@ bazel-bin
bazel-out
bazel-roleypoly
bazel-testlogs
node_modules
node_modules
.env

View file

@ -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
}
}

View file

@ -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`.

View file

@ -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",

2
go.mod
View file

@ -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

4
go.sum
View file

@ -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=

View file

@ -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"],
)

View file

@ -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() {

View 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__"],
)

View 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()
}

View 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
}

View 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},
),
)
}

View file

@ -1,5 +1,5 @@
// Package gripkit provides wrappers and helpers for
package gripkit // import "github.com/roleypoly/gripkit"
package gripkit
import (
"net/http"