ndb: switch to nostrdb notes
This is a refactor of the codebase to use a more memory-efficient representation of notes. It should also be much faster at decoding since we're using a custom C json parser now. Changelog-Changed: Improved memory usage and performance when processing events
This commit is contained in:
@@ -30,9 +30,9 @@ class Contacts {
|
||||
func set_mutelist(_ ev: NostrEvent) {
|
||||
let oldlist = self.mutelist
|
||||
self.mutelist = ev
|
||||
|
||||
let old = Set(oldlist?.referenced_pubkeys.map({ $0.ref_id }) ?? [])
|
||||
let new = Set(ev.referenced_pubkeys.map({ $0.ref_id }))
|
||||
|
||||
let old = oldlist.map({ ev in Set(ev.referenced_pubkeys) }) ?? Set<Pubkey>()
|
||||
let new = Set(ev.referenced_pubkeys)
|
||||
let diff = old.symmetricDifference(new)
|
||||
|
||||
var new_mutes = Set<Pubkey>()
|
||||
@@ -40,14 +40,14 @@ class Contacts {
|
||||
|
||||
for d in diff {
|
||||
if new.contains(d) {
|
||||
new_mutes.insert(d.string())
|
||||
new_mutes.insert(d)
|
||||
} else {
|
||||
new_unmutes.insert(d.string())
|
||||
new_unmutes.insert(d)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: set local mutelist here
|
||||
self.muted = Set(ev.referenced_pubkeys.map({ $0.ref_id.string() }))
|
||||
self.muted = Set(ev.referenced_pubkeys)
|
||||
|
||||
if new_mutes.count > 0 {
|
||||
notify(.new_mutes(new_mutes))
|
||||
@@ -72,7 +72,7 @@ class Contacts {
|
||||
|
||||
func get_followed_hashtags() -> Set<String> {
|
||||
guard let ev = self.event else { return Set() }
|
||||
return Set(ev.referenced_hashtags.map({ $0.ref_id.string() }))
|
||||
return Set(ev.referenced_hashtags.map({ $0.hashtag }))
|
||||
}
|
||||
|
||||
func add_friend_pubkey(_ pubkey: Pubkey) {
|
||||
@@ -81,8 +81,7 @@ class Contacts {
|
||||
|
||||
func add_friend_contact(_ contact: NostrEvent) {
|
||||
friends.insert(contact.pubkey)
|
||||
for tag in contact.referenced_pubkeys {
|
||||
let pk = tag.id.string()
|
||||
for pk in contact.referenced_pubkeys {
|
||||
friend_of_friends.insert(pk)
|
||||
|
||||
// Exclude themself and us.
|
||||
@@ -122,7 +121,7 @@ class Contacts {
|
||||
}
|
||||
}
|
||||
|
||||
func follow_reference(box: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, follow: ReferencedId) -> NostrEvent? {
|
||||
func follow_reference(box: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, follow: FollowRef) -> NostrEvent? {
|
||||
guard let ev = follow_user_event(our_contacts: our_contacts, keypair: keypair, follow: follow) else {
|
||||
return nil
|
||||
}
|
||||
@@ -132,7 +131,7 @@ func follow_reference(box: PostBox, our_contacts: NostrEvent?, keypair: FullKeyp
|
||||
return ev
|
||||
}
|
||||
|
||||
func unfollow_reference(postbox: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, unfollow: ReferencedId) -> NostrEvent? {
|
||||
func unfollow_reference(postbox: PostBox, our_contacts: NostrEvent?, keypair: FullKeypair, unfollow: FollowRef) -> NostrEvent? {
|
||||
guard let cs = our_contacts else {
|
||||
return nil
|
||||
}
|
||||
@@ -146,14 +145,9 @@ func unfollow_reference(postbox: PostBox, our_contacts: NostrEvent?, keypair: Fu
|
||||
return ev
|
||||
}
|
||||
|
||||
func unfollow_reference_event(our_contacts: NostrEvent, keypair: FullKeypair, unfollow: ReferencedId) -> NostrEvent? {
|
||||
func unfollow_reference_event(our_contacts: NostrEvent, keypair: FullKeypair, unfollow: FollowRef) -> NostrEvent? {
|
||||
let tags = our_contacts.tags.reduce(into: [[String]]()) { ts, tag in
|
||||
if tag.count >= 2,
|
||||
let fst = unfollow.key.first,
|
||||
let afst = AsciiCharacter(fst),
|
||||
tag[0].matches_char(afst),
|
||||
tag[1].matches_str(unfollow.ref_id)
|
||||
{
|
||||
if let tag = FollowRef.from_tag(tag: tag), tag == unfollow {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -165,7 +159,7 @@ func unfollow_reference_event(our_contacts: NostrEvent, keypair: FullKeypair, un
|
||||
return NostrEvent(content: our_contacts.content, keypair: keypair.to_keypair(), kind: kind, tags: Array(tags))
|
||||
}
|
||||
|
||||
func follow_user_event(our_contacts: NostrEvent?, keypair: FullKeypair, follow: ReferencedId) -> NostrEvent? {
|
||||
func follow_user_event(our_contacts: NostrEvent?, keypair: FullKeypair, follow: FollowRef) -> NostrEvent? {
|
||||
guard let cs = our_contacts else {
|
||||
// don't create contacts for now so we don't nuke our contact list due to connectivity issues
|
||||
// we should only create contacts during profile creation
|
||||
@@ -219,12 +213,20 @@ func ensure_relay_info(relays: [RelayDescriptor], content: String) -> [String: R
|
||||
return relay_info
|
||||
}
|
||||
|
||||
func is_already_following(contacts: NostrEvent, follow: ReferencedId) -> Bool {
|
||||
guard let key = follow.key.first_char() else { return false }
|
||||
return contacts.references(id: follow.ref_id, key: key)
|
||||
func is_already_following(contacts: NostrEvent, follow: FollowRef) -> Bool {
|
||||
return contacts.references.contains { ref in
|
||||
switch (ref, follow) {
|
||||
case let (.hashtag(ht), .hashtag(follow_ht)):
|
||||
return ht.string() == follow_ht
|
||||
case let (.pubkey(pk), .pubkey(follow_pk)):
|
||||
return pk == follow_pk
|
||||
case (.hashtag, .pubkey), (.pubkey, .hashtag),
|
||||
(.event, _), (.quote, _), (.param, _):
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func follow_with_existing_contacts(keypair: FullKeypair, our_contacts: NostrEvent, follow: ReferencedId) -> NostrEvent? {
|
||||
func follow_with_existing_contacts(keypair: FullKeypair, our_contacts: NostrEvent, follow: FollowRef) -> NostrEvent? {
|
||||
// don't update if we're already following
|
||||
if is_already_following(contacts: our_contacts, follow: follow) {
|
||||
return nil
|
||||
@@ -233,7 +235,7 @@ func follow_with_existing_contacts(keypair: FullKeypair, our_contacts: NostrEven
|
||||
let kind = NostrKind.contacts.rawValue
|
||||
|
||||
var tags = our_contacts.tags.strings()
|
||||
tags.append(refid_to_tag(follow))
|
||||
tags.append(follow.tag)
|
||||
|
||||
return NostrEvent(content: our_contacts.content, keypair: keypair.to_keypair(), kind: kind, tags: tags)
|
||||
}
|
||||
|
||||
@@ -16,14 +16,6 @@ class CreateAccountModel: ObservableObject {
|
||||
@Published var privkey: Privkey = .empty
|
||||
@Published var profile_image: URL? = nil
|
||||
|
||||
var pubkey_bech32: String {
|
||||
return bech32_pubkey(self.pubkey) ?? ""
|
||||
}
|
||||
|
||||
var privkey_bech32: String {
|
||||
return bech32_privkey(self.privkey) ?? ""
|
||||
}
|
||||
|
||||
var rendered_name: String {
|
||||
if real_name.isEmpty {
|
||||
return nick_name
|
||||
|
||||
@@ -42,8 +42,8 @@ struct DamusState {
|
||||
// thread zaps
|
||||
if let ev = zap.event, !settings.nozaps, zap.is_in_thread {
|
||||
// [nozaps]: thread zaps are only available outside of the app store
|
||||
replies.count_replies(ev)
|
||||
events.add_replies(ev: ev)
|
||||
replies.count_replies(ev, privkey: self.keypair.privkey)
|
||||
events.add_replies(ev: ev, privkey: self.keypair.privkey)
|
||||
}
|
||||
|
||||
// associate with events as well
|
||||
|
||||
@@ -8,19 +8,17 @@
|
||||
import Foundation
|
||||
|
||||
enum EventRef: Equatable {
|
||||
case mention(Mention)
|
||||
case thread_id(ReferencedId)
|
||||
case reply(ReferencedId)
|
||||
case reply_to_root(ReferencedId)
|
||||
|
||||
var is_mention: Mention? {
|
||||
if case .mention(let m) = self {
|
||||
return m
|
||||
}
|
||||
case mention(Mention<NoteRef>)
|
||||
case thread_id(NoteRef)
|
||||
case reply(NoteRef)
|
||||
case reply_to_root(NoteRef)
|
||||
|
||||
var is_mention: NoteRef? {
|
||||
if case .mention(let m) = self { return m.ref }
|
||||
return nil
|
||||
}
|
||||
|
||||
var is_direct_reply: ReferencedId? {
|
||||
var is_direct_reply: NoteRef? {
|
||||
switch self {
|
||||
case .mention:
|
||||
return nil
|
||||
@@ -33,7 +31,7 @@ enum EventRef: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
var is_thread_id: ReferencedId? {
|
||||
var is_thread_id: NoteRef? {
|
||||
switch self {
|
||||
case .mention:
|
||||
return nil
|
||||
@@ -46,7 +44,7 @@ enum EventRef: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
var is_reply: ReferencedId? {
|
||||
var is_reply: NoteRef? {
|
||||
switch self {
|
||||
case .mention:
|
||||
return nil
|
||||
@@ -64,10 +62,8 @@ func build_mention_indices(_ blocks: [Block], type: MentionType) -> Set<Int> {
|
||||
return blocks.reduce(into: []) { acc, block in
|
||||
switch block {
|
||||
case .mention(let m):
|
||||
if m.type == type {
|
||||
if let idx = m.index {
|
||||
acc.insert(idx)
|
||||
}
|
||||
if m.ref.key == type, let idx = m.index {
|
||||
acc.insert(idx)
|
||||
}
|
||||
case .relay:
|
||||
return
|
||||
@@ -83,7 +79,7 @@ func build_mention_indices(_ blocks: [Block], type: MentionType) -> Set<Int> {
|
||||
}
|
||||
}
|
||||
|
||||
func interp_event_refs_without_mentions(_ refs: [ReferencedId]) -> [EventRef] {
|
||||
func interp_event_refs_without_mentions(_ refs: [NoteRef]) -> [EventRef] {
|
||||
if refs.count == 0 {
|
||||
return []
|
||||
}
|
||||
@@ -105,16 +101,15 @@ func interp_event_refs_without_mentions(_ refs: [ReferencedId]) -> [EventRef] {
|
||||
return evrefs
|
||||
}
|
||||
|
||||
func interp_event_refs_with_mentions(tags: [[String]], mention_indices: Set<Int>) -> [EventRef] {
|
||||
func interp_event_refs_with_mentions(tags: Tags, mention_indices: Set<Int>) -> [EventRef] {
|
||||
var mentions: [EventRef] = []
|
||||
var ev_refs: [ReferencedId] = []
|
||||
var ev_refs: [NoteRef] = []
|
||||
var i: Int = 0
|
||||
|
||||
|
||||
for tag in tags {
|
||||
if tag.count >= 2 && tag[0] == "e" {
|
||||
let ref = tag_to_refid(tag)!
|
||||
if let ref = NoteRef.from_tag(tag: tag) {
|
||||
if mention_indices.contains(i) {
|
||||
let mention = Mention(index: i, type: .event, ref: ref)
|
||||
let mention = Mention<NoteRef>(index: i, ref: ref)
|
||||
mentions.append(.mention(mention))
|
||||
} else {
|
||||
ev_refs.append(ref)
|
||||
@@ -128,18 +123,17 @@ func interp_event_refs_with_mentions(tags: [[String]], mention_indices: Set<Int>
|
||||
return replies
|
||||
}
|
||||
|
||||
func interpret_event_refs(blocks: [Block], tags: [[String]]) -> [EventRef] {
|
||||
func interpret_event_refs(blocks: [Block], tags: Tags) -> [EventRef] {
|
||||
if tags.count == 0 {
|
||||
return []
|
||||
}
|
||||
|
||||
/// build a set of indices for each event mention
|
||||
let mention_indices = build_mention_indices(blocks, type: .event)
|
||||
|
||||
let mention_indices = build_mention_indices(blocks, type: .e)
|
||||
|
||||
/// simpler case with no mentions
|
||||
if mention_indices.count == 0 {
|
||||
let ev_refs = get_referenced_ids(tags: tags, key: "e")
|
||||
return interp_event_refs_without_mentions(ev_refs)
|
||||
return interp_event_refs_without_mentions_ndb(References<NoteRef>(tags: tags))
|
||||
}
|
||||
|
||||
return interp_event_refs_with_mentions(tags: tags, mention_indices: mention_indices)
|
||||
|
||||
@@ -41,14 +41,11 @@ class EventsModel: ObservableObject {
|
||||
}
|
||||
|
||||
private func handle_event(relay_id: String, ev: NostrEvent) {
|
||||
guard ev.kind == kind.rawValue else {
|
||||
guard ev.kind == kind.rawValue,
|
||||
ev.referenced_ids.last == target else {
|
||||
return
|
||||
}
|
||||
|
||||
guard ev.referenced_ids.last?.ref_id.string() == target else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { a, b in a.created_at < b.created_at } ) {
|
||||
objectWillChange.send()
|
||||
}
|
||||
|
||||
@@ -10,7 +10,11 @@ import Foundation
|
||||
enum FollowTarget {
|
||||
case pubkey(Pubkey)
|
||||
case contact(NostrEvent)
|
||||
|
||||
|
||||
var follow_ref: FollowRef {
|
||||
FollowRef.pubkey(pubkey)
|
||||
}
|
||||
|
||||
var pubkey: Pubkey {
|
||||
switch self {
|
||||
case .pubkey(let pk): return pk
|
||||
|
||||
@@ -36,7 +36,7 @@ class FollowersModel: ObservableObject {
|
||||
func subscribe() {
|
||||
let filter = get_filter()
|
||||
let filters = [filter]
|
||||
print_filters(relay_id: "following", filters: [filters])
|
||||
//print_filters(relay_id: "following", filters: [filters])
|
||||
self.damus_state.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event)
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class FollowingModel {
|
||||
return
|
||||
}
|
||||
let filters = [filter]
|
||||
print_filters(relay_id: "following", filters: [filters])
|
||||
//print_filters(relay_id: "following", filters: [filters])
|
||||
self.damus_state.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event)
|
||||
}
|
||||
|
||||
|
||||
@@ -25,20 +25,17 @@ struct NewEventsBits: OptionSet {
|
||||
|
||||
enum Resubscribe {
|
||||
case following
|
||||
case unfollowing(ReferencedId)
|
||||
case unfollowing(FollowRef)
|
||||
}
|
||||
|
||||
enum HomeResubFilter {
|
||||
case pubkey(Pubkey)
|
||||
case hashtag(String)
|
||||
|
||||
init?(from: ReferencedId) {
|
||||
if from.key == "p" {
|
||||
self = .pubkey(from.ref_id)
|
||||
return
|
||||
} else if from.key == "t" {
|
||||
self = .hashtag(from.ref_id)
|
||||
return
|
||||
init?(from: FollowRef) {
|
||||
switch from {
|
||||
case .hashtag(let ht): self = .hashtag(ht.string())
|
||||
case .pubkey(let pk): self = .pubkey(pk)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -52,7 +49,9 @@ enum HomeResubFilter {
|
||||
if contacts.is_friend(ev.pubkey) {
|
||||
return false
|
||||
}
|
||||
return ev.references(id: ht, key: "t")
|
||||
return ev.referenced_hashtags.contains(where: { ref_ht in
|
||||
ht == ref_ht.hashtag
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,7 +62,6 @@ class HomeModel {
|
||||
|
||||
var damus_state: DamusState
|
||||
|
||||
var channels: [String: NostrEvent] = [:]
|
||||
// NDBTODO: let's get rid of this entirely, let nostrdb handle it
|
||||
var has_event: [String: Set<NoteId>] = [:]
|
||||
var deleted_events: Set<NoteId> = Set()
|
||||
@@ -183,10 +181,6 @@ class HomeModel {
|
||||
handle_dm(ev)
|
||||
case .delete:
|
||||
handle_delete_event(ev)
|
||||
case .channel_create:
|
||||
handle_channel_create(ev)
|
||||
case .channel_meta:
|
||||
break
|
||||
case .zap:
|
||||
handle_zap_event(ev)
|
||||
case .zap_request:
|
||||
@@ -262,10 +256,6 @@ class HomeModel {
|
||||
|
||||
}
|
||||
|
||||
func handle_channel_create(_ ev: NostrEvent) {
|
||||
self.channels[ev.id] = ev
|
||||
}
|
||||
|
||||
func filter_events() {
|
||||
events.filter { ev in
|
||||
!damus_state.contacts.is_muted(ev.pubkey)
|
||||
@@ -301,12 +291,11 @@ class HomeModel {
|
||||
}
|
||||
|
||||
func handle_boost_event(sub_id: String, _ ev: NostrEvent) {
|
||||
var boost_ev_id = ev.last_refid()?.ref_id
|
||||
var boost_ev_id = ev.last_refid()
|
||||
|
||||
if let inner_ev = ev.get_inner_event(cache: damus_state.events) {
|
||||
boost_ev_id = inner_ev.id
|
||||
|
||||
|
||||
|
||||
Task {
|
||||
guard validate_event(ev: inner_ev) == .ok else {
|
||||
return
|
||||
@@ -318,7 +307,6 @@ class HomeModel {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
guard let e = boost_ev_id else {
|
||||
@@ -345,14 +333,14 @@ class HomeModel {
|
||||
return
|
||||
}
|
||||
|
||||
switch damus_state.likes.add_event(ev, target: e.ref_id) {
|
||||
switch damus_state.likes.add_event(ev, target: e) {
|
||||
case .already_counted:
|
||||
break
|
||||
case .success(let n):
|
||||
handle_notification(ev: ev)
|
||||
let liked = Counted(event: ev, id: e.ref_id, total: n)
|
||||
let liked = Counted(event: ev, id: e, total: n)
|
||||
notify(.liked(liked))
|
||||
notify(.update_stats(note_id: e.ref_id))
|
||||
notify(.update_stats(note_id: e))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,14 +541,10 @@ class HomeModel {
|
||||
}
|
||||
}
|
||||
|
||||
guard let name = get_referenced_ids(tags: ev.tags, key: "d").first else {
|
||||
guard ev.referenced_params.contains(where: { p in p.param.matches_str("mute") }) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard name.ref_id == "mute" else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
damus_state.contacts.set_mutelist(ev)
|
||||
}
|
||||
|
||||
@@ -631,7 +615,7 @@ class HomeModel {
|
||||
|
||||
// TODO: will we need to process this in other places like zap request contents, etc?
|
||||
process_image_metadatas(cache: damus_state.events, ev: ev)
|
||||
damus_state.replies.count_replies(ev)
|
||||
damus_state.replies.count_replies(ev, privkey: self.damus_state.keypair.privkey)
|
||||
damus_state.events.insert(ev)
|
||||
|
||||
if sub_id == home_subid {
|
||||
@@ -699,33 +683,26 @@ func add_contact_if_friend(contacts: Contacts, ev: NostrEvent) {
|
||||
|
||||
func load_our_contacts(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) {
|
||||
let contacts = state.contacts
|
||||
var new_refs = Set<ReferencedId>()
|
||||
// our contacts
|
||||
for tag in ev.tags {
|
||||
guard let ref = tag_to_refid(tag) else { continue }
|
||||
new_refs.insert(ref)
|
||||
}
|
||||
|
||||
var old_refs = Set<ReferencedId>()
|
||||
// find removed contacts
|
||||
if let old_ev = m_old_ev {
|
||||
for tag in old_ev.tags {
|
||||
guard let ref = tag_to_refid(tag) else { continue }
|
||||
old_refs.insert(ref)
|
||||
}
|
||||
}
|
||||
|
||||
let new_refs = Set<FollowRef>(ev.referenced_follows)
|
||||
let old_refs = m_old_ev.map({ old_ev in Set(old_ev.referenced_follows) }) ?? Set()
|
||||
|
||||
let diff = new_refs.symmetricDifference(old_refs)
|
||||
for ref in diff {
|
||||
if new_refs.contains(ref) {
|
||||
notify(.followed(ref))
|
||||
if ref.key == "p" {
|
||||
contacts.add_friend_pubkey(ref.ref_id)
|
||||
switch ref {
|
||||
case .pubkey(let pk):
|
||||
contacts.add_friend_pubkey(pk)
|
||||
case .hashtag:
|
||||
// I guess I could cache followed hashtags here... whatever
|
||||
break
|
||||
}
|
||||
} else {
|
||||
notify(.unfollowed(ref))
|
||||
if ref.key == "p" {
|
||||
contacts.remove_friend(ref.ref_id)
|
||||
switch ref {
|
||||
case .pubkey(let pk):
|
||||
contacts.remove_friend(pk)
|
||||
case .hashtag: break
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -758,6 +735,7 @@ func abbrev_ids_field(_ n: String, _ ids: [String]?) -> String {
|
||||
return "\(n): \(abbrev_ids(ids))"
|
||||
}
|
||||
|
||||
/*
|
||||
func print_filter(_ f: NostrFilter) {
|
||||
let fmt = [
|
||||
abbrev_ids_field("ids", f.ids),
|
||||
@@ -783,6 +761,7 @@ func print_filters(relay_id: String?, filters groups: [[NostrFilter]]) {
|
||||
}
|
||||
print("-----")
|
||||
}
|
||||
*/
|
||||
|
||||
func process_metadata_profile(our_pubkey: Pubkey, profiles: Profiles, profile: Profile, ev: NostrEvent) {
|
||||
var old_nip05: String? = nil
|
||||
@@ -1003,7 +982,7 @@ func handle_incoming_dm(debouncer: Debouncer?, ev: NostrEvent, our_pubkey: Pubke
|
||||
var the_pk = ev.pubkey
|
||||
if ours {
|
||||
if let ref_pk = ev.referenced_pubkeys.first {
|
||||
the_pk = ref_pk.ref_id
|
||||
the_pk = ref_pk
|
||||
} else {
|
||||
// self dm!?
|
||||
print("TODO: handle self dm?")
|
||||
@@ -1123,14 +1102,8 @@ func handle_last_events(debouncer: Debouncer?, new_events: NewEventsBits, ev: No
|
||||
|
||||
|
||||
/// Sometimes we get garbage in our notifications. Ensure we have our pubkey on this event
|
||||
func event_has_our_pubkey(_ ev: NostrEvent, our_pubkey: String) -> Bool {
|
||||
for tag in ev.tags {
|
||||
if tag.count >= 2 && tag[0] == "p" && tag[1] == our_pubkey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
func event_has_our_pubkey(_ ev: NostrEvent, our_pubkey: Pubkey) -> Bool {
|
||||
return ev.referenced_pubkeys.contains(our_pubkey)
|
||||
}
|
||||
|
||||
|
||||
@@ -1185,7 +1158,7 @@ func create_in_app_profile_zap_notification(profiles: Profiles, zap: Zap, locale
|
||||
content.title = zap_notification_title(zap)
|
||||
content.body = zap_notification_body(profiles: profiles, zap: zap, locale: locale)
|
||||
content.sound = UNNotificationSound.default
|
||||
content.userInfo = LossyLocalNotification(type: .profile_zap, event_id: profile_id).to_user_info()
|
||||
content.userInfo = LossyLocalNotification(type: .profile_zap, mention: .pubkey(profile_id)).to_user_info()
|
||||
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
||||
|
||||
@@ -1206,7 +1179,7 @@ func create_in_app_event_zap_notification(profiles: Profiles, zap: Zap, locale:
|
||||
content.title = zap_notification_title(zap)
|
||||
content.body = zap_notification_body(profiles: profiles, zap: zap, locale: locale)
|
||||
content.sound = UNNotificationSound.default
|
||||
content.userInfo = LossyLocalNotification(type: .zap, event_id: evId).to_user_info()
|
||||
content.userInfo = LossyLocalNotification(type: .zap, mention: .note(evId)).to_user_info()
|
||||
|
||||
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
|
||||
|
||||
@@ -1264,19 +1237,26 @@ func process_local_notification(damus_state: DamusState, event ev: NostrEvent) {
|
||||
return
|
||||
}
|
||||
|
||||
if type == .text && damus_state.settings.mention_notification {
|
||||
if type == .text, damus_state.settings.mention_notification {
|
||||
let blocks = ev.blocks(damus_state.keypair.privkey).blocks
|
||||
for case .mention(let mention) in blocks where mention.ref.ref_id == damus_state.keypair.pubkey {
|
||||
for case .mention(let mention) in blocks {
|
||||
guard case .pubkey(let pk) = mention.ref, pk == damus_state.keypair.pubkey else {
|
||||
continue
|
||||
}
|
||||
let content_preview = render_notification_content_preview(cache: damus_state.events, ev: ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey)
|
||||
let notify = LocalNotification(type: .mention, event: ev, target: ev, content: content_preview)
|
||||
create_local_notification(profiles: damus_state.profiles, notify: notify )
|
||||
}
|
||||
} else if type == .boost && damus_state.settings.repost_notification, let inner_ev = ev.get_inner_event(cache: damus_state.events) {
|
||||
} else if type == .boost,
|
||||
damus_state.settings.repost_notification,
|
||||
let inner_ev = ev.get_inner_event(cache: damus_state.events)
|
||||
{
|
||||
let content_preview = render_notification_content_preview(cache: damus_state.events, ev: inner_ev, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey)
|
||||
let notify = LocalNotification(type: .repost, event: ev, target: inner_ev, content: content_preview)
|
||||
create_local_notification(profiles: damus_state.profiles, notify: notify)
|
||||
} else if type == .like && damus_state.settings.like_notification,
|
||||
let evid = ev.referenced_ids.last?.ref_id,
|
||||
} else if type == .like,
|
||||
damus_state.settings.like_notification,
|
||||
let evid = ev.referenced_ids.last,
|
||||
let liked_event = damus_state.events.lookup(evid)
|
||||
{
|
||||
let content_preview = render_notification_content_preview(cache: damus_state.events, ev: liked_event, profiles: damus_state.profiles, privkey: damus_state.keypair.privkey)
|
||||
@@ -1335,22 +1315,35 @@ enum ProcessZapResult {
|
||||
case failed
|
||||
}
|
||||
|
||||
extension Sequence {
|
||||
func just_one() -> Element? {
|
||||
var got_one = false
|
||||
var the_x: Element? = nil
|
||||
for x in self {
|
||||
guard !got_one else {
|
||||
return nil
|
||||
}
|
||||
the_x = x
|
||||
got_one = true
|
||||
}
|
||||
return the_x
|
||||
}
|
||||
}
|
||||
|
||||
// securely get the zap target's pubkey. this can be faked so we need to be
|
||||
// careful
|
||||
func get_zap_target_pubkey(ev: NostrEvent, events: EventCache) -> Pubkey? {
|
||||
let etags = ev.referenced_ids
|
||||
let etags = Array(ev.referenced_ids)
|
||||
|
||||
guard let etag = etags.first else {
|
||||
// no etags, ptag-only case
|
||||
|
||||
let ptags = ev.referenced_pubkeys
|
||||
|
||||
// ensure that there is only 1 ptag to stop fake profile zap attacks
|
||||
guard ptags.count == 1 else {
|
||||
guard let a = ev.referenced_pubkeys.just_one() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ptags.first?.id
|
||||
// TODO: just return data here
|
||||
return a
|
||||
}
|
||||
|
||||
// we have an e-tag
|
||||
@@ -1361,7 +1354,7 @@ func get_zap_target_pubkey(ev: NostrEvent, events: EventCache) -> Pubkey? {
|
||||
}
|
||||
|
||||
// we can't trust the p tag on note zaps because they can be faked
|
||||
return events.lookup(etag.id)?.pubkey
|
||||
return events.lookup(etag)?.pubkey
|
||||
}
|
||||
|
||||
func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @escaping (ProcessZapResult) -> Void) {
|
||||
|
||||
@@ -7,31 +7,93 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
enum MentionType {
|
||||
case pubkey
|
||||
case event
|
||||
enum MentionType: AsciiCharacter, TagKey {
|
||||
case p
|
||||
case e
|
||||
|
||||
var ref: String {
|
||||
var keychar: AsciiCharacter {
|
||||
self.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
enum MentionRef: TagKeys, TagConvertible, Equatable, Hashable {
|
||||
case pubkey(Pubkey) // TODO: handle nprofile
|
||||
case note(NoteId)
|
||||
|
||||
var key: MentionType {
|
||||
switch self {
|
||||
case .pubkey:
|
||||
return "p"
|
||||
case .event:
|
||||
return "e"
|
||||
case .pubkey: return .p
|
||||
case .note: return .e
|
||||
}
|
||||
}
|
||||
|
||||
var bech32: String {
|
||||
switch self {
|
||||
case .pubkey(let pubkey): return bech32_pubkey(pubkey)
|
||||
case .note(let noteId): return bech32_note_id(noteId)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
var tag: [String] {
|
||||
switch self {
|
||||
case .pubkey(let pubkey): return ["p", pubkey.hex()]
|
||||
case .note(let noteId): return ["e", noteId.hex()]
|
||||
}
|
||||
}
|
||||
|
||||
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 id = i.next()?.id()
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch mention_type {
|
||||
case .p: return .pubkey(Pubkey(id))
|
||||
case .e: return .note(NoteId(id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Mention: Equatable {
|
||||
struct Mention<T: Equatable>: Equatable {
|
||||
let index: Int?
|
||||
let type: MentionType
|
||||
let ref: ReferencedId
|
||||
let ref: T
|
||||
|
||||
static func note(_ id: String) -> Mention {
|
||||
return Mention(index: nil, type: .event, ref: .e(id))
|
||||
static func any(_ mention_id: MentionRef, index: Int? = nil) -> Mention<MentionRef> {
|
||||
return Mention<MentionRef>(index: index, ref: mention_id)
|
||||
}
|
||||
|
||||
static func pubkey(_ pubkey: String) -> Mention {
|
||||
return Mention(index: nil, type: .pubkey, ref: .p(pubkey))
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +142,7 @@ enum Block: Equatable {
|
||||
}
|
||||
|
||||
case text(String)
|
||||
case mention(Mention)
|
||||
case mention(Mention<MentionRef>)
|
||||
case hashtag(String)
|
||||
case url(URL)
|
||||
case invoice(Invoice)
|
||||
@@ -116,14 +178,14 @@ enum Block: Equatable {
|
||||
}
|
||||
|
||||
var is_note_mention: Bool {
|
||||
guard case .mention(let mention) = self else {
|
||||
return false
|
||||
if case .mention(let mention) = self,
|
||||
case .note = mention.ref {
|
||||
return true
|
||||
}
|
||||
|
||||
return mention.type == .event
|
||||
return false
|
||||
}
|
||||
|
||||
var is_mention: Mention? {
|
||||
var is_mention: Mention<MentionRef>? {
|
||||
if case .mention(let m) = self {
|
||||
return m
|
||||
}
|
||||
@@ -137,12 +199,11 @@ func render_blocks(blocks: [Block]) -> String {
|
||||
case .mention(let m):
|
||||
if let idx = m.index {
|
||||
return str + "#[\(idx)]"
|
||||
} else if m.type == .pubkey, let pk = bech32_pubkey(m.ref.ref_id) {
|
||||
return str + "nostr:\(pk)"
|
||||
} else if let note_id = bech32_note_id(m.ref.ref_id) {
|
||||
return str + "nostr:\(note_id)"
|
||||
} else {
|
||||
return str + m.ref.ref_id
|
||||
}
|
||||
|
||||
switch m.ref {
|
||||
case .pubkey(let pk): return str + "nostr:\(pk.npub)"
|
||||
case .note(let note_id): return str + "nostr:\(note_id.bech32)"
|
||||
}
|
||||
case .relay(let relay):
|
||||
return str + relay
|
||||
@@ -163,43 +224,13 @@ struct Blocks: Equatable {
|
||||
let blocks: [Block]
|
||||
}
|
||||
|
||||
func parse_note_content(content: String, tags: [[String]]) -> Blocks {
|
||||
var out: [Block] = []
|
||||
|
||||
var bs = note_blocks()
|
||||
bs.num_blocks = 0;
|
||||
|
||||
blocks_init(&bs)
|
||||
|
||||
let bytes = content.utf8CString
|
||||
let _ = bytes.withUnsafeBufferPointer { p in
|
||||
damus_parse_content(&bs, p.baseAddress)
|
||||
}
|
||||
|
||||
var i = 0
|
||||
while (i < bs.num_blocks) {
|
||||
let block = bs.blocks[i]
|
||||
|
||||
if let converted = convert_block(block, tags: tags) {
|
||||
out.append(converted)
|
||||
}
|
||||
|
||||
i += 1
|
||||
}
|
||||
|
||||
let words = Int(bs.words)
|
||||
blocks_free(&bs)
|
||||
|
||||
return Blocks(words: words, blocks: out)
|
||||
}
|
||||
|
||||
func strblock_to_string(_ s: str_block_t) -> String? {
|
||||
let len = s.end - s.start
|
||||
let bytes = Data(bytes: s.start, count: len)
|
||||
return String(bytes: bytes, encoding: .utf8)
|
||||
}
|
||||
|
||||
func convert_block(_ b: block_t, tags: [[String]]) -> Block? {
|
||||
func convert_block(_ b: block_t, tags: TagsSequence?) -> Block? {
|
||||
if b.type == BLOCK_HASHTAG {
|
||||
guard let str = strblock_to_string(b.block.str) else {
|
||||
return nil
|
||||
@@ -211,7 +242,7 @@ func convert_block(_ b: block_t, tags: [[String]]) -> Block? {
|
||||
}
|
||||
return .text(str)
|
||||
} else if b.type == BLOCK_MENTION_INDEX {
|
||||
return convert_mention_index_block(ind: b.block.mention_index, tags: tags)
|
||||
return convert_mention_index_block(ind: Int(b.block.mention_index), tags: tags)
|
||||
} else if b.type == BLOCK_URL {
|
||||
return convert_url_block(b.block.str)
|
||||
} else if b.type == BLOCK_INVOICE {
|
||||
@@ -321,41 +352,29 @@ func convert_mention_bech32_block(_ b: mention_bech32_block) -> Block?
|
||||
switch b.bech32.type {
|
||||
case NOSTR_BECH32_NOTE:
|
||||
let note = b.bech32.data.note;
|
||||
let event_id = hex_encode(Data(bytes: note.event_id, count: 32))
|
||||
let event_id_ref = ReferencedId(ref_id: event_id, relay_id: nil, key: "e")
|
||||
return .mention(Mention(index: nil, type: .event, ref: event_id_ref))
|
||||
|
||||
let note_id = NoteId(Data(bytes: note.event_id, count: 32))
|
||||
return .mention(.any(.note(note_id)))
|
||||
|
||||
case NOSTR_BECH32_NEVENT:
|
||||
let nevent = b.bech32.data.nevent;
|
||||
let event_id = hex_encode(Data(bytes: nevent.event_id, count: 32))
|
||||
var relay_id: String? = nil
|
||||
if nevent.relays.num_relays > 0 {
|
||||
relay_id = strblock_to_string(nevent.relays.relays.0)
|
||||
}
|
||||
let event_id_ref = ReferencedId(ref_id: event_id, relay_id: relay_id, key: "e")
|
||||
return .mention(Mention(index: nil, type: .event, ref: event_id_ref))
|
||||
let note_id = NoteId(Data(bytes: nevent.event_id, count: 32))
|
||||
return .mention(.any(.note(note_id)))
|
||||
|
||||
case NOSTR_BECH32_NPUB:
|
||||
let npub = b.bech32.data.npub
|
||||
let pubkey = hex_encode(Data(bytes: npub.pubkey, count: 32))
|
||||
let pubkey_ref = ReferencedId(ref_id: pubkey, relay_id: nil, key: "p")
|
||||
return .mention(Mention(index: nil, type: .pubkey, ref: pubkey_ref))
|
||||
let pubkey = Pubkey(Data(bytes: npub.pubkey, count: 32))
|
||||
return .mention(.any(.pubkey(pubkey)))
|
||||
|
||||
case NOSTR_BECH32_NSEC:
|
||||
let nsec = b.bech32.data.nsec
|
||||
let nsec_bytes = Data(bytes: nsec.nsec, count: 32)
|
||||
let pubkey = privkey_to_pubkey_raw(sec: nsec_bytes.bytes) ?? hex_encode(nsec_bytes)
|
||||
return .mention(.pubkey(pubkey))
|
||||
let privkey = Privkey(Data(bytes: nsec.nsec, count: 32))
|
||||
guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
|
||||
return .mention(.any(.pubkey(pubkey)))
|
||||
|
||||
case NOSTR_BECH32_NPROFILE:
|
||||
let nprofile = b.bech32.data.nprofile
|
||||
let pubkey = hex_encode(Data(bytes: nprofile.pubkey, count: 32))
|
||||
var relay_id: String? = nil
|
||||
if nprofile.relays.num_relays > 0 {
|
||||
relay_id = strblock_to_string(nprofile.relays.relays.0)
|
||||
}
|
||||
let pubkey_ref = ReferencedId(ref_id: pubkey, relay_id: relay_id, key: "p")
|
||||
return .mention(Mention(index: nil, type: .pubkey, ref: pubkey_ref))
|
||||
let pubkey = Pubkey(Data(bytes: nprofile.pubkey, count: 32))
|
||||
return .mention(.any(.pubkey(pubkey)))
|
||||
|
||||
case NOSTR_BECH32_NRELAY:
|
||||
let nrelay = b.bech32.data.nrelay
|
||||
@@ -388,24 +407,22 @@ func convert_invoice_description(b11: bolt11) -> InvoiceDescription? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func convert_mention_index_block(ind: Int32, tags: [[String]]) -> Block?
|
||||
func convert_mention_index_block(ind: Int, tags: TagsSequence?) -> Block?
|
||||
{
|
||||
let ind = Int(ind)
|
||||
|
||||
if ind < 0 || (ind + 1 > tags.count) || tags[ind].count < 2 {
|
||||
guard let tags,
|
||||
ind >= 0,
|
||||
ind + 1 <= tags.count
|
||||
else {
|
||||
return .text("#[\(ind)]")
|
||||
}
|
||||
|
||||
|
||||
let tag = tags[ind]
|
||||
guard let mention_type = parse_mention_type(tag[0]) else {
|
||||
|
||||
guard let mention = MentionRef.from_tag(tag: tag) else {
|
||||
return .text("#[\(ind)]")
|
||||
}
|
||||
|
||||
guard let ref = tag_to_refid(tag) else {
|
||||
return .text("#[\(ind)]")
|
||||
}
|
||||
|
||||
return .mention(Mention(index: ind, type: mention_type, ref: ref))
|
||||
|
||||
return .mention(.any(mention, index: ind))
|
||||
}
|
||||
|
||||
func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? {
|
||||
@@ -427,25 +444,6 @@ struct PostTags {
|
||||
let tags: [[String]]
|
||||
}
|
||||
|
||||
func parse_mention_type_ndb(_ tag: NdbTagElem) -> MentionType? {
|
||||
if tag.matches_char("e") {
|
||||
return .event
|
||||
} else if tag.matches_char("p") {
|
||||
return .pubkey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parse_mention_type(_ c: String) -> MentionType? {
|
||||
if c == "e" {
|
||||
return .event
|
||||
} else if c == "p" {
|
||||
return .pubkey
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/// Convert
|
||||
func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags {
|
||||
var new_tags = tags
|
||||
@@ -453,12 +451,11 @@ func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags {
|
||||
for post_block in post_blocks {
|
||||
switch post_block {
|
||||
case .mention(let mention):
|
||||
let mention_type = mention.type
|
||||
if mention_type == .event {
|
||||
if case .note = mention.ref {
|
||||
continue
|
||||
}
|
||||
|
||||
new_tags.append(refid_to_tag(mention.ref))
|
||||
new_tags.append(mention.ref.tag)
|
||||
case .hashtag(let hashtag):
|
||||
new_tags.append(["t", hashtag.lowercased()])
|
||||
case .text: break
|
||||
@@ -474,7 +471,7 @@ func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags {
|
||||
}
|
||||
|
||||
func post_to_event(post: NostrPost, keypair: FullKeypair) -> NostrEvent? {
|
||||
let tags = post.references.map(refid_to_tag) + post.tags
|
||||
let tags = post.references.map({ r in r.tag }) + post.tags
|
||||
let post_blocks = parse_post_blocks(content: post.content)
|
||||
let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags)
|
||||
let content = render_blocks(blocks: post_tags.blocks)
|
||||
|
||||
@@ -13,7 +13,11 @@ fileprivate func getMutedThreadsKey(pubkey: Pubkey) -> String {
|
||||
|
||||
func loadMutedThreads(pubkey: Pubkey) -> [NoteId] {
|
||||
let key = getMutedThreadsKey(pubkey: pubkey)
|
||||
return UserDefaults.standard.stringArray(forKey: key) ?? []
|
||||
let xs = UserDefaults.standard.stringArray(forKey: key) ?? []
|
||||
return xs.reduce(into: [NoteId]()) { ids, k in
|
||||
guard let note_id = hex_decode(k) else { return }
|
||||
ids.append(NoteId(Data(note_id)))
|
||||
}
|
||||
}
|
||||
|
||||
func saveMutedThreads(pubkey: Pubkey, currentValue: [NoteId], value: [NoteId]) -> Bool {
|
||||
|
||||
@@ -193,17 +193,15 @@ class NotificationsModel: ObservableObject, ScrollQueue {
|
||||
}
|
||||
|
||||
private func insert_reaction(_ ev: NostrEvent) -> Bool {
|
||||
guard let ref_id = ev.referenced_ids.last else {
|
||||
guard let id = ev.referenced_ids.last else {
|
||||
return false
|
||||
}
|
||||
|
||||
let id = ref_id.id
|
||||
|
||||
if let evgrp = self.reactions[id.string()] {
|
||||
|
||||
if let evgrp = self.reactions[id] {
|
||||
return evgrp.insert(ev)
|
||||
} else {
|
||||
let evgrp = EventGroup()
|
||||
self.reactions[id.string()] = evgrp
|
||||
self.reactions[id] = evgrp
|
||||
return evgrp.insert(ev)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,10 @@ import Foundation
|
||||
struct NostrPost {
|
||||
let kind: NostrKind
|
||||
let content: String
|
||||
let references: [ReferencedId]
|
||||
let references: [RefId]
|
||||
let tags: [[String]]
|
||||
|
||||
init(content: String, references: [ReferencedId], kind: NostrKind = .text, tags: [[String]] = []) {
|
||||
|
||||
init(content: String, references: [RefId], kind: NostrKind = .text, tags: [[String]] = []) {
|
||||
self.content = content
|
||||
self.references = references
|
||||
self.kind = kind
|
||||
@@ -21,96 +21,9 @@ struct NostrPost {
|
||||
}
|
||||
}
|
||||
|
||||
func parse_post_mention_type(_ p: Parser) -> MentionType? {
|
||||
if parse_char(p, "@") {
|
||||
return .pubkey
|
||||
}
|
||||
|
||||
if parse_char(p, "&") {
|
||||
return .event
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parse_post_reference(_ p: Parser) -> ReferencedId? {
|
||||
let start = p.pos
|
||||
|
||||
guard let typ = parse_post_mention_type(p) else {
|
||||
return parse_nostr_ref_uri(p)
|
||||
}
|
||||
|
||||
if let ref = parse_post_mention(p, mention_type: typ) {
|
||||
return ref
|
||||
}
|
||||
|
||||
p.pos = start
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func is_bech32_char(_ c: Character) -> Bool {
|
||||
let contains = "qpzry9x8gf2tvdw0s3jn54khce6mua7l".contains(c)
|
||||
return contains
|
||||
}
|
||||
|
||||
func parse_post_mention(_ p: Parser, mention_type: MentionType) -> ReferencedId? {
|
||||
if let id = parse_hexstr(p, len: 64) {
|
||||
return ReferencedId(ref_id: id, relay_id: nil, key: mention_type.ref)
|
||||
} else if let bech32_ref = parse_post_bech32_mention(p) {
|
||||
return bech32_ref
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: replace this with our C parser
|
||||
func parse_post_bech32_mention(_ p: Parser) -> ReferencedId? {
|
||||
let start = p.pos
|
||||
if parse_str(p, "note") {
|
||||
} else if parse_str(p, "npub") {
|
||||
} else if parse_str(p, "nsec") {
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !parse_char(p, "1") {
|
||||
p.pos = start
|
||||
return nil
|
||||
}
|
||||
|
||||
guard consume_until(p, match: { c in !is_bech32_char(c) }, end_ok: true) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let end = p.pos
|
||||
|
||||
let sliced = String(substring(p.str, start: start, end: end))
|
||||
guard let decoded = try? bech32_decode(sliced) else {
|
||||
p.pos = start
|
||||
return nil
|
||||
}
|
||||
|
||||
let hex = hex_encode(decoded.data)
|
||||
switch decoded.hrp {
|
||||
case "note":
|
||||
return ReferencedId(ref_id: hex, relay_id: nil, key: "e")
|
||||
case "npub":
|
||||
return ReferencedId(ref_id: hex, relay_id: nil, key: "p")
|
||||
case "nsec":
|
||||
guard let pubkey = privkey_to_pubkey(privkey: hex) else {
|
||||
p.pos = start
|
||||
return nil
|
||||
}
|
||||
return ReferencedId(ref_id: pubkey, relay_id: nil, key: "p")
|
||||
default:
|
||||
p.pos = start
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a list of tags
|
||||
func parse_post_blocks(content: String) -> [Block] {
|
||||
return parse_note_content(content: content, tags: []).blocks
|
||||
return parse_note_content(content: .content(content, nil)).blocks
|
||||
}
|
||||
|
||||
|
||||
@@ -6,34 +6,3 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum PostBlock {
|
||||
case text(String)
|
||||
case ref(ReferencedId)
|
||||
case hashtag(String)
|
||||
|
||||
var is_text: String? {
|
||||
if case .text(let txt) = self {
|
||||
return txt
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var is_hashtag: String? {
|
||||
if case .hashtag(let ht) = self {
|
||||
return ht
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var is_ref: ReferencedId? {
|
||||
if case .ref(let ref) = self {
|
||||
return ref
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func parse_post_textblock(str: String, from: Int, to: Int) -> PostBlock {
|
||||
return .text(String(substring(str, start: from, end: to)))
|
||||
}
|
||||
|
||||
@@ -33,17 +33,8 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
guard let contacts = self.contacts else {
|
||||
return false
|
||||
}
|
||||
|
||||
for tag in contacts.tags {
|
||||
guard tag.count >= 2,
|
||||
tag[0].matches_char("p"),
|
||||
tag[1].matches_str(pubkey)
|
||||
else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
|
||||
return contacts.referenced_pubkeys.contains(pubkey)
|
||||
}
|
||||
|
||||
func get_follow_target() -> FollowTarget {
|
||||
@@ -77,7 +68,7 @@ class ProfileModel: ObservableObject, Equatable {
|
||||
text_filter.limit = 500
|
||||
|
||||
print("subscribing to profile \(pubkey) with sub_id \(sub_id)")
|
||||
print_filters(relay_id: "profile", filters: [[text_filter], [profile_filter]])
|
||||
//print_filters(relay_id: "profile", filters: [[text_filter], [profile_filter]])
|
||||
damus.pool.subscribe(sub_id: sub_id, filters: [text_filter], handler: handle_event)
|
||||
damus.pool.subscribe(sub_id: prof_subid, filters: [profile_filter], handler: handle_event)
|
||||
}
|
||||
|
||||
@@ -12,21 +12,20 @@ struct ReplyDesc {
|
||||
let others: Int
|
||||
}
|
||||
|
||||
func make_reply_description(_ tags: [[String]]) -> ReplyDesc {
|
||||
func make_reply_description(_ tags: Tags) -> ReplyDesc {
|
||||
var c = 0
|
||||
var ns: [Pubkey] = []
|
||||
var i = tags.count - 1
|
||||
|
||||
while i >= 0 {
|
||||
let tag = tags[i]
|
||||
if tag.count >= 2 && tag[0] == "p" {
|
||||
var i = tags.count
|
||||
|
||||
for tag in tags {
|
||||
if let pk = Pubkey.from_tag(tag: tag) {
|
||||
c += 1
|
||||
if ns.count < 2 {
|
||||
ns.append(tag[1])
|
||||
ns.append(pk)
|
||||
}
|
||||
}
|
||||
i -= 1
|
||||
}
|
||||
|
||||
|
||||
return ReplyDesc(pubkeys: ns, others: c)
|
||||
}
|
||||
|
||||
@@ -68,10 +68,6 @@ class SearchModel: ObservableObject {
|
||||
let (sub_id, done) = handle_subid_event(pool: state.pool, relay_id: relay_id, ev: ev) { sub_id, ev in
|
||||
if ev.is_textlike && ev.should_show_event {
|
||||
self.add_event(ev)
|
||||
} else if ev.known_kind == .channel_create {
|
||||
// unimplemented
|
||||
} else if ev.known_kind == .channel_meta {
|
||||
// unimplemented
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,16 +85,16 @@ class SearchModel: ObservableObject {
|
||||
|
||||
func event_matches_hashtag(_ ev: NostrEvent, hashtags: [String]) -> Bool {
|
||||
for tag in ev.tags {
|
||||
if tag_is_hashtag(tag) && hashtags.contains(tag[1]) {
|
||||
if tag_is_hashtag(tag) && hashtags.contains(tag[1].string()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func tag_is_hashtag(_ tag: [String]) -> Bool {
|
||||
func tag_is_hashtag(_ tag: Tag) -> Bool {
|
||||
// "hashtag" is deprecated, will remove in the future
|
||||
return tag.count >= 2 && (tag[0] == "hashtag" || tag[0] == "t")
|
||||
return tag.count >= 2 && tag[0].matches_char("t")
|
||||
}
|
||||
|
||||
func event_matches_filter(_ ev: NostrEvent, filter: NostrFilter) -> Bool {
|
||||
|
||||
@@ -18,7 +18,7 @@ class ThreadModel: ObservableObject {
|
||||
self.event_map = Set()
|
||||
self.event = event
|
||||
self.original_event = event
|
||||
add_event(event)
|
||||
add_event(event, privkey: damus_state.keypair.privkey)
|
||||
}
|
||||
|
||||
var is_original: Bool {
|
||||
@@ -46,10 +46,10 @@ class ThreadModel: ObservableObject {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func set_active_event(_ ev: NostrEvent) -> Bool {
|
||||
func set_active_event(_ ev: NostrEvent, privkey: Privkey?) -> Bool {
|
||||
self.event = ev
|
||||
add_event(ev)
|
||||
|
||||
add_event(ev, privkey: privkey)
|
||||
|
||||
//self.objectWillChange.send()
|
||||
return false
|
||||
}
|
||||
@@ -85,15 +85,15 @@ class ThreadModel: ObservableObject {
|
||||
damus_state.pool.subscribe(sub_id: meta_subid, filters: meta_filters, handler: handle_event)
|
||||
}
|
||||
|
||||
func add_event(_ ev: NostrEvent) {
|
||||
func add_event(_ ev: NostrEvent, privkey: Privkey?) {
|
||||
if event_map.contains(ev) {
|
||||
return
|
||||
}
|
||||
|
||||
let the_ev = damus_state.events.upsert(ev)
|
||||
damus_state.replies.count_replies(the_ev)
|
||||
damus_state.events.add_replies(ev: the_ev)
|
||||
|
||||
damus_state.replies.count_replies(the_ev, privkey: privkey)
|
||||
damus_state.events.add_replies(ev: the_ev, privkey: privkey)
|
||||
|
||||
event_map.insert(ev)
|
||||
objectWillChange.send()
|
||||
}
|
||||
@@ -112,7 +112,7 @@ class ThreadModel: ObservableObject {
|
||||
|
||||
}
|
||||
} else if ev.is_textlike {
|
||||
self.add_event(ev)
|
||||
self.add_event(ev, privkey: damus_state.keypair.privkey)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -61,31 +61,35 @@ class UserSearchCache {
|
||||
return
|
||||
}
|
||||
|
||||
var petnames: [String: String] = [:]
|
||||
|
||||
// Gets all petnames from our new contacts list.
|
||||
newEvent.tags.forEach { tag in
|
||||
guard tag.count >= 4 && tag[0] == "p" else {
|
||||
var petnames: [Pubkey: String] = [:]
|
||||
for tag in newEvent.tags {
|
||||
guard tag.count > 3,
|
||||
let chr = tag[0].single_char, chr == "p",
|
||||
let id = tag[1].id()
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let pubkey = tag[1]
|
||||
let petname = tag[3]
|
||||
let pubkey = Pubkey(id)
|
||||
|
||||
petnames[pubkey] = petname
|
||||
petnames[pubkey] = tag[3].string()
|
||||
}
|
||||
|
||||
// Compute the diff with the old contacts list, if it exists,
|
||||
// mark the ones that are the same to not be removed from the user search cache,
|
||||
// and remove the old ones that are different from the user search cache.
|
||||
if let oldEvent, oldEvent.known_kind == .contacts && oldEvent.pubkey == id {
|
||||
oldEvent.tags.forEach { tag in
|
||||
guard tag.count >= 4 && tag[0] == "p" else {
|
||||
if let oldEvent, oldEvent.known_kind == .contacts, oldEvent.pubkey == id {
|
||||
for tag in oldEvent.tags {
|
||||
guard tag.count >= 4,
|
||||
tag[0].matches_char("p"),
|
||||
let id = tag[1].id()
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
let pubkey = tag[1]
|
||||
let oldPetname = tag[3]
|
||||
let pubkey = Pubkey(id)
|
||||
|
||||
let oldPetname = tag[3].string()
|
||||
|
||||
if let newPetname = petnames[pubkey] {
|
||||
if newPetname.caseInsensitiveCompare(oldPetname) == .orderedSame {
|
||||
|
||||
@@ -56,15 +56,10 @@ class ZapsModel: ObservableObject {
|
||||
let events = state.events.lookup_zaps(target: target).map { $0.request.ev }
|
||||
load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: state)
|
||||
case .event(_, let ev):
|
||||
guard ev.kind == 9735 else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let zapper = state.profiles.lookup_zapper(pubkey: target.pubkey) else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: state.keypair.privkey) else {
|
||||
guard ev.kind == 9735,
|
||||
let zapper = state.profiles.lookup_zapper(pubkey: target.pubkey),
|
||||
let zap = Zap.from_zap_event(zap_ev: ev, zapper: zapper, our_privkey: state.keypair.privkey)
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user