// Package mute manages the relay's mute blacklist. The admin nominates one // pubkey whose kind-10000 mute list is treated as the authoritative blocklist. // Any event from a muted pubkey is rejected at dispatch time. package mute import ( "encoding/hex" "fmt" "smesh.lol/pkg/nostr/filter" "smesh.lol/pkg/nostr/kind" "smesh.lol/pkg/nostr/tag" "smesh.lol/pkg/store" ) // Blacklist holds the mute blacklist state for one configured admin pubkey. type Blacklist struct { adminPK []byte pubkeys map[string]bool } // New creates a Blacklist for the given admin pubkey (hex-encoded). // Returns nil if adminHexPK is empty or invalid. func New(adminHexPK string) *Blacklist { if adminHexPK == "" { return nil } pk, err := hex.DecodeString(adminHexPK) if err != nil || len(pk) != 32 { return nil } return &Blacklist{adminPK: pk, pubkeys: map[string]bool{}} } // Check returns true if pubkey (hex string) is on the mute list. func (b *Blacklist) Check(pubkeyHex string) bool { return b.pubkeys[pubkeyHex] } // AdminPK returns the raw 32-byte admin pubkey. func (b *Blacklist) AdminPK() []byte { return b.adminPK } // Load reads the admin's latest kind-10000 event from the store and refreshes // the in-memory mute set. func (b *Blacklist) Load(eng *store.Engine) { one := uint(1) filt := &filter.F{ Authors: tag.NewFromBytesSlice(b.adminPK), Kinds: kind.NewS(kind.MuteList), Limit: &one, } events, err := eng.QueryEvents(filt) if err != nil || len(events) == 0 { return } m := map[string]bool{} ev := events[0] if ev.Tags != nil { for _, t := range ev.Tags.GetAll([]byte("p")) { if t.Len() < 2 { continue } v := t.Value() if len(v) == 32 { m[hex.EncodeToString(v)] = true } else if len(v) == 64 { m[string(v)] = true } } } b.pubkeys = m fmt.Printf("mute blacklist: loaded %d pubkeys from %s\n", len(m), hex.EncodeToString(b.adminPK)) } // Purge deletes all events in the store authored by muted pubkeys. func (b *Blacklist) Purge(eng *store.Engine) { total := 0 for pkHex := range b.pubkeys { pk, _ := hex.DecodeString(pkHex) if len(pk) != 32 { continue } filt := &filter.F{Authors: tag.NewFromBytesSlice(pk)} events, err := eng.QueryEvents(filt) if err != nil { continue } for _, ev := range events { eng.DeleteEvent(ev.ID) total++ } } if total > 0 { fmt.Printf("mute blacklist: purged %d events from blacklisted authors\n", total) } }