You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
328 lines
7.0 KiB
328 lines
7.0 KiB
package nostr |
|
|
|
import ( |
|
"fmt" |
|
"strings" |
|
|
|
"github.com/nbd-wtf/go-nostr" |
|
) |
|
|
|
// IndexEvent represents a kind 30040 publication index event (NKBIP-01) |
|
type IndexEvent struct { |
|
Event *nostr.Event |
|
Title string |
|
DTag string |
|
Author string |
|
Items []IndexItem |
|
AutoUpdate string |
|
Type string |
|
Version string |
|
Summary string |
|
Image string |
|
} |
|
|
|
// IndexItem represents an item in a publication index (from 'a' tags) |
|
type IndexItem struct { |
|
Kind int |
|
Pubkey string |
|
DTag string |
|
RelayHint string |
|
EventID string // Optional event ID for version tracking |
|
} |
|
|
|
// ParseIndexEvent parses an index event according to NKBIP-01 |
|
func ParseIndexEvent(event *nostr.Event, expectedKind int) (*IndexEvent, error) { |
|
if event.Kind != expectedKind { |
|
return nil, fmt.Errorf("expected kind %d, got %d", expectedKind, event.Kind) |
|
} |
|
|
|
index := &IndexEvent{ |
|
Event: event, |
|
Author: event.PubKey, |
|
} |
|
|
|
// Extract d tag |
|
var dTag string |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 { |
|
dTag = tag[1] |
|
break |
|
} |
|
} |
|
index.DTag = dTag |
|
|
|
// Extract title |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "title" && len(tag) > 1 { |
|
index.Title = tag[1] |
|
break |
|
} |
|
} |
|
|
|
// Extract 'a' tags (index items) |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "a" && len(tag) > 1 { |
|
// Format: ["a", "<kind:pubkey:dtag>", "<relay hint>", "<event id>"] |
|
ref := tag[1] |
|
parts := strings.Split(ref, ":") |
|
if len(parts) >= 3 { |
|
var kind int |
|
fmt.Sscanf(parts[0], "%d", &kind) |
|
item := IndexItem{ |
|
Kind: kind, |
|
Pubkey: parts[1], |
|
DTag: parts[2], |
|
RelayHint: "", |
|
EventID: "", |
|
} |
|
if len(tag) > 2 { |
|
item.RelayHint = tag[2] |
|
} |
|
if len(tag) > 3 { |
|
item.EventID = tag[3] |
|
} |
|
index.Items = append(index.Items, item) |
|
} |
|
} |
|
} |
|
|
|
// Extract auto-update tag |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "auto-update" && len(tag) > 1 { |
|
index.AutoUpdate = tag[1] |
|
break |
|
} |
|
} |
|
|
|
// Extract type tag |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "type" && len(tag) > 1 { |
|
index.Type = tag[1] |
|
break |
|
} |
|
} |
|
|
|
// Extract version tag |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "version" && len(tag) > 1 { |
|
index.Version = tag[1] |
|
break |
|
} |
|
} |
|
|
|
// Extract summary tag |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "summary" && len(tag) > 1 { |
|
index.Summary = tag[1] |
|
break |
|
} |
|
} |
|
|
|
// Extract image tag |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "image" && len(tag) > 1 { |
|
index.Image = tag[1] |
|
break |
|
} |
|
} |
|
|
|
return index, nil |
|
} |
|
|
|
// WikiEvent represents a kind 30818 wiki event (NIP-54) |
|
type WikiEvent struct { |
|
Event *nostr.Event |
|
DTag string |
|
Title string |
|
Summary string |
|
Content string |
|
} |
|
|
|
// ParseWikiEvent parses a wiki event according to NIP-54 |
|
func ParseWikiEvent(event *nostr.Event, expectedKind int) (*WikiEvent, error) { |
|
if event.Kind != expectedKind { |
|
return nil, fmt.Errorf("expected kind %d, got %d", expectedKind, event.Kind) |
|
} |
|
|
|
wiki := &WikiEvent{ |
|
Event: event, |
|
Content: event.Content, |
|
} |
|
|
|
// Extract d tag (normalized identifier) |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 { |
|
wiki.DTag = tag[1] |
|
break |
|
} |
|
} |
|
|
|
// Extract title tag (optional, falls back to d tag) |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "title" && len(tag) > 1 { |
|
wiki.Title = tag[1] |
|
break |
|
} |
|
} |
|
if wiki.Title == "" { |
|
wiki.Title = wiki.DTag |
|
} |
|
|
|
// Extract summary tag (optional) |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "summary" && len(tag) > 1 { |
|
wiki.Summary = tag[1] |
|
break |
|
} |
|
} |
|
|
|
return wiki, nil |
|
} |
|
|
|
// BlogEvent represents a kind 30041 blog event |
|
type BlogEvent struct { |
|
Event *nostr.Event |
|
DTag string |
|
Title string |
|
Summary string |
|
Content string |
|
Image string |
|
} |
|
|
|
// ParseBlogEvent parses a blog event |
|
func ParseBlogEvent(event *nostr.Event, expectedKind int) (*BlogEvent, error) { |
|
if event.Kind != expectedKind { |
|
return nil, fmt.Errorf("expected kind %d, got %d", expectedKind, event.Kind) |
|
} |
|
|
|
blog := &BlogEvent{ |
|
Event: event, |
|
Content: event.Content, |
|
} |
|
|
|
// Extract d tag (normalized identifier) |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 { |
|
blog.DTag = tag[1] |
|
break |
|
} |
|
} |
|
|
|
// Extract title tag (optional, falls back to d tag) |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "title" && len(tag) > 1 { |
|
blog.Title = tag[1] |
|
break |
|
} |
|
} |
|
if blog.Title == "" { |
|
blog.Title = blog.DTag |
|
} |
|
|
|
// Extract summary tag (optional) |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "summary" && len(tag) > 1 { |
|
blog.Summary = tag[1] |
|
break |
|
} |
|
} |
|
|
|
// Extract image tag (optional) |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "image" && len(tag) > 1 { |
|
blog.Image = tag[1] |
|
break |
|
} |
|
} |
|
|
|
return blog, nil |
|
} |
|
|
|
// LongformEvent represents a kind 30023 longform article event |
|
type LongformEvent struct { |
|
Event *nostr.Event |
|
DTag string |
|
Title string |
|
Summary string |
|
Content string |
|
Image string |
|
} |
|
|
|
// ParseLongformEvent parses a longform article event |
|
func ParseLongformEvent(event *nostr.Event, expectedKind int) (*LongformEvent, error) { |
|
if event.Kind != expectedKind { |
|
return nil, fmt.Errorf("expected kind %d, got %d", expectedKind, event.Kind) |
|
} |
|
|
|
longform := &LongformEvent{ |
|
Event: event, |
|
Content: event.Content, |
|
} |
|
|
|
// Extract d tag (normalized identifier) |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "d" && len(tag) > 1 { |
|
longform.DTag = tag[1] |
|
break |
|
} |
|
} |
|
|
|
// Extract title tag (optional, falls back to d tag) |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "title" && len(tag) > 1 { |
|
longform.Title = tag[1] |
|
break |
|
} |
|
} |
|
if longform.Title == "" { |
|
longform.Title = longform.DTag |
|
} |
|
|
|
// Extract summary tag (optional) |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "summary" && len(tag) > 1 { |
|
longform.Summary = tag[1] |
|
break |
|
} |
|
} |
|
|
|
// Extract image tag (optional) |
|
for _, tag := range event.Tags { |
|
if len(tag) > 0 && tag[0] == "image" && len(tag) > 1 { |
|
longform.Image = tag[1] |
|
break |
|
} |
|
} |
|
|
|
return longform, nil |
|
} |
|
|
|
// NormalizeDTag normalizes a d tag according to NIP-54 rules |
|
func NormalizeDTag(dTag string) string { |
|
// Convert to lowercase |
|
dTag = strings.ToLower(dTag) |
|
|
|
// Convert whitespace to hyphens |
|
dTag = strings.ReplaceAll(dTag, " ", "-") |
|
dTag = strings.ReplaceAll(dTag, "\t", "-") |
|
dTag = strings.ReplaceAll(dTag, "\n", "-") |
|
|
|
// Remove punctuation and symbols (keep alphanumeric, hyphens, and non-ASCII) |
|
var result strings.Builder |
|
for _, r := range dTag { |
|
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' || r > 127 { |
|
result.WriteRune(r) |
|
} |
|
} |
|
dTag = result.String() |
|
|
|
// Collapse multiple consecutive hyphens |
|
for strings.Contains(dTag, "--") { |
|
dTag = strings.ReplaceAll(dTag, "--", "-") |
|
} |
|
|
|
// Remove leading and trailing hyphens |
|
dTag = strings.Trim(dTag, "-") |
|
|
|
return dTag |
|
}
|
|
|