this repo has no description
0
fork

Configure Feed

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

automod: start sketching package API

+255
+67
automod/countstore.go
··· 1 + package automod 2 + 3 + import ( 4 + "context" 5 + "fmt" 6 + "log/slog" 7 + "time" 8 + ) 9 + 10 + const ( 11 + PeriodTotal = "total" 12 + PeriodDay = "day" 13 + PeriodHour = "hour" 14 + ) 15 + 16 + type CountStore interface { 17 + GetCount(ctx context.Context, key, period string) (int, error) 18 + Increment(ctx context.Context, key string) (int, error) 19 + } 20 + 21 + // TODO: this implementation isn't race-safe (yet)! 22 + type MemCountStore struct { 23 + Counts map[string]int 24 + } 25 + 26 + func NewMemCountStore() MemCountStore { 27 + return MemCountStore{ 28 + Counts: make(map[string]int), 29 + } 30 + } 31 + 32 + func PeriodKey(key, period string) string { 33 + switch period { 34 + case PeriodTotal: 35 + return key 36 + case PeriodDay: 37 + t := time.Now().UTC().Format(time.DateOnly) 38 + return fmt.Sprintf("%s:%s", key, t) 39 + case PeriodHour: 40 + t := time.Now().UTC().Format(time.RFC3339)[0:13] 41 + return fmt.Sprintf("%s:%s", key, t) 42 + default: 43 + slog.Warn("unhandled counter period", "period", period) 44 + return key 45 + } 46 + } 47 + 48 + func (s *MemCountStore) GetCount(ctx context.Context, key, period string) (int, error) { 49 + v, ok := s.Counts[PeriodKey(key, period)] 50 + if !ok { 51 + return 0, nil 52 + } 53 + return v, nil 54 + } 55 + 56 + func (s *MemCountStore) Increment(ctx context.Context, key string) error { 57 + for _, p := range []string{PeriodTotal, PeriodDay, PeriodHour} { 58 + k := PeriodKey(key, p) 59 + v, ok := s.Counts[k] 60 + if !ok { 61 + v = 0 62 + } 63 + v = v + 1 64 + s.Counts[k] = v 65 + } 66 + return nil 67 + }
+66
automod/engine.go
··· 1 + package automod 2 + 3 + import ( 4 + "context" 5 + "log/slog" 6 + "sync" 7 + 8 + "github.com/bluesky-social/indigo/atproto/identity" 9 + "github.com/bluesky-social/indigo/xrpc" 10 + ) 11 + 12 + // runtime for executing rules, managing state, and recording moderation actions 13 + type Engine struct { 14 + // current rule sets. will eventually be possible to swap these out at runtime 15 + RulesMap sync.Map 16 + Directory identity.Directory 17 + // used to persist moderation actions in mod service (optional) 18 + AdminClient *xrpc.Client 19 + CountStore CountStore 20 + } 21 + 22 + func (e *Engine) ExecuteIdentity() error { 23 + ctx := context.Background() 24 + 25 + // similar to an HTTP server, we want to recover any panics from rule execution 26 + defer func() { 27 + if r := recover(); r != nil { 28 + slog.Error("automod event execution exception", "err", r) 29 + // TODO: mark repo as dirty? 30 + // TODO: circuit-break on repeated panics? 31 + } 32 + }() 33 + 34 + _ = ctx 35 + return nil 36 + } 37 + 38 + func (e *Engine) ExecuteCommit() error { 39 + ctx := context.Background() 40 + 41 + // similar to an HTTP server, we want to recover any panics from rule execution 42 + defer func() { 43 + if r := recover(); r != nil { 44 + slog.Error("automod event execution exception", "err", r) 45 + // TODO: mark repo as dirty? 46 + // TODO: circuit-break on repeated panics? 47 + } 48 + }() 49 + 50 + _ = ctx 51 + return nil 52 + } 53 + 54 + func (e *Engine) PersistModActions() error { 55 + // XXX 56 + return nil 57 + } 58 + 59 + func (e *Engine) GetCount(key, period string) (int, error) { 60 + return e.CountStore.GetCount(context.TODO(), key, period) 61 + } 62 + 63 + func (e *Engine) InSet(name, val string) (bool, error) { 64 + // XXX: implement 65 + return false, nil 66 + }
+122
automod/event.go
··· 1 + package automod 2 + 3 + import ( 4 + "github.com/bluesky-social/indigo/atproto/identity" 5 + ) 6 + 7 + type ModReport struct { 8 + Reason string 9 + Comment string 10 + } 11 + 12 + // information about a repo/account/identity, always pre-populated and relevant to many rules 13 + type AccountMeta struct { 14 + Identity identity.Identity 15 + // TODO: createdAt / age 16 + } 17 + 18 + // base type for events. events are both containers for data about the event itself (similar to an HTTP request type); aggregate results and state (counters, mod actions) to be persisted after all rules are run; and act as an API for additional network reads and operations. 19 + type Event struct { 20 + engine Engine 21 + Err *error 22 + Account AccountMeta 23 + CounterIncrements []string 24 + AccountLabels []string 25 + AccountFlags []string 26 + AccountReports []ModReport 27 + AccountTakedown bool 28 + } 29 + 30 + func (e *Event) CountTotal(key string) int { 31 + v, err := e.engine.GetCount(key, PeriodTotal) 32 + if err != nil { 33 + e.Err = &err 34 + return 0 35 + } 36 + return v 37 + } 38 + 39 + func (e *Event) CountDay(key string) int { 40 + v, err := e.engine.GetCount(key, PeriodDay) 41 + if err != nil { 42 + e.Err = &err 43 + return 0 44 + } 45 + return v 46 + } 47 + 48 + func (e *Event) CountHour(key string) int { 49 + v, err := e.engine.GetCount(key, PeriodHour) 50 + if err != nil { 51 + e.Err = &err 52 + return 0 53 + } 54 + return v 55 + } 56 + 57 + func (e *Event) InSet(name, val string) bool { 58 + v, err := e.engine.InSet(name, val) 59 + if err != nil { 60 + e.Err = &err 61 + return false 62 + } 63 + return v 64 + } 65 + 66 + func (e *Event) IncrementCounter(key string) { 67 + e.CounterIncrements = append(e.CounterIncrements, key) 68 + } 69 + 70 + func (e *Event) TakedownAccount() { 71 + e.AccountTakedown = true 72 + } 73 + 74 + func (e *Event) AddLabelAccount(val string) { 75 + e.AccountLabels = append(e.AccountLabels, val) 76 + } 77 + 78 + func (e *Event) AddFlag(val string) { 79 + e.AccountFlags = append(e.AccountFlags, val) 80 + } 81 + 82 + func (e *Event) ReportAccount(reason, comment string) { 83 + e.AccountReports = append(e.AccountReports, ModReport{Reason: reason, Comment: comment}) 84 + } 85 + 86 + type IdentityEvent struct { 87 + Event 88 + } 89 + 90 + type RecordEvent struct { 91 + Event 92 + RecordLabels []string 93 + RecordTakedown bool 94 + RecordReports []ModReport 95 + RecordFlags []string 96 + // TODO: commit metadata 97 + } 98 + 99 + func (e *RecordEvent) Takedown() { 100 + e.RecordTakedown = true 101 + } 102 + 103 + func (e *RecordEvent) AddLabel(val string) { 104 + e.RecordLabels = append(e.RecordLabels, val) 105 + } 106 + 107 + func (e *RecordEvent) AddFlag(val string) { 108 + e.RecordFlags = append(e.RecordFlags, val) 109 + } 110 + 111 + func (e *RecordEvent) Report(reason, comment string) { 112 + e.RecordReports = append(e.RecordReports, ModReport{Reason: reason, Comment: comment}) 113 + } 114 + 115 + type PostEvent struct { 116 + RecordEvent 117 + // TODO: thread context 118 + } 119 + 120 + type IdentityRuleFunc = func(evt IdentityEvent) error 121 + type RecordRuleFunc = func(evt RecordEvent) error 122 + type PostRuleFunc = func(evt PostEvent) error