this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

automod: engine support for adding tags (#752)

I want to test this (eg, in staging) with example rules for both
accounts and records before merging. But adding for visibility, in case
we need this for other rules sooner.

authored by

bnewbold and committed by
GitHub
ed5b6f29 5bbea226

+130 -10
+8
automod/engine/context.go
··· 271 271 c.effects.AddAccountLabel(val) 272 272 } 273 273 274 + func (c *AccountContext) AddAccountTag(val string) { 275 + c.effects.AddAccountTag(val) 276 + } 277 + 274 278 func (c *AccountContext) ReportAccount(reason, comment string) { 275 279 c.effects.ReportAccount(reason, comment) 276 280 } ··· 293 297 294 298 func (c *RecordContext) AddRecordLabel(val string) { 295 299 c.effects.AddRecordLabel(val) 300 + } 301 + 302 + func (c *RecordContext) AddRecordTag(val string) { 303 + c.effects.AddRecordTag(val) 296 304 } 297 305 298 306 func (c *RecordContext) ReportRecord(reason, comment string) {
+29 -1
automod/engine/effects.go
··· 40 40 CounterDistinctIncrements []CounterDistinctRef // TODO: better variable names 41 41 // Label values which should be applied to the overall account, as a result of rule execution. 42 42 AccountLabels []string 43 - // Moderation flags (similar to labels, but private) which should be applied to the overall account, as a result of rule execution. 43 + // Moderation tags (similar to labels, but private) which should be applied to the overall account, as a result of rule execution. 44 + AccountTags []string 45 + // automod flags (metadata) which should be applied to the account as a result of rule execution. 44 46 AccountFlags []string 45 47 // Reports which should be filed against this account, as a result of rule execution. 46 48 AccountReports []ModReport ··· 52 54 AccountAcknowledge bool 53 55 // Same as "AccountLabels", but at record-level 54 56 RecordLabels []string 57 + // Same as "AccountTags", but at record-level 58 + RecordTags []string 55 59 // Same as "AccountFlags", but at record-level 56 60 RecordFlags []string 57 61 // Same as "AccountReports", but at record-level ··· 102 106 e.AccountLabels = append(e.AccountLabels, val) 103 107 } 104 108 109 + // Enqueues the provided label (string value) to be added to the account at the end of rule processing. 110 + func (e *Effects) AddAccountTag(val string) { 111 + e.mu.Lock() 112 + defer e.mu.Unlock() 113 + for _, v := range e.AccountTags { 114 + if v == val { 115 + return 116 + } 117 + } 118 + e.AccountTags = append(e.AccountTags, val) 119 + } 120 + 105 121 // Enqueues the provided flag (string value) to be recorded (in the Engine's flagstore) at the end of rule processing. 106 122 func (e *Effects) AddAccountFlag(val string) { 107 123 e.mu.Lock() ··· 154 170 } 155 171 } 156 172 e.RecordLabels = append(e.RecordLabels, val) 173 + } 174 + 175 + // Enqueues the provided tag (string value) to be added to the record at the end of rule processing. 176 + func (e *Effects) AddRecordTag(val string) { 177 + e.mu.Lock() 178 + defer e.mu.Unlock() 179 + for _, v := range e.RecordTags { 180 + if v == val { 181 + return 182 + } 183 + } 184 + e.RecordTags = append(e.RecordTags, val) 157 185 } 158 186 159 187 // Enqueues the provided flag (string value) to be recorded (in the Engine's flagstore) at the end of rule processing.
+5
automod/engine/metrics.go
··· 25 25 Help: "Number of new labels persisted", 26 26 }, []string{"type", "val"}) 27 27 28 + var actionNewTagCount = promauto.NewCounterVec(prometheus.CounterOpts{ 29 + Name: "automod_new_action_tags", 30 + Help: "Number of new tags persisted", 31 + }, []string{"type", "val"}) 32 + 28 33 var actionNewFlagCount = promauto.NewCounterVec(prometheus.CounterOpts{ 29 34 Name: "automod_new_action_flags", 30 35 Help: "Number of new flags persisted",
+69 -9
automod/engine/persist.go
··· 32 32 return nil 33 33 } 34 34 35 - // Persists account-level moderation actions: new labels, new flags, new takedowns, and reports. 35 + // Persists account-level moderation actions: new labels, new tags, new flags, new takedowns, and reports. 36 36 // 37 37 // If necessary, will "purge" identity and account caches, so that state updates will be picked up for subsequent events. 38 38 // ··· 42 42 43 43 // de-dupe actions 44 44 newLabels := dedupeLabelActions(c.effects.AccountLabels, c.Account.AccountLabels, c.Account.AccountNegatedLabels) 45 + existingTags := []string{} 46 + if c.Account.Private != nil { 47 + existingTags = c.Account.Private.AccountTags 48 + } 49 + newTags := dedupeTagActions(c.effects.AccountTags, existingTags) 45 50 newFlags := dedupeFlagActions(c.effects.AccountFlags, c.Account.AccountFlags) 46 51 47 52 // don't report the same account multiple times on the same day for the same reason. this is a quick check; we also query the mod service API just before creating the report. ··· 78 83 } 79 84 } 80 85 81 - anyModActions := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newFlags) > 0 || len(newReports) > 0 86 + anyModActions := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0 82 87 if anyModActions && eng.Notifier != nil { 83 88 for _, srv := range dedupeStrings(c.effects.NotifyServices) { 84 89 if err := eng.Notifier.SendAccount(ctx, srv, c); err != nil { ··· 107 112 xrpcc := eng.OzoneClient 108 113 109 114 if len(newLabels) > 0 { 110 - c.Logger.Info("labeling record", "newLabels", newLabels) 115 + c.Logger.Info("labeling account", "newLabels", newLabels) 111 116 for _, val := range newLabels { 112 117 // note: WithLabelValues is a prometheus label, not an atproto label 113 118 actionNewLabelCount.WithLabelValues("account", val).Inc() ··· 133 138 } 134 139 } 135 140 141 + if len(newTags) > 0 { 142 + c.Logger.Info("tagging account", "newTags", newTags) 143 + for _, val := range newTags { 144 + // note: WithLabelValues is a prometheus label, not an atproto label 145 + actionNewTagCount.WithLabelValues("account", val).Inc() 146 + } 147 + comment := "[automod]: auto-tagging account" 148 + _, err := toolsozone.ModerationEmitEvent(ctx, xrpcc, &toolsozone.ModerationEmitEvent_Input{ 149 + CreatedBy: xrpcc.Auth.Did, 150 + Event: &toolsozone.ModerationEmitEvent_Input_Event{ 151 + ModerationDefs_ModEventTag: &toolsozone.ModerationDefs_ModEventTag{ 152 + Add: newTags, 153 + Remove: []string{}, 154 + Comment: &comment, 155 + }, 156 + }, 157 + Subject: &toolsozone.ModerationEmitEvent_Input_Subject{ 158 + AdminDefs_RepoRef: &comatproto.AdminDefs_RepoRef{ 159 + Did: c.Account.Identity.DID.String(), 160 + }, 161 + }, 162 + }) 163 + if err != nil { 164 + c.Logger.Error("failed to create account tags", "err", err) 165 + } 166 + } 167 + 136 168 // reports are additionally de-duped when persisting the action, so track with a flag 137 169 createdReports := false 138 170 for _, mr := range newReports { ··· 214 246 } 215 247 } 216 248 217 - needCachePurge := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newFlags) > 0 || createdReports 249 + needCachePurge := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || createdReports 218 250 if needCachePurge { 219 251 return eng.PurgeAccountCaches(ctx, c.Account.Identity.DID) 220 252 } ··· 222 254 return nil 223 255 } 224 256 225 - // Persists some record-level state: labels, takedowns, reports. 257 + // Persists some record-level state: labels, tags, takedowns, reports. 226 258 // 227 259 // NOTE: this method currently does *not* persist record-level flags to any storage, and does not de-dupe most actions, on the assumption that the record is new (from firehose) and has no existing mod state. 228 260 func (eng *Engine) persistRecordModActions(c *RecordContext) error { ··· 233 265 234 266 atURI := c.RecordOp.ATURI().String() 235 267 newLabels := dedupeStrings(c.effects.RecordLabels) 236 - if len(newLabels) > 0 && eng.OzoneClient != nil { 268 + newTags := dedupeStrings(c.effects.RecordTags) 269 + if (len(newLabels) > 0 || len(newTags) > 0) && eng.OzoneClient != nil { 270 + // fetch existing record labels, tags, etc 237 271 rv, err := toolsozone.ModerationGetRecord(ctx, eng.OzoneClient, c.RecordOp.CID.String(), c.RecordOp.ATURI().String()) 238 272 if err != nil { 239 273 // NOTE: there is a frequent 4xx error here from Ozone because this record has not been indexed yet ··· 250 284 } 251 285 existingLabels = dedupeStrings(existingLabels) 252 286 negLabels = dedupeStrings(negLabels) 253 - // fetch existing record labels 254 287 newLabels = dedupeLabelActions(newLabels, existingLabels, negLabels) 288 + newTags = dedupeTagActions(newTags, rv.Moderation.SubjectStatus.Tags) 255 289 } 256 290 } 291 + 257 292 newFlags := dedupeStrings(c.effects.RecordFlags) 258 293 if len(newFlags) > 0 { 259 294 // fetch existing flags, and de-dupe ··· 278 313 return fmt.Errorf("failed to circuit break takedowns: %w", err) 279 314 } 280 315 281 - if newTakedown || len(newLabels) > 0 || len(newFlags) > 0 || len(newReports) > 0 { 316 + if newTakedown || len(newLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0 { 282 317 if eng.Notifier != nil { 283 318 for _, srv := range dedupeStrings(c.effects.NotifyServices) { 284 319 if err := eng.Notifier.SendRecord(ctx, srv, c); err != nil { ··· 298 333 } 299 334 300 335 // exit early 301 - if !newTakedown && len(newLabels) == 0 && len(newReports) == 0 { 336 + if !newTakedown && len(newLabels) == 0 && len(newTags) == 0 && len(newReports) == 0 { 302 337 return nil 303 338 } 304 339 ··· 340 375 }) 341 376 if err != nil { 342 377 c.Logger.Error("failed to create record label", "err", err) 378 + } 379 + } 380 + 381 + if len(newTags) > 0 { 382 + c.Logger.Info("tagging record", "newTags", newTags) 383 + for _, val := range newTags { 384 + // note: WithLabelValues is a prometheus label, not an atproto label 385 + actionNewTagCount.WithLabelValues("record", val).Inc() 386 + } 387 + comment := "[automod]: auto-tagging record" 388 + _, err := toolsozone.ModerationEmitEvent(ctx, xrpcc, &toolsozone.ModerationEmitEvent_Input{ 389 + CreatedBy: xrpcc.Auth.Did, 390 + Event: &toolsozone.ModerationEmitEvent_Input_Event{ 391 + ModerationDefs_ModEventTag: &toolsozone.ModerationDefs_ModEventTag{ 392 + Add: newLabels, 393 + Remove: []string{}, 394 + Comment: &comment, 395 + }, 396 + }, 397 + Subject: &toolsozone.ModerationEmitEvent_Input_Subject{ 398 + RepoStrongRef: &strongRef, 399 + }, 400 + }) 401 + if err != nil { 402 + c.Logger.Error("failed to create record tag", "err", err) 343 403 } 344 404 } 345 405
+17
automod/engine/persisthelpers.go
··· 35 35 return newLabels 36 36 } 37 37 38 + func dedupeTagActions(tags, existing []string) []string { 39 + newTags := []string{} 40 + for _, val := range dedupeStrings(tags) { 41 + exists := false 42 + for _, e := range existing { 43 + if val == e { 44 + exists = true 45 + break 46 + } 47 + } 48 + if !exists { 49 + newTags = append(newTags, val) 50 + } 51 + } 52 + return newTags 53 + } 54 + 38 55 func dedupeFlagActions(flags, existing []string) []string { 39 56 newFlags := []string{} 40 57 for _, val := range dedupeStrings(flags) {
+2
automod/rules/gtube.go
··· 16 16 if strings.Contains(post.Text, gtubeString) { 17 17 c.AddRecordLabel("spam") 18 18 c.Notify("slack") 19 + c.AddRecordTag("gtube-record") 19 20 } 20 21 return nil 21 22 } ··· 26 27 if profile.Description != nil && strings.Contains(*profile.Description, gtubeString) { 27 28 c.AddRecordLabel("spam") 28 29 c.Notify("slack") 30 + c.AddAccountTag("gtuber-account") 29 31 } 30 32 return nil 31 33 }