diff --git a/api/chat/convodefs.go b/api/chat/convodefs.go index 9e215d0fe..59193ce08 100644 --- a/api/chat/convodefs.go +++ b/api/chat/convodefs.go @@ -18,6 +18,7 @@ type ConvoDefs_ConvoView struct { LastMessage *ConvoDefs_ConvoView_LastMessage `json:"lastMessage,omitempty" cborgen:"lastMessage,omitempty"` Members []*ActorDefs_ProfileViewBasic `json:"members" cborgen:"members"` Muted bool `json:"muted" cborgen:"muted"` + Opened *bool `json:"opened,omitempty" cborgen:"opened,omitempty"` Rev string `json:"rev" cborgen:"rev"` UnreadCount int64 `json:"unreadCount" cborgen:"unreadCount"` } diff --git a/api/ozone/moderationdefs.go b/api/ozone/moderationdefs.go index f419676ec..0033c9358 100644 --- a/api/ozone/moderationdefs.go +++ b/api/ozone/moderationdefs.go @@ -13,6 +13,33 @@ import ( "github.com/bluesky-social/indigo/lex/util" ) +// ModerationDefs_AccountEvent is a "accountEvent" in the tools.ozone.moderation.defs schema. +// +// Logs account status related events on a repo subject. Normally captured by automod from the firehose and emitted to ozone for historical tracking. +// +// RECORDTYPE: ModerationDefs_AccountEvent +type ModerationDefs_AccountEvent struct { + LexiconTypeID string `json:"$type,const=tools.ozone.moderation.defs#accountEvent" cborgen:"$type,const=tools.ozone.moderation.defs#accountEvent"` + // active: Indicates that the account has a repository which can be fetched from the host that emitted this event. + Active bool `json:"active" cborgen:"active"` + Comment *string `json:"comment,omitempty" cborgen:"comment,omitempty"` + Status *string `json:"status,omitempty" cborgen:"status,omitempty"` + Timestamp string `json:"timestamp" cborgen:"timestamp"` +} + +// ModerationDefs_AccountHosting is a "accountHosting" in the tools.ozone.moderation.defs schema. +// +// RECORDTYPE: ModerationDefs_AccountHosting +type ModerationDefs_AccountHosting struct { + LexiconTypeID string `json:"$type,const=tools.ozone.moderation.defs#accountHosting" cborgen:"$type,const=tools.ozone.moderation.defs#accountHosting"` + CreatedAt *string `json:"createdAt,omitempty" cborgen:"createdAt,omitempty"` + DeactivatedAt *string `json:"deactivatedAt,omitempty" cborgen:"deactivatedAt,omitempty"` + DeletedAt *string `json:"deletedAt,omitempty" cborgen:"deletedAt,omitempty"` + ReactivatedAt *string `json:"reactivatedAt,omitempty" cborgen:"reactivatedAt,omitempty"` + Status string `json:"status" cborgen:"status"` + UpdatedAt *string `json:"updatedAt,omitempty" cborgen:"updatedAt,omitempty"` +} + // ModerationDefs_BlobView is a "blobView" in the tools.ozone.moderation.defs schema. type ModerationDefs_BlobView struct { Cid string `json:"cid" cborgen:"cid"` @@ -58,6 +85,20 @@ func (t *ModerationDefs_BlobView_Details) UnmarshalJSON(b []byte) error { } } +// ModerationDefs_IdentityEvent is a "identityEvent" in the tools.ozone.moderation.defs schema. +// +// Logs identity related events on a repo subject. Normally captured by automod from the firehose and emitted to ozone for historical tracking. +// +// RECORDTYPE: ModerationDefs_IdentityEvent +type ModerationDefs_IdentityEvent struct { + LexiconTypeID string `json:"$type,const=tools.ozone.moderation.defs#identityEvent" cborgen:"$type,const=tools.ozone.moderation.defs#identityEvent"` + Comment *string `json:"comment,omitempty" cborgen:"comment,omitempty"` + Handle *string `json:"handle,omitempty" cborgen:"handle,omitempty"` + PdsHost *string `json:"pdsHost,omitempty" cborgen:"pdsHost,omitempty"` + Timestamp string `json:"timestamp" cborgen:"timestamp"` + Tombstone *bool `json:"tombstone,omitempty" cborgen:"tombstone,omitempty"` +} + // ModerationDefs_ImageDetails is a "imageDetails" in the tools.ozone.moderation.defs schema. // // RECORDTYPE: ModerationDefs_ImageDetails @@ -152,8 +193,8 @@ type ModerationDefs_ModEventMute struct { type ModerationDefs_ModEventMuteReporter struct { LexiconTypeID string `json:"$type,const=tools.ozone.moderation.defs#modEventMuteReporter" cborgen:"$type,const=tools.ozone.moderation.defs#modEventMuteReporter"` Comment *string `json:"comment,omitempty" cborgen:"comment,omitempty"` - // durationInHours: Indicates how long the account should remain muted. - DurationInHours int64 `json:"durationInHours" cborgen:"durationInHours"` + // durationInHours: Indicates how long the account should remain muted. Falsy value here means a permanent mute. + DurationInHours *int64 `json:"durationInHours,omitempty" cborgen:"durationInHours,omitempty"` } // ModerationDefs_ModEventReport is a "modEventReport" in the tools.ozone.moderation.defs schema. @@ -280,6 +321,9 @@ type ModerationDefs_ModEventViewDetail_Event struct { ModerationDefs_ModEventResolveAppeal *ModerationDefs_ModEventResolveAppeal ModerationDefs_ModEventDivert *ModerationDefs_ModEventDivert ModerationDefs_ModEventTag *ModerationDefs_ModEventTag + ModerationDefs_AccountEvent *ModerationDefs_AccountEvent + ModerationDefs_IdentityEvent *ModerationDefs_IdentityEvent + ModerationDefs_RecordEvent *ModerationDefs_RecordEvent } func (t *ModerationDefs_ModEventViewDetail_Event) MarshalJSON() ([]byte, error) { @@ -343,6 +387,18 @@ func (t *ModerationDefs_ModEventViewDetail_Event) MarshalJSON() ([]byte, error) t.ModerationDefs_ModEventTag.LexiconTypeID = "tools.ozone.moderation.defs#modEventTag" return json.Marshal(t.ModerationDefs_ModEventTag) } + if t.ModerationDefs_AccountEvent != nil { + t.ModerationDefs_AccountEvent.LexiconTypeID = "tools.ozone.moderation.defs#accountEvent" + return json.Marshal(t.ModerationDefs_AccountEvent) + } + if t.ModerationDefs_IdentityEvent != nil { + t.ModerationDefs_IdentityEvent.LexiconTypeID = "tools.ozone.moderation.defs#identityEvent" + return json.Marshal(t.ModerationDefs_IdentityEvent) + } + if t.ModerationDefs_RecordEvent != nil { + t.ModerationDefs_RecordEvent.LexiconTypeID = "tools.ozone.moderation.defs#recordEvent" + return json.Marshal(t.ModerationDefs_RecordEvent) + } return nil, fmt.Errorf("cannot marshal empty enum") } func (t *ModerationDefs_ModEventViewDetail_Event) UnmarshalJSON(b []byte) error { @@ -397,6 +453,15 @@ func (t *ModerationDefs_ModEventViewDetail_Event) UnmarshalJSON(b []byte) error case "tools.ozone.moderation.defs#modEventTag": t.ModerationDefs_ModEventTag = new(ModerationDefs_ModEventTag) return json.Unmarshal(b, t.ModerationDefs_ModEventTag) + case "tools.ozone.moderation.defs#accountEvent": + t.ModerationDefs_AccountEvent = new(ModerationDefs_AccountEvent) + return json.Unmarshal(b, t.ModerationDefs_AccountEvent) + case "tools.ozone.moderation.defs#identityEvent": + t.ModerationDefs_IdentityEvent = new(ModerationDefs_IdentityEvent) + return json.Unmarshal(b, t.ModerationDefs_IdentityEvent) + case "tools.ozone.moderation.defs#recordEvent": + t.ModerationDefs_RecordEvent = new(ModerationDefs_RecordEvent) + return json.Unmarshal(b, t.ModerationDefs_RecordEvent) default: return nil @@ -470,6 +535,9 @@ type ModerationDefs_ModEventView_Event struct { ModerationDefs_ModEventResolveAppeal *ModerationDefs_ModEventResolveAppeal ModerationDefs_ModEventDivert *ModerationDefs_ModEventDivert ModerationDefs_ModEventTag *ModerationDefs_ModEventTag + ModerationDefs_AccountEvent *ModerationDefs_AccountEvent + ModerationDefs_IdentityEvent *ModerationDefs_IdentityEvent + ModerationDefs_RecordEvent *ModerationDefs_RecordEvent } func (t *ModerationDefs_ModEventView_Event) MarshalJSON() ([]byte, error) { @@ -533,6 +601,18 @@ func (t *ModerationDefs_ModEventView_Event) MarshalJSON() ([]byte, error) { t.ModerationDefs_ModEventTag.LexiconTypeID = "tools.ozone.moderation.defs#modEventTag" return json.Marshal(t.ModerationDefs_ModEventTag) } + if t.ModerationDefs_AccountEvent != nil { + t.ModerationDefs_AccountEvent.LexiconTypeID = "tools.ozone.moderation.defs#accountEvent" + return json.Marshal(t.ModerationDefs_AccountEvent) + } + if t.ModerationDefs_IdentityEvent != nil { + t.ModerationDefs_IdentityEvent.LexiconTypeID = "tools.ozone.moderation.defs#identityEvent" + return json.Marshal(t.ModerationDefs_IdentityEvent) + } + if t.ModerationDefs_RecordEvent != nil { + t.ModerationDefs_RecordEvent.LexiconTypeID = "tools.ozone.moderation.defs#recordEvent" + return json.Marshal(t.ModerationDefs_RecordEvent) + } return nil, fmt.Errorf("cannot marshal empty enum") } func (t *ModerationDefs_ModEventView_Event) UnmarshalJSON(b []byte) error { @@ -587,6 +667,15 @@ func (t *ModerationDefs_ModEventView_Event) UnmarshalJSON(b []byte) error { case "tools.ozone.moderation.defs#modEventTag": t.ModerationDefs_ModEventTag = new(ModerationDefs_ModEventTag) return json.Unmarshal(b, t.ModerationDefs_ModEventTag) + case "tools.ozone.moderation.defs#accountEvent": + t.ModerationDefs_AccountEvent = new(ModerationDefs_AccountEvent) + return json.Unmarshal(b, t.ModerationDefs_AccountEvent) + case "tools.ozone.moderation.defs#identityEvent": + t.ModerationDefs_IdentityEvent = new(ModerationDefs_IdentityEvent) + return json.Unmarshal(b, t.ModerationDefs_IdentityEvent) + case "tools.ozone.moderation.defs#recordEvent": + t.ModerationDefs_RecordEvent = new(ModerationDefs_RecordEvent) + return json.Unmarshal(b, t.ModerationDefs_RecordEvent) default: return nil @@ -646,6 +735,30 @@ type ModerationDefs_ModerationDetail struct { SubjectStatus *ModerationDefs_SubjectStatusView `json:"subjectStatus,omitempty" cborgen:"subjectStatus,omitempty"` } +// ModerationDefs_RecordEvent is a "recordEvent" in the tools.ozone.moderation.defs schema. +// +// Logs lifecycle event on a record subject. Normally captured by automod from the firehose and emitted to ozone for historical tracking. +// +// RECORDTYPE: ModerationDefs_RecordEvent +type ModerationDefs_RecordEvent struct { + LexiconTypeID string `json:"$type,const=tools.ozone.moderation.defs#recordEvent" cborgen:"$type,const=tools.ozone.moderation.defs#recordEvent"` + Cid *string `json:"cid,omitempty" cborgen:"cid,omitempty"` + Comment *string `json:"comment,omitempty" cborgen:"comment,omitempty"` + Op string `json:"op" cborgen:"op"` + Timestamp string `json:"timestamp" cborgen:"timestamp"` +} + +// ModerationDefs_RecordHosting is a "recordHosting" in the tools.ozone.moderation.defs schema. +// +// RECORDTYPE: ModerationDefs_RecordHosting +type ModerationDefs_RecordHosting struct { + LexiconTypeID string `json:"$type,const=tools.ozone.moderation.defs#recordHosting" cborgen:"$type,const=tools.ozone.moderation.defs#recordHosting"` + CreatedAt *string `json:"createdAt,omitempty" cborgen:"createdAt,omitempty"` + DeletedAt *string `json:"deletedAt,omitempty" cborgen:"deletedAt,omitempty"` + Status string `json:"status" cborgen:"status"` + UpdatedAt *string `json:"updatedAt,omitempty" cborgen:"updatedAt,omitempty"` +} + // ModerationDefs_RecordView is a "recordView" in the tools.ozone.moderation.defs schema. // // RECORDTYPE: ModerationDefs_RecordView @@ -737,8 +850,9 @@ type ModerationDefs_SubjectStatusView struct { // comment: Sticky comment on the subject. Comment *string `json:"comment,omitempty" cborgen:"comment,omitempty"` // createdAt: Timestamp referencing the first moderation status impacting event was emitted on the subject - CreatedAt string `json:"createdAt" cborgen:"createdAt"` - Id int64 `json:"id" cborgen:"id"` + CreatedAt string `json:"createdAt" cborgen:"createdAt"` + Hosting *ModerationDefs_SubjectStatusView_Hosting `json:"hosting,omitempty" cborgen:"hosting,omitempty"` + Id int64 `json:"id" cborgen:"id"` // lastAppealedAt: Timestamp referencing when the author of the subject appealed a moderation action LastAppealedAt *string `json:"lastAppealedAt,omitempty" cborgen:"lastAppealedAt,omitempty"` LastReportedAt *string `json:"lastReportedAt,omitempty" cborgen:"lastReportedAt,omitempty"` @@ -757,6 +871,41 @@ type ModerationDefs_SubjectStatusView struct { UpdatedAt string `json:"updatedAt" cborgen:"updatedAt"` } +type ModerationDefs_SubjectStatusView_Hosting struct { + ModerationDefs_AccountHosting *ModerationDefs_AccountHosting + ModerationDefs_RecordHosting *ModerationDefs_RecordHosting +} + +func (t *ModerationDefs_SubjectStatusView_Hosting) MarshalJSON() ([]byte, error) { + if t.ModerationDefs_AccountHosting != nil { + t.ModerationDefs_AccountHosting.LexiconTypeID = "tools.ozone.moderation.defs#accountHosting" + return json.Marshal(t.ModerationDefs_AccountHosting) + } + if t.ModerationDefs_RecordHosting != nil { + t.ModerationDefs_RecordHosting.LexiconTypeID = "tools.ozone.moderation.defs#recordHosting" + return json.Marshal(t.ModerationDefs_RecordHosting) + } + return nil, fmt.Errorf("cannot marshal empty enum") +} +func (t *ModerationDefs_SubjectStatusView_Hosting) UnmarshalJSON(b []byte) error { + typ, err := util.TypeExtract(b) + if err != nil { + return err + } + + switch typ { + case "tools.ozone.moderation.defs#accountHosting": + t.ModerationDefs_AccountHosting = new(ModerationDefs_AccountHosting) + return json.Unmarshal(b, t.ModerationDefs_AccountHosting) + case "tools.ozone.moderation.defs#recordHosting": + t.ModerationDefs_RecordHosting = new(ModerationDefs_RecordHosting) + return json.Unmarshal(b, t.ModerationDefs_RecordHosting) + + default: + return nil + } +} + type ModerationDefs_SubjectStatusView_Subject struct { AdminDefs_RepoRef *comatprototypes.AdminDefs_RepoRef RepoStrongRef *comatprototypes.RepoStrongRef diff --git a/api/ozone/moderationemitEvent.go b/api/ozone/moderationemitEvent.go index 064eef3a6..09f8f3497 100644 --- a/api/ozone/moderationemitEvent.go +++ b/api/ozone/moderationemitEvent.go @@ -37,6 +37,9 @@ type ModerationEmitEvent_Input_Event struct { ModerationDefs_ModEventResolveAppeal *ModerationDefs_ModEventResolveAppeal ModerationDefs_ModEventEmail *ModerationDefs_ModEventEmail ModerationDefs_ModEventTag *ModerationDefs_ModEventTag + ModerationDefs_AccountEvent *ModerationDefs_AccountEvent + ModerationDefs_IdentityEvent *ModerationDefs_IdentityEvent + ModerationDefs_RecordEvent *ModerationDefs_RecordEvent } func (t *ModerationEmitEvent_Input_Event) MarshalJSON() ([]byte, error) { @@ -96,6 +99,18 @@ func (t *ModerationEmitEvent_Input_Event) MarshalJSON() ([]byte, error) { t.ModerationDefs_ModEventTag.LexiconTypeID = "tools.ozone.moderation.defs#modEventTag" return json.Marshal(t.ModerationDefs_ModEventTag) } + if t.ModerationDefs_AccountEvent != nil { + t.ModerationDefs_AccountEvent.LexiconTypeID = "tools.ozone.moderation.defs#accountEvent" + return json.Marshal(t.ModerationDefs_AccountEvent) + } + if t.ModerationDefs_IdentityEvent != nil { + t.ModerationDefs_IdentityEvent.LexiconTypeID = "tools.ozone.moderation.defs#identityEvent" + return json.Marshal(t.ModerationDefs_IdentityEvent) + } + if t.ModerationDefs_RecordEvent != nil { + t.ModerationDefs_RecordEvent.LexiconTypeID = "tools.ozone.moderation.defs#recordEvent" + return json.Marshal(t.ModerationDefs_RecordEvent) + } return nil, fmt.Errorf("cannot marshal empty enum") } func (t *ModerationEmitEvent_Input_Event) UnmarshalJSON(b []byte) error { @@ -147,6 +162,15 @@ func (t *ModerationEmitEvent_Input_Event) UnmarshalJSON(b []byte) error { case "tools.ozone.moderation.defs#modEventTag": t.ModerationDefs_ModEventTag = new(ModerationDefs_ModEventTag) return json.Unmarshal(b, t.ModerationDefs_ModEventTag) + case "tools.ozone.moderation.defs#accountEvent": + t.ModerationDefs_AccountEvent = new(ModerationDefs_AccountEvent) + return json.Unmarshal(b, t.ModerationDefs_AccountEvent) + case "tools.ozone.moderation.defs#identityEvent": + t.ModerationDefs_IdentityEvent = new(ModerationDefs_IdentityEvent) + return json.Unmarshal(b, t.ModerationDefs_IdentityEvent) + case "tools.ozone.moderation.defs#recordEvent": + t.ModerationDefs_RecordEvent = new(ModerationDefs_RecordEvent) + return json.Unmarshal(b, t.ModerationDefs_RecordEvent) default: return nil diff --git a/api/ozone/moderationqueryStatuses.go b/api/ozone/moderationqueryStatuses.go index 969d77e9d..bbe0351f5 100644 --- a/api/ozone/moderationqueryStatuses.go +++ b/api/ozone/moderationqueryStatuses.go @@ -21,6 +21,11 @@ type ModerationQueryStatuses_Output struct { // appealed: Get subjects in unresolved appealed status // collections: If specified, subjects belonging to the given collections will be returned. When subjectType is set to 'account', this will be ignored. // comment: Search subjects by keyword from comments +// hostingDeletedAfter: Search subjects where the associated record/account was deleted after a given timestamp +// hostingDeletedBefore: Search subjects where the associated record/account was deleted before a given timestamp +// hostingStatuses: Search subjects by the status of the associated record/account +// hostingUpdatedAfter: Search subjects where the associated record/account was updated after a given timestamp +// hostingUpdatedBefore: Search subjects where the associated record/account was updated before a given timestamp // includeAllUserRecords: All subjects, or subjects from given 'collections' param, belonging to the account specified in the 'subject' param will be returned. // includeMuted: By default, we don't include muted subjects in the results. Set this to true to include them. // lastReviewedBy: Get all subject statuses that were reviewed by a specific moderator @@ -33,7 +38,7 @@ type ModerationQueryStatuses_Output struct { // subject: The subject to get the status for. // subjectType: If specified, subjects of the given type (account or record) will be returned. When this is set to 'account' the 'collections' parameter will be ignored. When includeAllUserRecords or subject is set, this will be ignored. // takendown: Get subjects that were taken down -func ModerationQueryStatuses(ctx context.Context, c *xrpc.Client, appealed bool, collections []string, comment string, cursor string, excludeTags []string, ignoreSubjects []string, includeAllUserRecords bool, includeMuted bool, lastReviewedBy string, limit int64, onlyMuted bool, reportedAfter string, reportedBefore string, reviewState string, reviewedAfter string, reviewedBefore string, sortDirection string, sortField string, subject string, subjectType string, tags []string, takendown bool) (*ModerationQueryStatuses_Output, error) { +func ModerationQueryStatuses(ctx context.Context, c *xrpc.Client, appealed bool, collections []string, comment string, cursor string, excludeTags []string, hostingDeletedAfter string, hostingDeletedBefore string, hostingStatuses []string, hostingUpdatedAfter string, hostingUpdatedBefore string, ignoreSubjects []string, includeAllUserRecords bool, includeMuted bool, lastReviewedBy string, limit int64, onlyMuted bool, reportedAfter string, reportedBefore string, reviewState string, reviewedAfter string, reviewedBefore string, sortDirection string, sortField string, subject string, subjectType string, tags []string, takendown bool) (*ModerationQueryStatuses_Output, error) { var out ModerationQueryStatuses_Output params := map[string]interface{}{ @@ -42,6 +47,11 @@ func ModerationQueryStatuses(ctx context.Context, c *xrpc.Client, appealed bool, "comment": comment, "cursor": cursor, "excludeTags": excludeTags, + "hostingDeletedAfter": hostingDeletedAfter, + "hostingDeletedBefore": hostingDeletedBefore, + "hostingStatuses": hostingStatuses, + "hostingUpdatedAfter": hostingUpdatedAfter, + "hostingUpdatedBefore": hostingUpdatedBefore, "ignoreSubjects": ignoreSubjects, "includeAllUserRecords": includeAllUserRecords, "includeMuted": includeMuted, diff --git a/api/ozone/settingdefs.go b/api/ozone/settingdefs.go new file mode 100644 index 000000000..ccf334413 --- /dev/null +++ b/api/ozone/settingdefs.go @@ -0,0 +1,23 @@ +// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. + +package ozone + +// schema: tools.ozone.setting.defs + +import ( + "github.com/bluesky-social/indigo/lex/util" +) + +// SettingDefs_Option is a "option" in the tools.ozone.setting.defs schema. +type SettingDefs_Option struct { + CreatedAt *string `json:"createdAt,omitempty" cborgen:"createdAt,omitempty"` + CreatedBy string `json:"createdBy" cborgen:"createdBy"` + Description *string `json:"description,omitempty" cborgen:"description,omitempty"` + Did string `json:"did" cborgen:"did"` + Key string `json:"key" cborgen:"key"` + LastUpdatedBy string `json:"lastUpdatedBy" cborgen:"lastUpdatedBy"` + ManagerRole *string `json:"managerRole,omitempty" cborgen:"managerRole,omitempty"` + Scope string `json:"scope" cborgen:"scope"` + UpdatedAt *string `json:"updatedAt,omitempty" cborgen:"updatedAt,omitempty"` + Value *util.LexiconTypeDecoder `json:"value" cborgen:"value"` +} diff --git a/api/ozone/settinglistOptions.go b/api/ozone/settinglistOptions.go new file mode 100644 index 000000000..8fbdadeca --- /dev/null +++ b/api/ozone/settinglistOptions.go @@ -0,0 +1,38 @@ +// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. + +package ozone + +// schema: tools.ozone.setting.listOptions + +import ( + "context" + + "github.com/bluesky-social/indigo/xrpc" +) + +// SettingListOptions_Output is the output of a tools.ozone.setting.listOptions call. +type SettingListOptions_Output struct { + Cursor *string `json:"cursor,omitempty" cborgen:"cursor,omitempty"` + Options []*SettingDefs_Option `json:"options" cborgen:"options"` +} + +// SettingListOptions calls the XRPC method "tools.ozone.setting.listOptions". +// +// keys: Filter for only the specified keys. Ignored if prefix is provided +// prefix: Filter keys by prefix +func SettingListOptions(ctx context.Context, c *xrpc.Client, cursor string, keys []string, limit int64, prefix string, scope string) (*SettingListOptions_Output, error) { + var out SettingListOptions_Output + + params := map[string]interface{}{ + "cursor": cursor, + "keys": keys, + "limit": limit, + "prefix": prefix, + "scope": scope, + } + if err := c.Do(ctx, xrpc.Query, "", "tools.ozone.setting.listOptions", params, nil, &out); err != nil { + return nil, err + } + + return &out, nil +} diff --git a/api/ozone/settingremoveOptions.go b/api/ozone/settingremoveOptions.go new file mode 100644 index 000000000..5171bb617 --- /dev/null +++ b/api/ozone/settingremoveOptions.go @@ -0,0 +1,31 @@ +// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. + +package ozone + +// schema: tools.ozone.setting.removeOptions + +import ( + "context" + + "github.com/bluesky-social/indigo/xrpc" +) + +// SettingRemoveOptions_Input is the input argument to a tools.ozone.setting.removeOptions call. +type SettingRemoveOptions_Input struct { + Keys []string `json:"keys" cborgen:"keys"` + Scope string `json:"scope" cborgen:"scope"` +} + +// SettingRemoveOptions_Output is the output of a tools.ozone.setting.removeOptions call. +type SettingRemoveOptions_Output struct { +} + +// SettingRemoveOptions calls the XRPC method "tools.ozone.setting.removeOptions". +func SettingRemoveOptions(ctx context.Context, c *xrpc.Client, input *SettingRemoveOptions_Input) (*SettingRemoveOptions_Output, error) { + var out SettingRemoveOptions_Output + if err := c.Do(ctx, xrpc.Procedure, "application/json", "tools.ozone.setting.removeOptions", nil, input, &out); err != nil { + return nil, err + } + + return &out, nil +} diff --git a/api/ozone/settingupsertOption.go b/api/ozone/settingupsertOption.go new file mode 100644 index 000000000..c7d12cffb --- /dev/null +++ b/api/ozone/settingupsertOption.go @@ -0,0 +1,36 @@ +// Code generated by cmd/lexgen (see Makefile's lexgen); DO NOT EDIT. + +package ozone + +// schema: tools.ozone.setting.upsertOption + +import ( + "context" + + "github.com/bluesky-social/indigo/lex/util" + "github.com/bluesky-social/indigo/xrpc" +) + +// SettingUpsertOption_Input is the input argument to a tools.ozone.setting.upsertOption call. +type SettingUpsertOption_Input struct { + Description *string `json:"description,omitempty" cborgen:"description,omitempty"` + Key string `json:"key" cborgen:"key"` + ManagerRole *string `json:"managerRole,omitempty" cborgen:"managerRole,omitempty"` + Scope string `json:"scope" cborgen:"scope"` + Value *util.LexiconTypeDecoder `json:"value" cborgen:"value"` +} + +// SettingUpsertOption_Output is the output of a tools.ozone.setting.upsertOption call. +type SettingUpsertOption_Output struct { + Option *SettingDefs_Option `json:"option" cborgen:"option"` +} + +// SettingUpsertOption calls the XRPC method "tools.ozone.setting.upsertOption". +func SettingUpsertOption(ctx context.Context, c *xrpc.Client, input *SettingUpsertOption_Input) (*SettingUpsertOption_Output, error) { + var out SettingUpsertOption_Output + if err := c.Do(ctx, xrpc.Procedure, "application/json", "tools.ozone.setting.upsertOption", nil, input, &out); err != nil { + return nil, err + } + + return &out, nil +} diff --git a/automod/engine/context.go b/automod/engine/context.go index 2e447bebd..56e62e536 100644 --- a/automod/engine/context.go +++ b/automod/engine/context.go @@ -250,7 +250,6 @@ func (c *BaseContext) GetAccountMeta(did syntax.DID) *AccountMeta { func (c *BaseContext) Increment(name, val string) { c.effects.Increment(name, val) } - func (c *BaseContext) IncrementDistinct(name, bucket, val string) { c.effects.IncrementDistinct(name, bucket, val) } @@ -315,6 +314,10 @@ func (c *RecordContext) TakedownBlob(cid string) { c.effects.TakedownBlob(cid) } +func (c *RecordContext) PersistRecordOzoneEvent() { + c.effects.PersistRecordOzoneEvent() +} + func (c *NotificationContext) Reject() { c.effects.Reject() } diff --git a/automod/engine/effects.go b/automod/engine/effects.go index 10bbc1fb0..a61cf6a9e 100644 --- a/automod/engine/effects.go +++ b/automod/engine/effects.go @@ -56,6 +56,8 @@ type Effects struct { RejectEvent bool // Services, if any, which should blast out a notification about this even (eg, Slack) NotifyServices []string + // If "true", and Ozone event history is configured/enable, then sent a mod event to ozone backend for this event + PersistOzoneRecordEvent bool } // Enqueues the named counter to be incremented at the end of all rule processing. Will automatically increment for all time periods. @@ -231,3 +233,8 @@ func (e *Effects) Notify(srv string) { func (e *Effects) Reject() { e.RejectEvent = true } + +// Marks that this subject should be recorded in ozone history +func (e *Effects) PersistRecordOzoneEvent() { + e.PersistOzoneRecordEvent = true +} diff --git a/automod/engine/engine.go b/automod/engine/engine.go index 933987072..0689cec62 100644 --- a/automod/engine/engine.go +++ b/automod/engine/engine.go @@ -52,6 +52,8 @@ type Engine struct { type EngineConfig struct { // if enabled, account metadata is not hydrated for every event by default SkipAccountMeta bool + // if true, sent firehose identity and account events to ozone backend as events + PersistSubjectHistoryOzone bool // time period within which automod will not re-report an account for the same reasonType ReportDupePeriod time.Duration // number of reports automod can file per day, for all subjects and types combined (circuit breaker) @@ -122,6 +124,11 @@ func (eng *Engine) ProcessIdentityEvent(ctx context.Context, evt comatproto.Sync return fmt.Errorf("rule execution failed: %w", err) } eng.CanonicalLogLineAccount(&ac) + if eng.Config.PersistSubjectHistoryOzone { + if err := eng.RerouteIdentityEventToOzone(ctx, &evt); err != nil { + return fmt.Errorf("failed to persist identity event to ozone history: %w", err) + } + } if err := eng.persistAccountModActions(&ac); err != nil { eventErrorCount.WithLabelValues("identity").Inc() return fmt.Errorf("failed to persist actions for identity event: %w", err) @@ -193,6 +200,11 @@ func (eng *Engine) ProcessAccountEvent(ctx context.Context, evt comatproto.SyncS return fmt.Errorf("rule execution failed: %w", err) } eng.CanonicalLogLineAccount(&ac) + if eng.Config.PersistSubjectHistoryOzone { + if err := eng.RerouteAccountEventToOzone(ctx, &evt); err != nil { + return fmt.Errorf("failed to persist account event to ozone history: %w", err) + } + } if err := eng.persistAccountModActions(&ac); err != nil { eventErrorCount.WithLabelValues("account").Inc() return fmt.Errorf("failed to persist actions for account event: %w", err) @@ -283,6 +295,11 @@ func (eng *Engine) ProcessRecordOp(ctx context.Context, op RecordOp) error { eventErrorCount.WithLabelValues("record").Inc() return fmt.Errorf("failed to persist counts for record event: %w", err) } + if eng.Config.PersistSubjectHistoryOzone { + if err := eng.RerouteRecordOpToOzone(&rc); err != nil { + return fmt.Errorf("failed to persist account event to ozone history: %w", err) + } + } return nil } diff --git a/automod/engine/reroute.go b/automod/engine/reroute.go new file mode 100644 index 000000000..5eeec2fe0 --- /dev/null +++ b/automod/engine/reroute.go @@ -0,0 +1,181 @@ +package engine + +import ( + "context" + "time" + + comatproto "github.com/bluesky-social/indigo/api/atproto" + toolsozone "github.com/bluesky-social/indigo/api/ozone" +) + +func (eng *Engine) RerouteAccountEventToOzone(c context.Context, e *comatproto.SyncSubscribeRepos_Account) error { + comment := "[automod]: Account status event" + eng.rerouteEventToOzone(c, toolsozone.ModerationEmitEvent_Input_Event{ + ModerationDefs_AccountEvent: &toolsozone.ModerationDefs_AccountEvent{ + Comment: &comment, + Timestamp: e.Time, + Status: e.Status, + Active: e.Active, + }, + }, toolsozone.ModerationEmitEvent_Input_Subject{ + AdminDefs_RepoRef: &comatproto.AdminDefs_RepoRef{ + Did: e.Did, + }, + }) + return nil +} + +/* +func (eng *Engine) RerouteTombstoneEventToOzone(c context.Context, e *comatproto.SyncSubscribeRepos_Tombstone) error { + comment := "[automod]: Tombstone event" + tombstone := true + eng.rerouteEventToOzone(c, toolsozone.ModerationEmitEvent_Input_Event{ + ModerationDefs_IdentityEvent: &toolsozone.ModerationDefs_IdentityEvent{ + Comment: &comment, + // @TODO: These don't seem to exist in the Identity event? + // Handle: e.Handle, + // PdsHost: &e.PdsHost, + Tombstone: &tombstone, + Timestamp: e.Time, + }, + }, toolsozone.ModerationEmitEvent_Input_Subject{ + AdminDefs_RepoRef: &comatproto.AdminDefs_RepoRef{ + Did: e.Did, + }, + }) + return nil +} +*/ + +func (eng *Engine) RerouteRecordOpToOzone(c *RecordContext) error { + comment := "[automod]: Record event" + + eng.rerouteEventToOzone(c.Ctx, toolsozone.ModerationEmitEvent_Input_Event{ + ModerationDefs_RecordEvent: &toolsozone.ModerationDefs_RecordEvent{ + Comment: &comment, + Op: c.RecordOp.Action, + Timestamp: time.Now().Format(time.RFC3339), + }, + }, toolsozone.ModerationEmitEvent_Input_Subject{ + RepoStrongRef: &comatproto.RepoStrongRef{ + LexiconTypeID: "com.atproto.repo.strongRef", + Uri: c.RecordOp.ATURI().String(), + Cid: c.RecordOp.CID.String(), + }, + }) + + return nil +} + +func (eng *Engine) RerouteIdentityEventToOzone(c context.Context, e *comatproto.SyncSubscribeRepos_Identity) error { + comment := "[automod]: Identity event" + tombstone := false + + eng.rerouteEventToOzone(c, toolsozone.ModerationEmitEvent_Input_Event{ + ModerationDefs_IdentityEvent: &toolsozone.ModerationDefs_IdentityEvent{ + Comment: &comment, + Handle: e.Handle, + // @TODO: This doesn't seem to exist in the Identity event? + // PdsHost: &e.PdsHost, + Tombstone: &tombstone, + Timestamp: e.Time, + }, + }, toolsozone.ModerationEmitEvent_Input_Subject{ + AdminDefs_RepoRef: &comatproto.AdminDefs_RepoRef{ + Did: e.Did, + }, + }) + + return nil +} + +// For the given subject, checks if there is already an event of the given type within the 5 minutes. +func (eng *Engine) IsDuplicatingEvent(ctx context.Context, event toolsozone.ModerationEmitEvent_Input_Event, subject toolsozone.ModerationEmitEvent_Input_Subject) (bool, error) { + if eng.OzoneClient == nil { + eng.Logger.Warn("can not check if event is duplicate, mod service client not configured") + return false, nil + } + + eventType := "" + if event.ModerationDefs_AccountEvent != nil { + eventType = event.ModerationDefs_AccountEvent.LexiconTypeID + } else if event.ModerationDefs_IdentityEvent != nil { + eventType = event.ModerationDefs_IdentityEvent.LexiconTypeID + } else if event.ModerationDefs_RecordEvent != nil { + eventType = event.ModerationDefs_RecordEvent.LexiconTypeID + } + + eventSubject := "" + if subject.AdminDefs_RepoRef != nil { + eventSubject = subject.AdminDefs_RepoRef.Did + } else if subject.RepoStrongRef != nil { + eventSubject = subject.RepoStrongRef.Uri + } + + xrpcc := eng.OzoneClient + resp, err := toolsozone.ModerationQueryEvents( + ctx, + xrpcc, + nil, + nil, + []string{}, + "", + time.Now().Add(-time.Minute*5).Format(time.RFC3339), + "", + "", + "", + false, + false, + 1, + nil, + nil, + nil, + "", + eventSubject, + "", + []string{eventType}, + ) + + if err != nil { + eng.Logger.Error("failed to query events", "err", err) + return false, err + } + + if len(resp.Events) > 0 { + return true, nil + } + + return false, nil +} + +func (eng *Engine) rerouteEventToOzone(ctx context.Context, event toolsozone.ModerationEmitEvent_Input_Event, subject toolsozone.ModerationEmitEvent_Input_Subject) error { + // if we can't actually talk to service, bail out early + if eng.OzoneClient == nil { + eng.Logger.Warn("not persisting ozone account event, mod service client not configured") + return nil + } + + isDuplicate, duplicateCheckError := eng.IsDuplicatingEvent(ctx, event, subject) + if duplicateCheckError != nil { + eng.Logger.Error("failed to check if event is duplicate", "err", duplicateCheckError) + return duplicateCheckError + } + + if isDuplicate { + eng.Logger.Info("event was already emitted, not emitting again") + return nil + } + + xrpcc := eng.OzoneClient + _, err := toolsozone.ModerationEmitEvent(ctx, xrpcc, &toolsozone.ModerationEmitEvent_Input{ + CreatedBy: xrpcc.Auth.Did, + Event: &event, + Subject: &subject, + }) + if err != nil { + eng.Logger.Error("failed to re route event to ozone", "err", err) + return err + } + + return nil +} diff --git a/automod/pkg.go b/automod/pkg.go index e04589698..a54633e9c 100644 --- a/automod/pkg.go +++ b/automod/pkg.go @@ -22,6 +22,7 @@ type NotificationContext = engine.NotificationContext type RecordOp = engine.RecordOp type IdentityRuleFunc = engine.IdentityRuleFunc +type AccountRuleFunc = engine.AccountRuleFunc type RecordRuleFunc = engine.RecordRuleFunc type PostRuleFunc = engine.PostRuleFunc type ProfileRuleFunc = engine.ProfileRuleFunc diff --git a/automod/rules/all.go b/automod/rules/all.go index 8d66b509d..475ee3f7d 100644 --- a/automod/rules/all.go +++ b/automod/rules/all.go @@ -42,6 +42,7 @@ func DefaultRules() automod.RuleSet { BadWordRecordKeyRule, BadWordOtherRecordRule, TooManyRepostRule, + OzoneRecordHistoryPersistRule, }, RecordDeleteRules: []automod.RecordRuleFunc{ DeleteInteractionRule, @@ -61,6 +62,7 @@ func DefaultRules() automod.RuleSet { }, OzoneEventRules: []automod.OzoneEventRuleFunc{ HarassmentProtectionOzoneEventRule, + CountModEventRule, }, } return rules diff --git a/automod/rules/mod_event.go b/automod/rules/mod_event.go new file mode 100644 index 000000000..88708e57d --- /dev/null +++ b/automod/rules/mod_event.go @@ -0,0 +1,21 @@ +package rules + +import ( + "github.com/bluesky-social/indigo/automod" + "github.com/labstack/gommon/log" +) + +var _ automod.OzoneEventRuleFunc = CountModEventRule + +// looks for appeals on records/accounts and flags subjects +func CountModEventRule(c *automod.OzoneEventContext) error { + counterKey := c.Event.SubjectDID.String() + if c.Event.SubjectURI != nil { + counterKey = c.Event.SubjectURI.String() + } + + c.Increment("mod-event", counterKey) + log.Print("mod-event", counterKey, c.GetCount("mod-event", counterKey, automod.PeriodTotal)) + + return nil +} diff --git a/automod/rules/ozone_persist.go b/automod/rules/ozone_persist.go new file mode 100644 index 000000000..afaabd0bd --- /dev/null +++ b/automod/rules/ozone_persist.go @@ -0,0 +1,25 @@ +package rules + +import ( + "github.com/bluesky-social/indigo/automod" +) + +var _ automod.RecordRuleFunc = OzoneRecordHistoryPersistRule + +func OzoneRecordHistoryPersistRule(c *automod.RecordContext) error { + switch c.RecordOp.Collection { + case "app.bsky.labeler.service": + c.PersistRecordOzoneEvent() + case "app.bsky.feed.post": + if c.RecordOp.Action == "delete" { + if c.GetCount("mod-event", c.RecordOp.ATURI().String(), automod.PeriodTotal) > 0 { + c.PersistRecordOzoneEvent() + } + } + case "app.bsky.actor.profile": + if c.GetCount("mod-event", c.RecordOp.ATURI().String(), automod.PeriodTotal) > 0 { + c.PersistRecordOzoneEvent() + } + } + return nil +} diff --git a/cmd/hepa/main.go b/cmd/hepa/main.go index 9e3029b6c..dfcb9cfc6 100644 --- a/cmd/hepa/main.go +++ b/cmd/hepa/main.go @@ -139,6 +139,11 @@ func run(args []string) error { Usage: "force a fixed number of parallel firehose workers. default (or 0) for auto-scaling; 200 works for a large instance", EnvVars: []string{"HEPA_FIREHOSE_PARALLELISM"}, }, + &cli.BoolFlag{ + Name: "reroute-events", + Usage: "Attempt to reroute firehose events to all configured destinations (for now, only Ozone).", + EnvVars: []string{"HEPA_REROUTE_EVENTS"}, + }, &cli.StringFlag{ Name: "prescreen-host", Usage: "hostname of prescreen server", @@ -277,6 +282,7 @@ var runCmd = &cli.Command{ RatelimitBypass: cctx.String("ratelimit-bypass"), RulesetName: cctx.String("ruleset"), FirehoseParallelism: cctx.Int("firehose-parallelism"), // DEPRECATED + RerouteEvents: cctx.Bool("reroute-events"), PreScreenHost: cctx.String("prescreen-host"), PreScreenToken: cctx.String("prescreen-token"), ReportDupePeriod: cctx.Duration("report-dupe-period"), @@ -376,6 +382,7 @@ func configEphemeralServer(cctx *cli.Context) (*Server, error) { RatelimitBypass: cctx.String("ratelimit-bypass"), RulesetName: cctx.String("ruleset"), FirehoseParallelism: cctx.Int("firehose-parallelism"), + RerouteEvents: cctx.Bool("reroute-events"), PreScreenHost: cctx.String("prescreen-host"), PreScreenToken: cctx.String("prescreen-token"), }, diff --git a/cmd/hepa/server.go b/cmd/hepa/server.go index a4ca252d1..13d2a3947 100644 --- a/cmd/hepa/server.go +++ b/cmd/hepa/server.go @@ -14,7 +14,6 @@ import ( "github.com/bluesky-social/indigo/automod" "github.com/bluesky-social/indigo/automod/cachestore" "github.com/bluesky-social/indigo/automod/countstore" - "github.com/bluesky-social/indigo/automod/engine" "github.com/bluesky-social/indigo/automod/flagstore" "github.com/bluesky-social/indigo/automod/rules" "github.com/bluesky-social/indigo/automod/setstore" @@ -53,6 +52,7 @@ type Config struct { RulesetName string RatelimitBypass string FirehoseParallelism int // DEPRECATED + RerouteEvents bool PreScreenHost string PreScreenToken string ReportDupePeriod time.Duration @@ -224,11 +224,12 @@ func NewServer(dir identity.Directory, config Config) (*Server, error) { OzoneClient: ozoneClient, AdminClient: adminClient, BlobClient: blobClient, - Config: engine.EngineConfig{ - ReportDupePeriod: config.ReportDupePeriod, - QuotaModReportDay: config.QuotaModReportDay, - QuotaModTakedownDay: config.QuotaModTakedownDay, - QuotaModActionDay: config.QuotaModActionDay, + Config: automod.EngineConfig{ + PersistSubjectHistoryOzone: config.RerouteEvents, + ReportDupePeriod: config.ReportDupePeriod, + QuotaModReportDay: config.QuotaModReportDay, + QuotaModTakedownDay: config.QuotaModTakedownDay, + QuotaModActionDay: config.QuotaModActionDay, }, }