Skip to content

Commit

Permalink
fix tags (nostr-sdk#58)
Browse files Browse the repository at this point in the history
  • Loading branch information
bryanmontz authored and RandyMcMillan committed Sep 1, 2024
1 parent 93b672e commit ca4dcd6
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 132 deletions.
8 changes: 4 additions & 4 deletions Sources/NostrSDK/Events/TextNoteEvent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ public final class TextNoteEvent: NostrEvent {

/// Pubkeys mentioned in the note content.
public var mentionedPubkeys: [String] {
let pubkeyTags = tags.filter { $0.identifier == .pubkey }
return pubkeyTags.map { $0.contentIdentifier }
let pubkeyTags = tags.filter { $0.name == .pubkey }
return pubkeyTags.map { $0.value }
}

/// Events mentioned in the note content.
public var mentionedEventIds: [String] {
let eventTags = tags.filter { $0.identifier == .event }
return eventTags.map { $0.contentIdentifier }
let eventTags = tags.filter { $0.name == .event }
return eventTags.map { $0.value }
}
}
167 changes: 47 additions & 120 deletions Sources/NostrSDK/Tag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
import Foundation

/// A constant that describes the type of a ``Tag``.
public enum TagIdentifier: Codable, Equatable {
public enum TagName: Codable, Equatable {

/// points to the id of an event this event is quoting, replying to or referring to somehow
case event

/// points to a pubkey of someone that is referred to in the event
case pubkey

/// a stringified kind number
case kind

/// a tag of unknown type
case unknown(String)

Expand All @@ -25,14 +28,16 @@ public enum TagIdentifier: Codable, Equatable {
return "e"
case .pubkey:
return "p"
case .kind:
return "k"
case .unknown(let id):
return id
}
}

public static func == (lhs: TagIdentifier, rhs: TagIdentifier) -> Bool {
public static func == (lhs: TagName, rhs: TagName) -> Bool {
switch (lhs, rhs) {
case (.event, .event), (.pubkey, .pubkey): return true
case (.event, .event), (.pubkey, .pubkey), (.kind, .kind): return true
case (.unknown(let id1), .unknown(let id2)): return id1 == id2
default: return false
}
Expand Down Expand Up @@ -95,30 +100,36 @@ public enum EventTagMarker: Codable, Equatable {
}
}

/// A reference to an event, pubkey, or other content
/// A reference to an event, pubkey, or other content.
///
/// See [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) for an initial definition of tags.
/// See [NIP-10](https://github.com/nostr-protocol/nips/blob/master/01.md) for further refinement and additions to tags.
/// See https://github.com/nostr-protocol/nips/tree/b4cdc1a73d415c79c35655fa02f5e55cd1f2a60c#standardized-tags for a list of all standardized tags.
public class Tag: Codable, Equatable {
public static func == (lhs: Tag, rhs: Tag) -> Bool {
lhs.isEqual(to: rhs)
}

/// The type of tag: event, pubkey, or other unknown type.
let identifier: TagIdentifier
/// The name of the tag: event, pubkey, kind etc.
let name: TagName

/// The main value associated with the tag. For example, for the
/// pubkey name, the `value` is the 32-byte, hex-encoded pubkey.
let value: String

/// The content identifier associated with the type. For example, for the
/// pubkey type, the `contentIdentifier` is the 32-byte, hex-encoded pubkey.
let contentIdentifier: String
/// The remaining parameters in the array of strings the tag consists of.
let otherParameters: [String]

/// Creates and returns a tag object that references some piece of content.
/// Creates and returns a ``Tag`` object that references some piece of content.
/// - Parameters:
/// - identifier: The type of tag: event, pubkey, or other unknown type.
/// - contentIdentifier: The content identifier associated with the type. For example, for the
/// pubkey type, the `contentIdentifier` is the 32-byte, hex-encoded pubkey.
init(identifier: TagIdentifier, contentIdentifier: String) {
self.identifier = identifier
self.contentIdentifier = contentIdentifier
/// - name: The name of the tag: event, pubkey, or other unknown type.
/// - value: The content identifier associated with the type. For example, for the
/// pubkey type, the `value` is the 32-byte, hex-encoded pubkey.
/// - otherParameters: The remaining parameters in the array of strings the tag consists of.
init(name: TagName, value: String, otherParameters: [String] = []) {
self.name = name
self.value = value
self.otherParameters = otherParameters
}

required public init(from decoder: Decoder) throws {
Expand All @@ -127,121 +138,37 @@ public class Tag: Codable, Equatable {
let type = try container.decode(String.self)
switch type {
case "p":
identifier = .pubkey
name = .pubkey
case "e":
identifier = .event
name = .event
case "k":
name = .kind
default:
identifier = .unknown(type)
name = .unknown(type)
}

contentIdentifier = try container.decode(String.self)
}

public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
try container.encode(identifier.rawValue)
try container.encode(contentIdentifier)
}

func isEqual(to tag: Tag) -> Bool {
identifier == tag.identifier &&
contentIdentifier == tag.contentIdentifier
}
}

/// A tag referencing a pubkey
///
/// See [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) and [NIP-02](https://github.com/nostr-protocol/nips/blob/master/02.md#contact-list-and-petnames)
public class PubkeyTag: Tag {
/// The URL of a recommended relay associated with the reference.
let recommendedRelayURL: String?

/// A local name for the profile (can also be set to an empty string or not provided).
let petname: String?

/// Creates and returns a tag for a pubkey.
/// - Parameters:
/// - contentIdentifier: The content identifier associated with the type. For example, for the
/// pubkey type, the `contentIdentifier` is the 32-byte, hex-encoded pubkey.
/// - recommendedRelayURL: The URL of a recommended relay associated with the reference.
/// - petname: A local name for the profile (can also be set to an empty string or not provided).
init(contentIdentifier: String, recommendedRelayURL: String? = nil, petname: String? = nil) {
self.recommendedRelayURL = recommendedRelayURL
self.petname = petname
super.init(identifier: .pubkey, contentIdentifier: contentIdentifier)
}

public required init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
recommendedRelayURL = try container.decodeIfPresent(String.self)
petname = try container.decodeIfPresent(String.self)

try super.init(from: decoder)
}

public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)
value = try container.decode(String.self)

var container = encoder.unkeyedContainer()
if let recommendedRelayURL {
try container.encode(recommendedRelayURL)
}
if let petname {
try container.encode(petname)
}
}

override func isEqual(to tag: Tag) -> Bool {
guard let pTag = tag as? PubkeyTag else {
return false
}
return super.isEqual(to: tag) &&
recommendedRelayURL == pTag.recommendedRelayURL &&
petname == pTag.petname
}
}

/// A tag referencing an event
///
/// See [NIP-10](https://github.com/nostr-protocol/nips/blob/master/10.md#marked-e-tags-preferred)
public class EventTag: Tag {
/// The type of the ``EventTag``.
let marker: EventTagMarker?

/// Creates and returns tag referencing an event.
/// - Parameters:
/// - contentIdentifier: The event id.
/// - marker: The type of reference. See [NIP-10](https://github.com/nostr-protocol/nips/blob/master/10.md#marked-e-tags-preferred) for a description of marked "e" tags.
init(contentIdentifier: String, marker: EventTagMarker? = nil) {
self.marker = marker
super.init(identifier: .event, contentIdentifier: contentIdentifier)
}

public required init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
if let rawMarker = try container.decodeIfPresent(String.self) {
marker = EventTagMarker(rawValue: rawMarker)
} else {
marker = nil
var otherParameters = [String]()
while !container.isAtEnd {
let value = try container.decode(String.self)
otherParameters.append(value)
}

try super.init(from: decoder)
self.otherParameters = otherParameters
}

public override func encode(to encoder: Encoder) throws {
try super.encode(to: encoder)

public func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
if let marker {
try container.encode(marker)
try container.encode(name.rawValue)
try container.encode(value)
for value in otherParameters {
try container.encode(value)
}
}

override func isEqual(to tag: Tag) -> Bool {
guard let eTag = tag as? EventTag else {
return false
}
return super.isEqual(to: tag) &&
marker == eTag.marker
func isEqual(to tag: Tag) -> Bool {
name == tag.name &&
value == tag.value &&
otherParameters == tag.otherParameters
}
}
12 changes: 6 additions & 6 deletions Tests/NostrSDKTests/EventDecodingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ final class EventDecodingTests: XCTestCase, FixtureLoading {
XCTAssertEqual(event.kind, .textNote)

let expectedTags = [
EventTag(contentIdentifier: "93930d65435d49db723499335473920795e7f13c45600dcfad922135cf44bd63"),
PubkeyTag(contentIdentifier: "f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9")
Tag(name: .event, value: "93930d65435d49db723499335473920795e7f13c45600dcfad922135cf44bd63"),
Tag(name: .pubkey, value: "f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9")
]
XCTAssertEqual(event.tags, expectedTags)
XCTAssertEqual(event.content, "I think it stays persistent on your profile, but interface setting doesn’t persist. Bug. ")
Expand Down Expand Up @@ -86,8 +86,8 @@ final class EventDecodingTests: XCTestCase, FixtureLoading {
XCTAssertEqual(event.kind, .contactList)

let expectedTags: [Tag] = [
PubkeyTag(contentIdentifier: "pubkey1", recommendedRelayURL: "wss://relay1.com", petname: "alice"),
PubkeyTag(contentIdentifier: "pubkey2", recommendedRelayURL: "wss://relay2.com", petname: "bob")
Tag(name: .pubkey, value: "pubkey1", otherParameters: ["wss://relay1.com", "alice"]),
Tag(name: .pubkey, value: "pubkey2", otherParameters: ["wss://relay2.com", "bob"])
]
XCTAssertEqual(event.tags, expectedTags)
XCTAssertEqual(event.signature, "hex-signature")
Expand All @@ -103,8 +103,8 @@ final class EventDecodingTests: XCTestCase, FixtureLoading {
XCTAssertEqual(event.kind, .repost)

let expectedTags = [
EventTag(contentIdentifier: "6663efd8ffb35325af90a84cb223dc388e9d355abf7319fe5c4c5ca7f37e9a34"),
PubkeyTag(contentIdentifier: "33eecd2e2fae31f36c0bdb843d43611426ee5c023889f0401c1b8f5008e59689")
Tag(name: .event, value: "6663efd8ffb35325af90a84cb223dc388e9d355abf7319fe5c4c5ca7f37e9a34"),
Tag(name: .pubkey, value: "33eecd2e2fae31f36c0bdb843d43611426ee5c023889f0401c1b8f5008e59689")
]
XCTAssertEqual(event.tags, expectedTags)
XCTAssertTrue(event.content.hasPrefix("{\"pubkey\":\"33eecd2e2fae31f36c0bdb843d43611426ee5c023889f0401c1b8f5008e59689\""))
Expand Down
4 changes: 2 additions & 2 deletions Tests/NostrSDKTests/RelayRequestEncodingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ final class RelayRequestEncodingTests: XCTestCase, FixtureLoading, JSONTesting {
}

func testEncodeEvent() throws {
let eventTag = EventTag(contentIdentifier: "93930d65435d49db723499335473920795e7f13c45600dcfad922135cf44bd63")
let pubkeyTag = PubkeyTag(contentIdentifier: "f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9")
let eventTag = Tag(name: .event, value: "93930d65435d49db723499335473920795e7f13c45600dcfad922135cf44bd63")
let pubkeyTag = Tag(name: .pubkey, value: "f8e6c64342f1e052480630e27e1016dce35fc3a614e60434fef4aa2503328ca9")
let event = NostrEvent(id: "fa5ed84fc8eeb959fd39ad8e48388cfc33075991ef8e50064cfcecfd918bb91b",
pubkey: "82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2",
createdAt: 1682080184,
Expand Down

0 comments on commit ca4dcd6

Please sign in to comment.