Skip to content

Commit 1351d6e

Browse files
authored
feature: adds discord integration to GoMud (#245)
This PR adds a very simple discord integration to GoMud. To use the integration: 1. Setup a discord webhook: https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks 2. Set the environment variable "DISCORD_WEBHOOK_URL" prior to running GoMud. (`DISCORD_WEBHOOK_URL=https://www.mywebhook.com ./gomud` on linux). All feedback is encouraged and welcome, learning Golang slowly.
1 parent 7967ca7 commit 1351d6e

File tree

5 files changed

+196
-0
lines changed

5 files changed

+196
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ private-notes.txt
33
_datafiles/**/users/*
44
**/config-overrides.yaml
55
**/.roundcount
6+
vendor/
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Package discord
2+
//
3+
// This package provides programatic access to discord messages integrated with GoMud.
4+
//
5+
// References:
6+
// https://leovoel.github.io/embed-visualizer/
7+
// https://birdie0.github.io/discord-webhooks-guide/discord_webhook.html
8+
package discord
9+
10+
import (
11+
"bytes"
12+
"encoding/json"
13+
"errors"
14+
"fmt"
15+
"net/http"
16+
"strconv"
17+
"strings"
18+
19+
"github.com/volte6/gomud/internal/events"
20+
)
21+
22+
var (
23+
WebhookUrl string
24+
initialized bool
25+
)
26+
27+
// Initializes and sets the webhook so we can send messages to discord
28+
// and registers listeners to listen for events
29+
func Init(webhookUrl string) {
30+
if initialized {
31+
return
32+
}
33+
34+
WebhookUrl = webhookUrl
35+
registerListeners()
36+
initialized = true
37+
}
38+
39+
func registerListeners() {
40+
events.RegisterListener(events.PlayerSpawn{}, HandlePlayerSpawn)
41+
events.RegisterListener(events.PlayerDespawn{}, HandlePlayerDespawn)
42+
}
43+
44+
// Sends an embed message to discord which includes a colored bar to the left
45+
// hexColor should be specified as a string in this format "#000000"
46+
func SendRichMessage(message string, hexColor string) error {
47+
if !initialized {
48+
return errors.New("Discord client was not initialized.")
49+
}
50+
51+
if strings.HasPrefix(hexColor, "#") {
52+
hexColor = hexColor[1:]
53+
}
54+
55+
color, err := strconv.ParseInt(hexColor, 16, 32)
56+
if err != nil {
57+
message := fmt.Sprintf("Invalid color specified, expected format #000000")
58+
return errors.New(message)
59+
}
60+
61+
payload := richMessage{
62+
Embeds: []embed{
63+
{
64+
Description: message,
65+
Color: int32(color),
66+
},
67+
},
68+
}
69+
70+
marshalled, err := json.Marshal(payload)
71+
if err != nil {
72+
message := fmt.Sprintf("Couldn't marshal discord message")
73+
return errors.New(message)
74+
}
75+
76+
return send(marshalled)
77+
}
78+
79+
// Sends a simple message to discord
80+
func SendMessage(message string) error {
81+
if !initialized {
82+
return errors.New("Discord client was not initialized.")
83+
}
84+
85+
payload := simpleMessage{
86+
Content: message,
87+
}
88+
89+
marshalled, err := json.Marshal(payload)
90+
if err != nil {
91+
message := fmt.Sprintf("Couldn't marshal discord message")
92+
return errors.New(message)
93+
}
94+
95+
return send(marshalled)
96+
}
97+
98+
func send(marshalled []byte) error {
99+
request, err := http.NewRequest("POST", WebhookUrl, bytes.NewReader(marshalled))
100+
request.Header.Set("Content-Type", "application/json; charset=UTF-8")
101+
102+
client := &http.Client{}
103+
response, err := client.Do(request)
104+
if err != nil {
105+
message := fmt.Sprintf("Couldn't send POST request to discord.")
106+
return errors.New(message)
107+
}
108+
109+
// Expect 204 No Content reply
110+
if response.StatusCode != 204 {
111+
message := fmt.Sprintf("Expected discord to send status code 204, got %v.", response.StatusCode)
112+
return errors.New(message)
113+
}
114+
115+
return nil
116+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package discord
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/volte6/gomud/internal/events"
7+
"github.com/volte6/gomud/internal/users"
8+
)
9+
10+
// Player enters the world event
11+
func HandlePlayerSpawn(e events.Event) bool {
12+
evt, typeOk := e.(events.PlayerSpawn)
13+
if !typeOk {
14+
return false
15+
}
16+
17+
user := users.GetByUserId(evt.UserId)
18+
if user == nil {
19+
return false
20+
}
21+
22+
message := fmt.Sprintf(":white_check_mark: **%v** connected", user.Character.Name)
23+
err := SendMessage(message)
24+
if err != nil {
25+
return false
26+
}
27+
28+
return true
29+
}
30+
31+
// Player leaves the world event
32+
func HandlePlayerDespawn(e events.Event) bool {
33+
evt, typeOk := e.(events.PlayerDespawn)
34+
if !typeOk {
35+
return false
36+
}
37+
38+
user := users.GetByUserId(evt.UserId)
39+
if user == nil {
40+
return false
41+
}
42+
43+
message := fmt.Sprintf(":x: **%v** disconnected", user.Character.Name)
44+
err := SendMessage(message)
45+
if err != nil {
46+
return false
47+
}
48+
49+
return true
50+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package discord
2+
3+
// Reference: https://birdie0.github.io/discord-webhooks-guide/discord_webhook.html
4+
5+
// Discord message payload for non-rich content
6+
// This object has many more fields, see reference
7+
type simpleMessage struct {
8+
Content string `json:"content"`
9+
}
10+
11+
// Discord message payload for rich content
12+
type richMessage struct {
13+
Embeds []embed `json:"embeds"`
14+
}
15+
16+
// These objects have many more fields, see reference
17+
type embed struct {
18+
Description string `json:"description"`
19+
Color int32 `json:"color"`
20+
}

main.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/volte6/gomud/internal/gametime"
2626
"github.com/volte6/gomud/internal/hooks"
2727
"github.com/volte6/gomud/internal/inputhandlers"
28+
"github.com/volte6/gomud/internal/integrations/discord"
2829
"github.com/volte6/gomud/internal/items"
2930
"github.com/volte6/gomud/internal/keywords"
3031
"github.com/volte6/gomud/internal/leaderboard"
@@ -119,6 +120,14 @@ func main() {
119120
os.Exit(1)
120121
}
121122

123+
// Discord integration
124+
if webhookUrl := os.Getenv("DISCORD_WEBHOOK_URL"); webhookUrl != "" {
125+
discord.Init(webhookUrl)
126+
mudlog.Info("Discord", "info", "integration is enabled")
127+
} else {
128+
mudlog.Warn("Discord", "info", "integration is disabled")
129+
}
130+
122131
hooks.RegisterListeners()
123132

124133
// Load all the data files up front.

0 commit comments

Comments
 (0)