Skip to content

Commit

Permalink
automod: add removing of labels from accounts and records (#950)
Browse files Browse the repository at this point in the history
  • Loading branch information
haileyok authored Feb 25, 2025
2 parents dd179c3 + 324929c commit 464ab8d
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 14 deletions.
8 changes: 8 additions & 0 deletions automod/engine/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,10 @@ func (c *AccountContext) AddAccountLabel(val string) {
c.effects.AddAccountLabel(val)
}

func (c *AccountContext) RemoveAccountLabel(val string) {
c.effects.RemoveAccountLabel(val)
}

func (c *AccountContext) AddAccountTag(val string) {
c.effects.AddAccountTag(val)
}
Expand Down Expand Up @@ -270,6 +274,10 @@ func (c *RecordContext) AddRecordLabel(val string) {
c.effects.AddRecordLabel(val)
}

func (c *RecordContext) RemoveRecordLabel(val string) {
c.effects.RemoveRecordLabel(val)
}

func (c *RecordContext) AddRecordTag(val string) {
c.effects.AddRecordTag(val)
}
Expand Down
28 changes: 28 additions & 0 deletions automod/engine/effects.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ type Effects struct {
CounterDistinctIncrements []CounterDistinctRef // TODO: better variable names
// Label values which should be applied to the overall account, as a result of rule execution.
AccountLabels []string
// Label values which should be removed from the overall account, as a result of rule execution.
RemovedAccountLabels []string
// Moderation tags (similar to labels, but private) which should be applied to the overall account, as a result of rule execution.
AccountTags []string
// automod flags (metadata) which should be applied to the account as a result of rule execution.
Expand All @@ -42,6 +44,8 @@ type Effects struct {
AccountAcknowledge bool
// Same as "AccountLabels", but at record-level
RecordLabels []string
// Same as "RemovedRecordLabels", but at record-level
RemovedRecordLabels []string
// Same as "AccountTags", but at record-level
RecordTags []string
// Same as "AccountFlags", but at record-level
Expand Down Expand Up @@ -98,6 +102,18 @@ func (e *Effects) AddAccountLabel(val string) {
e.AccountLabels = append(e.AccountLabels, val)
}

// Enqueues the provided label (string value) to be removed from the account at the end of rule processing.
func (e *Effects) RemoveAccountLabel(val string) {
e.mu.Lock()
defer e.mu.Unlock()
for _, v := range e.RemovedAccountLabels {
if v == val {
return
}
}
e.RemovedAccountLabels = append(e.RemovedAccountLabels, val)
}

// Enqueues the provided label (string value) to be added to the account at the end of rule processing.
func (e *Effects) AddAccountTag(val string) {
e.mu.Lock()
Expand Down Expand Up @@ -164,6 +180,18 @@ func (e *Effects) AddRecordLabel(val string) {
e.RecordLabels = append(e.RecordLabels, val)
}

// Enqueues the provided label (string value) to be removed from the record at the end of rule processing.
func (e *Effects) RemoveRecordLabel(val string) {
e.mu.Lock()
defer e.mu.Unlock()
for _, v := range e.RemovedRecordLabels {
if v == val {
return
}
}
e.RemovedRecordLabels = append(e.RemovedRecordLabels, val)
}

// Enqueues the provided tag (string value) to be added to the record at the end of rule processing.
func (e *Effects) AddRecordTag(val string) {
e.mu.Lock()
Expand Down
39 changes: 29 additions & 10 deletions automod/engine/persist.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

comatproto "github.com/bluesky-social/indigo/api/atproto"
toolsozone "github.com/bluesky-social/indigo/api/ozone"
"github.com/bluesky-social/indigo/automod/keyword"
)

func (eng *Engine) persistCounters(ctx context.Context, eff *Effects) error {
Expand Down Expand Up @@ -42,6 +43,15 @@ func (eng *Engine) persistAccountModActions(c *AccountContext) error {

// de-dupe actions
newLabels := dedupeLabelActions(c.effects.AccountLabels, c.Account.AccountLabels, c.Account.AccountNegatedLabels)
rmdLabels := []string{}
for _, lbl := range dedupeStrings(c.effects.RemovedAccountLabels) {
// we don't need to try and remove labels whenever they are either _not_ already in the account labels, _or_ if they are
// being applied by some other rule before persisting
if !keyword.TokenInSet(lbl, c.Account.AccountLabels) || keyword.TokenInSet(lbl, c.effects.AccountLabels) {
continue
}
rmdLabels = append(rmdLabels, lbl)
}
existingTags := []string{}
if c.Account.Private != nil {
existingTags = c.Account.Private.AccountTags
Expand Down Expand Up @@ -83,7 +93,7 @@ func (eng *Engine) persistAccountModActions(c *AccountContext) error {
}
}

anyModActions := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0
anyModActions := newTakedown || newEscalation || newAcknowledge || len(newLabels) > 0 || len(rmdLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0
if anyModActions && eng.Notifier != nil {
for _, srv := range dedupeStrings(c.effects.NotifyServices) {
if err := eng.Notifier.SendAccount(ctx, srv, c); err != nil {
Expand Down Expand Up @@ -111,8 +121,8 @@ func (eng *Engine) persistAccountModActions(c *AccountContext) error {

xrpcc := eng.OzoneClient

if len(newLabels) > 0 {
c.Logger.Info("labeling account", "newLabels", newLabels)
if len(newLabels) > 0 || len(rmdLabels) > 0 {
c.Logger.Info("updating account labels", "newLabels", newLabels, "rmdLabels", rmdLabels)
for _, val := range newLabels {
// note: WithLabelValues is a prometheus label, not an atproto label
actionNewLabelCount.WithLabelValues("account", val).Inc()
Expand All @@ -123,7 +133,7 @@ func (eng *Engine) persistAccountModActions(c *AccountContext) error {
Event: &toolsozone.ModerationEmitEvent_Input_Event{
ModerationDefs_ModEventLabel: &toolsozone.ModerationDefs_ModEventLabel{
CreateLabelVals: newLabels,
NegateLabelVals: []string{},
NegateLabelVals: rmdLabels,
Comment: &comment,
},
},
Expand Down Expand Up @@ -265,11 +275,12 @@ func (eng *Engine) persistRecordModActions(c *RecordContext) error {

atURI := c.RecordOp.ATURI().String()
newLabels := dedupeStrings(c.effects.RecordLabels)
rmdLabels := []string{}
newTags := dedupeStrings(c.effects.RecordTags)
newEscalation := c.effects.RecordEscalate
newAcknowledge := c.effects.RecordAcknowledge

if (newEscalation || newAcknowledge || len(newLabels) > 0 || len(newTags) > 0) && eng.OzoneClient != nil {
if (newEscalation || newAcknowledge || len(newLabels) > 0 || len(rmdLabels) > 0 || len(newTags) > 0) && eng.OzoneClient != nil {
// fetch existing record labels, tags, etc
rv, err := toolsozone.ModerationGetRecord(ctx, eng.OzoneClient, c.RecordOp.CID.String(), c.RecordOp.ATURI().String())
if err != nil {
Expand All @@ -288,6 +299,14 @@ func (eng *Engine) persistRecordModActions(c *RecordContext) error {
existingLabels = dedupeStrings(existingLabels)
negLabels = dedupeStrings(negLabels)
newLabels = dedupeLabelActions(newLabels, existingLabels, negLabels)
for _, lbl := range dedupeStrings(c.effects.RemovedRecordLabels) {
// we don't need to try and remove labels whenever they are either _not_ already in the record labels, _or_ if they are
// being applied by some other rule before persisting
if !keyword.TokenInSet(lbl, existingLabels) || keyword.TokenInSet(lbl, newLabels) {
continue
}
rmdLabels = append(rmdLabels, lbl)
}
existingTags := []string{}
hasSubjectStatus := rv.Moderation != nil && rv.Moderation.SubjectStatus != nil
if hasSubjectStatus && rv.Moderation.SubjectStatus.Tags != nil {
Expand Down Expand Up @@ -331,7 +350,7 @@ func (eng *Engine) persistRecordModActions(c *RecordContext) error {
return fmt.Errorf("circuit-breaking acknowledge: %w", err)
}

if newEscalation || newAcknowledge || newTakedown || len(newLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0 {
if newEscalation || newAcknowledge || newTakedown || len(newLabels) > 0 || len(rmdLabels) > 0 || len(newTags) > 0 || len(newFlags) > 0 || len(newReports) > 0 {
if eng.Notifier != nil {
for _, srv := range dedupeStrings(c.effects.NotifyServices) {
if err := eng.Notifier.SendRecord(ctx, srv, c); err != nil {
Expand All @@ -351,7 +370,7 @@ func (eng *Engine) persistRecordModActions(c *RecordContext) error {
}

// exit early
if !newAcknowledge && !newEscalation && !newTakedown && len(newLabels) == 0 && len(newTags) == 0 && len(newReports) == 0 {
if !newAcknowledge && !newEscalation && !newTakedown && len(newLabels) == 0 && len(rmdLabels) == 0 && len(newTags) == 0 && len(newReports) == 0 {
return nil
}

Expand All @@ -371,8 +390,8 @@ func (eng *Engine) persistRecordModActions(c *RecordContext) error {
}

xrpcc := eng.OzoneClient
if len(newLabels) > 0 {
c.Logger.Info("labeling record", "newLabels", newLabels)
if len(newLabels) > 0 || len(rmdLabels) > 0 {
c.Logger.Info("updating record labels", "newLabels", newLabels, "rmdLabels", rmdLabels)
for _, val := range newLabels {
// note: WithLabelValues is a prometheus label, not an atproto label
actionNewLabelCount.WithLabelValues("record", val).Inc()
Expand All @@ -383,7 +402,7 @@ func (eng *Engine) persistRecordModActions(c *RecordContext) error {
Event: &toolsozone.ModerationEmitEvent_Input_Event{
ModerationDefs_ModEventLabel: &toolsozone.ModerationDefs_ModEventLabel{
CreateLabelVals: newLabels,
NegateLabelVals: []string{},
NegateLabelVals: rmdLabels,
Comment: &comment,
},
},
Expand Down
11 changes: 7 additions & 4 deletions automod/engine/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func (n *SlackNotifier) SendAccount(ctx context.Context, service string, c *Acco
if service != "slack" {
return nil
}
msg := slackBody("⚠️ Automod Account Action ⚠️\n", c.Account, c.effects.AccountLabels, c.effects.AccountFlags, c.effects.AccountReports, c.effects.AccountTakedown)
msg := slackBody("⚠️ Automod Account Action ⚠️\n", c.Account, c.effects.AccountLabels, c.effects.RemovedAccountLabels, c.effects.AccountFlags, c.effects.AccountReports, c.effects.AccountTakedown)
c.Logger.Debug("sending slack notification")
return n.sendSlackMsg(ctx, msg)
}
Expand All @@ -27,7 +27,7 @@ func (n *SlackNotifier) SendRecord(ctx context.Context, service string, c *Recor
return nil
}
atURI := fmt.Sprintf("at://%s/%s/%s", c.Account.Identity.DID, c.RecordOp.Collection, c.RecordOp.RecordKey)
msg := slackBody("⚠️ Automod Record Action ⚠️\n", c.Account, c.effects.RecordLabels, c.effects.RecordFlags, c.effects.RecordReports, c.effects.RecordTakedown)
msg := slackBody("⚠️ Automod Record Action ⚠️\n", c.Account, c.effects.RecordLabels, c.effects.RemovedRecordLabels, c.effects.RecordFlags, c.effects.RecordReports, c.effects.RecordTakedown)
msg += fmt.Sprintf("`%s`\n", atURI)
c.Logger.Debug("sending slack notification")
return n.sendSlackMsg(ctx, msg)
Expand Down Expand Up @@ -68,7 +68,7 @@ func (n *SlackNotifier) sendSlackMsg(ctx context.Context, msg string) error {
return nil
}

func slackBody(header string, acct AccountMeta, newLabels, newFlags []string, newReports []ModReport, newTakedown bool) string {
func slackBody(header string, acct AccountMeta, newLabels, rmdLabels, newFlags []string, newReports []ModReport, newTakedown bool) string {
msg := header
msg += fmt.Sprintf("`%s` / `%s` / <https://bsky.app/profile/%s|bsky> / <https://admin.prod.bsky.dev/repositories/%s|ozone>\n",
acct.Identity.DID,
Expand All @@ -77,7 +77,10 @@ func slackBody(header string, acct AccountMeta, newLabels, newFlags []string, ne
acct.Identity.DID,
)
if len(newLabels) > 0 {
msg += fmt.Sprintf("Labels: `%s`\n", strings.Join(newLabels, ", "))
msg += fmt.Sprintf("Added Labels: `%s`\n", strings.Join(newLabels, ", "))
}
if len(rmdLabels) > 0 {
msg += fmt.Sprintf("Removed Labels: `%s`\n", strings.Join(rmdLabels, ", "))
}
if len(newFlags) > 0 {
msg += fmt.Sprintf("Flags: `%s`\n", strings.Join(newFlags, ", "))
Expand Down

0 comments on commit 464ab8d

Please sign in to comment.