1package rules
2
3import (
4 "bytes"
5 "fmt"
6 "strings"
7
8 appbsky "github.com/bluesky-social/indigo/api/bsky"
9 "github.com/bluesky-social/indigo/automod"
10 "github.com/bluesky-social/indigo/automod/helpers"
11 "github.com/bluesky-social/indigo/automod/keyword"
12)
13
14func BadWordPostRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
15 isJapanese := false
16 for _, lang := range post.Langs {
17 if lang == "ja" || strings.HasPrefix(lang, "ja-") {
18 isJapanese = true
19 }
20 }
21 for _, tok := range helpers.ExtractTextTokensPost(post) {
22 word := keyword.SlugIsExplicitSlur(tok)
23 // used very frequently in a reclaimed context
24 if word != "" && word != "faggot" && word != "tranny" && word != "coon" && !(word == "kike" && isJapanese) {
25 c.AddRecordFlag("bad-word-text")
26 c.ReportRecord(automod.ReportReasonRude, fmt.Sprintf("possible bad word in post text or alttext: %s", word))
27 //c.Notify("slack")
28 break
29 }
30 // de-pluralize
31 tok = strings.TrimSuffix(tok, "s")
32 if c.InSet("worst-words", tok) {
33 // skip this specific term, if used in a Japanese language post
34 if isJapanese && tok == "kike" {
35 continue
36 }
37
38 c.AddRecordFlag("bad-word-text")
39 c.ReportRecord(automod.ReportReasonRude, fmt.Sprintf("possible bad word in post text or alttext: %s", tok))
40 //c.Notify("slack")
41 break
42 }
43 }
44 return nil
45}
46
47var _ automod.PostRuleFunc = BadWordPostRule
48
49func BadWordProfileRule(c *automod.RecordContext, profile *appbsky.ActorProfile) error {
50 if profile.DisplayName != nil {
51 word := keyword.SlugContainsExplicitSlur(keyword.Slugify(*profile.DisplayName))
52 if word != "" {
53 c.AddRecordFlag("bad-word-name")
54 c.ReportRecord(automod.ReportReasonRude, fmt.Sprintf("possible bad word in display name: %s", word))
55 //c.Notify("slack")
56 }
57 }
58 for _, tok := range helpers.ExtractTextTokensProfile(profile) {
59 // de-pluralize
60 tok = strings.TrimSuffix(tok, "s")
61 if c.InSet("worst-words", tok) {
62 c.AddRecordFlag("bad-word-text")
63 c.ReportRecord(automod.ReportReasonRude, fmt.Sprintf("possible bad word in profile description: %s", tok))
64 //c.Notify("slack")
65 break
66 }
67 }
68 return nil
69}
70
71var _ automod.ProfileRuleFunc = BadWordProfileRule
72
73// looks for the specific harassment situation of a replay to another user with only a single word
74func ReplySingleBadWordPostRule(c *automod.RecordContext, post *appbsky.FeedPost) error {
75 if post.Reply != nil && !helpers.IsSelfThread(c, post) {
76 tokens := helpers.ExtractTextTokensPost(post)
77 if len(tokens) != 1 {
78 return nil
79 }
80 tok := tokens[0]
81 if c.InSet("bad-words", tok) || keyword.SlugIsExplicitSlur(tok) != "" {
82 c.AddRecordFlag("reply-single-bad-word")
83 c.ReportRecord(automod.ReportReasonRude, fmt.Sprintf("bad single-word reply: %s", tok))
84 //c.Notify("slack")
85 }
86 }
87 return nil
88}
89
90var _ automod.PostRuleFunc = ReplySingleBadWordPostRule
91
92// scans for bad keywords in records other than posts and profiles
93func BadWordOtherRecordRule(c *automod.RecordContext) error {
94 name := ""
95 text := ""
96 switch c.RecordOp.Collection.String() {
97 case "app.bsky.graph.list":
98 var list appbsky.GraphList
99 if err := list.UnmarshalCBOR(bytes.NewReader(c.RecordOp.RecordCBOR)); err != nil {
100 return fmt.Errorf("failed to parse app.bsky.graph.list record: %v", err)
101 }
102 name += " " + list.Name
103 if list.Description != nil {
104 text += " " + *list.Description
105 }
106 if list.Purpose != nil {
107 text += " " + *list.Purpose
108 }
109 case "app.bsky.feed.generator":
110 var generator appbsky.FeedGenerator
111 if err := generator.UnmarshalCBOR(bytes.NewReader(c.RecordOp.RecordCBOR)); err != nil {
112 return fmt.Errorf("failed to parse app.bsky.feed.generator record: %v", err)
113 }
114 name += " " + generator.DisplayName
115 if generator.Description != nil {
116 text += " " + *generator.Description
117 }
118 }
119 if name != "" {
120 // check for explicit slurs or bad word tokens
121 word := keyword.SlugContainsExplicitSlur(keyword.Slugify(name))
122 if word != "" {
123 c.AddRecordFlag("bad-word-name")
124 c.ReportRecord(automod.ReportReasonRude, fmt.Sprintf("possible bad word in name: %s", word))
125 c.Notify("slack")
126 }
127 tokens := keyword.TokenizeText(name)
128 for _, tok := range tokens {
129 if c.InSet("bad-words", tok) {
130 c.AddRecordFlag("bad-word-name")
131 c.ReportRecord(automod.ReportReasonRude, fmt.Sprintf("possible bad word in name: %s", tok))
132 c.Notify("slack")
133 break
134 }
135 }
136 }
137 if text != "" {
138 // check for explicit slurs or worst word tokens
139 word := keyword.SlugContainsExplicitSlur(keyword.Slugify(text))
140 if word != "" {
141 c.AddRecordFlag("bad-word-text")
142 c.ReportRecord(automod.ReportReasonRude, fmt.Sprintf("possible bad word in description: %s", word))
143 c.Notify("slack")
144 }
145 tokens := keyword.TokenizeText(text)
146 for _, tok := range tokens {
147 // de-pluralize
148 tok = strings.TrimSuffix(tok, "s")
149 if c.InSet("worst-words", tok) {
150 c.AddRecordFlag("bad-word-text")
151 c.ReportRecord(automod.ReportReasonRude, fmt.Sprintf("possible bad word in description: %s", tok))
152 c.Notify("slack")
153 break
154 }
155 }
156 }
157 return nil
158}
159
160var _ automod.RecordRuleFunc = BadWordOtherRecordRule
161
162// scans the record-key for all records
163func BadWordRecordKeyRule(c *automod.RecordContext) error {
164 // check record key
165 word := keyword.SlugIsExplicitSlur(keyword.Slugify(c.RecordOp.RecordKey.String()))
166 if word != "" {
167 c.AddRecordFlag("bad-word-recordkey")
168 c.ReportRecord(automod.ReportReasonRude, fmt.Sprintf("possible bad word in record-key (URL): %s", word))
169 c.Notify("slack")
170 }
171 tokens := keyword.TokenizeIdentifier(c.RecordOp.RecordKey.String())
172 for _, tok := range tokens {
173 if c.InSet("bad-words", tok) {
174 c.AddRecordFlag("bad-word-recordkey")
175 c.ReportRecord(automod.ReportReasonRude, fmt.Sprintf("possible bad word in record-key (URL): %s", tok))
176 c.Notify("slack")
177 break
178 }
179 }
180
181 return nil
182}
183
184var _ automod.RecordRuleFunc = BadWordRecordKeyRule
185
186func BadWordHandleRule(c *automod.AccountContext) error {
187 word := keyword.SlugContainsExplicitSlur(keyword.Slugify(c.Account.Identity.Handle.String()))
188 if word != "" {
189 c.AddAccountFlag("bad-word-handle")
190 c.ReportAccount(automod.ReportReasonRude, fmt.Sprintf("possible bad word in handle (username): %s", word))
191 //c.Notify("slack")
192 return nil
193 }
194
195 tokens := keyword.TokenizeIdentifier(c.Account.Identity.Handle.String())
196 for _, tok := range tokens {
197 if c.InSet("bad-words", tok) {
198 c.AddAccountFlag("bad-word-handle")
199 c.ReportAccount(automod.ReportReasonRude, fmt.Sprintf("possible bad word in handle (username): %s", tok))
200 //c.Notify("slack")
201 break
202 }
203 }
204
205 return nil
206}
207
208var _ automod.IdentityRuleFunc = BadWordHandleRule
209
210func BadWordDIDRule(c *automod.AccountContext) error {
211 if c.Account.Identity.DID.Method() == "plc" {
212 return nil
213 }
214 word := keyword.SlugContainsExplicitSlur(keyword.Slugify(c.Account.Identity.DID.String()))
215 if word != "" {
216 c.AddAccountFlag("bad-word-did")
217 c.ReportAccount(automod.ReportReasonRude, fmt.Sprintf("possible bad word in DID (account identifier): %s", word))
218 c.Notify("slack")
219 return nil
220 }
221
222 tokens := keyword.TokenizeIdentifier(c.Account.Identity.DID.String())
223 for _, tok := range tokens {
224 if c.InSet("bad-words", tok) {
225 c.AddAccountFlag("bad-word-did")
226 c.ReportAccount(automod.ReportReasonRude, fmt.Sprintf("possible bad word in DID (account identifier): %s", tok))
227 c.Notify("slack")
228 break
229 }
230 }
231
232 return nil
233}
234
235var _ automod.IdentityRuleFunc = BadWordDIDRule