porting all github actions from bluesky-social/indigo to tangled CI

merge events and effects packages back in to engine

+1 -1
automod/effects/effects.go automod/engine/effects.go
··· 1 - package effects 1 + package engine 2 2 3 3 import ( 4 4 "log/slog"
+1 -1
automod/effects/report.go automod/engine/report.go
··· 1 - package effects 1 + package engine 2 2 3 3 // Simplified variant of input parameters for com.atproto.moderation.createReport, for internal tracking 4 4 type ModReport struct {
+1 -2
automod/engine/capture.go
··· 5 5 6 6 comatproto "github.com/bluesky-social/indigo/api/atproto" 7 7 "github.com/bluesky-social/indigo/atproto/syntax" 8 - "github.com/bluesky-social/indigo/automod/event" 9 8 ) 10 9 11 10 // REVIEW: if this "capture" code can leave the engine package. It seems likely. 12 11 13 12 type AccountCapture struct { 14 13 CapturedAt syntax.Datetime `json:"capturedAt"` 15 - AccountMeta event.AccountMeta `json:"accountMeta"` 14 + AccountMeta AccountMeta `json:"accountMeta"` 16 15 PostRecords []comatproto.RepoListRecords_Record `json:"postRecords"` 17 16 } 18 17
+12 -14
automod/engine/engine.go
··· 10 10 "github.com/bluesky-social/indigo/atproto/syntax" 11 11 "github.com/bluesky-social/indigo/automod/cachestore" 12 12 "github.com/bluesky-social/indigo/automod/countstore" 13 - "github.com/bluesky-social/indigo/automod/effects" 14 - "github.com/bluesky-social/indigo/automod/event" 15 13 "github.com/bluesky-social/indigo/automod/flagstore" 16 14 "github.com/bluesky-social/indigo/automod/setstore" 17 15 "github.com/bluesky-social/indigo/xrpc" ··· 55 53 if err != nil { 56 54 return err 57 55 } 58 - evt := &event.IdentityEvent{ 59 - RepoEvent: event.RepoEvent{ 56 + evt := &IdentityEvent{ 57 + RepoEvent: RepoEvent{ 60 58 Account: *am, 61 59 }, 62 60 } 63 - eff := &effects.Effects{ 61 + eff := &Effects{ 64 62 // XXX: Logger: eng.Logger.With("did", am.Identity.DID), 65 63 } 66 64 if err := eng.Rules.CallIdentityRules(evt, eff); err != nil { ··· 160 158 return nil 161 159 } 162 160 163 - func (e *Engine) NewRecordProcessingContext(am event.AccountMeta, path, recCID string, rec any) (*event.RecordEvent, *effects.Effects) { 161 + func (e *Engine) NewRecordProcessingContext(am AccountMeta, path, recCID string, rec any) (*RecordEvent, *Effects) { 164 162 // REVIEW: Only reason for this to be a method on the engine is because it's bifrucating the logger off from there. Should we pinch that off? 165 163 parts := strings.SplitN(path, "/", 2) 166 - return &event.RecordEvent{ 167 - RepoEvent: event.RepoEvent{ 164 + return &RecordEvent{ 165 + RepoEvent: RepoEvent{ 168 166 Account: am, 169 167 }, 170 168 Record: rec, 171 169 Collection: parts[0], 172 170 RecordKey: parts[1], 173 171 CID: recCID, 174 - }, &effects.Effects{ 172 + }, &Effects{ 175 173 // XXX: Logger: e.Logger.With("did", am.Identity.DID, "collection", parts[0], "rkey", parts[1]), 176 174 RecordLabels: []string{}, 177 175 RecordFlags: []string{}, 178 - RecordReports: []effects.ModReport{}, 176 + RecordReports: []ModReport{}, 179 177 RecordTakedown: false, 180 178 } 181 179 } 182 180 183 - func (e *Engine) NewRecordDeleteProcessingContext(am event.AccountMeta, path string) (*event.RecordDeleteEvent, *effects.Effects) { 181 + func (e *Engine) NewRecordDeleteProcessingContext(am AccountMeta, path string) (*RecordDeleteEvent, *Effects) { 184 182 parts := strings.SplitN(path, "/", 2) 185 - return &event.RecordDeleteEvent{ 186 - RepoEvent: event.RepoEvent{ 183 + return &RecordDeleteEvent{ 184 + RepoEvent: RepoEvent{ 187 185 Account: am, 188 186 }, 189 187 Collection: parts[0], 190 188 RecordKey: parts[1], 191 - }, &effects.Effects{ 189 + }, &Effects{ 192 190 // XXX: Logger: e.Logger.With("did", am.Identity.DID, "collection", parts[0], "rkey", parts[1]), 193 191 } 194 192 }
+7 -8
automod/engine/fetchaccountmeta.go
··· 9 9 appbsky "github.com/bluesky-social/indigo/api/bsky" 10 10 "github.com/bluesky-social/indigo/atproto/identity" 11 11 "github.com/bluesky-social/indigo/atproto/syntax" 12 - "github.com/bluesky-social/indigo/automod/event" 13 12 "github.com/bluesky-social/indigo/automod/util" 14 13 ) 15 14 16 - func (e *Engine) GetAccountMeta(ctx context.Context, ident *identity.Identity) (*event.AccountMeta, error) { 15 + func (e *Engine) GetAccountMeta(ctx context.Context, ident *identity.Identity) (*AccountMeta, error) { 17 16 18 17 // wipe parsed public key; it's a waste of space and can't serialize 19 18 ident.ParsedPublicKey = nil ··· 21 20 // fallback in case client wasn't configured (eg, testing) 22 21 if e.BskyClient == nil { 23 22 e.Logger.Warn("skipping account meta hydration") 24 - am := event.AccountMeta{ 23 + am := AccountMeta{ 25 24 Identity: ident, 26 - Profile: event.ProfileSummary{}, 25 + Profile: ProfileSummary{}, 27 26 } 28 27 return &am, nil 29 28 } ··· 33 32 return nil, err 34 33 } 35 34 if existing != "" { 36 - var am event.AccountMeta 35 + var am AccountMeta 37 36 err := json.Unmarshal([]byte(existing), &am) 38 37 if err != nil { 39 38 return nil, fmt.Errorf("parsing AccountMeta from cache: %v", err) ··· 63 62 return nil, err 64 63 } 65 64 66 - am := event.AccountMeta{ 65 + am := AccountMeta{ 67 66 Identity: ident, 68 - Profile: event.ProfileSummary{ 67 + Profile: ProfileSummary{ 69 68 HasAvatar: pv.Avatar != nil, 70 69 Description: pv.Description, 71 70 DisplayName: pv.DisplayName, ··· 89 88 if err != nil { 90 89 return nil, err 91 90 } 92 - ap := event.AccountPrivate{} 91 + ap := AccountPrivate{} 93 92 if pv.Email != nil && *pv.Email != "" { 94 93 ap.Email = *pv.Email 95 94 }
+3 -5
automod/engine/persist.go
··· 5 5 "fmt" 6 6 7 7 comatproto "github.com/bluesky-social/indigo/api/atproto" 8 - "github.com/bluesky-social/indigo/automod/effects" 9 - "github.com/bluesky-social/indigo/automod/event" 10 8 "github.com/bluesky-social/indigo/automod/util" 11 9 ) 12 10 13 - func (eng *Engine) persistCounters(ctx context.Context, eff *effects.Effects) error { 11 + func (eng *Engine) persistCounters(ctx context.Context, eff *Effects) error { 14 12 // TODO: dedupe this array 15 13 for _, ref := range eff.CounterIncrements { 16 14 if ref.Period != nil { ··· 39 37 // If necessary, will "purge" identity and account caches, so that state updates will be picked up for subsequent events. 40 38 // 41 39 // Note that this method expects to run *before* counts are persisted (it accesses and updates some counts) 42 - func (eng *Engine) persistAccountEffects(ctx context.Context, evt *event.RepoEvent, eff *effects.Effects) error { 40 + func (eng *Engine) persistAccountEffects(ctx context.Context, evt *RepoEvent, eff *Effects) error { 43 41 44 42 // de-dupe actions 45 43 newLabels := dedupeLabelActions(eff.AccountLabels, evt.Account.AccountLabels, evt.Account.AccountNegatedLabels) ··· 136 134 // Persists some record-level state: labels, takedowns, reports. 137 135 // 138 136 // 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. 139 - func (eng *Engine) persistEffectss(ctx context.Context, evt *event.RecordEvent, eff *effects.Effects) error { 137 + func (eng *Engine) persistEffectss(ctx context.Context, evt *RecordEvent, eff *Effects) error { 140 138 if err := eng.persistAccountEffects(ctx, &evt.RepoEvent, eff); err != nil { 141 139 return err 142 140 }
+13 -15
automod/engine/persisthelpers.go
··· 9 9 comatproto "github.com/bluesky-social/indigo/api/atproto" 10 10 "github.com/bluesky-social/indigo/atproto/syntax" 11 11 "github.com/bluesky-social/indigo/automod/countstore" 12 - "github.com/bluesky-social/indigo/automod/effects" 13 - "github.com/bluesky-social/indigo/automod/event" 14 12 "github.com/bluesky-social/indigo/automod/util" 15 13 "github.com/bluesky-social/indigo/xrpc" 16 14 ) ··· 56 54 } 57 55 58 56 // REVIEW: this does does both reads and then mutations of the planned effect, rather than just returning things, which neither the name nor signiture clearly suggests. 59 - func (eng *Engine) dedupeReportActions(evt *event.RepoEvent, eff *effects.Effects, reports []effects.ModReport) []effects.ModReport { 60 - newReports := []effects.ModReport{} 57 + func (eng *Engine) dedupeReportActions(evt *RepoEvent, eff *Effects, reports []ModReport) []ModReport { 58 + newReports := []ModReport{} 61 59 for _, r := range reports { 62 - counterName := "automod-account-report-" + effects.ReasonShortName(r.ReasonType) 60 + counterName := "automod-account-report-" + ReasonShortName(r.ReasonType) 63 61 existing, err := eng.GetCount(counterName, evt.Account.Identity.DID.String(), countstore.PeriodDay) 64 62 if err != nil { 65 63 panic(err) // XXX 66 64 } 67 65 if existing > 0 { 68 - eng.Logger.Debug("skipping account report due to counter", "existing", existing, "reason", effects.ReasonShortName(r.ReasonType)) 66 + eng.Logger.Debug("skipping account report due to counter", "existing", existing, "reason", ReasonShortName(r.ReasonType)) 69 67 } else { 70 68 eff.Increment(counterName, evt.Account.Identity.DID.String()) 71 69 newReports = append(newReports, r) ··· 74 72 return newReports 75 73 } 76 74 77 - func (eng *Engine) circuitBreakReports(eff *effects.Effects, reports []effects.ModReport) []effects.ModReport { 75 + func (eng *Engine) circuitBreakReports(eff *Effects, reports []ModReport) []ModReport { 78 76 if len(reports) == 0 { 79 - return []effects.ModReport{} 77 + return []ModReport{} 80 78 } 81 79 c, err := eng.GetCount("automod-quota", "report", countstore.PeriodDay) 82 80 if err != nil { 83 81 panic(err) // XXX 84 82 } 85 - if c >= effects.QuotaModReportDay { 83 + if c >= QuotaModReportDay { 86 84 eng.Logger.Warn("CIRCUIT BREAKER: automod reports") 87 - return []effects.ModReport{} 85 + return []ModReport{} 88 86 } 89 87 eff.Increment("automod-quota", "report") // REVIEW: should this increment just happen directly on the engine? it's not part of the relatively pure rule application logic, and we just had to read the engine again for it, so, maybe? 90 88 return reports 91 89 } 92 90 93 - func (eng *Engine) circuitBreakTakedown(eff *effects.Effects, takedown bool) bool { 91 + func (eng *Engine) circuitBreakTakedown(eff *Effects, takedown bool) bool { 94 92 if !takedown { 95 93 return takedown 96 94 } ··· 98 96 if err != nil { 99 97 panic(err) // XXX 100 98 } 101 - if c >= effects.QuotaModTakedownDay { 99 + if c >= QuotaModTakedownDay { 102 100 eng.Logger.Warn("CIRCUIT BREAKER: automod takedowns") 103 101 return false 104 102 } ··· 109 107 // Creates a moderation report, but checks first if there was a similar recent one, and skips if so. 110 108 // 111 109 // Returns a bool indicating if a new report was created. 112 - func (eng *Engine) createReportIfFresh(ctx context.Context, xrpcc *xrpc.Client, evt *event.RepoEvent, mr effects.ModReport) (bool, error) { 110 + func (eng *Engine) createReportIfFresh(ctx context.Context, xrpcc *xrpc.Client, evt *RepoEvent, mr ModReport) (bool, error) { 113 111 // before creating a report, query to see if automod has already reported this account in the past week for the same reason 114 112 // NOTE: this is running in an inner loop (if there are multiple reports), which is a bit inefficient, but seems acceptable 115 113 ··· 128 126 if err != nil { 129 127 return false, err 130 128 } 131 - if time.Since(created.Time()) > effects.ReportDupePeriod { 129 + if time.Since(created.Time()) > ReportDupePeriod { 132 130 continue 133 131 } 134 132 ··· 153 151 return true, nil 154 152 } 155 153 156 - func slackBody(header string, acct event.AccountMeta, newLabels, newFlags []string, newReports []effects.ModReport, newTakedown bool) string { 154 + func slackBody(header string, acct AccountMeta, newLabels, newFlags []string, newReports []ModReport, newTakedown bool) string { 157 155 msg := header 158 156 msg += fmt.Sprintf("`%s` / `%s` / <https://bsky.app/profile/%s|bsky> / <https://admin.prod.bsky.dev/repositories/%s|ozone>\n", 159 157 acct.Identity.DID,
+3 -5
automod/engine/ruleset.go
··· 4 4 "fmt" 5 5 6 6 appbsky "github.com/bluesky-social/indigo/api/bsky" 7 - "github.com/bluesky-social/indigo/automod/effects" 8 - "github.com/bluesky-social/indigo/automod/event" 9 7 ) 10 8 11 9 type RuleSet struct { ··· 16 14 IdentityRules []IdentityRuleFunc 17 15 } 18 16 19 - func (r *RuleSet) CallRecordRules(evt *event.RecordEvent, eff *effects.Effects) error { 17 + func (r *RuleSet) CallRecordRules(evt *RecordEvent, eff *Effects) error { 20 18 // first the generic rules 21 19 for _, f := range r.RecordRules { 22 20 err := f(evt, eff) ··· 52 50 return nil 53 51 } 54 52 55 - func (r *RuleSet) CallRecordDeleteRules(evt *event.RecordDeleteEvent, eff *effects.Effects) error { 53 + func (r *RuleSet) CallRecordDeleteRules(evt *RecordDeleteEvent, eff *Effects) error { 56 54 for _, f := range r.RecordDeleteRules { 57 55 err := f(evt, eff) 58 56 if err != nil { ··· 62 60 return nil 63 61 } 64 62 65 - func (r *RuleSet) CallIdentityRules(evt *event.IdentityEvent, eff *effects.Effects) error { 63 + func (r *RuleSet) CallIdentityRules(evt *IdentityEvent, eff *Effects) error { 66 64 for _, f := range r.IdentityRules { 67 65 err := f(evt, eff) 68 66 if err != nil {
+5 -7
automod/engine/ruletypes.go
··· 2 2 3 3 import ( 4 4 appbsky "github.com/bluesky-social/indigo/api/bsky" 5 - "github.com/bluesky-social/indigo/automod/effects" 6 - "github.com/bluesky-social/indigo/automod/event" 7 5 ) 8 6 9 - type IdentityRuleFunc = func(evt *event.IdentityEvent, eff *effects.Effects) error 10 - type RecordRuleFunc = func(evt *event.RecordEvent, eff *effects.Effects) error 11 - type PostRuleFunc = func(evt *event.RecordEvent, eff *effects.Effects, post *appbsky.FeedPost) error 12 - type ProfileRuleFunc = func(evt *event.RecordEvent, eff *effects.Effects, profile *appbsky.ActorProfile) error 13 - type RecordDeleteRuleFunc = func(evt *event.RecordDeleteEvent, eff *effects.Effects) error 7 + type IdentityRuleFunc = func(evt *IdentityEvent, eff *Effects) error 8 + type RecordRuleFunc = func(evt *RecordEvent, eff *Effects) error 9 + type PostRuleFunc = func(evt *RecordEvent, eff *Effects, post *appbsky.FeedPost) error 10 + type ProfileRuleFunc = func(evt *RecordEvent, eff *Effects, profile *appbsky.ActorProfile) error 11 + type RecordDeleteRuleFunc = func(evt *RecordDeleteEvent, eff *Effects) error
+4 -6
automod/engine/testing.go
··· 13 13 "github.com/bluesky-social/indigo/atproto/syntax" 14 14 "github.com/bluesky-social/indigo/automod/cachestore" 15 15 "github.com/bluesky-social/indigo/automod/countstore" 16 - "github.com/bluesky-social/indigo/automod/effects" 17 - "github.com/bluesky-social/indigo/automod/event" 18 16 "github.com/bluesky-social/indigo/automod/flagstore" 19 17 "github.com/bluesky-social/indigo/automod/setstore" 20 18 ) 21 19 22 20 var _ PostRuleFunc = simpleRule 23 21 24 - func simpleRule(evt *event.RecordEvent, eff *effects.Effects, post *appbsky.FeedPost) error { 22 + func simpleRule(evt *RecordEvent, eff *Effects, post *appbsky.FeedPost) error { 25 23 for _, tag := range post.Tags { 26 24 if evt.InSet("bad-hashtags", tag) { 27 25 eff.AddRecordLabel("bad-hashtag") ··· 102 100 103 101 // initial identity rules 104 102 // REVIEW: this area should... use the real code path that does the same thing, if at all possible? Currently this seems like great drift danger. 105 - idevt := &event.IdentityEvent{ 106 - RepoEvent: event.RepoEvent{ 103 + idevt := &IdentityEvent{ 104 + RepoEvent: RepoEvent{ 107 105 Account: capture.AccountMeta, 108 106 }, 109 107 } 110 - ideff := &effects.Effects{ 108 + ideff := &Effects{ 111 109 // XXX: Logger: eng.Logger.With("did", capture.AccountMeta.Identity.DID), 112 110 } 113 111 if err := eng.Rules.CallIdentityRules(idevt, ideff); err != nil {
+1 -1
automod/event/account_meta.go automod/engine/account_meta.go
··· 1 - package event 1 + package engine 2 2 3 3 import ( 4 4 "time"
+1 -1
automod/event/event.go automod/engine/event.go
··· 1 - package event 1 + package engine 2 2 3 3 // Base type for events specific to an account, usually derived from a repo event stream message (one such message may result in multiple `RepoEvent`) 4 4 //