This commit fixes the ambiguity in tags used in highlights with comments, by adding specifiers to help clients understand: - If a URL reference is the source of the highlight or just a URL mentioned in the comment - If a pubkey reference is the author of the highlighted content, or just a generic mention in the comment This tries to be backwards compatible with previous versions of NIP-84. Testing -------- PASS Device: iPhone 15 simulator iOS: 17.5 Damus: This commit Steps: 1. Create a new highlight from a webpage using the extension. Tag a user and attach an image 2. Check the newly-created highlight: 1. Highlight description line should just say "Highlighted", not "Highlighted <username>" 2. Highlight source link preview should present the URL of the highlighted page, NOT the image URL 3. Inspect the JSON for the newly-created highlight: 1. "r" tags should include specifiers in the 3rd slot, such as "source" or "mention" 2. "p" tags should include specifiers in the 3rd slot, such as "mention" 4. Go to an older, generic highlight (without comment) to another nostr event and check the view. 1. Highlight description line should say "Highlighted <author_name_of_other_event>" 2. Clicking on the highlight should lead to the highlighted event itself. Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
259 lines
7.1 KiB
Swift
259 lines
7.1 KiB
Swift
//
|
|
// Mentions.swift
|
|
// damus
|
|
//
|
|
// Created by William Casarin on 2022-05-04.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
enum MentionType: AsciiCharacter, TagKey {
|
|
case p
|
|
case e
|
|
case a
|
|
case r
|
|
|
|
var keychar: AsciiCharacter {
|
|
self.rawValue
|
|
}
|
|
}
|
|
|
|
enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable {
|
|
case pubkey(Pubkey)
|
|
case note(NoteId)
|
|
case nevent(NEvent)
|
|
case nprofile(NProfile)
|
|
case nrelay(String)
|
|
case naddr(NAddr)
|
|
|
|
var key: MentionType {
|
|
switch self {
|
|
case .pubkey: return .p
|
|
case .note: return .e
|
|
case .nevent: return .e
|
|
case .nprofile: return .p
|
|
case .nrelay: return .r
|
|
case .naddr: return .a
|
|
}
|
|
}
|
|
|
|
var bech32: String {
|
|
return Bech32Object.encode(toBech32Object())
|
|
}
|
|
|
|
static func from_bech32(str: String) -> MentionRef? {
|
|
switch Bech32Object.parse(str) {
|
|
case .note(let noteid): return .note(noteid)
|
|
case .npub(let pubkey): return .pubkey(pubkey)
|
|
default: return nil
|
|
}
|
|
}
|
|
|
|
var pubkey: Pubkey? {
|
|
switch self {
|
|
case .pubkey(let pubkey): return pubkey
|
|
case .note: return nil
|
|
case .nevent(let nevent): return nevent.author
|
|
case .nprofile(let nprofile): return nprofile.author
|
|
case .nrelay: return nil
|
|
case .naddr: return nil
|
|
}
|
|
}
|
|
|
|
var tag: [String] {
|
|
switch self {
|
|
case .pubkey(let pubkey): return ["p", pubkey.hex()]
|
|
case .note(let noteId): return ["e", noteId.hex()]
|
|
case .nevent(let nevent): return ["e", nevent.noteid.hex()]
|
|
case .nprofile(let nprofile): return ["p", nprofile.author.hex()]
|
|
case .nrelay(let url): return ["r", url]
|
|
case .naddr(let naddr): return ["a", naddr.kind.description + ":" + naddr.author.hex() + ":" + naddr.identifier.string()]
|
|
}
|
|
}
|
|
|
|
static func from_tag(tag: TagSequence) -> MentionRef? {
|
|
guard tag.count >= 2 else { return nil }
|
|
|
|
var i = tag.makeIterator()
|
|
|
|
guard let t0 = i.next(),
|
|
let chr = t0.single_char,
|
|
let mention_type = MentionType(rawValue: chr),
|
|
let element = i.next()
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
switch mention_type {
|
|
case .p:
|
|
guard let data = element.id() else { return nil }
|
|
return .pubkey(Pubkey(data))
|
|
case .e:
|
|
guard let data = element.id() else { return nil }
|
|
return .note(NoteId(data))
|
|
case .a:
|
|
let str = element.string()
|
|
let data = str.split(separator: ":")
|
|
if(data.count != 3) { return nil }
|
|
|
|
guard let pubkey = Pubkey(hex: String(data[1])) else { return nil }
|
|
guard let kind = UInt32(data[0]) else { return nil }
|
|
|
|
return .naddr(NAddr(identifier: String(data[2]), author: pubkey, relays: [], kind: kind))
|
|
case .r: return .nrelay(element.string())
|
|
}
|
|
}
|
|
|
|
func toBech32Object() -> Bech32Object {
|
|
switch self {
|
|
case .pubkey(let pk):
|
|
return .npub(pk)
|
|
case .note(let noteid):
|
|
return .note(noteid)
|
|
case .naddr(let naddr):
|
|
return .naddr(naddr)
|
|
case .nevent(let nevent):
|
|
return .nevent(nevent)
|
|
case .nprofile(let nprofile):
|
|
return .nprofile(nprofile)
|
|
case .nrelay(let url):
|
|
return .nrelay(url)
|
|
}
|
|
}
|
|
}
|
|
|
|
struct Mention<T: Equatable>: Equatable {
|
|
let index: Int?
|
|
let ref: T
|
|
|
|
static func any(_ mention_id: MentionRef, index: Int? = nil) -> Mention<MentionRef> {
|
|
return Mention<MentionRef>(index: index, ref: mention_id)
|
|
}
|
|
|
|
static func noteref(_ id: NoteRef, index: Int? = nil) -> Mention<NoteRef> {
|
|
return Mention<NoteRef>(index: index, ref: id)
|
|
}
|
|
|
|
static func note(_ id: NoteId, index: Int? = nil) -> Mention<NoteId> {
|
|
return Mention<NoteId>(index: index, ref: id)
|
|
}
|
|
|
|
static func pubkey(_ pubkey: Pubkey, index: Int? = nil) -> Mention<Pubkey> {
|
|
return Mention<Pubkey>(index: index, ref: pubkey)
|
|
}
|
|
}
|
|
|
|
typealias Invoice = LightningInvoice<Amount>
|
|
typealias ZapInvoice = LightningInvoice<Int64>
|
|
|
|
enum InvoiceDescription {
|
|
case description(String)
|
|
case description_hash(Data)
|
|
}
|
|
|
|
struct LightningInvoice<T> {
|
|
let description: InvoiceDescription
|
|
let amount: T
|
|
let string: String
|
|
let expiry: UInt64
|
|
let payment_hash: Data
|
|
let created_at: UInt64
|
|
|
|
var description_string: String {
|
|
switch description {
|
|
case .description(let string):
|
|
return string
|
|
case .description_hash:
|
|
return ""
|
|
}
|
|
}
|
|
}
|
|
|
|
func maybe_pointee<T>(_ p: UnsafeMutablePointer<T>!) -> T? {
|
|
guard p != nil else {
|
|
return nil
|
|
}
|
|
return p.pointee
|
|
}
|
|
|
|
enum Amount: Equatable {
|
|
case any
|
|
case specific(Int64)
|
|
|
|
func amount_sats_str() -> String {
|
|
switch self {
|
|
case .any:
|
|
return NSLocalizedString("Any", comment: "Any amount of sats")
|
|
case .specific(let amt):
|
|
return format_msats(amt)
|
|
}
|
|
}
|
|
}
|
|
|
|
func format_msats_abbrev(_ msats: Int64) -> String {
|
|
let formatter = NumberFormatter()
|
|
formatter.numberStyle = .decimal
|
|
formatter.positiveSuffix = "m"
|
|
formatter.positivePrefix = ""
|
|
formatter.minimumFractionDigits = 0
|
|
formatter.maximumFractionDigits = 3
|
|
formatter.roundingMode = .down
|
|
formatter.roundingIncrement = 0.1
|
|
formatter.multiplier = 1
|
|
|
|
let sats = NSNumber(value: (Double(msats) / 1000.0))
|
|
|
|
if msats >= 1_000_000*1000 {
|
|
formatter.positiveSuffix = "m"
|
|
formatter.multiplier = 0.000001
|
|
} else if msats >= 1000*1000 {
|
|
formatter.positiveSuffix = "k"
|
|
formatter.multiplier = 0.001
|
|
} else {
|
|
return sats.stringValue
|
|
}
|
|
|
|
return formatter.string(from: sats) ?? sats.stringValue
|
|
}
|
|
|
|
func format_msats(_ msat: Int64, locale: Locale = Locale.current) -> String {
|
|
let numberFormatter = NumberFormatter()
|
|
numberFormatter.numberStyle = .decimal
|
|
numberFormatter.minimumFractionDigits = 0
|
|
numberFormatter.maximumFractionDigits = 3
|
|
numberFormatter.roundingMode = .down
|
|
numberFormatter.locale = locale
|
|
|
|
let sats = NSNumber(value: (Double(msat) / 1000.0))
|
|
let formattedSats = numberFormatter.string(from: sats) ?? sats.stringValue
|
|
|
|
let format = localizedStringFormat(key: "sats_count", locale: locale)
|
|
return String(format: format, locale: locale, sats.decimalValue as NSDecimalNumber, formattedSats)
|
|
}
|
|
|
|
func convert_invoice_description(b11: bolt11) -> InvoiceDescription? {
|
|
if let desc = b11.description {
|
|
return .description(String(cString: desc))
|
|
}
|
|
|
|
if var deschash = maybe_pointee(b11.description_hash) {
|
|
return .description_hash(Data(bytes: &deschash, count: 32))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? {
|
|
var i: Int = 0
|
|
for tag in tags {
|
|
if tag.count >= 2 {
|
|
if tag[0] == type && tag[1] == id {
|
|
return i
|
|
}
|
|
}
|
|
i += 1
|
|
}
|
|
|
|
return nil
|
|
}
|