Skip to content

Commit

Permalink
✨ Emit record delete events on post/profile with label and appeal
Browse files Browse the repository at this point in the history
  • Loading branch information
foysalit committed Sep 20, 2024
1 parent d41c7cd commit 6e68a26
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 5 deletions.
1 change: 1 addition & 0 deletions automod/countstore/countstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type CountStore interface {
// TODO: batch increment method
GetCountDistinct(ctx context.Context, name, bucket, period string) (int, error)
IncrementDistinct(ctx context.Context, name, bucket, val string) error
Reset(ctx context.Context, name, val string) error
}

func periodBucket(name, val, period string) string {
Expand Down
6 changes: 6 additions & 0 deletions automod/countstore/countstore_mem.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ func (s MemCountStore) Increment(ctx context.Context, name, val string) error {
return nil
}

func (s *MemCountStore) Reset(ctx context.Context, name, val string) error {
key := periodBucket(name, val, PeriodTotal)
s.Counts.Delete(key)
return nil
}

func (s MemCountStore) IncrementPeriod(ctx context.Context, name, val, period string) error {
k := periodBucket(name, val, period)
s.Counts.Compute(k, func(oldVal int, _ bool) (int, bool) {
Expand Down
12 changes: 12 additions & 0 deletions automod/countstore/countstore_redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ func (s *RedisCountStore) Increment(ctx context.Context, name, val string) error
return err
}

// Deletes a counter key
func (s *RedisCountStore) Reset(ctx context.Context, name, val string) error {
var key string

// increment multiple counters in a single redis round-trip
multi := s.Client.Pipeline()
key = redisCountPrefix + periodBucket(name, val, PeriodHour)
multi.Del(ctx, key)
_, err := multi.Exec(ctx)
return err
}

// Variant of Increment() which only acts on a single specified time period. The intended us of this variant is to control the total number of counters persisted, by using a relatively short time period, for which the counters will expire.
func (s *RedisCountStore) IncrementPeriod(ctx context.Context, name, val, period string) error {

Expand Down
3 changes: 3 additions & 0 deletions automod/engine/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ func (c *BaseContext) Increment(name, val string) {
c.effects.Increment(name, val)
}

func (c *BaseContext) ResetCounter(name, val string) {
c.effects.ResetCounter(name, val)
}
func (c *BaseContext) IncrementDistinct(name, bucket, val string) {
c.effects.IncrementDistinct(name, bucket, val)
}
Expand Down
11 changes: 11 additions & 0 deletions automod/engine/effects.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Effects struct {
mu sync.Mutex
// List of counters which should be incremented as part of processing this event. These are collected during rule execution and persisted in bulk at the end.
CounterIncrements []CounterRef
CounterResets []CounterRef
// Similar to "CounterIncrements", but for "distinct" style counters
CounterDistinctIncrements []CounterDistinctRef // TODO: better variable names
// Label values which should be applied to the overall account, as a result of rule execution.
Expand Down Expand Up @@ -79,6 +80,16 @@ func (e *Effects) IncrementPeriod(name, val string, period string) {
e.CounterIncrements = append(e.CounterIncrements, CounterRef{Name: name, Val: val, Period: &period})
}

// Enqueues the named counter to be reset at the end of all rule processing.
//
// "name" is the counter namespace.
// "val" is the specific counter with that namespace.
func (e *Effects) ResetCounter(name, val string) {
e.mu.Lock()
defer e.mu.Unlock()
e.CounterResets = append(e.CounterResets, CounterRef{Name: name, Val: val})
}

// Enqueues the named "distinct value" counter based on the supplied string value ("val") to be incremented at the end of all rule processing. Will automatically increment for all time periods.
func (e *Effects) IncrementDistinct(name, bucket, val string) {
e.mu.Lock()
Expand Down
1 change: 1 addition & 0 deletions automod/rules/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func DefaultRules() automod.RuleSet {
},
OzoneEventRules: []automod.OzoneEventRuleFunc{
HarassmentProtectionOzoneEventRule,
MarkAppealOzoneEventRule,
},
}
return rules
Expand Down
18 changes: 13 additions & 5 deletions automod/rules/ozone_persist.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,30 @@ package rules

import (
"github.com/bluesky-social/indigo/automod"
"github.com/bluesky-social/indigo/automod/countstore"
)

var _ automod.RecordRuleFunc = OzoneRecordHistoryPersistRule

func OzoneRecordHistoryPersistRule(c *automod.RecordContext) error {
// TODO: flesh out this logic
// based on record type
switch c.RecordOp.Collection {
case "app.bsky.labeler.service":
c.PersistRecordOzoneEvent()
case "app.bsky.feed.post":
if c.RecordOp.Action == "update" {
// @TODO: we should probably persist if a deleted post has reports on it but right now, we are not keeping track of reports on records
if c.RecordOp.Action == "delete" {
// If a post being deleted has an active appeal, persist the event
if c.GetCount("appeal", c.RecordOp.ATURI().String(), countstore.PeriodTotal) > 0 {
c.PersistRecordOzoneEvent()
}

}
case "app.bsky.actor.profile":
// TODO: fix this logic
if len(c.Account.AccountLabels) > 2 {
hasLabels := len(c.Account.AccountLabels) > 0
// If there is an appeal on the account or the profile record
// Appeal counts are reset when appeals are resolved so this should only be true when there is an unresolved appeal
hasAppeals := c.GetCount("appeal", c.Account.Identity.DID.String(), countstore.PeriodTotal) > 0 || c.GetCount("appeal", c.RecordOp.ATURI().String(), countstore.PeriodTotal) > 0
if hasLabels || hasAppeals {
c.PersistRecordOzoneEvent()
}
}
Expand Down
31 changes: 31 additions & 0 deletions automod/rules/resolve_appeal_on_delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package rules

import (
"github.com/bluesky-social/indigo/automod"
)

var _ automod.OzoneEventRuleFunc = MarkAppealOzoneEventRule

// looks for appeals on records/accounts and flags subjects
func MarkAppealOzoneEventRule(c *automod.OzoneEventContext) error {
isResolveAppealEvent := c.Event.Event.ModerationDefs_ModEventResolveAppeal != nil
// appeals are just report events emitted by the author of the reported content with a special report type
isAppealEvent := c.Event.Event.ModerationDefs_ModEventReport != nil && *c.Event.Event.ModerationDefs_ModEventReport.ReportType == "com.atproto.moderation.defs#reasonAppeal"

if !isAppealEvent && !isResolveAppealEvent {
return nil
}

counterKey := c.Event.SubjectDID.String()
if c.Event.SubjectURI != nil {
counterKey = c.Event.SubjectURI.String()
}

if isAppealEvent {
c.Increment("appeal", counterKey)
} else {
c.ResetCounter("appeal", counterKey)
}

return nil
}

0 comments on commit 6e68a26

Please sign in to comment.