Files
nostrlib/nip29/moderation_actions.go

281 lines
6.6 KiB
Go

package nip29
import (
"fmt"
"slices"
"fiatjaf.com/nostr"
)
var PTagNotValidPublicKey = fmt.Errorf("'p' tag value is not a valid public key")
type Action interface {
Apply(group *Group)
Name() string
}
var (
_ Action = PutUser{}
_ Action = RemoveUser{}
_ Action = CreateGroup{}
_ Action = DeleteEvent{}
_ Action = EditMetadata{}
_ Action = CreateInvite{}
)
func PrepareModerationAction(evt nostr.Event) (Action, error) {
factory, ok := moderationActionFactories[evt.Kind]
if !ok {
return nil, fmt.Errorf("event kind %d is not a supported moderation action", evt.Kind)
}
return factory(evt)
}
var moderationActionFactories = map[nostr.Kind]func(nostr.Event) (Action, error){
nostr.KindSimpleGroupPutUser: func(evt nostr.Event) (Action, error) {
targets := make([]PubKeyRoles, 0, len(evt.Tags))
for tag := range evt.Tags.FindAll("p") {
target, err := nostr.PubKeyFromHex(tag[1])
if err != nil {
return nil, PTagNotValidPublicKey
}
targets = append(targets, PubKeyRoles{
PubKey: target,
RoleNames: tag[2:],
})
}
var inviteCode string
if ctag := evt.Tags.Find("code"); ctag != nil {
inviteCode = ctag[1]
}
if len(targets) > 0 {
return PutUser{
Targets: targets,
InviteCode: inviteCode,
When: evt.CreatedAt,
}, nil
}
return nil, fmt.Errorf("missing 'p' tags")
},
nostr.KindSimpleGroupRemoveUser: func(evt nostr.Event) (Action, error) {
targets := make([]nostr.PubKey, 0, len(evt.Tags))
for tag := range evt.Tags.FindAll("p") {
target, err := nostr.PubKeyFromHex(tag[1])
if err != nil {
return nil, PTagNotValidPublicKey
}
targets = append(targets, target)
}
if len(targets) > 0 {
return RemoveUser{Targets: targets, When: evt.CreatedAt}, nil
}
return nil, fmt.Errorf("missing 'p' tags")
},
nostr.KindSimpleGroupEditMetadata: func(evt nostr.Event) (Action, error) {
ok := false
edit := EditMetadata{When: evt.CreatedAt}
if t := evt.Tags.Find("name"); t != nil {
edit.NameValue = &t[1]
ok = true
}
if t := evt.Tags.Find("picture"); t != nil {
edit.PictureValue = &t[1]
ok = true
}
if t := evt.Tags.Find("about"); t != nil {
edit.AboutValue = &t[1]
ok = true
}
y := true
if evt.Tags.Has("private") {
edit.PrivateValue = &y
ok = true
}
if evt.Tags.Has("closed") {
edit.ClosedValue = &y
ok = true
}
if evt.Tags.Has("restricted") {
edit.RestrictedValue = &y
ok = true
}
if evt.Tags.Has("hidden") {
edit.HiddenValue = &y
ok = true
}
if ok {
return edit, nil
}
return nil, fmt.Errorf("missing metadata tags")
},
nostr.KindSimpleGroupDeleteEvent: func(evt nostr.Event) (Action, error) {
missing := true
targets := make([]nostr.ID, 0, 2)
for tag := range evt.Tags.FindAll("e") {
id, err := nostr.IDFromHex(tag[1])
if err != nil {
return nil, fmt.Errorf("invalid event id hex")
}
targets = append(targets, id)
missing = false
}
if missing {
return nil, fmt.Errorf("missing 'e' tag")
}
return DeleteEvent{Targets: targets}, nil
},
nostr.KindSimpleGroupCreateGroup: func(evt nostr.Event) (Action, error) {
return CreateGroup{Creator: evt.PubKey, When: evt.CreatedAt}, nil
},
nostr.KindSimpleGroupDeleteGroup: func(evt nostr.Event) (Action, error) {
return DeleteGroup{When: evt.CreatedAt}, nil
},
nostr.KindSimpleGroupCreateInvite: func(evt nostr.Event) (Action, error) {
codes := make([]string, 0)
for tag := range evt.Tags.FindAll("code") {
codes = append(codes, tag[1])
}
if len(codes) == 0 {
return nil, fmt.Errorf("missing 'code' tags")
} else if len(codes) > 10 {
return nil, fmt.Errorf("too many 'code' tags")
}
return CreateInvite{Codes: codes}, nil
},
}
type DeleteEvent struct {
Targets []nostr.ID
}
func (_ DeleteEvent) Name() string { return "delete-event" }
func (a DeleteEvent) Apply(group *Group) {}
type PubKeyRoles struct {
PubKey nostr.PubKey
RoleNames []string
}
type PutUser struct {
Targets []PubKeyRoles
InviteCode string
When nostr.Timestamp
}
func (_ PutUser) Name() string { return "put-user" }
func (a PutUser) Apply(group *Group) {
for _, target := range a.Targets {
roles := make([]*Role, 0, len(target.RoleNames))
for _, roleName := range target.RoleNames {
if slices.IndexFunc(roles, func(r *Role) bool { return r.Name == roleName }) != -1 {
continue
}
roles = append(roles, group.GetRoleByName(roleName))
}
group.Members[target.PubKey] = roles
if a.InviteCode != "" {
if idx := slices.Index(group.InviteCodes, a.InviteCode); idx != -1 {
group.InviteCodes[idx] = group.InviteCodes[len(group.InviteCodes)-1]
group.InviteCodes = group.InviteCodes[0 : len(group.InviteCodes)-1]
}
}
}
}
type RemoveUser struct {
Targets []nostr.PubKey
When nostr.Timestamp
}
func (_ RemoveUser) Name() string { return "remove-user" }
func (a RemoveUser) Apply(group *Group) {
for _, tpk := range a.Targets {
delete(group.Members, tpk)
}
}
type EditMetadata struct {
NameValue *string
PictureValue *string
AboutValue *string
PrivateValue *bool
RestrictedValue *bool
HiddenValue *bool
ClosedValue *bool
When nostr.Timestamp
}
func (_ EditMetadata) Name() string { return "edit-metadata" }
func (a EditMetadata) Apply(group *Group) {
group.LastMetadataUpdate = a.When
if a.NameValue != nil {
group.Name = *a.NameValue
}
if a.PictureValue != nil {
group.Picture = *a.PictureValue
}
if a.AboutValue != nil {
group.About = *a.AboutValue
}
if a.PrivateValue != nil {
group.Private = *a.PrivateValue
}
if a.RestrictedValue != nil {
group.Restricted = *a.RestrictedValue
}
if a.HiddenValue != nil {
group.Hidden = *a.HiddenValue
}
if a.ClosedValue != nil {
group.Closed = *a.ClosedValue
}
}
type CreateGroup struct {
Creator nostr.PubKey
When nostr.Timestamp
}
func (_ CreateGroup) Name() string { return "create-group" }
func (a CreateGroup) Apply(group *Group) {
group.LastMetadataUpdate = a.When
group.LastAdminsUpdate = a.When
group.LastMembersUpdate = a.When
}
type DeleteGroup struct {
When nostr.Timestamp
}
func (_ DeleteGroup) Name() string { return "delete-group" }
func (a DeleteGroup) Apply(group *Group) {
group.Members = make(map[nostr.PubKey][]*Role)
group.Closed = true
group.Private = true
group.Restricted = true
group.Hidden = true
group.Name = "[deleted]"
group.About = ""
group.Picture = ""
group.LastMetadataUpdate = a.When
group.LastAdminsUpdate = a.When
group.LastMembersUpdate = a.When
}
type CreateInvite struct {
Codes []string
}
func (_ CreateInvite) Name() string { return "create-invite" }
func (a CreateInvite) Apply(group *Group) {
group.InviteCodes = append(group.InviteCodes, a.Codes...)
}