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

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
}