profile.go raw

   1  package bridge
   2  
   3  import (
   4  	"encoding/json"
   5  	"fmt"
   6  	"os"
   7  	"strings"
   8  	"time"
   9  
  10  	"next.orly.dev/pkg/nostr/encoders/event"
  11  	"next.orly.dev/pkg/nostr/encoders/tag"
  12  	"next.orly.dev/pkg/lol/log"
  13  )
  14  
  15  // parseProfileTemplate reads a profile template file in email-header format
  16  // (key: value lines, one per line, blank line ends headers). Returns a map
  17  // of profile fields suitable for JSON marshaling as kind 0 content.
  18  func parseProfileTemplate(path string) (map[string]string, error) {
  19  	data, err := os.ReadFile(path)
  20  	if err != nil {
  21  		return nil, err
  22  	}
  23  
  24  	profile := make(map[string]string)
  25  	for _, line := range strings.Split(string(data), "\n") {
  26  		line = strings.TrimSpace(line)
  27  		if line == "" {
  28  			break // blank line ends headers
  29  		}
  30  		if line[0] == '#' {
  31  			continue // skip comments
  32  		}
  33  		i := strings.IndexByte(line, ':')
  34  		if i <= 0 {
  35  			continue
  36  		}
  37  		key := strings.TrimSpace(strings.ToLower(line[:i]))
  38  		val := strings.TrimSpace(line[i+1:])
  39  		if val != "" {
  40  			profile[key] = val
  41  		}
  42  	}
  43  	return profile, nil
  44  }
  45  
  46  // publishProfile reads the profile template and publishes a kind 0 metadata
  47  // event to the relay. Silently returns nil if the template file doesn't exist.
  48  func (b *Bridge) publishProfile() error {
  49  	path := b.cfg.ProfilePath
  50  	if path == "" {
  51  		return nil
  52  	}
  53  
  54  	profile, err := parseProfileTemplate(path)
  55  	if err != nil {
  56  		if os.IsNotExist(err) {
  57  			log.D.F("no profile template at %s, skipping kind 0 publish", path)
  58  			return nil
  59  		}
  60  		return fmt.Errorf("parse profile template %s: %w", path, err)
  61  	}
  62  	if len(profile) == 0 {
  63  		log.D.F("profile template %s is empty, skipping kind 0 publish", path)
  64  		return nil
  65  	}
  66  
  67  	content, err := json.Marshal(profile)
  68  	if err != nil {
  69  		return fmt.Errorf("marshal profile: %w", err)
  70  	}
  71  
  72  	ev := &event.E{
  73  		Content:   content,
  74  		CreatedAt: time.Now().Unix(),
  75  		Kind:      0,
  76  		Tags:      tag.NewS(),
  77  	}
  78  	if err := ev.Sign(b.sign); err != nil {
  79  		return fmt.Errorf("sign profile event: %w", err)
  80  	}
  81  	if err := b.relay.Publish(b.ctx, ev); err != nil {
  82  		return fmt.Errorf("publish profile event: %w", err)
  83  	}
  84  
  85  	log.D.F("published kind 0 profile (%d fields) for bridge identity", len(profile))
  86  	return nil
  87  }
  88