Skip to content

Commit c026eb5

Browse files
committed
Initial commit
0 parents  commit c026eb5

14 files changed

+637
-0
lines changed

.dockerignore

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# flyctl launch added from .gitignore
2+
**/Modmail
3+
**/*.exe
4+
5+
# flyctl launch added from .idea/.gitignore
6+
# Default ignored files
7+
.idea/shelf
8+
.idea/workspace.xml
9+
# Editor-based HTTP Client requests
10+
.idea/httpRequests
11+
# Datasource local storage ignored files
12+
.idea/dataSources
13+
.idea/dataSources.local.xml
14+
fly.toml

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
config.toml
2+
Modmail
3+
*.exe

.idea/.gitignore

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

.idea/Modmail.iml

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

.idea/modules.xml

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

.idea/vcs.xml

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

Dockerfile

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
FROM golang:alpine as build
2+
WORKDIR /src
3+
4+
# Copy Go code
5+
COPY ./go.mod ./go.sum ./
6+
COPY *.go ./
7+
8+
# Copy help
9+
COPY ./help.md ./
10+
11+
RUN go mod download
12+
RUN go build -o /bin/modmail
13+
14+
FROM alpine:latest
15+
COPY --from=build /bin/modmail /bin/modmail
16+
17+
EXPOSE 8080
18+
CMD ["/bin/modmail"]

config.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"github.com/spf13/pflag"
6+
"github.com/spf13/viper"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
)
11+
12+
var configPath *string = pflag.StringP("config", "C", "", "Path to config file")
13+
14+
func init() {
15+
pflag.Parse()
16+
17+
viper.SetConfigName("config")
18+
viper.SetConfigType("toml")
19+
20+
viper.SetEnvPrefix("modmail")
21+
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
22+
23+
var dotConfigPath string
24+
if os.Getenv("XDG_CONFIG_HOME") != "" {
25+
dotConfigPath = os.Getenv("XDG_CONFIG_HOME") + "/modmail"
26+
} else {
27+
dotConfigPath = os.Getenv("HOME") + "/.config/modmail"
28+
}
29+
viper.AddConfigPath(dotConfigPath)
30+
31+
viper.AddConfigPath("/etc/modmail")
32+
viper.AddConfigPath(".")
33+
34+
viper.SetDefault("discord.token", "")
35+
viper.SetDefault("discord.pub_key", "")
36+
viper.SetDefault("dev_mode.enabled", false)
37+
viper.SetDefault("dev_mode.guild", 0)
38+
viper.SetDefault("http_server.enabled", false)
39+
viper.SetDefault("http_server.port", 8080)
40+
41+
viper.AutomaticEnv()
42+
43+
if *configPath != "" {
44+
viper.SetConfigFile(*configPath)
45+
}
46+
47+
if err := viper.ReadInConfig(); err != nil {
48+
if errors.As(err, &viper.ConfigFileNotFoundError{}) {
49+
//saveDefaultConfig(*configPath, dotConfigPath)
50+
} else {
51+
panic(err)
52+
}
53+
}
54+
}
55+
56+
func saveDefaultConfig(configPath, dotPath string) {
57+
var err error
58+
if configPath != "" {
59+
if err = os.MkdirAll(filepath.Dir(configPath), 0755); err != nil {
60+
panic(err)
61+
}
62+
err = viper.SafeWriteConfigAs(configPath)
63+
} else {
64+
if err = os.MkdirAll(dotPath, 0755); err != nil {
65+
panic(err)
66+
}
67+
err = viper.SafeWriteConfig()
68+
}
69+
if err != nil {
70+
panic(err)
71+
}
72+
}

create_commands.go

+237
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package main
2+
3+
import (
4+
_ "embed"
5+
"errors"
6+
"fmt"
7+
"github.com/disgoorg/disgo/discord"
8+
"github.com/disgoorg/disgo/handler"
9+
"github.com/disgoorg/disgo/rest"
10+
"github.com/disgoorg/json"
11+
"log/slog"
12+
)
13+
14+
var pingCommand = discord.SlashCommandCreate{
15+
Name: "ping",
16+
Description: "Ping...... pong!",
17+
DefaultMemberPermissions: json.NewNullablePtr(discord.PermissionManageGuild),
18+
}
19+
20+
func pingHandler(ev *handler.CommandEvent) error {
21+
return ev.CreateMessage(discord.MessageCreate{
22+
Content: "Pong!",
23+
Flags: discord.MessageFlagEphemeral,
24+
})
25+
}
26+
27+
var createReportButtonCommand = discord.SlashCommandCreate{
28+
Name: "create-report-button",
29+
Description: "Create a button",
30+
DefaultMemberPermissions: json.NewNullablePtr(discord.PermissionManageGuild),
31+
DMPermission: ref(false),
32+
Options: []discord.ApplicationCommandOption{
33+
discord.ApplicationCommandOptionString{
34+
Name: "label",
35+
Description: "The label on the button to create",
36+
Required: true,
37+
},
38+
discord.ApplicationCommandOptionString{
39+
Name: "button-color",
40+
Description: "The color of the button",
41+
Required: false,
42+
Choices: []discord.ApplicationCommandOptionChoiceString{
43+
{
44+
Name: "red",
45+
Value: "red",
46+
},
47+
{
48+
Name: "green",
49+
Value: "green",
50+
},
51+
{
52+
Name: "blue",
53+
Value: "blue",
54+
},
55+
{
56+
Name: "grey",
57+
Value: "grey",
58+
},
59+
},
60+
},
61+
discord.ApplicationCommandOptionRole{
62+
Name: "role",
63+
Description: "The role that should be tagged when submitting a report",
64+
Required: false,
65+
},
66+
},
67+
}
68+
69+
var stringToButtonStyle = map[string]discord.ButtonStyle{
70+
"red": discord.ButtonStyleDanger,
71+
"green": discord.ButtonStyleSuccess,
72+
"blue": discord.ButtonStylePrimary,
73+
"grey": discord.ButtonStyleSecondary,
74+
}
75+
76+
func createReportButtonHandler(ev *handler.CommandEvent) error {
77+
data := ev.SlashCommandInteractionData()
78+
label := data.String("label")
79+
color := data.String("button-color")
80+
role := data.Role("role")
81+
if color == "" {
82+
color = "blue"
83+
}
84+
85+
return ev.CreateMessage(discord.MessageCreate{
86+
Components: []discord.ContainerComponent{
87+
discord.ActionRowComponent{
88+
discord.NewButton(
89+
stringToButtonStyle[color],
90+
label,
91+
fmt.Sprintf("/report-button/%d", uint64(role.ID)),
92+
"",
93+
),
94+
},
95+
},
96+
})
97+
}
98+
99+
func reportButtonHandler(ev *handler.ComponentEvent) error {
100+
slog.Info("Received report button interaction",
101+
"user_id", ev.User().ID,
102+
"user_name", ev.User().Username,
103+
"guild_id", *ev.GuildID(),
104+
"channel_id", ev.Channel().ID(),
105+
)
106+
role := ev.Variables["role"]
107+
customID := fmt.Sprintf("/report-modal/%s", role)
108+
slog.Info("Sending modal", "custom_id", customID)
109+
modal := discord.NewModalCreateBuilder().
110+
SetCustomID(customID).
111+
SetTitle("Report").
112+
AddActionRow(
113+
discord.NewShortTextInput("title", "Subject").
114+
WithPlaceholder("Subject or topic of the report").
115+
WithRequired(true).
116+
WithMinLength(5).
117+
WithMaxLength(72)).
118+
AddActionRow(
119+
discord.NewParagraphTextInput("description", "Description").
120+
WithPlaceholder(
121+
"Report information\n\n" +
122+
"Markdown is supported.\n" +
123+
"More details, images, etc. can be submitted afterwards.").
124+
WithRequired(true).
125+
WithMinLength(24),
126+
).
127+
Build()
128+
129+
err := ev.Modal(modal)
130+
if err != nil {
131+
slog.Error("Failed to send modal", "err", err)
132+
133+
var restErr rest.Error
134+
if errors.As(err, &restErr) {
135+
println(string(restErr.RsBody))
136+
}
137+
} else {
138+
slog.Info("Sent modal")
139+
}
140+
return err
141+
}
142+
143+
func reportModalHandler(ev *handler.ModalEvent) error {
144+
_ = ev.DeferCreateMessage(true)
145+
role := ev.Variables["role"]
146+
title := ev.Data.Text("title")
147+
description := ev.Data.Text("description")
148+
149+
thread, err := ev.Client().Rest().CreateThread(
150+
ev.Channel().ID(),
151+
discord.GuildPrivateThreadCreate{
152+
Name: title,
153+
AutoArchiveDuration: 10080,
154+
Invitable: false,
155+
})
156+
if err != nil {
157+
return err
158+
}
159+
160+
user := ev.User()
161+
avatarUrl := ""
162+
if url := user.AvatarURL(); url != nil {
163+
avatarUrl = *url
164+
} else if url := user.DefaultAvatarURL(); url != "" {
165+
avatarUrl = url
166+
}
167+
embed := discord.NewEmbedBuilder().
168+
SetTitle(title).
169+
SetDescription(description).
170+
SetColor(0x4848FF).
171+
SetAuthor(user.Username, "", avatarUrl).
172+
Build()
173+
174+
message, err := ev.Client().Rest().CreateMessage(
175+
thread.ID(),
176+
discord.MessageCreate{
177+
Content: fmt.Sprintf("%s%s",
178+
iif(role != "0", fmt.Sprintf("<@&%s> ", role), ""),
179+
user.Mention(),
180+
),
181+
Embeds: []discord.Embed{embed},
182+
})
183+
if err != nil {
184+
return err
185+
}
186+
187+
_, err = ev.CreateFollowupMessage(discord.MessageCreate{
188+
Content: "Report created!",
189+
Flags: discord.MessageFlagEphemeral,
190+
Components: []discord.ContainerComponent{
191+
discord.ActionRowComponent{
192+
discord.NewLinkButton("View", message.JumpURL()),
193+
},
194+
},
195+
})
196+
return err
197+
}
198+
199+
var helpCommand = discord.SlashCommandCreate{
200+
Name: "help",
201+
Description: "Show help for setting up the bot",
202+
DefaultMemberPermissions: json.NewNullablePtr(discord.PermissionManageGuild),
203+
DMPermission: ref(true),
204+
}
205+
206+
//go:embed help.md
207+
var helpText string
208+
209+
func helpHandler(ev *handler.CommandEvent) error {
210+
isInGuild := true
211+
if ev.GuildID() == nil {
212+
isInGuild = false
213+
}
214+
215+
return ev.CreateMessage(discord.MessageCreate{
216+
Content: helpText,
217+
Embeds: []discord.Embed{
218+
discord.NewEmbedBuilder().
219+
SetTitle("Modmail Help").
220+
SetDescription(helpText).
221+
SetColor(0x20FF20).
222+
Build(),
223+
},
224+
Flags: iif(isInGuild, discord.MessageFlagEphemeral, 0),
225+
})
226+
}
227+
228+
func iif[T any](cond bool, ifTrue, ifFalse T) T {
229+
if cond {
230+
return ifTrue
231+
}
232+
return ifFalse
233+
}
234+
235+
func ref[T any](v T) *T {
236+
return &v
237+
}

0 commit comments

Comments
 (0)