-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for NIP-52 date-based and time-based calendar events
- Loading branch information
Showing
18 changed files
with
789 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
Sources/NostrSDK/Events/Calendars/CalendarEventParticipant.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// | ||
// CalendarEventParticipant.swift | ||
// | ||
// | ||
// Created by Terry Yiu on 11/15/23. | ||
// | ||
|
||
import Foundation | ||
|
||
public struct CalendarEventParticipant: PubkeyTag, RelayTagParameter, Equatable { | ||
public static func == (lhs: Self, rhs: Self) -> Bool { | ||
lhs.tag == rhs.tag | ||
} | ||
|
||
public let tag: Tag | ||
|
||
public var pubkey: PublicKey? { | ||
PublicKey(hex: tag.value) | ||
} | ||
|
||
public var relay: URL? { | ||
guard !tag.otherParameters.isEmpty else { | ||
return nil | ||
} | ||
|
||
let relayString = tag.otherParameters[0] | ||
guard !relayString.isEmpty else { | ||
return nil | ||
} | ||
|
||
let components = URLComponents(string: relayString) | ||
guard components?.scheme == "wss" || components?.scheme == "ws" else { | ||
return nil | ||
} | ||
return components?.url | ||
} | ||
|
||
public var role: String? { | ||
guard tag.otherParameters.count >= 2 else { | ||
return nil | ||
} | ||
|
||
return tag.otherParameters[1] | ||
} | ||
|
||
public init?(tag: Tag) { | ||
guard tag.name == .pubkey else { | ||
return nil | ||
} | ||
|
||
self.tag = tag | ||
} | ||
|
||
public init(pubkey: PublicKey, relay: URL? = nil, role: String? = nil) { | ||
var otherParameters: [String] = [relay?.absoluteString ?? ""] | ||
if let role, !role.isEmpty { | ||
otherParameters.append(role) | ||
} | ||
|
||
tag = Tag(name: .pubkey, value: pubkey.hex, otherParameters: otherParameters) | ||
} | ||
} | ||
|
||
public protocol CalendarEventParticipantInterpreting: NostrEvent {} | ||
public extension CalendarEventParticipantInterpreting { | ||
var participants: [CalendarEventParticipant] { | ||
tags.filter { $0.name == .pubkey }.compactMap { CalendarEventParticipant(tag: $0) } | ||
} | ||
} |
62 changes: 62 additions & 0 deletions
62
Sources/NostrSDK/Events/Calendars/DateBasedCalendarEventNostrEvent.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
// | ||
// DateBasedCalendarEventNostrEvent.swift | ||
// | ||
// | ||
// Created by Terry Yiu on 11/13/23. | ||
// | ||
|
||
import Foundation | ||
|
||
/// Date-based calendar event starts on a date and ends before a different date in the future. | ||
/// Its use is appropriate for all-day or multi-day events where time and time zone hold no significance. e.g., anniversary, public holidays, vacation days. | ||
/// See [NIP-52](https://github.com/nostr-protocol/nips/blob/master/52.md). | ||
public final class DateBasedCalendarEventNostrEvent: NostrEvent, CalendarEventParticipantInterpreting, HashtagInterpreting, ReferenceTagInterpreting { | ||
public required init(from decoder: Decoder) throws { | ||
try super.init(from: decoder) | ||
} | ||
|
||
@available(*, unavailable, message: "This initializer is unavailable for this class.") | ||
override init(kind: EventKind, content: String, tags: [Tag] = [], createdAt: Int64 = Int64(Date.now.timeIntervalSince1970), signedBy keypair: Keypair) throws { | ||
try super.init(kind: kind, content: content, tags: tags, createdAt: createdAt, signedBy: keypair) | ||
} | ||
|
||
public init(content: String, tags: [Tag] = [], createdAt: Int64 = Int64(Date.now.timeIntervalSince1970), signedBy keypair: Keypair) throws { | ||
try super.init(kind: .dateBasedCalendarEvent, content: content, tags: tags, createdAt: createdAt, signedBy: keypair) | ||
} | ||
|
||
public var uuid: String? { | ||
tags.first { $0.name.rawValue == "d" }?.value | ||
} | ||
|
||
public var name: String? { | ||
tags.first { $0.name.rawValue == "name" }?.value | ||
} | ||
|
||
/// Start date represented by ``DateComponents`` in the calendar context of ``Calendar.Identifier.iso8601``, with `year`, `month`, and `day` populated. | ||
/// `nil` is returned if the backing `start` tag is malformed. | ||
public var start: DateComponents? { | ||
guard let startString = tags.first(where: { $0.name.rawValue == "start" })?.value else { | ||
return nil | ||
} | ||
|
||
return startString.dateStringAsDateComponents | ||
} | ||
|
||
/// End date represented by ``DateComponents`` in the calendar context of ``Calendar.Identifier.iso8601``, with `year`, `month`, and `day` populated. | ||
/// `nil` is returned if the backing `end` tag is malformed. | ||
public var end: DateComponents? { | ||
guard let startString = tags.first(where: { $0.name.rawValue == "end" })?.value else { | ||
return nil | ||
} | ||
|
||
return startString.dateStringAsDateComponents | ||
} | ||
|
||
public var location: String? { | ||
tags.first { $0.name.rawValue == "location" }?.value | ||
} | ||
|
||
public var geohash: String? { | ||
tags.first { $0.name.rawValue == "g" }?.value | ||
} | ||
} |
80 changes: 80 additions & 0 deletions
80
Sources/NostrSDK/Events/Calendars/TimeBasedCalendarEventNostrEvent.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// | ||
// TimeBasedCalendarEventEvent.swift | ||
// | ||
// | ||
// Created by Terry Yiu on 11/16/23. | ||
// | ||
|
||
import Foundation | ||
|
||
/// Time-based calendar event spans between a start time and end time. | ||
/// | ||
/// Unlike the term `calendar event` specific to NIP-52, the term `event` is used broadly in all the NIPs to describe any Nostr event. | ||
/// That is the reason why the word `event` appears twice. It is not a typo. | ||
public final class TimeBasedCalendarEventNostrEvent: NostrEvent, CalendarEventParticipantInterpreting, HashtagInterpreting, ReferenceTagInterpreting { | ||
public required init(from decoder: Decoder) throws { | ||
try super.init(from: decoder) | ||
} | ||
|
||
@available(*, unavailable, message: "This initializer is unavailable for this class.") | ||
override init(kind: EventKind, content: String, tags: [Tag] = [], createdAt: Int64 = Int64(Date.now.timeIntervalSince1970), signedBy keypair: Keypair) throws { | ||
try super.init(kind: kind, content: content, tags: tags, createdAt: createdAt, signedBy: keypair) | ||
} | ||
|
||
public init(content: String, tags: [Tag] = [], createdAt: Int64 = Int64(Date.now.timeIntervalSince1970), signedBy keypair: Keypair) throws { | ||
try super.init(kind: .timeBasedCalendarEvent, content: content, tags: tags, createdAt: createdAt, signedBy: keypair) | ||
} | ||
|
||
public var uuid: String? { | ||
tags.first { $0.name.rawValue == "d" }?.value | ||
} | ||
|
||
public var name: String? { | ||
tags.first { $0.name.rawValue == "name" }?.value | ||
} | ||
|
||
/// Start timestamp of calendar event represented by ``Date``. | ||
/// `nil` is returned if the backing `start` tag is malformed. | ||
public var start: Date? { | ||
guard let startString = tags.first(where: { $0.name.rawValue == "start" })?.value, let startSeconds = Int(startString) else { | ||
return nil | ||
} | ||
|
||
return Date(timeIntervalSince1970: TimeInterval(startSeconds)) | ||
} | ||
|
||
/// End timestamp represented by ``Date``. | ||
/// `nil` is returned if the backing `end` tag is malformed. | ||
public var end: Date? { | ||
guard let endString = tags.first(where: { $0.name.rawValue == "end" })?.value, let endSeconds = Int(endString) else { | ||
return nil | ||
} | ||
|
||
return Date(timeIntervalSince1970: TimeInterval(endSeconds)) | ||
} | ||
|
||
public var startTimeZone: TimeZone? { | ||
guard let timeZoneIdentifier = tags.first(where: { $0.name.rawValue == "start_tzid" })?.value else { | ||
return nil | ||
} | ||
|
||
return TimeZone(identifier: timeZoneIdentifier) | ||
} | ||
|
||
public var endTimeZone: TimeZone? { | ||
guard let timeZoneIdentifier = tags.first(where: { $0.name.rawValue == "end_tzid" })?.value else { | ||
return nil | ||
} | ||
|
||
return TimeZone(identifier: timeZoneIdentifier) | ||
} | ||
|
||
public var location: String? { | ||
tags.first { $0.name.rawValue == "location" }?.value | ||
} | ||
|
||
public var geohash: String? { | ||
tags.first { $0.name.rawValue == "g" }?.value | ||
} | ||
} | ||
|
||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// | ||
// HashtagInterpreting.swift | ||
// | ||
// | ||
// Created by Terry Yiu on 11/15/23. | ||
// | ||
|
||
import Foundation | ||
|
||
public protocol HashtagInterpreting: NostrEvent {} | ||
public extension HashtagInterpreting { | ||
var hashtags: [String] { | ||
tags.filter { $0.name == .hashtag } | ||
.map { $0.value } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
// | ||
// PubkeyTag.swift | ||
// | ||
// | ||
// Created by Terry Yiu on 11/15/23. | ||
// | ||
|
||
import Foundation | ||
|
||
public protocol PubkeyTag { | ||
var pubkey: PublicKey? { get } | ||
} |
Oops, something went wrong.