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.
243 lines
6.2 KiB
243 lines
6.2 KiB
package directory |
|
|
|
import ( |
|
"encoding/json" |
|
|
|
"lol.mleku.dev/chk" |
|
"lol.mleku.dev/errorf" |
|
"git.mleku.dev/mleku/nostr/encoders/event" |
|
"git.mleku.dev/mleku/nostr/encoders/tag" |
|
) |
|
|
|
// RelayIdentityContent represents the JSON content of a Relay Identity |
|
// Announcement event (Kind 39100). |
|
type RelayIdentityContent struct { |
|
Name string `json:"name"` |
|
Description string `json:"description,omitempty"` |
|
Contact string `json:"contact,omitempty"` |
|
} |
|
|
|
// RelayIdentityAnnouncement represents a complete Relay Identity Announcement |
|
// event with typed access to its components. |
|
type RelayIdentityAnnouncement struct { |
|
Event *event.E |
|
Content *RelayIdentityContent |
|
RelayURL string |
|
SigningKey string |
|
EncryptionKey string |
|
Version string |
|
} |
|
|
|
// NewRelayIdentityAnnouncement creates a new Relay Identity Announcement event. |
|
func NewRelayIdentityAnnouncement( |
|
pubkey []byte, |
|
name, description, contact string, |
|
relayURL, signingKey, encryptionKey, version string, |
|
) (ria *RelayIdentityAnnouncement, err error) { |
|
|
|
// Validate required fields |
|
if len(pubkey) != 32 { |
|
return nil, errorf.E("pubkey must be 32 bytes") |
|
} |
|
if name == "" { |
|
return nil, errorf.E("name is required") |
|
} |
|
if relayURL == "" { |
|
return nil, errorf.E("relay URL is required") |
|
} |
|
if signingKey == "" { |
|
return nil, errorf.E("signing key is required") |
|
} |
|
if encryptionKey == "" { |
|
return nil, errorf.E("encryption key is required") |
|
} |
|
if version == "" { |
|
version = "1" // Default version |
|
} |
|
|
|
// Create content |
|
content := &RelayIdentityContent{ |
|
Name: name, |
|
Description: description, |
|
Contact: contact, |
|
} |
|
|
|
// Marshal content to JSON |
|
var contentBytes []byte |
|
if contentBytes, err = json.Marshal(content); chk.E(err) { |
|
return |
|
} |
|
|
|
// Create base event |
|
ev := CreateBaseEvent(pubkey, RelayIdentityAnnouncementKind) |
|
ev.Content = contentBytes |
|
|
|
// Add required tags |
|
ev.Tags.Append(tag.NewFromAny(string(DTag), "relay-identity")) |
|
ev.Tags.Append(tag.NewFromAny(string(RelayTag), relayURL)) |
|
ev.Tags.Append(tag.NewFromAny(string(SigningKeyTag), signingKey)) |
|
ev.Tags.Append(tag.NewFromAny(string(EncryptionKeyTag), encryptionKey)) |
|
ev.Tags.Append(tag.NewFromAny(string(VersionTag), version)) |
|
|
|
ria = &RelayIdentityAnnouncement{ |
|
Event: ev, |
|
Content: content, |
|
RelayURL: relayURL, |
|
SigningKey: signingKey, |
|
EncryptionKey: encryptionKey, |
|
Version: version, |
|
} |
|
|
|
return |
|
} |
|
|
|
// ParseRelayIdentityAnnouncement parses an event into a RelayIdentityAnnouncement |
|
// structure with validation. |
|
func ParseRelayIdentityAnnouncement(ev *event.E) (ria *RelayIdentityAnnouncement, err error) { |
|
if ev == nil { |
|
return nil, errorf.E("event cannot be nil") |
|
} |
|
|
|
// Validate event kind |
|
if ev.Kind != RelayIdentityAnnouncementKind.K { |
|
return nil, errorf.E("invalid event kind: expected %d, got %d", |
|
RelayIdentityAnnouncementKind.K, ev.Kind) |
|
} |
|
|
|
// Parse content |
|
var content RelayIdentityContent |
|
if len(ev.Content) > 0 { |
|
if err = json.Unmarshal(ev.Content, &content); chk.E(err) { |
|
return nil, errorf.E("failed to parse content: %w", err) |
|
} |
|
} |
|
|
|
// Extract required tags |
|
dTag := ev.Tags.GetFirst(DTag) |
|
if dTag == nil || string(dTag.Value()) != "relay-identity" { |
|
return nil, errorf.E("missing or invalid d tag") |
|
} |
|
|
|
relayTag := ev.Tags.GetFirst(RelayTag) |
|
if relayTag == nil { |
|
return nil, errorf.E("missing relay tag") |
|
} |
|
|
|
signingKeyTag := ev.Tags.GetFirst(SigningKeyTag) |
|
if signingKeyTag == nil { |
|
return nil, errorf.E("missing signing_key tag") |
|
} |
|
|
|
encryptionKeyTag := ev.Tags.GetFirst(EncryptionKeyTag) |
|
if encryptionKeyTag == nil { |
|
return nil, errorf.E("missing encryption_key tag") |
|
} |
|
|
|
versionTag := ev.Tags.GetFirst(VersionTag) |
|
if versionTag == nil { |
|
return nil, errorf.E("missing version tag") |
|
} |
|
|
|
ria = &RelayIdentityAnnouncement{ |
|
Event: ev, |
|
Content: &content, |
|
RelayURL: string(relayTag.Value()), |
|
SigningKey: string(signingKeyTag.Value()), |
|
EncryptionKey: string(encryptionKeyTag.Value()), |
|
Version: string(versionTag.Value()), |
|
} |
|
|
|
return |
|
} |
|
|
|
// Validate performs comprehensive validation of a RelayIdentityAnnouncement. |
|
func (ria *RelayIdentityAnnouncement) Validate() (err error) { |
|
if ria == nil { |
|
return errorf.E("RelayIdentityAnnouncement cannot be nil") |
|
} |
|
|
|
if ria.Event == nil { |
|
return errorf.E("event cannot be nil") |
|
} |
|
|
|
// Validate event signature |
|
if _, err = ria.Event.Verify(); chk.E(err) { |
|
return errorf.E("invalid event signature: %w", err) |
|
} |
|
|
|
// Validate required fields |
|
if ria.Content.Name == "" { |
|
return errorf.E("name is required") |
|
} |
|
|
|
if ria.RelayURL == "" { |
|
return errorf.E("relay URL is required") |
|
} |
|
|
|
if ria.SigningKey == "" { |
|
return errorf.E("signing key is required") |
|
} |
|
|
|
if ria.EncryptionKey == "" { |
|
return errorf.E("encryption key is required") |
|
} |
|
|
|
if ria.Version == "" { |
|
return errorf.E("version is required") |
|
} |
|
|
|
// Validate hex-encoded keys (should be 64 characters for 32-byte keys) |
|
if len(ria.SigningKey) != 64 { |
|
return errorf.E("signing key must be 64 hex characters") |
|
} |
|
|
|
if len(ria.EncryptionKey) != 64 { |
|
return errorf.E("encryption key must be 64 hex characters") |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// GetRelayURL returns the relay WebSocket URL. |
|
func (ria *RelayIdentityAnnouncement) GetRelayURL() string { |
|
return ria.RelayURL |
|
} |
|
|
|
// GetSigningKey returns the hex-encoded signing public key. |
|
func (ria *RelayIdentityAnnouncement) GetSigningKey() string { |
|
return ria.SigningKey |
|
} |
|
|
|
// GetEncryptionKey returns the hex-encoded encryption public key. |
|
func (ria *RelayIdentityAnnouncement) GetEncryptionKey() string { |
|
return ria.EncryptionKey |
|
} |
|
|
|
// GetVersion returns the protocol version. |
|
func (ria *RelayIdentityAnnouncement) GetVersion() string { |
|
return ria.Version |
|
} |
|
|
|
// GetName returns the relay name from the content. |
|
func (ria *RelayIdentityAnnouncement) GetName() string { |
|
if ria.Content == nil { |
|
return "" |
|
} |
|
return ria.Content.Name |
|
} |
|
|
|
// GetDescription returns the relay description from the content. |
|
func (ria *RelayIdentityAnnouncement) GetDescription() string { |
|
if ria.Content == nil { |
|
return "" |
|
} |
|
return ria.Content.Description |
|
} |
|
|
|
// GetContact returns the relay contact information from the content. |
|
func (ria *RelayIdentityAnnouncement) GetContact() string { |
|
if ria.Content == nil { |
|
return "" |
|
} |
|
return ria.Content.Contact |
|
}
|
|
|