Skip to content

Commit 4f51c97

Browse files
committed
Add Echo as HTTP Server.
1 parent c026eb5 commit 4f51c97

10 files changed

+180
-19
lines changed

.idea/Modmail.iml

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Dockerfile

+3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ COPY *.go ./
88
# Copy help
99
COPY ./help.md ./
1010

11+
# Copy HTML
12+
COPY *.html ./
13+
1114
RUN go mod download
1215
RUN go build -o /bin/modmail
1316

create_commands.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ func helpHandler(ev *handler.CommandEvent) error {
213213
}
214214

215215
return ev.CreateMessage(discord.MessageCreate{
216-
Content: helpText,
216+
Content: "",
217217
Embeds: []discord.Embed{
218218
discord.NewEmbedBuilder().
219219
SetTitle("Modmail Help").

fly.toml

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
# fly.toml app configuration file generated for nll-modmail on 2024-01-17T12:38:22+01:00
1+
# fly.toml app configuration file generated for nll-modmail-dev on 2024-01-21T02:40:24+01:00
22
#
33
# See https://fly.io/docs/reference/configuration/ for information about how to use this file.
44
#
55

66
app = "nll-modmail"
77
primary_region = "ams"
88

9+
[build]
910

1011
[env]
11-
PORT = "8080"
1212
MODMAIL_HTTP_SERVER_ENABLED = "true"
13-
14-
13+
PORT = "8080"
1514

1615
[http_service]
1716
internal_port = 8080
@@ -24,4 +23,4 @@ primary_region = "ams"
2423
[[vm]]
2524
cpu_kind = "shared"
2625
cpus = 1
27-
memory_mb = 256
26+
memory_mb = 1024

go.mod

+6
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ require (
1515
github.com/google/go-cmp v0.6.0 // indirect
1616
github.com/gorilla/websocket v1.5.1 // indirect
1717
github.com/hashicorp/hcl v1.0.0 // indirect
18+
github.com/labstack/echo/v4 v4.11.4 // indirect
19+
github.com/labstack/gommon v0.4.2 // indirect
1820
github.com/magiconair/properties v1.8.7 // indirect
21+
github.com/mattn/go-colorable v0.1.13 // indirect
22+
github.com/mattn/go-isatty v0.0.20 // indirect
1923
github.com/mitchellh/mapstructure v1.5.0 // indirect
2024
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
2125
github.com/sagikazarmark/locafero v0.4.0 // indirect
@@ -25,6 +29,8 @@ require (
2529
github.com/spf13/afero v1.11.0 // indirect
2630
github.com/spf13/cast v1.6.0 // indirect
2731
github.com/subosito/gotenv v1.6.0 // indirect
32+
github.com/valyala/bytebufferpool v1.0.0 // indirect
33+
github.com/valyala/fasttemplate v1.2.2 // indirect
2834
go.uber.org/multierr v1.11.0 // indirect
2935
golang.org/x/crypto v0.18.0 // indirect
3036
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect

go.sum

+15
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,17 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/
1313
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
1414
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
1515
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
16+
github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8=
17+
github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8=
18+
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
19+
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
1620
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
1721
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
22+
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
23+
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
24+
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
25+
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
26+
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
1827
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
1928
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
2029
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
@@ -44,6 +53,10 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
4453
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
4554
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
4655
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
56+
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
57+
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
58+
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
59+
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
4760
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
4861
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
4962
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
@@ -52,6 +65,8 @@ golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjs
5265
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
5366
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
5467
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
68+
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
69+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
5570
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
5671
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
5772
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=

help.md

+12-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
Modmail is a bot that helps users contact moderators privately.
2-
It does so by providing a thin layer on top of Discord's existing
3-
Threads feature, allowing users to send messages in a private channel,
4-
that is attached to a parent channel.
1+
Modmail is a bot that helps users contact moderators privately. It does so by providing a thin layer on top of Discord's existing Threads feature, allowing users to send messages in a private channel, that is attached to a parent channel.
52

63
## Commands
74
- `/help` - Show this help message.
@@ -10,8 +7,7 @@ that is attached to a parent channel.
107
- `label: text` - The label of the button.
118
- `color: color` - (Optional) The color of the button. Defaults to blue.
129
- `role: role` - (Optional) The role that can use the button. Defaults to nobody.
13-
- **Note:** If no role is specified, moderators or other intended support will not be notified of new reports.
14-
If a non-mentionable role is specified, the bot must have the permission for mentioning all roles.
10+
- **Note:** If no role is specified, moderators or other intended support will not be notified of new reports. If a non-mentionable role is specified, the bot must have the permission for mentioning all roles.
1511

1612
## Permissions
1713

@@ -22,4 +18,13 @@ that is attached to a parent channel.
2218
- **Embed Links:** This is required to include the content submitted by the user.
2319
- **__Optional Permissions__**
2420
- **Manage Threads:** To set Threads to non-invitable and extend the thread's archive timer.
25-
- **Mention Everyone:** To mention the role specified in `/create-report-button`.
21+
- **Mention Everyone:** To mention the role specified in `/create-report-button`.
22+
23+
## Getting Started
24+
1. Set up a channel that will be used for modmail.
25+
2. Set it up so that users can only read messages, but cannot create threads.
26+
- If desired, you can allow users to manually create private threads.
27+
3. Ensure the bot has the required permissions; it should by default, but you can restrict it to only the modmail channel if you want.
28+
4. Type up some guidelines or other information that may be pertinent to users for when they contact you.
29+
5. Run `/create-report-button` in the channel you want the button to be in.
30+
6. Done! Users can now click the button to contact you, and threads will be created in the modmail channel.

main.go

+37-6
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package main
22

33
import (
44
"context"
5+
_ "embed"
6+
"encoding/hex"
57
"fmt"
68
"github.com/disgoorg/disgo"
79
"github.com/disgoorg/disgo/bot"
@@ -10,6 +12,7 @@ import (
1012
"github.com/disgoorg/disgo/handler"
1113
"github.com/disgoorg/disgo/httpserver"
1214
"github.com/disgoorg/snowflake/v2"
15+
"github.com/labstack/echo/v4"
1316
"github.com/spf13/viper"
1417
"log/slog"
1518
"os"
@@ -46,16 +49,12 @@ func main() {
4649
})
4750
r.Command("/ping", pingHandler)
4851
r.Command("/create-report-button", createReportButtonHandler)
52+
r.Command("/help", helpHandler)
4953
r.Component("/report-button/{role}", reportButtonHandler)
5054
r.Modal("/report-modal/{role}", reportModalHandler)
5155

5256
client, err := disgo.New(
5357
BOT_TOKEN,
54-
bot.WithHTTPServerConfigOpts(
55-
PUB_KEY,
56-
httpserver.WithURL("/interactions"),
57-
httpserver.WithAddress(fmt.Sprintf(":%d", viper.GetUint("http_server.port"))),
58-
),
5958
bot.WithDefaultGateway(),
6059
bot.WithEventListeners(r),
6160
bot.WithEventListenerFunc(func(ev *events.Ready) {
@@ -95,7 +94,8 @@ func main() {
9594
}
9695

9796
if viper.GetBool("http_server.enabled") {
98-
err = client.OpenHTTPServer()
97+
address := fmt.Sprintf(":%d", viper.GetInt("http_server.port"))
98+
openHTTPServer(client, PUB_KEY, "/interactions", address)
9999
slog.Info("HTTP server is listening", "port", viper.GetInt("http_server.port"))
100100
} else {
101101
err = client.OpenGateway(context.Background())
@@ -108,3 +108,34 @@ func main() {
108108
signal.Notify(s, os.Interrupt, syscall.SIGTERM)
109109
<-s
110110
}
111+
112+
//go:embed welcome.html
113+
var welcomePage string
114+
115+
func openHTTPServer(client bot.Client, publicKey, webhookPath, address string) {
116+
r := echo.New()
117+
pubKeyBytes, err := hex.DecodeString(publicKey)
118+
if err != nil {
119+
panic(err)
120+
}
121+
122+
handlerFunc := httpserver.HandleInteraction(
123+
pubKeyBytes,
124+
slog.Default(),
125+
client.EventManager().HandleHTTPEvent,
126+
)
127+
128+
r.POST(webhookPath, func(c echo.Context) error {
129+
handlerFunc.ServeHTTP(c.Response().Writer, c.Request())
130+
return nil
131+
})
132+
133+
r.GET("/", func(c echo.Context) error {
134+
return c.HTML(200, welcomePage)
135+
})
136+
137+
slog.Info("HTTP server is listening", "address", address)
138+
139+
r.HideBanner = true
140+
go r.Start(address)
141+
}

server.go

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"encoding/hex"
6+
"github.com/disgoorg/disgo/httpserver"
7+
"github.com/labstack/echo/v4"
8+
"log/slog"
9+
)
10+
11+
type Server struct {
12+
*echo.Echo
13+
EventHandlerFunc httpserver.EventHandlerFunc
14+
publicKey string
15+
}
16+
17+
var _ httpserver.Server = (*Server)(nil)
18+
19+
func NewServer(publicKey string, eventHandlerFunc httpserver.EventHandlerFunc) Server {
20+
return Server{
21+
Echo: echo.New(),
22+
EventHandlerFunc: eventHandlerFunc,
23+
publicKey: publicKey,
24+
}
25+
}
26+
27+
func (s *Server) Start() {
28+
hexKey, err := hex.DecodeString(s.publicKey)
29+
if err != nil {
30+
slog.Error("failed to decode public key",
31+
"error", err)
32+
panic(err)
33+
}
34+
handlerFunc := httpserver.HandleInteraction(
35+
hexKey,
36+
slog.Default(),
37+
s.EventHandlerFunc,
38+
)
39+
s.POST("/interactions", func(ctx echo.Context) error {
40+
handlerFunc(ctx.Response().Writer, ctx.Request())
41+
return nil
42+
})
43+
44+
s.Logger.Fatal(s.Echo.Start(":8080"))
45+
}
46+
47+
func (s *Server) Close(ctx context.Context) {
48+
if err := s.Echo.Shutdown(ctx); err != nil {
49+
slog.Error("failed to shutdown http server",
50+
"error", err)
51+
}
52+
}
53+
54+
func startServer() {
55+
r := echo.New()
56+
r.GET("/", func(c echo.Context) error {
57+
return c.String(200, "Hello, World!")
58+
})
59+
60+
r.Logger.Fatal(r.Start(":8080"))
61+
}

welcome.html

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<script src="https://cdn.twind.style" crossorigin></script>
5+
<!--<script>
6+
twind.install({
7+
/* options */
8+
})
9+
</script>-->
10+
</head>
11+
<body>
12+
<div class="relative flex min-h-screen flex-col justify-center overflow-hidden bg-amber-400 py-6 sm:py-12">
13+
<div class="relative bg-amber-100 px-6 pt-10 pb-8 shadow-xl ring-1 ring-gray-900/5 sm:mx-auto sm:max-w-lg sm:rounded-lg sm:px-10">
14+
<div class="mx-auto max-w-md">
15+
<h1 class="text-4xl font-thin">Modmail</h1>
16+
<div class="divide-y divide-gray-300/50">
17+
<div class="space-y-6 py-8 text-base leading-7 text-gray-600">
18+
<p>
19+
<i>Modmail</i> is a simple bot for handling staff support requests for a Dsicord server. It uses Discord's existing threads and adds a more user-friendly interface for starting a new one, with the intent of getting in touch with moderators or other support staff.
20+
</p>
21+
<p>
22+
The bot works entirely by Discord's interactions system. This means that the bot does not read any messages from the server, and instead functions entirely passively.
23+
</p>
24+
<p>
25+
<a href="https://discord.com/api/oauth2/authorize?client_id=1195759348438282281&permissions=395137138688&scope=bot"
26+
class="border-2 border-stone-600 rounded-md p-0.5 bg-amber-200 hover:bg-amber-300 select-none">
27+
Add Modmail to your server!
28+
</a>
29+
</p>
30+
<hr class="border-stone-600">
31+
<p>
32+
This bot is maintained by
33+
<a href="https://myrkvi.no/" class="underline">Myrkvi</a>.
34+
</div>
35+
</div>
36+
</div>
37+
</div>
38+
</div>
39+
</body>
40+
</html>

0 commit comments

Comments
 (0)