Files
damus/damus/Core/Nostr/ReferencedId.swift
ericholguin 65a22813a3 refactor: Adding structure
Huge refactor to add better structure to the project.
Separating features with their associated view and model structure.
This should be better organization and will allow us to improve the
overall architecture in the future.

I forsee many more improvements that can follow this change. e.g. MVVM Arch
As well as cleaning up duplicate, unused, functionality.
Many files have global functions that can also be moved or be renamed.

damus/
├── Features/
│   ├── <Feature>/
│   │   ├── Views/
│   │   └── Models/
├── Shared/
│   ├── Components/
│   ├── Media/
│   ├── Buttons/
│   ├── Extensions/
│   ├── Empty Views/
│   ├── ErrorHandling/
│   ├── Modifiers/
│   └── Utilities/
├── Core/
│   ├── Nostr/
│   ├── NIPs/
│   ├── DIPs/
│   ├── Types/
│   ├── Networking/
│   └── Storage/

Signed-off-by: ericholguin <ericholguin@apache.org>
2025-08-06 10:24:00 -07:00

216 lines
5.7 KiB
Swift

//
// ReferencedId.swift
// damus
//
// Created by William Casarin on 2023-07-22.
//
import Foundation
func tagref_should_be_id(_ tag: NdbTagElem) -> Bool {
return !(tag.matches_char("t") || tag.matches_char("d"))
}
struct References<T: TagConvertible>: Sequence, IteratorProtocol {
let tags: TagsSequence
var tags_iter: TagsIterator
init(tags: TagsSequence) {
self.tags = tags
self.tags_iter = tags.makeIterator()
}
mutating func next() -> T? {
while let tag = tags_iter.next() {
guard let evref = T.from_tag(tag: tag) else { continue }
return evref
}
return nil
}
}
extension References {
var first: T? {
self.first(where: { _ in true })
}
var last: T? {
var last: T? = nil
for t in self {
last = t
}
return last
}
}
// NdbTagElem transition helpers
extension String {
func string() -> String {
return self
}
func first_char() -> AsciiCharacter? {
self.first.flatMap { chr in AsciiCharacter(chr) }
}
func matches_char(_ c: AsciiCharacter) -> Bool {
return self.first == c.character
}
func matches_str(_ str: String) -> Bool {
return self == str
}
}
enum FollowRef: TagKeys, Hashable, TagConvertible, Equatable {
// NOTE: When adding cases make sure to update key and from_tag
case pubkey(Pubkey)
case hashtag(String)
var key: FollowKeys {
switch self {
case .hashtag: return .t
case .pubkey: return .p
}
}
enum FollowKeys: AsciiCharacter, TagKey, CustomStringConvertible {
case p, t
var keychar: AsciiCharacter { self.rawValue }
var description: String { self.rawValue.description }
}
static func from_tag(tag: TagSequence) -> FollowRef? {
guard tag.count >= 2 else { return nil }
var i = tag.makeIterator()
guard let t0 = i.next(),
let c = t0.single_char,
let fkey = FollowKeys(rawValue: c),
let t1 = i.next()
else {
return nil
}
switch fkey {
case .p: return t1.id().map({ .pubkey(Pubkey($0)) })
case .t: return .hashtag(t1.string())
}
}
var tag: [String] {
[key.description, self.description]
}
var description: String {
switch self {
case .pubkey(let pubkey): return pubkey.description
case .hashtag(let string): return string
}
}
}
/// Models common tag references defined by the Nostr protocol, and their associated values.
///
/// For example, this raw JSON tag sequence:
/// ```json
/// ["p", "8b2be0a0ad34805d76679272c28a77dbede9adcbfdca48c681ec8b624a1208a6"]
/// ```
///
/// would be parsed into something equivalent to `.pubkey(Pubkey(hex: "8b2be0a0ad34805d76679272c28a77dbede9adcbfdca48c681ec8b624a1208a6"))`
///
/// ## Notes
///
/// - Not all tag information from all NIPs can be modelled using this alone, as some NIPs may define extra associated values for specific event types. You may need to use a specialized type for some event kinds
///
enum RefId: TagConvertible, TagKeys, Equatable, Hashable {
case event(NoteId)
case pubkey(Pubkey)
case quote(QuoteId)
case hashtag(Hashtag)
case param(TagElem)
case naddr(NAddr)
case reference(String)
/// The key that defines the type of reference being made
var key: RefKey {
switch self {
case .event: return .e
case .pubkey: return .p
case .quote: return .q
case .hashtag: return .t
case .param: return .d
case .naddr: return .a
case .reference: return .r
}
}
/// Defines the type of reference being made on a Nostr event tag
///
/// Example:
/// ```json
/// ["p", "8b2be0a0ad34805d76679272c28a77dbede9adcbfdca48c681ec8b624a1208a6"]
/// ```
///
/// The `RefKey` is "p"
enum RefKey: AsciiCharacter, TagKey, CustomStringConvertible {
case e, p, t, d, q, a, r
var keychar: AsciiCharacter {
self.rawValue
}
var description: String {
self.keychar.description
}
}
/// A raw nostr-style tag sequence representation of this object
var tag: [String] {
[self.key.description, self.description]
}
/// Describes what is being referenced, as a `String`
var description: String {
switch self {
case .event(let noteId): return noteId.hex()
case .pubkey(let pubkey): return pubkey.hex()
case .quote(let quote): return quote.hex()
case .hashtag(let string): return string.hashtag
case .param(let string): return string.string()
case .naddr(let naddr):
return naddr.kind.description + ":" + naddr.author.hex() + ":" + naddr.identifier
case .reference(let string):
return string
}
}
/// Parses a raw tag sequence
static func from_tag(tag: TagSequence) -> RefId? {
var i = tag.makeIterator()
guard tag.count >= 2,
let t0 = i.next(),
let key = t0.single_char,
let rkey = RefKey(rawValue: key),
let t1 = i.next()
else { return nil }
switch rkey {
case .e: return t1.id().map({ .event(NoteId($0)) })
case .p: return t1.id().map({ .pubkey(Pubkey($0)) })
case .q: return t1.id().map({ .quote(QuoteId($0)) })
case .t: return .hashtag(Hashtag(hashtag: t1.string()))
case .d: return .param(t1)
case .a: return .naddr(NAddr(identifier: "", author: Pubkey(Data()), relays: [], kind: 0))
case .r: return .reference(t1.string())
}
}
}