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