2023-07-21 21:54:03 +00:00
|
|
|
|
//
|
|
|
|
|
// NdbNote.swift
|
|
|
|
|
// damus
|
|
|
|
|
//
|
|
|
|
|
// Created by William Casarin on 2023-07-21.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import Foundation
|
2023-07-24 19:40:27 +00:00
|
|
|
|
import NaturalLanguage
|
2023-11-16 02:09:28 +00:00
|
|
|
|
import CommonCrypto
|
|
|
|
|
import secp256k1
|
|
|
|
|
import secp256k1_implementation
|
|
|
|
|
import CryptoKit
|
2023-07-21 21:54:03 +00:00
|
|
|
|
|
2023-11-16 02:09:28 +00:00
|
|
|
|
let MAX_NOTE_SIZE: Int = 2 << 18
|
2023-07-23 18:55:36 +00:00
|
|
|
|
|
|
|
|
|
struct NdbStr {
|
|
|
|
|
let note: NdbNote
|
|
|
|
|
let str: UnsafePointer<CChar>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct NdbId {
|
|
|
|
|
let note: NdbNote
|
|
|
|
|
let id: Data
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum NdbData {
|
|
|
|
|
case id(NdbId)
|
|
|
|
|
case str(NdbStr)
|
|
|
|
|
|
|
|
|
|
init(note: NdbNote, str: ndb_str) {
|
|
|
|
|
guard str.flag == NDB_PACKED_ID else {
|
|
|
|
|
self = .str(NdbStr(note: note, str: str.str))
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let buffer = UnsafeBufferPointer(start: str.id, count: 32)
|
|
|
|
|
self = .id(NdbId(note: note, id: Data(buffer: buffer)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
class NdbNote: Encodable, Equatable, Hashable {
|
2023-07-21 23:01:28 +00:00
|
|
|
|
// we can have owned notes, but we can also have lmdb virtual-memory mapped notes so its optional
|
2023-12-04 19:52:39 +00:00
|
|
|
|
let owned: Bool
|
2023-07-23 18:55:36 +00:00
|
|
|
|
let count: Int
|
2023-09-10 21:51:55 +00:00
|
|
|
|
let key: NoteKey?
|
2023-07-21 21:54:03 +00:00
|
|
|
|
let note: UnsafeMutablePointer<ndb_note>
|
|
|
|
|
|
2023-07-24 17:55:34 +00:00
|
|
|
|
// cached stuff (TODO: remove these)
|
|
|
|
|
var decrypted_content: String? = nil
|
Bring local notification logic into the push notification target
This commit brings key local notification logic into the notification
extension target to allow the extension to reuse much of the
functionality surrounding the processing and formatting of
notifications. More specifically, the functions
`process_local_notification` and `create_local_notification` were
brought into the extension target.
This will enable us to reuse much of the pre-existing notification logic
(and avoid having to reimplement all of that)
However, those functions had high dependencies on other parts of the
code, so significant refactorings were needed to make this happen:
- `create_local_notification` and `process_local_notification` had its
function signatures changed to avoid the need to `DamusState` (which
pulls too many other dependecies)
- Other necessary dependencies, such as `Profiles`, `UserSettingsStore`
had to be pulled into the extension target. Subsequently,
sub-dependencies of those items had to be pulled in as well
- In several cases, files were split to avoid pulling too many
dependencies (e.g. Some Model files depended on some functions in View
files, so in those cases I moved those functions into their own
separate file to avoid pulling in view logic into the extension
target)
- Notification processing logic was changed a bit to remove dependency
on `EventCache` in favor of using ndb directly (As instructed in a
TODO comment in EventCache, and because EventCache has too many other
dependencies)
tldr: A LOT of things were moved around, a bit of logic was changed
around local notifications to avoid using `EventCache`, but otherwise
this commit is meant to be a no-op without any new features or
user-facing functional changes.
Testing
-------
Device: iPhone 15 Pro
iOS: 17.0.1
Damus: This commit
Coverage:
1. Ran unit tests to check for regressions (none detected)
2. Launched the app and navigated around and did some interactions to
perform a quick functional smoke test (no regressions found)
3. Sent a few push notifications to check they still work as expected (PASS)
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Signed-off-by: William Casarin <jb55@jb55.com>
2023-12-01 21:26:06 +00:00
|
|
|
|
|
|
|
|
|
private var inner_event: NdbNote? {
|
|
|
|
|
get {
|
|
|
|
|
return NdbNote.owned_from_json_cstr(json: content_raw, json_len: content_len)
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-07-24 17:55:34 +00:00
|
|
|
|
|
2023-12-04 06:12:31 +00:00
|
|
|
|
init(note: UnsafeMutablePointer<ndb_note>, size: Int, owned: Bool, key: NoteKey?) {
|
2023-07-21 23:01:28 +00:00
|
|
|
|
self.note = note
|
2023-12-04 06:12:31 +00:00
|
|
|
|
self.owned = owned
|
|
|
|
|
self.count = size
|
2023-09-10 21:51:55 +00:00
|
|
|
|
self.key = key
|
2023-07-26 15:46:44 +00:00
|
|
|
|
|
2023-08-06 16:07:09 +00:00
|
|
|
|
#if DEBUG_NOTE_SIZE
|
2023-07-26 15:46:44 +00:00
|
|
|
|
if let owned_size {
|
|
|
|
|
NdbNote.total_ndb_size += Int(owned_size)
|
|
|
|
|
NdbNote.notes_created += 1
|
|
|
|
|
|
|
|
|
|
print("\(NdbNote.notes_created) ndb_notes, \(NdbNote.total_ndb_size) bytes")
|
|
|
|
|
}
|
2023-08-06 16:07:09 +00:00
|
|
|
|
#endif
|
2023-07-26 15:46:44 +00:00
|
|
|
|
|
2023-07-22 14:40:20 +00:00
|
|
|
|
}
|
2023-07-23 18:55:36 +00:00
|
|
|
|
|
2023-12-04 06:12:31 +00:00
|
|
|
|
func to_owned() -> NdbNote {
|
|
|
|
|
if self.owned {
|
|
|
|
|
return self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let buf = malloc(self.count)!
|
|
|
|
|
memcpy(buf, &self.note.pointee, self.count)
|
|
|
|
|
let new_note = buf.assumingMemoryBound(to: ndb_note.self)
|
|
|
|
|
|
|
|
|
|
return NdbNote(note: new_note, size: self.count, owned: true, key: self.key)
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-22 14:40:20 +00:00
|
|
|
|
var content: String {
|
2023-07-23 00:15:36 +00:00
|
|
|
|
String(cString: content_raw, encoding: .utf8) ?? ""
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var content_raw: UnsafePointer<CChar> {
|
|
|
|
|
ndb_note_content(note)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var content_len: UInt32 {
|
|
|
|
|
ndb_note_content_length(note)
|
2023-07-22 14:40:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-25 15:58:06 +00:00
|
|
|
|
/// NDBTODO: make this into data
|
2023-07-26 15:46:44 +00:00
|
|
|
|
var id: NoteId {
|
|
|
|
|
.init(Data(bytes: ndb_note_id(note), count: 32))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var sig: Signature {
|
|
|
|
|
.init(Data(bytes: ndb_note_sig(note), count: 64))
|
2023-07-21 21:54:03 +00:00
|
|
|
|
}
|
2023-07-23 00:15:36 +00:00
|
|
|
|
|
2023-07-25 15:58:06 +00:00
|
|
|
|
/// NDBTODO: make this into data
|
2023-07-26 15:46:44 +00:00
|
|
|
|
var pubkey: Pubkey {
|
|
|
|
|
.init(Data(bytes: ndb_note_pubkey(note), count: 32))
|
2023-07-21 23:01:28 +00:00
|
|
|
|
}
|
2023-07-23 00:15:36 +00:00
|
|
|
|
|
|
|
|
|
var created_at: UInt32 {
|
|
|
|
|
ndb_note_created_at(note)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var kind: UInt32 {
|
|
|
|
|
ndb_note_kind(note)
|
|
|
|
|
}
|
2023-07-23 18:55:36 +00:00
|
|
|
|
|
2023-07-24 20:08:18 +00:00
|
|
|
|
var tags: TagsSequence {
|
|
|
|
|
.init(note: self)
|
2023-07-21 21:54:03 +00:00
|
|
|
|
}
|
2023-07-23 18:55:36 +00:00
|
|
|
|
|
|
|
|
|
deinit {
|
|
|
|
|
if self.owned {
|
2023-08-06 16:07:09 +00:00
|
|
|
|
#if DEBUG_NOTE_SIZE
|
2023-07-26 15:46:44 +00:00
|
|
|
|
NdbNote.total_ndb_size -= Int(count)
|
|
|
|
|
NdbNote.notes_created -= 1
|
|
|
|
|
|
|
|
|
|
print("\(NdbNote.notes_created) ndb_notes, \(NdbNote.total_ndb_size) bytes")
|
2023-08-06 16:07:09 +00:00
|
|
|
|
#endif
|
2023-07-23 18:55:36 +00:00
|
|
|
|
free(note)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-24 20:09:27 +00:00
|
|
|
|
static func == (lhs: NdbNote, rhs: NdbNote) -> Bool {
|
|
|
|
|
return lhs.id == rhs.id
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-25 15:58:06 +00:00
|
|
|
|
func hash(into hasher: inout Hasher) {
|
|
|
|
|
hasher.combine(id)
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
private enum CodingKeys: String, CodingKey {
|
|
|
|
|
case id, sig, tags, pubkey, created_at, kind, content
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Implement the `Encodable` protocol
|
|
|
|
|
func encode(to encoder: Encoder) throws {
|
|
|
|
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
|
|
|
|
|
|
|
|
try container.encode(hex_encode(id.id), forKey: .id)
|
|
|
|
|
try container.encode(hex_encode(sig.data), forKey: .sig)
|
|
|
|
|
try container.encode(pubkey, forKey: .pubkey)
|
|
|
|
|
try container.encode(created_at, forKey: .created_at)
|
|
|
|
|
try container.encode(kind, forKey: .kind)
|
|
|
|
|
try container.encode(content, forKey: .content)
|
|
|
|
|
try container.encode(tags, forKey: .tags)
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-06 16:07:09 +00:00
|
|
|
|
#if DEBUG_NOTE_SIZE
|
2023-07-26 15:46:44 +00:00
|
|
|
|
static var total_ndb_size: Int = 0
|
|
|
|
|
static var notes_created: Int = 0
|
2023-08-06 16:07:09 +00:00
|
|
|
|
#endif
|
2023-07-25 15:58:06 +00:00
|
|
|
|
|
2023-07-25 23:22:25 +00:00
|
|
|
|
init?(content: String, keypair: Keypair, kind: UInt32 = 1, tags: [[String]] = [], createdAt: UInt32 = UInt32(Date().timeIntervalSince1970)) {
|
2023-07-25 15:58:06 +00:00
|
|
|
|
|
|
|
|
|
var builder = ndb_builder()
|
|
|
|
|
let buflen = MAX_NOTE_SIZE
|
|
|
|
|
let buf = malloc(buflen)
|
|
|
|
|
|
|
|
|
|
ndb_builder_init(&builder, buf, Int32(buflen))
|
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
var pk_raw = keypair.pubkey.bytes
|
2023-07-25 15:58:06 +00:00
|
|
|
|
|
|
|
|
|
ndb_builder_set_pubkey(&builder, &pk_raw)
|
|
|
|
|
ndb_builder_set_kind(&builder, UInt32(kind))
|
2023-08-25 19:32:30 +00:00
|
|
|
|
ndb_builder_set_created_at(&builder, UInt64(createdAt))
|
2023-07-25 15:58:06 +00:00
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
var ok = true
|
2023-07-25 15:58:06 +00:00
|
|
|
|
for tag in tags {
|
|
|
|
|
ndb_builder_new_tag(&builder);
|
|
|
|
|
for elem in tag {
|
2023-07-26 15:46:44 +00:00
|
|
|
|
ok = elem.withCString({ eptr in
|
|
|
|
|
return ndb_builder_push_tag_str(&builder, eptr, Int32(elem.utf8.count)) > 0
|
|
|
|
|
})
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil
|
2023-07-25 15:58:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
ok = content.withCString { cptr in
|
|
|
|
|
return ndb_builder_set_content(&builder, cptr, Int32(content.utf8.count)) > 0
|
|
|
|
|
}
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil
|
2023-07-25 15:58:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var n = UnsafeMutablePointer<ndb_note>?(nil)
|
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
|
|
|
|
|
var the_kp: ndb_keypair? = nil
|
|
|
|
|
|
|
|
|
|
if let sec = keypair.privkey {
|
2023-07-25 15:58:06 +00:00
|
|
|
|
var kp = ndb_keypair()
|
2023-07-26 15:46:44 +00:00
|
|
|
|
memcpy(&kp.secret.0, sec.id.bytes, 32);
|
|
|
|
|
|
|
|
|
|
if ndb_create_keypair(&kp) <= 0 {
|
|
|
|
|
print("bad keypair")
|
|
|
|
|
} else {
|
|
|
|
|
the_kp = kp
|
2023-07-25 15:58:06 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var len: Int32 = 0
|
2023-07-26 15:46:44 +00:00
|
|
|
|
if var the_kp {
|
|
|
|
|
len = ndb_builder_finalize(&builder, &n, &the_kp)
|
2023-07-25 15:58:06 +00:00
|
|
|
|
} else {
|
|
|
|
|
len = ndb_builder_finalize(&builder, &n, nil)
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
if len <= 0 {
|
|
|
|
|
free(buf)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//guard let n else { return nil }
|
2023-07-25 15:58:06 +00:00
|
|
|
|
|
|
|
|
|
self.owned = true
|
|
|
|
|
self.count = Int(len)
|
2023-07-26 15:46:44 +00:00
|
|
|
|
//self.note = n
|
|
|
|
|
let r = realloc(buf, Int(len))
|
|
|
|
|
guard let r else {
|
|
|
|
|
free(buf)
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.note = r.assumingMemoryBound(to: ndb_note.self)
|
2023-09-10 21:51:55 +00:00
|
|
|
|
self.key = nil
|
2023-07-25 15:58:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-21 21:54:03 +00:00
|
|
|
|
static func owned_from_json(json: String, bufsize: Int = 2 << 18) -> NdbNote? {
|
2023-07-24 17:55:34 +00:00
|
|
|
|
return json.withCString { cstr in
|
|
|
|
|
return NdbNote.owned_from_json_cstr(
|
|
|
|
|
json: cstr, json_len: UInt32(json.utf8.count), bufsize: bufsize)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static func owned_from_json_cstr(json: UnsafePointer<CChar>, json_len: UInt32, bufsize: Int = 2 << 18) -> NdbNote? {
|
2023-07-23 18:55:36 +00:00
|
|
|
|
let data = malloc(bufsize)
|
2023-07-24 17:55:34 +00:00
|
|
|
|
//guard var json_cstr = json.cString(using: .utf8) else { return nil }
|
|
|
|
|
|
|
|
|
|
//json_cs
|
2023-07-21 21:54:03 +00:00
|
|
|
|
var note: UnsafeMutablePointer<ndb_note>?
|
2023-07-23 00:15:36 +00:00
|
|
|
|
|
2023-07-24 17:55:34 +00:00
|
|
|
|
let len = ndb_note_from_json(json, Int32(json_len), ¬e, data, Int32(bufsize))
|
2023-07-23 18:55:36 +00:00
|
|
|
|
|
|
|
|
|
if len == 0 {
|
|
|
|
|
free(data)
|
|
|
|
|
return nil
|
2023-07-21 21:54:03 +00:00
|
|
|
|
}
|
2023-07-23 18:55:36 +00:00
|
|
|
|
|
2023-07-21 21:54:03 +00:00
|
|
|
|
// Create new Data with just the valid bytes
|
2023-07-23 18:55:36 +00:00
|
|
|
|
guard let note_data = realloc(data, Int(len)) else { return nil }
|
|
|
|
|
let new_note = note_data.assumingMemoryBound(to: ndb_note.self)
|
2023-07-24 17:55:34 +00:00
|
|
|
|
|
2023-12-04 06:12:31 +00:00
|
|
|
|
return NdbNote(note: new_note, size: Int(len), owned: true, key: nil)
|
2023-07-23 00:15:36 +00:00
|
|
|
|
}
|
Bring local notification logic into the push notification target
This commit brings key local notification logic into the notification
extension target to allow the extension to reuse much of the
functionality surrounding the processing and formatting of
notifications. More specifically, the functions
`process_local_notification` and `create_local_notification` were
brought into the extension target.
This will enable us to reuse much of the pre-existing notification logic
(and avoid having to reimplement all of that)
However, those functions had high dependencies on other parts of the
code, so significant refactorings were needed to make this happen:
- `create_local_notification` and `process_local_notification` had its
function signatures changed to avoid the need to `DamusState` (which
pulls too many other dependecies)
- Other necessary dependencies, such as `Profiles`, `UserSettingsStore`
had to be pulled into the extension target. Subsequently,
sub-dependencies of those items had to be pulled in as well
- In several cases, files were split to avoid pulling too many
dependencies (e.g. Some Model files depended on some functions in View
files, so in those cases I moved those functions into their own
separate file to avoid pulling in view logic into the extension
target)
- Notification processing logic was changed a bit to remove dependency
on `EventCache` in favor of using ndb directly (As instructed in a
TODO comment in EventCache, and because EventCache has too many other
dependencies)
tldr: A LOT of things were moved around, a bit of logic was changed
around local notifications to avoid using `EventCache`, but otherwise
this commit is meant to be a no-op without any new features or
user-facing functional changes.
Testing
-------
Device: iPhone 15 Pro
iOS: 17.0.1
Damus: This commit
Coverage:
1. Ran unit tests to check for regressions (none detected)
2. Launched the app and navigated around and did some interactions to
perform a quick functional smoke test (no regressions found)
3. Sent a few push notifications to check they still work as expected (PASS)
Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
Signed-off-by: William Casarin <jb55@jb55.com>
2023-12-01 21:26:06 +00:00
|
|
|
|
|
|
|
|
|
func get_inner_event() -> NdbNote? {
|
|
|
|
|
return self.inner_event
|
|
|
|
|
}
|
2023-07-23 00:15:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-11-16 02:09:28 +00:00
|
|
|
|
// Extension to make NdbNote compatible with NostrEvent's original API
|
2023-07-23 00:15:36 +00:00
|
|
|
|
extension NdbNote {
|
|
|
|
|
var is_textlike: Bool {
|
|
|
|
|
return kind == 1 || kind == 42 || kind == 30023
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-16 12:15:48 +00:00
|
|
|
|
var is_quote_repost: NoteId? {
|
|
|
|
|
guard kind == 1, let quoted_note_id = referenced_quote_ids.first else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return quoted_note_id.note_id
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-23 00:15:36 +00:00
|
|
|
|
var known_kind: NostrKind? {
|
2023-07-25 15:58:06 +00:00
|
|
|
|
return NostrKind.init(rawValue: kind)
|
2023-07-23 00:15:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var too_big: Bool {
|
|
|
|
|
return known_kind != .longform && self.content_len > 16000
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var should_show_event: Bool {
|
|
|
|
|
return !too_big
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 18:46:03 +00:00
|
|
|
|
func get_blocks(keypair: Keypair) -> Blocks {
|
|
|
|
|
return parse_note_content(content: .init(note: self, keypair: keypair))
|
2023-07-23 00:15:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-23 18:55:36 +00:00
|
|
|
|
// TODO: References iterator
|
2023-07-26 15:46:44 +00:00
|
|
|
|
public var referenced_ids: References<NoteId> {
|
|
|
|
|
References<NoteId>(tags: self.tags)
|
|
|
|
|
}
|
2023-10-06 22:54:05 +00:00
|
|
|
|
|
|
|
|
|
public var referenced_quote_ids: References<QuoteId> {
|
|
|
|
|
References<QuoteId>(tags: self.tags)
|
|
|
|
|
}
|
2023-07-26 15:46:44 +00:00
|
|
|
|
|
|
|
|
|
public var referenced_noterefs: References<NoteRef> {
|
|
|
|
|
References<NoteRef>(tags: self.tags)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public var referenced_follows: References<FollowRef> {
|
|
|
|
|
References<FollowRef>(tags: self.tags)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public var referenced_pubkeys: References<Pubkey> {
|
|
|
|
|
References<Pubkey>(tags: self.tags)
|
2023-07-23 18:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
public var referenced_hashtags: References<Hashtag> {
|
|
|
|
|
References<Hashtag>(tags: self.tags)
|
2023-07-23 18:55:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
public var referenced_params: References<ReplaceableParam> {
|
|
|
|
|
References<ReplaceableParam>(tags: self.tags)
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-18 01:17:38 +00:00
|
|
|
|
public var referenced_mute_items: References<MuteItem> {
|
|
|
|
|
References<MuteItem>(tags: self.tags)
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
public var references: References<RefId> {
|
|
|
|
|
References<RefId>(tags: self.tags)
|
2023-07-25 15:58:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 18:46:03 +00:00
|
|
|
|
func event_refs(_ keypair: Keypair) -> [EventRef] {
|
|
|
|
|
let refs = interpret_event_refs_ndb(blocks: self.blocks(keypair).blocks, tags: self.tags)
|
2023-07-23 00:15:36 +00:00
|
|
|
|
return refs
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 18:46:03 +00:00
|
|
|
|
func get_content(_ keypair: Keypair) -> String {
|
2023-07-24 17:55:34 +00:00
|
|
|
|
if known_kind == .dm {
|
2023-08-28 18:46:03 +00:00
|
|
|
|
return decrypted(keypair: keypair) ?? "*failed to decrypt content*"
|
2023-07-24 17:55:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
2024-02-10 16:36:46 +00:00
|
|
|
|
return content
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func maybe_get_content(_ keypair: Keypair) -> String? {
|
|
|
|
|
if known_kind == .dm {
|
|
|
|
|
return decrypted(keypair: keypair)
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-24 17:55:34 +00:00
|
|
|
|
return content
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 18:46:03 +00:00
|
|
|
|
func blocks(_ keypair: Keypair) -> Blocks {
|
2023-11-16 02:09:28 +00:00
|
|
|
|
return get_blocks(keypair: keypair)
|
2023-07-24 17:55:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NDBTODO: switch this to operating on bytes not strings
|
2023-08-28 18:46:03 +00:00
|
|
|
|
func decrypted(keypair: Keypair) -> String? {
|
2023-07-31 10:57:26 +00:00
|
|
|
|
if let decrypted_content {
|
2023-07-23 00:15:36 +00:00
|
|
|
|
return decrypted_content
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 18:46:03 +00:00
|
|
|
|
let our_pubkey = keypair.pubkey
|
2023-07-23 00:15:36 +00:00
|
|
|
|
|
2023-07-24 17:55:34 +00:00
|
|
|
|
// NDBTODO: don't hex encode
|
2023-07-25 15:58:06 +00:00
|
|
|
|
var pubkey = self.pubkey
|
2023-07-23 00:15:36 +00:00
|
|
|
|
// This is our DM, we need to use the pubkey of the person we're talking to instead
|
2023-07-24 17:55:34 +00:00
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
if our_pubkey == pubkey, let pk = self.referenced_pubkeys.first {
|
|
|
|
|
pubkey = pk
|
2023-07-23 00:15:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-24 17:55:34 +00:00
|
|
|
|
// NDBTODO: pass data to pubkey
|
2023-08-28 18:46:03 +00:00
|
|
|
|
let dec = decrypt_dm(keypair.privkey, pubkey: pubkey, content: self.content, encoding: .base64)
|
2023-07-23 00:15:36 +00:00
|
|
|
|
self.decrypted_content = dec
|
|
|
|
|
|
|
|
|
|
return dec
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 18:46:03 +00:00
|
|
|
|
public func direct_replies(_ keypair: Keypair) -> [NoteId] {
|
|
|
|
|
return event_refs(keypair).reduce(into: []) { acc, evref in
|
2023-07-23 00:15:36 +00:00
|
|
|
|
if let direct_reply = evref.is_direct_reply {
|
2023-07-26 15:46:44 +00:00
|
|
|
|
acc.append(direct_reply.note_id)
|
2023-07-23 00:15:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-24 19:40:27 +00:00
|
|
|
|
// NDBTODO: just use Id
|
2023-08-28 18:46:03 +00:00
|
|
|
|
public func thread_id(keypair: Keypair) -> NoteId {
|
|
|
|
|
for ref in event_refs(keypair) {
|
2023-07-23 00:15:36 +00:00
|
|
|
|
if let thread_id = ref.is_thread_id {
|
2023-07-26 15:46:44 +00:00
|
|
|
|
return thread_id.note_id
|
2023-07-23 00:15:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-25 15:58:06 +00:00
|
|
|
|
return self.id
|
2023-07-23 00:15:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
public func last_refid() -> NoteId? {
|
|
|
|
|
return self.referenced_ids.last
|
2023-07-23 00:15:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-24 19:40:27 +00:00
|
|
|
|
// NDBTODO: id -> data
|
2023-07-26 15:46:44 +00:00
|
|
|
|
/*
|
2023-07-24 19:40:27 +00:00
|
|
|
|
public func references(id: String, key: AsciiCharacter) -> Bool {
|
2023-07-26 15:46:44 +00:00
|
|
|
|
var matcher: (Reference) -> Bool = { ref in ref.ref_id.matches_str(id) }
|
|
|
|
|
if id.count == 64, let decoded = hex_decode(id) {
|
|
|
|
|
matcher = { ref in ref.ref_id.matches_id(decoded) }
|
|
|
|
|
}
|
2023-07-24 20:08:18 +00:00
|
|
|
|
for ref in References(tags: self.tags) {
|
2023-07-26 15:46:44 +00:00
|
|
|
|
if ref.key == key && matcher(ref) {
|
2023-07-24 19:40:27 +00:00
|
|
|
|
return true
|
2023-07-23 00:15:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
}
|
2023-07-26 15:46:44 +00:00
|
|
|
|
*/
|
2023-07-23 00:15:36 +00:00
|
|
|
|
|
2023-08-28 18:46:03 +00:00
|
|
|
|
func is_reply(_ keypair: Keypair) -> Bool {
|
|
|
|
|
return event_is_reply(self.event_refs(keypair))
|
2023-07-23 00:15:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-28 18:46:03 +00:00
|
|
|
|
func note_language(_ keypair: Keypair) -> String? {
|
2023-07-26 15:46:44 +00:00
|
|
|
|
assert(!Thread.isMainThread, "This function must not be run on the main thread.")
|
2023-07-23 00:15:36 +00:00
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
// Rely on Apple's NLLanguageRecognizer to tell us which language it thinks the note is in
|
|
|
|
|
// and filter on only the text portions of the content as URLs and hashtags confuse the language recognizer.
|
2023-08-28 18:46:03 +00:00
|
|
|
|
let originalBlocks = self.blocks(keypair).blocks
|
2023-08-21 21:17:21 +00:00
|
|
|
|
let originalOnlyText = originalBlocks.compactMap {
|
|
|
|
|
if case .text(let txt) = $0 {
|
2024-01-07 19:07:09 +00:00
|
|
|
|
// Replacing right single quotation marks (’) with "typewriter or ASCII apostrophes" (')
|
|
|
|
|
// as a workaround to get Apple's language recognizer to predict language the correctly.
|
|
|
|
|
// It is important to add this workaround to get the language right because it wastes users' money to send translation requests.
|
|
|
|
|
// Until Apple fixes their language model, this workaround will be kept in place.
|
|
|
|
|
// See https://en.wikipedia.org/wiki/Apostrophe#Unicode for an explanation of the differences between the two characters.
|
|
|
|
|
//
|
|
|
|
|
// For example,
|
|
|
|
|
// "nevent1qqs0wsknetaju06xk39cv8sttd064amkykqalvfue7ydtg3p0lyfksqzyrhxagf6h8l9cjngatumrg60uq22v66qz979pm32v985ek54ndh8gj42wtp"
|
|
|
|
|
// has the note content "It’s a meme".
|
|
|
|
|
// Without the character replacement, it is 61% confident that the text is in Turkish (tr) and 8% confident that the text is in English (en),
|
|
|
|
|
// which is a wildly incorrect hypothesis.
|
|
|
|
|
// With the character replacement, it is 65% confident that the text is in English (en) and 24% confident that the text is in Turkish (tr), which is more accurate.
|
|
|
|
|
//
|
|
|
|
|
// Similarly,
|
|
|
|
|
// "nevent1qqspjqlln6wvxrqg6kzl2p7gk0rgr5stc7zz5sstl34cxlw55gvtylgpp4mhxue69uhkummn9ekx7mqpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5qy28wumn8ghj7un9d3shjtnwdaehgu3wvfnsygpx6655ve67vqlcme9ld7ww73pqx7msclhwzu8lqmkhvuluxnyc7yhf3xut"
|
|
|
|
|
// has the note content "You’re funner".
|
|
|
|
|
// Without the character replacement, it is 52% confident that the text is in Norwegian Bokmål (nb) and 41% confident that the text is in English (en).
|
|
|
|
|
// With the character replacement, it is 93% confident that the text is in English (en) and 4% confident that the text is in Norwegian Bokmål (nb).
|
|
|
|
|
return txt.replacingOccurrences(of: "’", with: "'")
|
2023-08-21 21:17:21 +00:00
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.joined(separator: " ")
|
2023-07-23 00:15:36 +00:00
|
|
|
|
|
2024-01-07 19:07:09 +00:00
|
|
|
|
// If there is no text, there's nothing to use to detect language.
|
|
|
|
|
guard !originalOnlyText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
let languageRecognizer = NLLanguageRecognizer()
|
|
|
|
|
languageRecognizer.processString(originalOnlyText)
|
2023-07-23 00:15:36 +00:00
|
|
|
|
|
2024-01-07 19:07:09 +00:00
|
|
|
|
// Only accept language recognition hypothesis if there's at least a 50% probability that it's accurate.
|
2023-07-26 15:46:44 +00:00
|
|
|
|
guard let locale = languageRecognizer.languageHypotheses(withMaximum: 1).first(where: { $0.value >= 0.5 })?.key.rawValue else {
|
2024-01-07 19:07:09 +00:00
|
|
|
|
return nil
|
2023-07-24 19:40:27 +00:00
|
|
|
|
}
|
2023-07-23 00:15:36 +00:00
|
|
|
|
|
2023-07-26 15:46:44 +00:00
|
|
|
|
// Remove the variant component and just take the language part as translation services typically only supports the variant-less language.
|
|
|
|
|
// Moreover, speakers of one variant can generally understand other variants.
|
|
|
|
|
return localeToLanguage(locale)
|
2023-07-23 00:15:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var age: TimeInterval {
|
|
|
|
|
let event_date = Date(timeIntervalSince1970: TimeInterval(created_at))
|
|
|
|
|
return Date.now.timeIntervalSince(event_date)
|
|
|
|
|
}
|
2023-07-23 18:55:36 +00:00
|
|
|
|
}
|
2023-11-16 02:09:28 +00:00
|
|
|
|
|
|
|
|
|
func hex_encode(_ data: Data) -> String {
|
|
|
|
|
var str = ""
|
|
|
|
|
for c in data {
|
|
|
|
|
let c1 = hexchar(c >> 4)
|
|
|
|
|
let c2 = hexchar(c & 0xF)
|
|
|
|
|
|
|
|
|
|
str.append(Character(Unicode.Scalar(c1)))
|
|
|
|
|
str.append(Character(Unicode.Scalar(c2)))
|
|
|
|
|
}
|
|
|
|
|
return str
|
|
|
|
|
}
|