forked from prysmaticlabs/prysmbot
-
Notifications
You must be signed in to change notification settings - Fork 0
/
denylist.go
140 lines (125 loc) · 3.55 KB
/
denylist.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package main
import (
"fmt"
"io/ioutil"
"regexp"
"strings"
"time"
"github.com/bwmarrin/discordgo"
"github.com/fsnotify/fsnotify"
)
var denylist []*regexp.Regexp
func monitorDenylistFile(fp string) {
log.WithField("filepath", fp).Info("Monitoring denylist for file changes")
updateDenyList(fp)
w, err := fsnotify.NewWatcher()
if err != nil {
log.WithError(err).Error("Failed to create fsnotify watcher")
return
}
if err := w.Add(fp); err != nil {
log.WithError(err).Error("Failed to create fsnotify watcher")
return
}
for {
select {
case <-w.Events:
updateDenyList(fp)
}
}
}
func updateDenyList(fp string) {
newDenyList := make([]*regexp.Regexp, 0)
content, err := ioutil.ReadFile(fp)
if err != nil {
log.WithError(err).Error("Failed to read denylist")
return
}
s := string(content)
for _, row := range strings.Split(s, "\n") {
if len(row) == 0 {
continue
}
re, err := regexp.Compile("(?i)" + row) // Prefix (?i) to make case insenstive.
if err != nil {
log.WithError(err).Errorf("Failed to parse regex: %s", row)
continue
}
newDenyList = append(newDenyList, re)
}
if len(newDenyList) > 0 {
denylist = newDenyList
log.WithField("count", len(newDenyList)).Info("Updated deny list")
}
}
func deniedMessage(s *discordgo.Session, m *discordgo.MessageCreate) bool {
for _, re := range denylist {
if re.MatchString(m.Content) {
deleteMessage(s, m)
handleDenyListMessage(s, m, re)
return true
}
}
return false
}
func deleteMessage(s *discordgo.Session, m *discordgo.MessageCreate) {
if err := s.ChannelMessageDelete(m.ChannelID, m.ID); err != nil {
log.WithError(err).Error("Failed to delete denied message.")
}
}
func handleDenyListMessage(s *discordgo.Session, m *discordgo.MessageCreate, re *regexp.Regexp) {
ts, err := discordgo.SnowflakeTimestamp(m.Author.ID)
if err != nil {
log.WithError(err).Error("Could not determine user's timestamp")
}
age := time.Since(ts)
message := fmt.Sprintf("User %s (ID:%s) sent the following content:\n"+
"> %s\n"+
"Their message matched denylist filter %s and was deleted.\n"+
"%s's account age is: %v\n"+
"React with :hammer: to auto-ban.",
m.Author.Username,
m.Author.ID,
m.Content,
re.String(),
m.Author.Username,
age)
if m, err := s.ChannelMessageSend(prysmInternal, message); err != nil {
log.WithError(err).Error("Failed to notify prysm internal channel of denied message.")
} else {
if err := s.MessageReactionAdd(m.ChannelID, m.ID, "🔨"); err != nil {
log.WithError(err).Error("Failed to react to message")
}
}
}
func handleDenyListMessageReaction(s *discordgo.Session, m *discordgo.MessageReactionAdd) {
if m.ChannelID != prysmInternal {
return
}
if m.Emoji.Name != "🔨" {
log.WithField("emoji", m.Emoji.Name).Debug("Invalid emoji reaction")
return
}
om, err := s.ChannelMessage(m.ChannelID, m.MessageID)
if err != nil {
log.WithError(err).Error("Failed to get original message with reaction")
return
}
// Ignore all messages not created by the bot itself.
if om.Author.ID != s.State.User.ID {
return
}
re := regexp.MustCompile("\\(ID:(\\d*)\\)")
matches := re.FindStringSubmatch(om.Content)
if len(matches) < 2 {
log.Errorf("Could not extract user ID from message: %s", om.Content)
return
}
userID := matches[1]
log.WithField("userID", userID).Debug("Banning user")
if err := s.GuildBanCreateWithReason(m.GuildID, userID, "Posting forbidden content", 0 /*days*/); err != nil {
log.WithError(err).Error("Failed to ban user")
} else {
s.ChannelMessageSend(prysmInternal, fmt.Sprintf("Banned user %s", userID))
}
}