mute.mx raw

   1  // Package mute manages the relay's mute blacklist. The admin nominates one
   2  // pubkey whose kind-10000 mute list is treated as the authoritative blocklist.
   3  // Any event from a muted pubkey is rejected at dispatch time.
   4  package mute
   5  
   6  import (
   7  	"encoding/hex"
   8  	"fmt"
   9  
  10  	"smesh.lol/pkg/nostr/filter"
  11  	"smesh.lol/pkg/nostr/kind"
  12  	"smesh.lol/pkg/nostr/tag"
  13  	"smesh.lol/pkg/store"
  14  )
  15  
  16  // Blacklist holds the mute blacklist state for one configured admin pubkey.
  17  type Blacklist struct {
  18  	adminPK []byte
  19  	pubkeys map[string]bool
  20  }
  21  
  22  // New creates a Blacklist for the given admin pubkey (hex-encoded).
  23  // Returns nil if adminHexPK is empty or invalid.
  24  func New(adminHexPK string) *Blacklist {
  25  	if adminHexPK == "" {
  26  		return nil
  27  	}
  28  	pk, err := hex.DecodeString(adminHexPK)
  29  	if err != nil || len(pk) != 32 {
  30  		return nil
  31  	}
  32  	return &Blacklist{adminPK: pk, pubkeys: map[string]bool{}}
  33  }
  34  
  35  // Check returns true if pubkey (hex string) is on the mute list.
  36  func (b *Blacklist) Check(pubkeyHex string) bool {
  37  	return b.pubkeys[pubkeyHex]
  38  }
  39  
  40  // AdminPK returns the raw 32-byte admin pubkey.
  41  func (b *Blacklist) AdminPK() []byte { return b.adminPK }
  42  
  43  // Load reads the admin's latest kind-10000 event from the store and refreshes
  44  // the in-memory mute set.
  45  func (b *Blacklist) Load(eng *store.Engine) {
  46  	one := uint(1)
  47  	filt := &filter.F{
  48  		Authors: tag.NewFromBytesSlice(b.adminPK),
  49  		Kinds:   kind.NewS(kind.MuteList),
  50  		Limit:   &one,
  51  	}
  52  	events, err := eng.QueryEvents(filt)
  53  	if err != nil || len(events) == 0 {
  54  		return
  55  	}
  56  	m := map[string]bool{}
  57  	ev := events[0]
  58  	if ev.Tags != nil {
  59  		for _, t := range ev.Tags.GetAll([]byte("p")) {
  60  			if t.Len() < 2 {
  61  				continue
  62  			}
  63  			v := t.Value()
  64  			if len(v) == 32 {
  65  				m[hex.EncodeToString(v)] = true
  66  			} else if len(v) == 64 {
  67  				m[string(v)] = true
  68  			}
  69  		}
  70  	}
  71  	b.pubkeys = m
  72  	fmt.Printf("mute blacklist: loaded %d pubkeys from %s\n", len(m), hex.EncodeToString(b.adminPK))
  73  }
  74  
  75  // Purge deletes all events in the store authored by muted pubkeys.
  76  func (b *Blacklist) Purge(eng *store.Engine) {
  77  	total := 0
  78  	for pkHex := range b.pubkeys {
  79  		pk, _ := hex.DecodeString(pkHex)
  80  		if len(pk) != 32 {
  81  			continue
  82  		}
  83  		filt := &filter.F{Authors: tag.NewFromBytesSlice(pk)}
  84  		events, err := eng.QueryEvents(filt)
  85  		if err != nil {
  86  			continue
  87  		}
  88  		for _, ev := range events {
  89  			eng.DeleteEvent(ev.ID)
  90  			total++
  91  		}
  92  	}
  93  	if total > 0 {
  94  		fmt.Printf("mute blacklist: purged %d events from blacklisted authors\n", total)
  95  	}
  96  }
  97