diff --git a/README.md b/README.md index 93cc19f..c2abc44 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ The following [NIPs](https://github.com/nostr-protocol/nips) are implemented: - [ ] [NIP-28: Public Chat](https://github.com/nostr-protocol/nips/blob/master/28.md) - [ ] [NIP-29: Relay-based Groups](https://github.com/nostr-protocol/nips/blob/master/29.md) - [x] [NIP-30: Custom Emoji](https://github.com/nostr-protocol/nips/blob/master/30.md) -- [ ] [NIP-31: Dealing with Unknown Events](https://github.com/nostr-protocol/nips/blob/master/31.md) +- [x] [NIP-31: Dealing with Unknown Events](https://github.com/nostr-protocol/nips/blob/master/31.md) - [ ] [NIP-32: Labeling](https://github.com/nostr-protocol/nips/blob/master/32.md) - [ ] [NIP-34: `git` stuff](https://github.com/nostr-protocol/nips/blob/master/34.md) - [ ] [NIP-35: Torrents](https://github.com/nostr-protocol/nips/blob/master/35.md) diff --git a/Sources/NostrSDK/Events/NostrEvent.swift b/Sources/NostrSDK/Events/NostrEvent.swift index d1edbd3..6c6341a 100644 --- a/Sources/NostrSDK/Events/NostrEvent.swift +++ b/Sources/NostrSDK/Events/NostrEvent.swift @@ -150,6 +150,13 @@ public class NostrEvent: Codable, Equatable, Hashable { tags.compactMap { EventCoordinates(eventCoordinatesTag: $0) } } + /// A short human-readable plaintext summary of what the event is about + /// when the event kind is part of a custom protocol and isn't meant to be read as text (like kind:1). + /// See [NIP-31 - Dealing with unknown event kinds](https://github.com/nostr-protocol/nips/blob/master/31.md). + public var alternativeSummary: String? { + firstValueForTagName(.alternativeSummary) + } + /// Unix timestamp at which the message SHOULD be considered expired (by relays and clients) and SHOULD be deleted by relays. /// See [NIP-40 - Expiration Timestamp](https://github.com/nostr-protocol/nips/blob/master/40.md). public var expiration: Int64? { @@ -375,6 +382,15 @@ public extension NostrEvent { return self } + /// Specifies a short human-readable plaintext summary of what the event is about + /// when the event kind is part of a custom protocol and isn't meant to be read as text (like kind:1). + /// See [NIP-31 - Dealing with unknown event kinds](https://github.com/nostr-protocol/nips/blob/master/31.md). + @discardableResult + public final func alternativeSummary(_ alternativeSummary: String) -> Self { + tags.append(Tag(name: .alternativeSummary, value: alternativeSummary)) + return self + } + /// Specifies a unix timestamp at which the message SHOULD be considered expired (by relays and clients) and SHOULD be deleted by relays. /// See [NIP-40 - Expiration Timestamp](https://github.com/nostr-protocol/nips/blob/master/40.md). @discardableResult diff --git a/Sources/NostrSDK/Tag.swift b/Sources/NostrSDK/Tag.swift index 3af19c5..6c436f1 100644 --- a/Sources/NostrSDK/Tag.swift +++ b/Sources/NostrSDK/Tag.swift @@ -9,7 +9,12 @@ import Foundation /// A constant that describes the type of a ``Tag``. public enum TagName: String { - + + /// A short human-readable plaintext summary of what the event is about + /// when the event kind is part of a custom protocol and isn't meant to be read as text (like kind:1). + /// See [NIP-31 - Dealing with unknown event kinds](https://github.com/nostr-protocol/nips/blob/master/31.md). + case alternativeSummary = "alt" + /// a custom emoji that defines the shortcode name and image URL of the image file case emoji diff --git a/Tests/NostrSDKTests/Events/NostrEventTests.swift b/Tests/NostrSDKTests/Events/NostrEventTests.swift index 1527d8f..c6f1509 100644 --- a/Tests/NostrSDKTests/Events/NostrEventTests.swift +++ b/Tests/NostrSDKTests/Events/NostrEventTests.swift @@ -68,6 +68,17 @@ final class NostrEventTests: XCTestCase, FixtureLoading, MetadataCoding { XCTAssertEqual(metadata.relays?[1], relay2) } + func testAlternativeSummary() throws { + let alternativeSummary = "Alternative summary to display for clients that do not support this event kind." + let customEvent = try NostrEvent.Builder(kind: EventKind(rawValue: 23456)) + .alternativeSummary(alternativeSummary) + .build(signedBy: .test) + XCTAssertEqual(customEvent.alternativeSummary, alternativeSummary) + + let decodedCustomEventWithAltTag: NostrEvent = try decodeFixture(filename: "custom_event_alt_tag") + XCTAssertEqual(decodedCustomEventWithAltTag.alternativeSummary, alternativeSummary) + } + func testExpiration() throws { let futureExpiration = Int64(Date.now.timeIntervalSince1970 + 10000) let futureExpirationEvent = try NostrEvent.Builder(kind: .textNote) diff --git a/Tests/NostrSDKTests/Fixtures/custom_event_alt_tag.json b/Tests/NostrSDKTests/Fixtures/custom_event_alt_tag.json new file mode 100644 index 0000000..5a5361b --- /dev/null +++ b/Tests/NostrSDKTests/Fixtures/custom_event_alt_tag.json @@ -0,0 +1,9 @@ +{ + "content": "", + "created_at": 1687090842, + "id": "b7fbbb4d473db3b3a671ad995f1248d56e9a2ed7f145c1e5f74308e60db2eba7", + "kind": 23456, + "pubkey": "9947f9659dd80c3682402b612f5447e28249997fb3709500c32a585eb0977340", + "sig": "09a1dcace3cec8cd9934b544cede6955c9876e385d73491f1ca5354a0baecda834c43ac9510ef779f8c8c7543b3faccd821e40e0a52dde8527d071cb0d53230c", + "tags": [["alt","Alternative summary to display for clients that do not support this event kind."]] +}