mirror of
git://jb55.com/damus
synced 2024-09-30 00:40:45 +00:00
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:
parent
55bbe8f855
commit
cebd1f48ca
@ -95,6 +95,7 @@
|
||||
4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */; };
|
||||
4C28A4122A6D03D200C1A7A5 /* ReferencedId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C28A4112A6D03D200C1A7A5 /* ReferencedId.swift */; };
|
||||
4C2B10282A7B0F5C008AA43E /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.swift */; };
|
||||
4C2B7BF22A71B6540049DEE7 /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B7BF12A71B6540049DEE7 /* Id.swift */; };
|
||||
4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */; };
|
||||
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */; };
|
||||
4C30AC7429A5680900E2BD5A /* EventGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7329A5680900E2BD5A /* EventGroupView.swift */; };
|
||||
@ -229,7 +230,6 @@
|
||||
4CA352A02A76AE80003BB08B /* Notify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA3529F2A76AE80003BB08B /* Notify.swift */; };
|
||||
4CA352A22A76AEC5003BB08B /* LikedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A12A76AEC5003BB08B /* LikedNotify.swift */; };
|
||||
4CA352A42A76AFF3003BB08B /* UpdateStatsNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A32A76AFF3003BB08B /* UpdateStatsNotify.swift */; };
|
||||
4CA352A62A76B020003BB08B /* Pubkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A52A76B020003BB08B /* Pubkey.swift */; };
|
||||
4CA352A82A76B37E003BB08B /* NewMutesNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A72A76B37E003BB08B /* NewMutesNotify.swift */; };
|
||||
4CA352AA2A76BF3A003BB08B /* LocalNotificationNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A92A76BF3A003BB08B /* LocalNotificationNotify.swift */; };
|
||||
4CA352AC2A76C07F003BB08B /* NewUnmutesNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352AB2A76C07F003BB08B /* NewUnmutesNotify.swift */; };
|
||||
@ -268,6 +268,10 @@
|
||||
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */; };
|
||||
4CB9D4A92992D2F400A9A7E4 /* FollowsYou.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */; };
|
||||
4CBCA930297DB57F00EC6B2F /* WebsiteLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */; };
|
||||
4CC14FEF2A73FCCB007AEB17 /* IdType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FEE2A73FCCB007AEB17 /* IdType.swift */; };
|
||||
4CC14FF12A73FCDB007AEB17 /* Pubkey.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF02A73FCDB007AEB17 /* Pubkey.swift */; };
|
||||
4CC14FF52A740BB7007AEB17 /* NoteId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF42A740BB7007AEB17 /* NoteId.swift */; };
|
||||
4CC14FF92A741939007AEB17 /* Referenced.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF82A741939007AEB17 /* Referenced.swift */; };
|
||||
4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC6193929DC777C006A86D1 /* RelayBootstrap.swift */; };
|
||||
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAE6297EFA7B00430951 /* Zap.swift */; };
|
||||
4CC7AAEB297F0AEC00430951 /* BuilderEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */; };
|
||||
@ -589,6 +593,7 @@
|
||||
4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SaveKeysView.swift; sourceTree = "<group>"; };
|
||||
4C28A4112A6D03D200C1A7A5 /* ReferencedId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReferencedId.swift; sourceTree = "<group>"; };
|
||||
4C2B10272A7B0F5C008AA43E /* Log.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Log.swift; sourceTree = "<group>"; };
|
||||
4C2B7BF12A71B6540049DEE7 /* Id.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Id.swift; sourceTree = "<group>"; };
|
||||
4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debouncer.swift; sourceTree = "<group>"; };
|
||||
4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
|
||||
4C30AC7329A5680900E2BD5A /* EventGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventGroupView.swift; sourceTree = "<group>"; };
|
||||
@ -762,7 +767,6 @@
|
||||
4CA3529F2A76AE80003BB08B /* Notify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notify.swift; sourceTree = "<group>"; };
|
||||
4CA352A12A76AEC5003BB08B /* LikedNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikedNotify.swift; sourceTree = "<group>"; };
|
||||
4CA352A32A76AFF3003BB08B /* UpdateStatsNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateStatsNotify.swift; sourceTree = "<group>"; };
|
||||
4CA352A52A76B020003BB08B /* Pubkey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pubkey.swift; sourceTree = "<group>"; };
|
||||
4CA352A72A76B37E003BB08B /* NewMutesNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewMutesNotify.swift; sourceTree = "<group>"; };
|
||||
4CA352A92A76BF3A003BB08B /* LocalNotificationNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalNotificationNotify.swift; sourceTree = "<group>"; };
|
||||
4CA352AB2A76C07F003BB08B /* NewUnmutesNotify.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewUnmutesNotify.swift; sourceTree = "<group>"; };
|
||||
@ -808,6 +812,10 @@
|
||||
4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileNameView.swift; sourceTree = "<group>"; };
|
||||
4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowsYou.swift; sourceTree = "<group>"; };
|
||||
4CBCA92F297DB57F00EC6B2F /* WebsiteLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebsiteLink.swift; sourceTree = "<group>"; };
|
||||
4CC14FEE2A73FCCB007AEB17 /* IdType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdType.swift; sourceTree = "<group>"; };
|
||||
4CC14FF02A73FCDB007AEB17 /* Pubkey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pubkey.swift; sourceTree = "<group>"; };
|
||||
4CC14FF42A740BB7007AEB17 /* NoteId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteId.swift; sourceTree = "<group>"; };
|
||||
4CC14FF82A741939007AEB17 /* Referenced.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Referenced.swift; sourceTree = "<group>"; };
|
||||
4CC6193929DC777C006A86D1 /* RelayBootstrap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayBootstrap.swift; sourceTree = "<group>"; };
|
||||
4CC7AAE6297EFA7B00430951 /* Zap.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Zap.swift; sourceTree = "<group>"; };
|
||||
4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuilderEventView.swift; sourceTree = "<group>"; };
|
||||
@ -1309,7 +1317,7 @@
|
||||
4C363A8F28247A1D006E126D /* NostrLink.swift */,
|
||||
50088DA029E8271A008A1FDF /* WebSocket.swift */,
|
||||
4C28A4112A6D03D200C1A7A5 /* ReferencedId.swift */,
|
||||
4CA352A52A76B020003BB08B /* Pubkey.swift */,
|
||||
4C2B7BF12A71B6540049DEE7 /* Id.swift */,
|
||||
);
|
||||
path = Nostr;
|
||||
sourceTree = "<group>";
|
||||
@ -1555,6 +1563,25 @@
|
||||
path = Profile;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CC14FEC2A73FC9A007AEB17 /* Types */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CC14FED2A73FCBB007AEB17 /* Ids */,
|
||||
);
|
||||
path = Types;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CC14FED2A73FCBB007AEB17 /* Ids */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4CC14FEE2A73FCCB007AEB17 /* IdType.swift */,
|
||||
4CC14FF02A73FCDB007AEB17 /* Pubkey.swift */,
|
||||
4CC14FF42A740BB7007AEB17 /* NoteId.swift */,
|
||||
4CC14FF82A741939007AEB17 /* Referenced.swift */,
|
||||
);
|
||||
path = Ids;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4CC7AAEE297F11B300430951 /* Events */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -1652,6 +1679,7 @@
|
||||
children = (
|
||||
4C1D4FB32A7967990024F453 /* build-git-hash.txt */,
|
||||
4CA3529C2A76AE47003BB08B /* Notify */,
|
||||
4CC14FEC2A73FC9A007AEB17 /* Types */,
|
||||
F7F0BA23297892AE009531F3 /* Modifiers */,
|
||||
4C4A3A5A288A1B2200453788 /* damus.entitlements */,
|
||||
4CE4F9DF285287A000C00DD9 /* Components */,
|
||||
@ -1684,6 +1712,7 @@
|
||||
4CE6DEF627F7A08200C66700 /* damusTests */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
4C0379642A7BFE8E0037B8C4 /* Events */,
|
||||
4C9B0DEC2A65A74000CBDA21 /* Util */,
|
||||
4C0C03962A61E2670098B3B8 /* Fixtures */,
|
||||
4C7D097D2A0C58B900943473 /* WalletConnectTests.swift */,
|
||||
@ -2087,6 +2116,7 @@
|
||||
4C3EA67728FF7A9800C48A62 /* talstr.c in Sources */,
|
||||
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
|
||||
4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */,
|
||||
4CC14FF12A73FCDB007AEB17 /* Pubkey.swift in Sources */,
|
||||
4CA9275D2A28FF630098A105 /* LongformView.swift in Sources */,
|
||||
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */,
|
||||
504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */,
|
||||
@ -2124,7 +2154,6 @@
|
||||
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */,
|
||||
4C5D5C992A6AF8F80024563C /* NdbNote.swift in Sources */,
|
||||
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */,
|
||||
4CA352A62A76B020003BB08B /* Pubkey.swift in Sources */,
|
||||
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
|
||||
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
|
||||
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */,
|
||||
@ -2180,6 +2209,7 @@
|
||||
4C285C8228385570008A31F1 /* CarouselView.swift in Sources */,
|
||||
3A3040F129A8FF97008A0F29 /* LocalizationUtil.swift in Sources */,
|
||||
F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */,
|
||||
4CC14FEF2A73FCCB007AEB17 /* IdType.swift in Sources */,
|
||||
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */,
|
||||
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
|
||||
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
|
||||
@ -2197,6 +2227,7 @@
|
||||
4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */,
|
||||
4CA352A42A76AFF3003BB08B /* UpdateStatsNotify.swift in Sources */,
|
||||
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
|
||||
4C2B7BF22A71B6540049DEE7 /* Id.swift in Sources */,
|
||||
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */,
|
||||
4C7D09722A0AEF5E00943473 /* DamusGradient.swift in Sources */,
|
||||
4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */,
|
||||
@ -2271,6 +2302,7 @@
|
||||
4C3EA64128FF553900C48A62 /* hash_u5.c in Sources */,
|
||||
4C1253542A76C7D60004F4B8 /* LogoutNotify.swift in Sources */,
|
||||
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */,
|
||||
4CC14FF52A740BB7007AEB17 /* NoteId.swift in Sources */,
|
||||
4C19AE512A5CEF7C00C90DB7 /* NostrScript.swift in Sources */,
|
||||
4C3EA64F28FF59F200C48A62 /* tal.c in Sources */,
|
||||
4CB88393296F798300DC99E7 /* ReactionsModel.swift in Sources */,
|
||||
@ -2294,6 +2326,7 @@
|
||||
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */,
|
||||
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */,
|
||||
3A90B1812A4EA3AF00000D94 /* UserSearchCache.swift in Sources */,
|
||||
4CC14FF92A741939007AEB17 /* Referenced.swift in Sources */,
|
||||
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
|
||||
4CE1399429F0669900AC6A0B /* BigButton.swift in Sources */,
|
||||
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
|
||||
@ -2701,7 +2734,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 8;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@ -2750,7 +2783,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 7;
|
||||
CURRENT_PROJECT_VERSION = 8;
|
||||
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
|
||||
DEVELOPMENT_TEAM = XK7H4JAB3D;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
|
@ -51,12 +51,12 @@ struct SearchHeaderView: View {
|
||||
|
||||
func unfollow(_ hashtag: String) {
|
||||
is_following = false
|
||||
handle_unfollow(state: state, unfollow: .t(hashtag))
|
||||
handle_unfollow(state: state, unfollow: FollowRef.hashtag(hashtag))
|
||||
}
|
||||
|
||||
func follow(_ hashtag: String) {
|
||||
is_following = true
|
||||
handle_follow(state: state, follow: .t(hashtag))
|
||||
handle_follow(state: state, follow: .hashtag(hashtag))
|
||||
}
|
||||
|
||||
func FollowButton(_ ht: String) -> some View {
|
||||
@ -104,15 +104,20 @@ struct SearchHeaderView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func hashtag_matches_search(desc: DescribedSearch, ref: ReferencedId) -> Bool {
|
||||
guard let ht = desc.is_hashtag, ref.key == "t" && ref.ref_id == ht
|
||||
else { return false }
|
||||
func hashtag_matches_search(desc: DescribedSearch, ref: FollowRef) -> Bool {
|
||||
guard case .hashtag(let follow_ht) = ref,
|
||||
case .hashtag(let search_ht) = desc,
|
||||
follow_ht == search_ht
|
||||
else {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func is_following_hashtag(contacts: NostrEvent?, hashtag: String) -> Bool {
|
||||
guard let contacts else { return false }
|
||||
return is_already_following(contacts: contacts, follow: .t(hashtag))
|
||||
return is_already_following(contacts: contacts, follow: .hashtag(hashtag))
|
||||
}
|
||||
|
||||
|
||||
|
@ -143,7 +143,7 @@ func translate_note(profiles: Profiles, privkey: Privkey?, event: NostrEvent, se
|
||||
}
|
||||
|
||||
// Render translated note
|
||||
let translated_blocks = event.get_blocks(content: translated_note)
|
||||
let translated_blocks = parse_note_content(content: .content(translated_note, event.tags))
|
||||
let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles)
|
||||
|
||||
// and cache it
|
||||
|
@ -7,75 +7,56 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
func tag_to_refid_ndb(_ tag: TagSequence) -> ReferencedId? {
|
||||
guard tag.count >= 2 else { return nil }
|
||||
enum NoteContent {
|
||||
case note(NostrEvent)
|
||||
case content(String, TagsSequence?)
|
||||
|
||||
let key = tag[0].string()
|
||||
let ref_id = tag[1].string()
|
||||
|
||||
var relay_id: String? = nil
|
||||
if tag.count >= 3 {
|
||||
relay_id = tag[2].string()
|
||||
init(note: NostrEvent, privkey: Privkey?) {
|
||||
if note.known_kind == .dm {
|
||||
self = .content(note.get_content(privkey), note.tags)
|
||||
} else {
|
||||
self = .note(note)
|
||||
}
|
||||
}
|
||||
|
||||
return ReferencedId(ref_id: ref_id, relay_id: relay_id, key: key)
|
||||
}
|
||||
|
||||
func convert_mention_index_block_ndb(ind: Int, tags: TagsSequence) -> Block? {
|
||||
if ind < 0 || (ind + 1 > tags.count) || tags[ind]!.count < 2 {
|
||||
return .text("#[\(ind)]")
|
||||
}
|
||||
|
||||
guard let tag = tags[ind], let fst = tag.first(where: { _ in true }) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let mention_type = parse_mention_type_ndb(fst) else {
|
||||
return .text("#[\(ind)]")
|
||||
}
|
||||
|
||||
guard let ref = tag_to_refid_ndb(tag) else {
|
||||
return .text("#[\(ind)]")
|
||||
}
|
||||
|
||||
return .mention(Mention(index: ind, type: mention_type, ref: ref))
|
||||
}
|
||||
|
||||
|
||||
func convert_block_ndb(_ b: block_t, tags: TagsSequence) -> Block? {
|
||||
if b.type == BLOCK_MENTION_INDEX {
|
||||
return convert_mention_index_block_ndb(ind: Int(b.block.mention_index), tags: tags)
|
||||
}
|
||||
|
||||
return convert_block(b, tags: [])
|
||||
}
|
||||
|
||||
|
||||
func parse_note_content_ndb(note: NdbNote) -> Blocks {
|
||||
func parsed_blocks_finish(bs: inout note_blocks, tags: TagsSequence?) -> Blocks {
|
||||
var out: [Block] = []
|
||||
|
||||
var bs = note_blocks()
|
||||
bs.num_blocks = 0;
|
||||
|
||||
blocks_init(&bs)
|
||||
|
||||
damus_parse_content(&bs, note.content_raw)
|
||||
|
||||
var i = 0
|
||||
while (i < bs.num_blocks) {
|
||||
let block = bs.blocks[i]
|
||||
|
||||
if let converted = convert_block_ndb(block, tags: note.tags) {
|
||||
|
||||
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 parse_note_content(content: NoteContent) -> Blocks {
|
||||
var bs = note_blocks()
|
||||
bs.num_blocks = 0;
|
||||
|
||||
blocks_init(&bs)
|
||||
|
||||
switch content {
|
||||
case .content(let s, let tags):
|
||||
return s.withCString { cptr in
|
||||
damus_parse_content(&bs, cptr)
|
||||
return parsed_blocks_finish(bs: &bs, tags: tags)
|
||||
}
|
||||
case .note(let note):
|
||||
damus_parse_content(&bs, note.content_raw)
|
||||
return parsed_blocks_finish(bs: &bs, tags: note.tags)
|
||||
}
|
||||
}
|
||||
|
||||
func interpret_event_refs_ndb(blocks: [Block], tags: TagsSequence) -> [EventRef] {
|
||||
@ -84,38 +65,37 @@ func interpret_event_refs_ndb(blocks: [Block], tags: TagsSequence) -> [EventRef]
|
||||
}
|
||||
|
||||
/// 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 = References.ids(tags: tags)
|
||||
return interp_event_refs_without_mentions_ndb(ev_refs)
|
||||
return interp_event_refs_without_mentions_ndb(tags.note.referenced_noterefs)
|
||||
}
|
||||
|
||||
return interp_event_refs_with_mentions_ndb(tags: tags, mention_indices: mention_indices)
|
||||
}
|
||||
|
||||
func interp_event_refs_without_mentions_ndb(_ ev_tags: LazyFilterSequence<References>) -> [EventRef] {
|
||||
func interp_event_refs_without_mentions_ndb(_ ev_tags: References<NoteRef>) -> [EventRef] {
|
||||
|
||||
var count = 0
|
||||
var evrefs: [EventRef] = []
|
||||
var first: Bool = true
|
||||
var first_ref: Reference? = nil
|
||||
var first_ref: NoteRef? = nil
|
||||
|
||||
for ref in ev_tags {
|
||||
if first {
|
||||
first_ref = ref
|
||||
evrefs.append(.thread_id(ref.to_referenced_id()))
|
||||
evrefs.append(.thread_id(ref))
|
||||
first = false
|
||||
} else {
|
||||
|
||||
evrefs.append(.reply(ref.to_referenced_id()))
|
||||
evrefs.append(.reply(ref))
|
||||
}
|
||||
count += 1
|
||||
}
|
||||
|
||||
if let first_ref, count == 1 {
|
||||
let r = first_ref.to_referenced_id()
|
||||
let r = first_ref
|
||||
return [.reply_to_root(r)]
|
||||
}
|
||||
|
||||
@ -124,19 +104,15 @@ func interp_event_refs_without_mentions_ndb(_ ev_tags: LazyFilterSequence<Refere
|
||||
|
||||
func interp_event_refs_with_mentions_ndb(tags: TagsSequence, 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].matches_char("e"),
|
||||
let ref = tag_to_refid_ndb(tag)
|
||||
{
|
||||
if let note_id = NoteRef.from_tag(tag: tag) {
|
||||
if mention_indices.contains(i) {
|
||||
let mention = Mention(index: i, type: .event, ref: ref)
|
||||
mentions.append(.mention(mention))
|
||||
mentions.append(.mention(.noteref(note_id, index: i)))
|
||||
} else {
|
||||
ev_refs.append(ref)
|
||||
ev_refs.append(note_id)
|
||||
}
|
||||
}
|
||||
i += 1
|
||||
|
@ -43,9 +43,9 @@ enum Sheets: Identifiable {
|
||||
var id: String {
|
||||
switch self {
|
||||
case .report: return "report"
|
||||
case .post(let action): return "post-" + (action.ev?.id ?? "")
|
||||
case .event(let ev): return "event-" + ev.id
|
||||
case .zap(let sheet): return "zap-" + sheet.target.id
|
||||
case .post(let action): return "post-" + (action.ev?.id.hex() ?? "")
|
||||
case .event(let ev): return "event-" + ev.id.hex()
|
||||
case .zap(let sheet): return "zap-" + hex_encode(sheet.target.id)
|
||||
case .select_wallet: return "select-wallet"
|
||||
case .filter: return "filter"
|
||||
case .suggestedUsers: return "suggested-users"
|
||||
@ -397,14 +397,14 @@ struct ContentView: View {
|
||||
}
|
||||
.onReceive(handle_notify(.unfollow)) { target in
|
||||
guard let state = self.damus_state else { return }
|
||||
_ = handle_unfollow_notif(state: state, target: target)
|
||||
_ = handle_unfollow(state: state, unfollow: target.follow_ref)
|
||||
}
|
||||
.onReceive(handle_notify(.unfollowed)) { unfollow in
|
||||
home.resubscribe(.unfollowing(unfollow))
|
||||
}
|
||||
.onReceive(handle_notify(.follow)) { target in
|
||||
guard let state = self.damus_state else { return }
|
||||
guard handle_follow_notif(state: state, target: target) else { return }
|
||||
handle_follow_notif(state: state, target: target)
|
||||
}
|
||||
.onReceive(handle_notify(.followed)) { _ in
|
||||
home.resubscribe(.following)
|
||||
@ -469,26 +469,29 @@ struct ContentView: View {
|
||||
.onReceive(handle_notify(.local_notification)) { local in
|
||||
guard let damus_state else { return }
|
||||
|
||||
if local.type == .profile_zap {
|
||||
open_profile(pubkey: local.event_id)
|
||||
return
|
||||
}
|
||||
|
||||
guard let target = damus_state.events.lookup(local.event_id) else {
|
||||
return
|
||||
}
|
||||
|
||||
switch local.type {
|
||||
case .dm:
|
||||
selected_timeline = .dms
|
||||
damus_state.dms.set_active_dm(target.pubkey)
|
||||
navigationCoordinator.push(route: Route.DMChat(dms: damus_state.dms.active_model))
|
||||
case .like, .zap, .mention, .repost:
|
||||
open_event(ev: target)
|
||||
case .profile_zap:
|
||||
// Handled separately above.
|
||||
break
|
||||
switch local.mention {
|
||||
case .pubkey(let pubkey):
|
||||
open_profile(pubkey: pubkey)
|
||||
|
||||
case .note(let noteId):
|
||||
guard let target = damus_state.events.lookup(noteId) else {
|
||||
return
|
||||
}
|
||||
|
||||
switch local.type {
|
||||
case .dm:
|
||||
selected_timeline = .dms
|
||||
damus_state.dms.set_active_dm(target.pubkey)
|
||||
navigationCoordinator.push(route: Route.DMChat(dms: damus_state.dms.active_model))
|
||||
case .like, .zap, .mention, .repost:
|
||||
open_event(ev: target)
|
||||
case .profile_zap:
|
||||
// Handled separately above.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
.onReceive(handle_notify(.onlyzaps_mode)) { hide in
|
||||
home.filter_events()
|
||||
@ -529,7 +532,7 @@ struct ContentView: View {
|
||||
guard let ds = damus_state,
|
||||
let keypair = ds.keypair.to_full(),
|
||||
let pubkey = muting,
|
||||
let mutelist = create_or_update_mutelist(keypair: keypair, mprev: nil, to_add: pubkey)
|
||||
let mutelist = create_or_update_mutelist(keypair: keypair, mprev: nil, to_add: .pubkey(pubkey))
|
||||
else {
|
||||
return
|
||||
}
|
||||
@ -556,16 +559,16 @@ struct ContentView: View {
|
||||
if ds.contacts.mutelist == nil {
|
||||
confirm_overwrite_mutelist = true
|
||||
} else {
|
||||
guard let keypair = ds.keypair.to_full() else {
|
||||
return
|
||||
}
|
||||
guard let pubkey = muting else {
|
||||
guard let keypair = ds.keypair.to_full(),
|
||||
let pubkey = muting
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let ev = create_or_update_mutelist(keypair: keypair, mprev: ds.contacts.mutelist, to_add: pubkey) else {
|
||||
guard let ev = create_or_update_mutelist(keypair: keypair, mprev: ds.contacts.mutelist, to_add: .pubkey(pubkey)) else {
|
||||
return
|
||||
}
|
||||
|
||||
damus_state?.contacts.set_mutelist(ev)
|
||||
ds.postbox.send(ev)
|
||||
}
|
||||
@ -871,7 +874,7 @@ func timeline_name(_ timeline: Timeline?) -> String {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func handle_unfollow(state: DamusState, unfollow: ReferencedId) -> Bool {
|
||||
func handle_unfollow(state: DamusState, unfollow: FollowRef) -> Bool {
|
||||
guard let keypair = state.keypair.to_full() else {
|
||||
return false
|
||||
}
|
||||
@ -887,27 +890,20 @@ func handle_unfollow(state: DamusState, unfollow: ReferencedId) -> Bool {
|
||||
|
||||
state.contacts.event = ev
|
||||
|
||||
if unfollow.key == "p" {
|
||||
state.contacts.remove_friend(unfollow.ref_id)
|
||||
switch unfollow {
|
||||
case .pubkey(let pk):
|
||||
state.contacts.remove_friend(pk)
|
||||
state.user_search_cache.updateOwnContactsPetnames(id: state.pubkey, oldEvent: old_contacts, newEvent: ev)
|
||||
case .hashtag:
|
||||
// nothing to handle here really
|
||||
break
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func handle_unfollow_notif(state: DamusState, target: FollowTarget) -> ReferencedId? {
|
||||
let pk = target.pubkey
|
||||
|
||||
let ref = ReferencedId.p(pk)
|
||||
if handle_unfollow(state: state, unfollow: ref) {
|
||||
return ref
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func handle_follow(state: DamusState, follow: ReferencedId) -> Bool {
|
||||
func handle_follow(state: DamusState, follow: FollowRef) -> Bool {
|
||||
guard let keypair = state.keypair.to_full() else {
|
||||
return false
|
||||
}
|
||||
@ -920,8 +916,12 @@ func handle_follow(state: DamusState, follow: ReferencedId) -> Bool {
|
||||
notify(.followed(follow))
|
||||
|
||||
state.contacts.event = ev
|
||||
if follow.key == "p" {
|
||||
state.contacts.add_friend_pubkey(follow.ref_id)
|
||||
switch follow {
|
||||
case .pubkey(let pubkey):
|
||||
state.contacts.add_friend_pubkey(pubkey)
|
||||
case .hashtag:
|
||||
// nothing to do
|
||||
break
|
||||
}
|
||||
|
||||
return true
|
||||
@ -936,7 +936,7 @@ func handle_follow_notif(state: DamusState, target: FollowTarget) -> Bool {
|
||||
state.contacts.add_friend_contact(ev)
|
||||
}
|
||||
|
||||
return handle_follow(state: state, follow: .p(target.pubkey))
|
||||
return handle_follow(state: state, follow: target.follow_ref)
|
||||
}
|
||||
|
||||
func handle_post_notification(keypair: FullKeypair, postbox: PostBox, events: EventCache, post: NostrPostResult) -> Bool {
|
||||
@ -951,7 +951,7 @@ func handle_post_notification(keypair: FullKeypair, postbox: PostBox, events: Ev
|
||||
postbox.send(new_ev)
|
||||
for eref in new_ev.referenced_ids.prefix(3) {
|
||||
// also broadcast at most 3 referenced events
|
||||
if let ev = events.lookup(eref.ref_id) {
|
||||
if let ev = events.lookup(eref) {
|
||||
postbox.send(ev)
|
||||
}
|
||||
}
|
||||
@ -984,13 +984,19 @@ func on_open_url(state: DamusState, url: URL, result: @escaping (OpenResult?) ->
|
||||
|
||||
switch link {
|
||||
case .ref(let ref):
|
||||
if ref.key == "p" {
|
||||
result(.profile(ref.ref_id))
|
||||
} else if ref.key == "e" {
|
||||
find_event(state: state, query: .event(evid: ref.ref_id)) { res in
|
||||
switch ref {
|
||||
case .pubkey(let pk):
|
||||
result(.profile(pk))
|
||||
case .event(let noteid):
|
||||
find_event(state: state, query: .event(evid: noteid)) { res in
|
||||
guard let res, case .event(let ev) = res else { return }
|
||||
result(.event(ev))
|
||||
}
|
||||
case .hashtag(let ht):
|
||||
result(.filter(.filter_hashtag([ht.string()])))
|
||||
case .param, .quote:
|
||||
// doesn't really make sense here
|
||||
break
|
||||
}
|
||||
case .filter(let filt):
|
||||
result(.filter(filt))
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
116
damus/Nostr/Id.swift
Normal file
116
damus/Nostr/Id.swift
Normal file
@ -0,0 +1,116 @@
|
||||
//
|
||||
// Id.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-07-26.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TagRef<T>: Hashable, Equatable, Encodable {
|
||||
let elem: TagElem
|
||||
|
||||
init(_ elem: TagElem) {
|
||||
self.elem = elem
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(elem.string())
|
||||
}
|
||||
}
|
||||
|
||||
protocol TagKey {
|
||||
var keychar: AsciiCharacter { get }
|
||||
}
|
||||
|
||||
protocol TagKeys {
|
||||
associatedtype TagKeys: TagKey
|
||||
var key: TagKeys { get }
|
||||
}
|
||||
|
||||
protocol TagConvertible {
|
||||
var tag: [String] { get }
|
||||
static func from_tag(tag: TagSequence) -> Self?
|
||||
}
|
||||
|
||||
struct QuoteId: IdType, TagKey {
|
||||
let id: Data
|
||||
|
||||
init(_ data: Data) {
|
||||
self.id = data
|
||||
}
|
||||
|
||||
var keychar: AsciiCharacter { "q" }
|
||||
}
|
||||
|
||||
|
||||
struct Privkey: IdType {
|
||||
let id: Data
|
||||
|
||||
var nsec: String {
|
||||
bech32_privkey(self)
|
||||
}
|
||||
|
||||
init?(hex: String) {
|
||||
guard let id = hex_decode_id(hex) else {
|
||||
return nil
|
||||
}
|
||||
self.init(id)
|
||||
}
|
||||
|
||||
init(_ data: Data) {
|
||||
self.id = data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct Hashtag: TagConvertible {
|
||||
let hashtag: String
|
||||
|
||||
static func from_tag(tag: TagSequence) -> Hashtag? {
|
||||
var i = tag.makeIterator()
|
||||
|
||||
guard tag.count >= 2,
|
||||
let t0 = i.next(),
|
||||
let chr = t0.single_char,
|
||||
chr == "t",
|
||||
let t1 = i.next() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Hashtag(hashtag: t1.string())
|
||||
}
|
||||
|
||||
var tag: [String] { ["t", self.hashtag] }
|
||||
var keychar: AsciiCharacter { "t" }
|
||||
}
|
||||
|
||||
struct ReplaceableParam: TagConvertible {
|
||||
let param: TagElem
|
||||
|
||||
static func from_tag(tag: TagSequence) -> ReplaceableParam? {
|
||||
var i = tag.makeIterator()
|
||||
|
||||
guard tag.count >= 2,
|
||||
let t0 = i.next(),
|
||||
let chr = t0.single_char,
|
||||
chr == "d",
|
||||
let t1 = i.next() else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ReplaceableParam(param: t1)
|
||||
}
|
||||
|
||||
var tag: [String] { [self.keychar.description, self.param.string()] }
|
||||
var keychar: AsciiCharacter { "d" }
|
||||
}
|
||||
|
||||
struct Signature: Hashable, Equatable {
|
||||
let data: Data
|
||||
|
||||
init(_ p: Data) {
|
||||
self.data = p
|
||||
}
|
||||
}
|
@ -20,16 +20,20 @@ enum ValidationResult: Decodable {
|
||||
case bad_sig
|
||||
}
|
||||
|
||||
//typealias NostrEvent = NdbNote
|
||||
//typealias Tags = TagsSequence
|
||||
typealias Tags = [[String]]
|
||||
typealias NostrEvent = NostrEventOld
|
||||
typealias NostrEvent = NdbNote
|
||||
typealias TagElem = NdbTagElem
|
||||
typealias Tag = TagSequence
|
||||
typealias Tags = TagsSequence
|
||||
//typealias TagElem = String
|
||||
//typealias Tag = [TagElem]
|
||||
//typealias Tags = [Tag]
|
||||
//typealias NostrEvent = NostrEventOld
|
||||
|
||||
let MAX_NOTE_SIZE: Int = 2 << 18
|
||||
|
||||
/*
|
||||
class NostrEventOld: Codable, Identifiable, CustomStringConvertible, Equatable, Hashable, Comparable {
|
||||
// TODO: memory mapped db events
|
||||
/*
|
||||
private var note_data: UnsafeMutablePointer<ndb_note>
|
||||
|
||||
init(data: UnsafeMutablePointer<ndb_note>) {
|
||||
@ -51,7 +55,6 @@ class NostrEventOld: Codable, Identifiable, CustomStringConvertible, Equatable,
|
||||
}
|
||||
|
||||
var tags: TagIterator
|
||||
*/
|
||||
|
||||
let id: String
|
||||
let content: String
|
||||
@ -90,12 +93,16 @@ class NostrEventOld: Codable, Identifiable, CustomStringConvertible, Equatable,
|
||||
hasher.combine(id)
|
||||
}
|
||||
|
||||
static func owned_from_json(json: String) -> NostrEvent? {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id, sig, tags, pubkey, created_at, kind, content
|
||||
}
|
||||
|
||||
static func owned_from_json(json: String) -> NostrEventOld? {
|
||||
let decoder = JSONDecoder()
|
||||
guard let dat = json.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
guard let ev = try? decoder.decode(NostrEvent.self, from: dat) else {
|
||||
guard let ev = try? decoder.decode(NostrEventOld.self, from: dat) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -214,10 +221,6 @@ extension NostrEventOld {
|
||||
return NostrKind.init(rawValue: kind)
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id, sig, tags, pubkey, created_at, kind, content
|
||||
}
|
||||
|
||||
private func get_referenced_ids(key: String) -> [ReferencedId] {
|
||||
return damus.get_referenced_ids(tags: self.tags, key: key)
|
||||
}
|
||||
@ -309,6 +312,7 @@ extension NostrEventOld {
|
||||
return Date.now.timeIntervalSince(event_date)
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
func sign_id(privkey: String, id: String) -> String {
|
||||
let priv_key_bytes = try! privkey.bytes
|
||||
@ -316,7 +320,7 @@ func sign_id(privkey: String, id: String) -> String {
|
||||
|
||||
// Extra params for custom signing
|
||||
|
||||
var aux_rand = random_bytes(count: 64)
|
||||
var aux_rand = random_bytes(count: 64).bytes
|
||||
var digest = try! id.bytes
|
||||
|
||||
// API allows for signing variable length messages
|
||||
@ -326,7 +330,7 @@ func sign_id(privkey: String, id: String) -> String {
|
||||
}
|
||||
|
||||
func decode_nostr_event(txt: String) -> NostrResponse? {
|
||||
return decode_data(Data(txt.utf8))
|
||||
return NostrResponse.owned_from_json(json: txt)
|
||||
}
|
||||
|
||||
func encode_json<T: Encodable>(_ val: T) -> String? {
|
||||
@ -336,7 +340,7 @@ func encode_json<T: Encodable>(_ val: T) -> String? {
|
||||
}
|
||||
|
||||
func decode_nostr_event_json(json: String) -> NostrEvent? {
|
||||
return decode_json(json)
|
||||
return NostrEvent.owned_from_json(json: json)
|
||||
}
|
||||
|
||||
/*
|
||||
@ -390,7 +394,7 @@ func event_commitment(pubkey: Pubkey, created_at: UInt32, kind: UInt32, tags: [[
|
||||
let tags_data = try! tags_encoder.encode(tags)
|
||||
let tags = String(decoding: tags_data, as: UTF8.self)
|
||||
|
||||
return "[0,\"\(pubkey)\",\(created_at),\(kind),\(tags),\(content)]"
|
||||
return "[0,\"\(pubkey.hex())\",\(created_at),\(kind),\(tags),\(content)]"
|
||||
}
|
||||
|
||||
func calculate_event_commitment(pubkey: Pubkey, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> Data {
|
||||
@ -398,9 +402,9 @@ func calculate_event_commitment(pubkey: Pubkey, created_at: UInt32, kind: UInt32
|
||||
return target.data(using: .utf8)!
|
||||
}
|
||||
|
||||
func calculate_event_id(pubkey: Pubkey, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> Data {
|
||||
func calculate_event_id(pubkey: Pubkey, created_at: UInt32, kind: UInt32, tags: [[String]], content: String) -> NoteId {
|
||||
let commitment = calculate_event_commitment(pubkey: pubkey, created_at: created_at, kind: kind, tags: tags, content: content)
|
||||
return sha256(commitment)
|
||||
return NoteId(sha256(commitment))
|
||||
}
|
||||
|
||||
|
||||
@ -436,8 +440,6 @@ func hex_encode(_ data: Data) -> String {
|
||||
return str
|
||||
}
|
||||
|
||||
|
||||
|
||||
func random_bytes(count: Int) -> Data {
|
||||
var bytes = [Int8](repeating: 0, count: count)
|
||||
guard
|
||||
@ -448,42 +450,6 @@ func random_bytes(count: Int) -> Data {
|
||||
return Data(bytes: bytes, count: count)
|
||||
}
|
||||
|
||||
func refid_to_tag(_ ref: ReferencedId) -> [String] {
|
||||
var tag = [ref.key, ref.ref_id]
|
||||
if let relay_id = ref.relay_id {
|
||||
tag.append(relay_id)
|
||||
}
|
||||
return tag
|
||||
}
|
||||
|
||||
func tag_to_refid(_ tag: [String]) -> ReferencedId? {
|
||||
if tag.count == 0 {
|
||||
return nil
|
||||
}
|
||||
if tag.count == 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var relay_id: String? = nil
|
||||
if tag.count > 2 {
|
||||
relay_id = tag[2]
|
||||
}
|
||||
|
||||
return ReferencedId(ref_id: tag[1], relay_id: relay_id, key: tag[0])
|
||||
}
|
||||
|
||||
func get_referenced_ids(tags: [[String]], key: String) -> [ReferencedId] {
|
||||
return tags.reduce(into: []) { (acc, tag) in
|
||||
if tag.count >= 2 && tag[0] == key {
|
||||
var relay_id: String? = nil
|
||||
if tag.count >= 3 {
|
||||
relay_id = tag[2]
|
||||
}
|
||||
acc.append(ReferencedId(ref_id: tag[1], relay_id: relay_id, key: key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func make_first_contact_event(keypair: Keypair) -> NostrEvent? {
|
||||
let bootstrap_relays = load_bootstrap_relays(pubkey: keypair.pubkey)
|
||||
let rw_relay_info = RelayInfo(read: true, write: true)
|
||||
@ -511,18 +477,33 @@ func make_metadata_event(keypair: FullKeypair, metadata: Profile) -> NostrEvent?
|
||||
}
|
||||
|
||||
func make_boost_event(keypair: FullKeypair, boosted: NostrEvent) -> NostrEvent? {
|
||||
var tags: [[String]] = boosted.tags.filter { tag in tag.count >= 2 && (tag[0] == "e" || tag[0] == "p") }
|
||||
|
||||
tags.append(["e", boosted.id, "", "root"])
|
||||
var tags = boosted.tags.reduce(into: [[String]]()) { ts, tag in
|
||||
guard tag.count >= 2 && (tag[0].matches_char("e") || tag[0].matches_char("p")) else {
|
||||
return
|
||||
}
|
||||
|
||||
ts.append(tag.strings())
|
||||
}
|
||||
|
||||
tags.append(["e", boosted.id.hex(), "", "root"])
|
||||
tags.append(["p", boosted.pubkey.hex()])
|
||||
|
||||
return NostrEvent(content: event_to_json(ev: boosted), keypair: keypair.to_keypair(), kind: 6, tags: tags)
|
||||
let content = boosted.content_len <= 100 ? event_to_json(ev: boosted) : ""
|
||||
return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 6, tags: tags)
|
||||
}
|
||||
|
||||
func make_like_event(keypair: FullKeypair, liked: NostrEvent, content: String = "🤙") -> NostrEvent? {
|
||||
var tags: [[String]] = liked.tags.filter { tag in tag.count >= 2 && (tag[0] == "e" || tag[0] == "p") }
|
||||
var tags = liked.tags.reduce(into: [[String]]()) { ts, tag in
|
||||
guard tag.count >= 2,
|
||||
(tag[0].matches_char("e") || tag[0].matches_char("p")) else {
|
||||
return
|
||||
}
|
||||
ts.append(tag.strings())
|
||||
}
|
||||
|
||||
tags.append(["e", liked.id.hex()])
|
||||
tags.append(["p", liked.pubkey.hex()])
|
||||
|
||||
return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 7, tags: tags)
|
||||
}
|
||||
|
||||
@ -556,17 +537,19 @@ func make_private_zap_request_event(identity: FullKeypair, enc_key: FullKeypair,
|
||||
}
|
||||
|
||||
func decrypt_private_zap(our_privkey: Privkey, zapreq: NostrEvent, target: ZapTarget) -> NostrEvent? {
|
||||
guard let anon_tag = zapreq.tags.first(where: { t in t.count >= 2 && t[0] == "anon" }) else {
|
||||
guard let anon_tag = zapreq.tags.first(where: { t in
|
||||
t.count >= 2 && t[0].matches_str("anon")
|
||||
}) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let enc_note = anon_tag[1]
|
||||
|
||||
let enc_note = anon_tag[1].string()
|
||||
|
||||
var note = decrypt_note(our_privkey: our_privkey, their_pubkey: zapreq.pubkey, enc_note: enc_note, encoding: .bech32)
|
||||
|
||||
// check to see if the private note was from us
|
||||
if note == nil {
|
||||
guard let our_private_keypair = generate_private_keypair(our_privkey: our_privkey, id: target.id, created_at: zapreq.created_at) else {
|
||||
guard let our_private_keypair = generate_private_keypair(our_privkey: our_privkey, id: NoteId(target.id), created_at: zapreq.created_at) else {
|
||||
return nil
|
||||
}
|
||||
// use our private keypair and their pubkey to get the shared secret
|
||||
@ -602,17 +585,15 @@ func decrypt_private_zap(our_privkey: Privkey, zapreq: NostrEvent, target: ZapTa
|
||||
return note
|
||||
}
|
||||
|
||||
func generate_private_keypair(our_privkey: String, id: String, created_at: UInt32) -> FullKeypair? {
|
||||
let to_hash = our_privkey + id + String(created_at)
|
||||
func generate_private_keypair(our_privkey: Privkey, id: NoteId, created_at: UInt32) -> FullKeypair? {
|
||||
let to_hash = our_privkey.hex() + id.hex() + String(created_at)
|
||||
guard let dat = to_hash.data(using: .utf8) else {
|
||||
return nil
|
||||
}
|
||||
let privkey_bytes = sha256(dat)
|
||||
let privkey = hex_encode(privkey_bytes)
|
||||
guard let pubkey = privkey_to_pubkey(privkey: privkey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let privkey = Privkey(privkey_bytes)
|
||||
guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
|
||||
|
||||
return FullKeypair(pubkey: pubkey, privkey: privkey)
|
||||
}
|
||||
|
||||
@ -661,7 +642,7 @@ func make_zap_request_event(keypair: FullKeypair, content: String, relays: [Rela
|
||||
tags.append(["anon"])
|
||||
kp = generate_new_keypair()
|
||||
case .priv:
|
||||
guard let priv_kp = generate_private_keypair(our_privkey: keypair.privkey, id: target.id, created_at: now) else {
|
||||
guard let priv_kp = generate_private_keypair(our_privkey: keypair.privkey, id: NoteId(target.id), created_at: now) else {
|
||||
return nil
|
||||
}
|
||||
kp = priv_kp
|
||||
@ -699,27 +680,36 @@ func uniq<T: Hashable>(_ xs: [T]) -> [T] {
|
||||
return ys
|
||||
}
|
||||
|
||||
func gather_reply_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] {
|
||||
var ids = get_referenced_ids(tags: from.tags, key: "e").first.map { [$0] } ?? []
|
||||
func gather_reply_ids(our_pubkey: Pubkey, from: NostrEvent) -> [RefId] {
|
||||
var ids: [RefId] = from.referenced_ids.first.map({ ref in [ .event(ref) ] }) ?? []
|
||||
|
||||
ids.append(.e(from.id))
|
||||
ids.append(contentsOf: uniq(from.referenced_pubkeys.filter { $0.ref_id != our_pubkey }))
|
||||
if from.pubkey != our_pubkey {
|
||||
ids.append(ReferencedId(ref_id: from.pubkey, relay_id: nil, key: "p"))
|
||||
let pks = from.referenced_pubkeys.reduce(into: [RefId]()) { rs, pk in
|
||||
if pk == our_pubkey {
|
||||
return
|
||||
}
|
||||
rs.append(.pubkey(pk))
|
||||
}
|
||||
|
||||
ids.append(.event(from.id))
|
||||
ids.append(contentsOf: uniq(pks))
|
||||
|
||||
if from.pubkey != our_pubkey {
|
||||
ids.append(.pubkey(from.pubkey))
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
func gather_quote_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] {
|
||||
var ids: [ReferencedId] = [.q(from.id)]
|
||||
func gather_quote_ids(our_pubkey: Pubkey, from: NostrEvent) -> [RefId] {
|
||||
var ids: [RefId] = [.quote(from.id.quote_id)]
|
||||
if from.pubkey != our_pubkey {
|
||||
ids.append(ReferencedId(ref_id: from.pubkey, relay_id: nil, key: "p"))
|
||||
ids.append(.pubkey(from.pubkey))
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
func event_from_json(dat: String) -> NostrEvent? {
|
||||
return try? JSONDecoder().decode(NostrEvent.self, from: Data(dat.utf8))
|
||||
return NostrEvent.owned_from_json(json: dat)
|
||||
}
|
||||
|
||||
func event_to_json(ev: NostrEvent) -> String {
|
||||
@ -757,13 +747,10 @@ func decrypt_note(our_privkey: Privkey, their_pubkey: Pubkey, enc_note: String,
|
||||
return decode_nostr_event_json(json: dec)
|
||||
}
|
||||
|
||||
func get_shared_secret(privkey: String, pubkey: String) -> [UInt8]? {
|
||||
guard let privkey_bytes = try? privkey.bytes else {
|
||||
return nil
|
||||
}
|
||||
guard var pk_bytes = try? pubkey.bytes else {
|
||||
return nil
|
||||
}
|
||||
func get_shared_secret(privkey: Privkey, pubkey: Pubkey) -> [UInt8]? {
|
||||
let privkey_bytes = privkey.bytes
|
||||
var pk_bytes = pubkey.bytes
|
||||
|
||||
pk_bytes.insert(2, at: 0)
|
||||
|
||||
var publicKey = secp256k1_pubkey()
|
||||
@ -924,45 +911,33 @@ func aes_operation(operation: CCOperation, data: [UInt8], iv: [UInt8], shared_se
|
||||
|
||||
|
||||
func validate_event(ev: NostrEvent) -> ValidationResult {
|
||||
let raw_id = calculate_event_id(pubkey: ev.pubkey, created_at: ev.created_at, kind: ev.kind, tags: ev.tags, content: ev.content)
|
||||
let id = hex_encode(raw_id)
|
||||
|
||||
let id = calculate_event_id(pubkey: ev.pubkey, created_at: ev.created_at, kind: ev.kind, tags: ev.tags.strings(), content: ev.content)
|
||||
|
||||
if id != ev.id {
|
||||
return .bad_id
|
||||
}
|
||||
|
||||
// TODO: implement verify
|
||||
guard var sig64 = hex_decode(ev.sig)?.bytes else {
|
||||
return .bad_sig
|
||||
}
|
||||
|
||||
guard var ev_pubkey = hex_decode(ev.pubkey)?.bytes else {
|
||||
return .bad_sig
|
||||
}
|
||||
|
||||
let ctx = secp256k1.Context.raw
|
||||
var xonly_pubkey = secp256k1_xonly_pubkey.init()
|
||||
|
||||
var ev_pubkey = ev.pubkey.id.bytes
|
||||
|
||||
var ok = secp256k1_xonly_pubkey_parse(ctx, &xonly_pubkey, &ev_pubkey) != 0
|
||||
if !ok {
|
||||
return .bad_sig
|
||||
}
|
||||
var raw_id_bytes = raw_id.bytes
|
||||
|
||||
ok = secp256k1_schnorrsig_verify(ctx, &sig64, &raw_id_bytes, raw_id.count, &xonly_pubkey) > 0
|
||||
|
||||
var sig = ev.sig.data.bytes
|
||||
var idbytes = id.id.bytes
|
||||
|
||||
ok = secp256k1_schnorrsig_verify(ctx, &sig, &idbytes, 32, &xonly_pubkey) > 0
|
||||
return ok ? .ok : .bad_sig
|
||||
}
|
||||
|
||||
func first_eref_mention(ev: NostrEvent, privkey: String?) -> Mention? {
|
||||
func first_eref_mention(ev: NostrEvent, privkey: Privkey?) -> Mention<NoteId>? {
|
||||
let blocks = ev.blocks(privkey).blocks.filter { block in
|
||||
guard case .mention(let mention) = block else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard case .event = mention.type else {
|
||||
return false
|
||||
}
|
||||
|
||||
if mention.ref.key != "e" {
|
||||
guard case .mention(let mention) = block,
|
||||
case .note = mention.ref else {
|
||||
return false
|
||||
}
|
||||
|
||||
@ -970,10 +945,13 @@ func first_eref_mention(ev: NostrEvent, privkey: String?) -> Mention? {
|
||||
}
|
||||
|
||||
/// MARK: - Preview
|
||||
if let firstBlock = blocks.first, case .mention(let mention) = firstBlock, mention.ref.key == "e" {
|
||||
return mention
|
||||
if let firstBlock = blocks.first,
|
||||
case .mention(let mention) = firstBlock,
|
||||
case .note(let note_id) = mention.ref
|
||||
{
|
||||
return .note(note_id)
|
||||
}
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -999,20 +977,3 @@ func to_reaction_emoji(ev: NostrEvent) -> String? {
|
||||
}
|
||||
}
|
||||
|
||||
extension [ReferencedId] {
|
||||
var pRefs: [ReferencedId] {
|
||||
get {
|
||||
Set(self).filter { ref in
|
||||
ref.key == "p"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var eRefs: [ReferencedId] {
|
||||
get {
|
||||
self.filter { ref in
|
||||
ref.key == "e"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,6 @@ enum NostrKind: UInt32, Codable {
|
||||
case delete = 5
|
||||
case boost = 6
|
||||
case like = 7
|
||||
case channel_create = 40
|
||||
case channel_meta = 41
|
||||
case chat = 42
|
||||
case list = 30000
|
||||
case longform = 30023
|
||||
|
@ -9,18 +9,18 @@ import Foundation
|
||||
|
||||
|
||||
enum NostrLink: Equatable {
|
||||
case ref(ReferencedId)
|
||||
case ref(RefId)
|
||||
case filter(NostrFilter)
|
||||
case script([UInt8])
|
||||
}
|
||||
|
||||
func encode_pubkey_uri(_ ref: ReferencedId) -> String {
|
||||
return "p:" + ref.ref_id
|
||||
func encode_pubkey_uri(_ pubkey: Pubkey) -> String {
|
||||
return "p:" + pubkey.hex()
|
||||
}
|
||||
|
||||
// TODO: bech32 and relay hints
|
||||
func encode_event_id_uri(_ ref: ReferencedId) -> String {
|
||||
return "e:" + ref.ref_id
|
||||
func encode_event_id_uri(_ noteid: NoteId) -> String {
|
||||
return "e:" + noteid.hex()
|
||||
}
|
||||
|
||||
func parse_nostr_ref_uri_type(_ p: Parser) -> String? {
|
||||
@ -55,36 +55,21 @@ func parse_hexstr(_ p: Parser, len: Int) -> String? {
|
||||
return String(substring(p.str, start: start, end: p.pos))
|
||||
}
|
||||
|
||||
func parse_nostr_ref_uri(_ p: Parser) -> ReferencedId? {
|
||||
let start = p.pos
|
||||
|
||||
if !parse_str(p, "nostr:") {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let ref = parse_post_bech32_mention(p) else {
|
||||
p.pos = start
|
||||
return nil
|
||||
}
|
||||
|
||||
return ref
|
||||
}
|
||||
|
||||
func decode_universal_link(_ s: String) -> NostrLink? {
|
||||
var uri = s.replacingOccurrences(of: "https://damus.io/r/", with: "")
|
||||
uri = uri.replacingOccurrences(of: "https://damus.io/", with: "")
|
||||
uri = uri.replacingOccurrences(of: "/", with: "")
|
||||
|
||||
guard let decoded = try? bech32_decode(uri) else {
|
||||
guard let decoded = try? bech32_decode(uri),
|
||||
decoded.data.count == 32
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let h = hex_encode(decoded.data)
|
||||
|
||||
|
||||
if decoded.hrp == "note" {
|
||||
return .ref(ReferencedId(ref_id: h, relay_id: nil, key: "e"))
|
||||
return .ref(.event(NoteId(decoded.data)))
|
||||
} else if decoded.hrp == "npub" {
|
||||
return .ref(ReferencedId(ref_id: h, relay_id: nil, key: "p"))
|
||||
return .ref(.pubkey(Pubkey(decoded.data)))
|
||||
}
|
||||
// TODO: handle nprofile, etc
|
||||
|
||||
@ -98,14 +83,12 @@ func decode_nostr_bech32_uri(_ s: String) -> NostrLink? {
|
||||
|
||||
switch obj {
|
||||
case .nsec(let privkey):
|
||||
guard let pubkey = privkey_to_pubkey(privkey: privkey) else {
|
||||
return nil
|
||||
}
|
||||
return .ref(ReferencedId(ref_id: pubkey, relay_id: nil, key: "p"))
|
||||
guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
|
||||
return .ref(.pubkey(pubkey))
|
||||
case .npub(let pubkey):
|
||||
return .ref(ReferencedId(ref_id: pubkey, relay_id: nil, key: "p"))
|
||||
return .ref(.pubkey(pubkey))
|
||||
case .note(let id):
|
||||
return .ref(ReferencedId(ref_id: id, relay_id: nil, key: "e"))
|
||||
return .ref(.event(id))
|
||||
case .nscript(let data):
|
||||
return .script(data)
|
||||
}
|
||||
@ -134,19 +117,15 @@ func decode_nostr_uri(_ s: String) -> NostrLink? {
|
||||
acc.append(decoded)
|
||||
return
|
||||
}
|
||||
|
||||
if tag_is_hashtag(parts) {
|
||||
|
||||
if parts.count >= 2 && parts[0] == "t" {
|
||||
return .filter(NostrFilter(hashtag: [parts[1].lowercased()]))
|
||||
}
|
||||
|
||||
if let rid = tag_to_refid(parts) {
|
||||
return .ref(rid)
|
||||
}
|
||||
|
||||
|
||||
guard parts.count == 1 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
let part = parts[0]
|
||||
|
||||
return decode_nostr_bech32_uri(part)
|
||||
|
@ -13,7 +13,12 @@ struct CommandResult {
|
||||
let msg: String
|
||||
}
|
||||
|
||||
enum NostrResponse: Decodable {
|
||||
enum MaybeResponse {
|
||||
case bad
|
||||
case ok(NostrResponse)
|
||||
}
|
||||
|
||||
enum NostrResponse {
|
||||
case event(String, NostrEvent)
|
||||
case notice(String)
|
||||
case eose(String)
|
||||
@ -32,48 +37,73 @@ enum NostrResponse: Decodable {
|
||||
}
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
var container = try decoder.unkeyedContainer()
|
||||
static func owned_from_json(json: String) -> NostrResponse? {
|
||||
return json.withCString{ cstr in
|
||||
let bufsize: Int = max(Int(Double(json.utf8.count) * 2.0), Int(getpagesize()))
|
||||
let data = malloc(bufsize)
|
||||
|
||||
// Only use first item
|
||||
let typ = try container.decode(String.self)
|
||||
if typ == "EVENT" {
|
||||
let sub_id = try container.decode(String.self)
|
||||
var ev: NostrEvent
|
||||
do {
|
||||
ev = try container.decode(NostrEvent.self)
|
||||
} catch {
|
||||
print(error)
|
||||
throw error
|
||||
if data == nil {
|
||||
let r: NostrResponse? = nil
|
||||
return r
|
||||
}
|
||||
//ev.pow = count_hash_leading_zero_bits(ev.id)
|
||||
self = .event(sub_id, ev)
|
||||
return
|
||||
} else if typ == "NOTICE" {
|
||||
let msg = try container.decode(String.self)
|
||||
self = .notice(msg)
|
||||
return
|
||||
} else if typ == "EOSE" {
|
||||
let sub_id = try container.decode(String.self)
|
||||
self = .eose(sub_id)
|
||||
return
|
||||
} else if typ == "OK" {
|
||||
var cr: CommandResult
|
||||
do {
|
||||
let event_id = try container.decode(String.self)
|
||||
let ok = try container.decode(Bool.self)
|
||||
let msg = try container.decode(String.self)
|
||||
cr = CommandResult(event_id: event_id, ok: ok, msg: msg)
|
||||
} catch {
|
||||
print(error)
|
||||
throw error
|
||||
//guard var json_cstr = json.cString(using: .utf8) else { return nil }
|
||||
|
||||
//json_cs
|
||||
var tce = ndb_tce()
|
||||
|
||||
let len = ndb_ws_event_from_json(cstr, Int32(json.utf8.count), &tce, data, Int32(bufsize))
|
||||
if len <= 0 {
|
||||
free(data)
|
||||
return nil
|
||||
}
|
||||
|
||||
switch tce.evtype {
|
||||
case NDB_TCE_OK:
|
||||
defer { free(data) }
|
||||
|
||||
guard let evid_str = sized_cstr(cstr: tce.subid, len: tce.subid_len),
|
||||
let evid = hex_decode_noteid(evid_str),
|
||||
let msg = sized_cstr(cstr: tce.command_result.msg, len: tce.command_result.msglen) else {
|
||||
return nil
|
||||
}
|
||||
let cr = CommandResult(event_id: evid, ok: tce.command_result.ok == 1, msg: msg)
|
||||
|
||||
return .ok(cr)
|
||||
case NDB_TCE_EOSE:
|
||||
defer { free(data) }
|
||||
|
||||
guard let subid = sized_cstr(cstr: tce.subid, len: tce.subid_len) else {
|
||||
return nil
|
||||
}
|
||||
return .eose(subid)
|
||||
case NDB_TCE_EVENT:
|
||||
|
||||
// Create new Data with just the valid bytes
|
||||
guard let note_data = realloc(data, Int(len)) else {
|
||||
free(data)
|
||||
return nil
|
||||
}
|
||||
let new_note = note_data.assumingMemoryBound(to: ndb_note.self)
|
||||
let note = NdbNote(note: new_note, owned_size: Int(len))
|
||||
|
||||
guard let subid = sized_cstr(cstr: tce.subid, len: tce.subid_len) else {
|
||||
free(data)
|
||||
return nil
|
||||
}
|
||||
return .event(subid, note)
|
||||
case NDB_TCE_NOTICE:
|
||||
free(data)
|
||||
return .notice("")
|
||||
default:
|
||||
free(data)
|
||||
return nil
|
||||
}
|
||||
self = .ok(cr)
|
||||
return
|
||||
//ev.pow = count_hash_leading_zero_bits(ev.id)
|
||||
}
|
||||
|
||||
throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: "expected EVENT, NOTICE or OK, got \(typ)"))
|
||||
}
|
||||
}
|
||||
|
||||
func sized_cstr(cstr: UnsafePointer<CChar>, len: Int32) -> String? {
|
||||
let msgbuf = Data(bytes: cstr, count: Int(len))
|
||||
return String(data: msgbuf, encoding: .utf8)
|
||||
}
|
||||
|
||||
|
@ -55,3 +55,22 @@ func hex_decode(_ str: String) -> [UInt8]?
|
||||
}
|
||||
|
||||
|
||||
func hex_decode_id(_ str: String) -> Data? {
|
||||
guard str.utf8.count == 64, let decoded = hex_decode(str) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return Data(decoded)
|
||||
}
|
||||
|
||||
func hex_decode_noteid(_ str: String) -> NoteId? {
|
||||
return hex_decode_id(str).map(NoteId.init)
|
||||
}
|
||||
|
||||
func hex_decode_pubkey(_ str: String) -> Pubkey? {
|
||||
return hex_decode_id(str).map(Pubkey.init)
|
||||
}
|
||||
|
||||
func hex_decode_privkey(_ str: String) -> Privkey? {
|
||||
return hex_decode_id(str).map(Privkey.init)
|
||||
}
|
||||
|
@ -1,29 +0,0 @@
|
||||
//
|
||||
// Pubkey.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-07-30.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// prepare a more gradual transition to the ndb branch
|
||||
typealias FollowRef = ReferencedId
|
||||
typealias Pubkey = String
|
||||
typealias NoteId = String
|
||||
typealias Privkey = String
|
||||
|
||||
extension String {
|
||||
// Id constructors
|
||||
init?(hex: String) {
|
||||
self = hex
|
||||
}
|
||||
|
||||
static var empty: String {
|
||||
return ""
|
||||
}
|
||||
|
||||
func hex() -> String {
|
||||
return self
|
||||
}
|
||||
}
|
@ -7,79 +7,44 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Reference {
|
||||
let key: AsciiCharacter
|
||||
let id: NdbTagElem
|
||||
var ref_id: NdbTagElem {
|
||||
id
|
||||
}
|
||||
|
||||
func to_referenced_id() -> ReferencedId {
|
||||
ReferencedId(ref_id: id.string(), relay_id: nil, key: key.description)
|
||||
}
|
||||
}
|
||||
|
||||
func tagref_should_be_id(_ tag: NdbTagElem) -> Bool {
|
||||
return !tag.matches_char("t")
|
||||
return !(tag.matches_char("t") || tag.matches_char("d"))
|
||||
}
|
||||
|
||||
struct References: Sequence, IteratorProtocol {
|
||||
|
||||
struct References<T: TagConvertible>: Sequence, IteratorProtocol {
|
||||
let tags: TagsSequence
|
||||
var tags_iter: TagsIterator
|
||||
|
||||
mutating func next() -> Reference? {
|
||||
while let tag = tags_iter.next() {
|
||||
guard tag.count >= 2 else { continue }
|
||||
let key = tag[0]
|
||||
let id = tag[1]
|
||||
|
||||
guard key.count == 1, tagref_should_be_id(id) else { continue }
|
||||
|
||||
for c in key {
|
||||
guard let a = AsciiCharacter(c) else { break }
|
||||
return Reference(key: a, id: id)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
static func ids(tags: TagsSequence) -> LazyFilterSequence<References> {
|
||||
References(tags: tags).lazy
|
||||
.filter() { ref in ref.key == "e" }
|
||||
}
|
||||
|
||||
static func pubkeys(tags: TagsSequence) -> LazyFilterSequence<References> {
|
||||
References(tags: tags).lazy
|
||||
.filter() { ref in ref.key == "p" }
|
||||
}
|
||||
|
||||
static func hashtags(tags: TagsSequence) -> LazyFilterSequence<References> {
|
||||
References(tags: tags).lazy
|
||||
.filter() { ref in ref.key == "t" }
|
||||
}
|
||||
|
||||
init(tags: TagsSequence) {
|
||||
self.tags = tags
|
||||
self.tags_iter = tags.makeIterator()
|
||||
}
|
||||
}
|
||||
|
||||
// TagsSequence transition helpers
|
||||
extension [[String]] {
|
||||
func strings() -> [[String]] {
|
||||
return self
|
||||
mutating func next() -> T? {
|
||||
while let tag = tags_iter.next() {
|
||||
guard let evref = T.from_tag(tag: tag) else { continue }
|
||||
return evref
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// TagsSequence transition helpers
|
||||
extension [String] {
|
||||
func strings() -> [String] {
|
||||
return self
|
||||
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 {
|
||||
@ -99,29 +64,117 @@ extension String {
|
||||
}
|
||||
}
|
||||
|
||||
struct ReferencedId: Identifiable, Hashable, Equatable {
|
||||
let ref_id: String
|
||||
let relay_id: String?
|
||||
let key: String
|
||||
enum FollowRef: TagKeys, Hashable, TagConvertible, Equatable {
|
||||
|
||||
var id: String {
|
||||
return ref_id
|
||||
}
|
||||
|
||||
static func q(_ id: String, relay_id: String? = nil) -> ReferencedId {
|
||||
return ReferencedId(ref_id: id, relay_id: relay_id, key: "q")
|
||||
}
|
||||
|
||||
static func e(_ id: String, relay_id: String? = nil) -> ReferencedId {
|
||||
return ReferencedId(ref_id: id, relay_id: relay_id, key: "e")
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
static func p(_ pk: String, relay_id: String? = nil) -> ReferencedId {
|
||||
return ReferencedId(ref_id: pk, relay_id: relay_id, key: "p")
|
||||
enum FollowKeys: AsciiCharacter, TagKey, CustomStringConvertible {
|
||||
case p, t
|
||||
|
||||
var keychar: AsciiCharacter { self.rawValue }
|
||||
var description: String { self.rawValue.description }
|
||||
}
|
||||
|
||||
static func t(_ hashtag: String, relay_id: String? = nil) -> ReferencedId {
|
||||
return ReferencedId(ref_id: hashtag, relay_id: relay_id, key: "t")
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum RefId: TagConvertible, TagKeys, Equatable, Hashable {
|
||||
case event(NoteId)
|
||||
case pubkey(Pubkey)
|
||||
case quote(QuoteId)
|
||||
case hashtag(TagElem)
|
||||
case param(TagElem)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
enum RefKey: AsciiCharacter, TagKey, CustomStringConvertible {
|
||||
case e, p, t, d, q
|
||||
|
||||
var keychar: AsciiCharacter {
|
||||
self.rawValue
|
||||
}
|
||||
|
||||
var description: String {
|
||||
self.keychar.description
|
||||
}
|
||||
}
|
||||
|
||||
var tag: [String] {
|
||||
[self.key.description, self.description]
|
||||
}
|
||||
|
||||
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.string()
|
||||
case .param(let string): return string.string()
|
||||
}
|
||||
}
|
||||
|
||||
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(t1)
|
||||
case .d: return .param(t1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,6 +200,7 @@ final class RelayConnection: ObservableObject {
|
||||
}
|
||||
return
|
||||
}
|
||||
print("failed to decode event \(messageString)")
|
||||
case .data(let messageData):
|
||||
if let messageString = String(data: messageData, encoding: .utf8) {
|
||||
receive(message: .string(messageString))
|
||||
|
@ -7,8 +7,14 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
let test_seckey = "8e33316b227de8215d36f4787573beaaf532229bb00398430a0ae963b658e656"
|
||||
let test_pubkey = "a9952fe066ced622167acb8069a0dfd1d44d9493ef2a4c28cf93e2877248b41a"
|
||||
|
||||
let test_seckey = Privkey(Data([0xe0, 0xaa, 0x60, 0x26, 0x08, 0x18, 0xac, 0x10, 0x03, 0x86, 0x4d, 0x15, 0x24, 0x9a, 0xf7, 0xa3, 0x3e, 0x4f, 0x1f, 0xc9, 0x01, 0xcf, 0xee, 0xa9, 0xb4, 0x77, 0xc7, 0x07, 0x22, 0xb7, 0x25, 0xfd]))
|
||||
|
||||
|
||||
let test_pubkey = Pubkey(Data([0xf7, 0xda, 0xc4, 0x6a, 0xa2, 0x70, 0xf7, 0x28, 0x76, 0x06, 0xa2, 0x2b, 0xeb, 0x4d, 0x77, 0x25, 0x57, 0x3a, 0xfa, 0x0e, 0x02, 0x8c, 0xdf, 0xac, 0x39, 0xa4, 0xcb, 0x23, 0x31, 0x53, 0x7f, 0x66]))
|
||||
|
||||
let test_pubkey_2 = Pubkey(Data([0x18, 0x42, 0x95, 0xc7, 0x6d, 0x5f, 0xf9, 0x4e, 0x99, 0x6a, 0xa8, 0xc1, 0x75, 0x23, 0x93, 0xdf, 0x0e, 0x72, 0xb5, 0x51, 0x89, 0xfc, 0x88, 0xfa, 0x06, 0x41, 0x5c, 0xce, 0x20, 0x4a, 0xc5, 0xea]))
|
||||
|
||||
let test_keypair = Keypair(pubkey: test_pubkey, privkey: test_seckey)
|
||||
let test_keypair_full = test_keypair.to_full()!
|
||||
|
||||
|
62
damus/Types/Ids/IdType.swift
Normal file
62
damus/Types/Ids/IdType.swift
Normal file
@ -0,0 +1,62 @@
|
||||
//
|
||||
// IdType.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-07-28.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol IdType: Codable, CustomStringConvertible, Hashable, Equatable {
|
||||
var id: Data { get }
|
||||
|
||||
init(_ data: Data)
|
||||
init(from decoder: Decoder) throws
|
||||
func encode(to encoder: Encoder) throws
|
||||
}
|
||||
|
||||
|
||||
extension IdType {
|
||||
func hex() -> String {
|
||||
hex_encode(self.id)
|
||||
}
|
||||
|
||||
var bytes: [UInt8] {
|
||||
self.id.bytes
|
||||
}
|
||||
|
||||
static var empty: Self {
|
||||
return Self.init(Data(repeating: 0, count: 32))
|
||||
}
|
||||
|
||||
var description: String {
|
||||
self.hex()
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
self.init(try hex_decoder(decoder))
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
try hex_encoder(to: encoder, data: self.id)
|
||||
}
|
||||
}
|
||||
|
||||
func hex_decoder(_ decoder: Decoder, expected_len: Int = 32) throws -> Data {
|
||||
let container = try decoder.singleValueContainer()
|
||||
guard let arr = hex_decode(try container.decode(String.self)) else {
|
||||
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "hex string"))
|
||||
}
|
||||
|
||||
if arr.count != expected_len {
|
||||
throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, debugDescription: "too long"))
|
||||
}
|
||||
|
||||
return Data(bytes: arr, count: arr.count)
|
||||
}
|
||||
|
||||
func hex_encoder(to encoder: Encoder, data: Data) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(hex_encode(data))
|
||||
}
|
||||
|
52
damus/Types/Ids/NoteId.swift
Normal file
52
damus/Types/Ids/NoteId.swift
Normal file
@ -0,0 +1,52 @@
|
||||
//
|
||||
// NoteId.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-07-28.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct NoteId: IdType, TagKey, TagConvertible {
|
||||
let id: Data
|
||||
|
||||
init(_ data: Data) {
|
||||
self.id = data
|
||||
}
|
||||
|
||||
init?(hex: String) {
|
||||
guard let note_id = hex_decode_noteid(hex) else {
|
||||
return nil
|
||||
}
|
||||
self = note_id
|
||||
}
|
||||
|
||||
var bech32: String {
|
||||
bech32_note_id(self)
|
||||
}
|
||||
|
||||
/// Refer to this NoteId as a QuoteId
|
||||
var quote_id: QuoteId {
|
||||
QuoteId(self.id)
|
||||
}
|
||||
|
||||
var keychar: AsciiCharacter { "e" }
|
||||
|
||||
var tag: [String] {
|
||||
["e", self.hex()]
|
||||
}
|
||||
|
||||
static func from_tag(tag: TagSequence) -> NoteId? {
|
||||
var i = tag.makeIterator()
|
||||
|
||||
guard tag.count >= 2,
|
||||
let t0 = i.next(),
|
||||
let key = t0.single_char,
|
||||
key == "e",
|
||||
let t1 = i.next(),
|
||||
let note_id = t1.id().map(NoteId.init)
|
||||
else { return nil }
|
||||
|
||||
return note_id
|
||||
}
|
||||
}
|
48
damus/Types/Ids/Pubkey.swift
Normal file
48
damus/Types/Ids/Pubkey.swift
Normal file
@ -0,0 +1,48 @@
|
||||
//
|
||||
// Pubkey.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-07-28.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct Pubkey: IdType, TagKey, TagConvertible, Identifiable {
|
||||
let id: Data
|
||||
|
||||
var tag: [String] {
|
||||
[keychar.description, self.hex()]
|
||||
}
|
||||
|
||||
init?(hex: String) {
|
||||
guard let id = hex_decode_pubkey(hex) else {
|
||||
return nil
|
||||
}
|
||||
self = id
|
||||
}
|
||||
|
||||
init(_ data: Data) {
|
||||
self.id = data
|
||||
}
|
||||
|
||||
var npub: String {
|
||||
bech32_pubkey(self)
|
||||
}
|
||||
|
||||
var keychar: AsciiCharacter { "p" }
|
||||
|
||||
static func from_tag(tag: TagSequence) -> Pubkey? {
|
||||
var i = tag.makeIterator()
|
||||
guard tag.count >= 2,
|
||||
let t0 = i.next(),
|
||||
let key = t0.single_char,
|
||||
key == "p",
|
||||
let t1 = i.next(),
|
||||
let pubkey = t1.id().map(Pubkey.init)
|
||||
else { return nil }
|
||||
|
||||
return pubkey
|
||||
}
|
||||
|
||||
}
|
||||
|
91
damus/Types/Ids/Referenced.swift
Normal file
91
damus/Types/Ids/Referenced.swift
Normal file
@ -0,0 +1,91 @@
|
||||
//
|
||||
// Referenced.swift
|
||||
// damus
|
||||
//
|
||||
// Created by William Casarin on 2023-07-28.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum Marker: String {
|
||||
case root
|
||||
case reply
|
||||
case mention
|
||||
|
||||
init?(_ tag: TagElem) {
|
||||
let len = tag.count
|
||||
|
||||
if len == 4, tag.matches_str("root", tag_len: len) {
|
||||
self = .root
|
||||
} else if len == 5, tag.matches_str("reply", tag_len: len) {
|
||||
self = .reply
|
||||
} else if len == 7, tag.matches_str("mention", tag_len: len) {
|
||||
self = .mention
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct NoteRef: IdType, TagConvertible, Equatable {
|
||||
let note_id: NoteId
|
||||
let relay: String?
|
||||
let marker: Marker?
|
||||
|
||||
var id: Data {
|
||||
self.note_id.id
|
||||
}
|
||||
|
||||
init(note_id: NoteId, relay: String? = nil, marker: Marker? = nil) {
|
||||
self.note_id = note_id
|
||||
self.relay = relay
|
||||
self.marker = marker
|
||||
}
|
||||
|
||||
static func note_id(_ note_id: NoteId) -> NoteRef {
|
||||
return NoteRef(note_id: note_id)
|
||||
}
|
||||
|
||||
init(_ data: Data) {
|
||||
self.note_id = NoteId(data)
|
||||
self.relay = nil
|
||||
self.marker = nil
|
||||
}
|
||||
|
||||
var tag: [String] {
|
||||
var t = ["e", self.hex()]
|
||||
if let marker {
|
||||
t.append(relay ?? "")
|
||||
t.append(marker.rawValue)
|
||||
} else if let relay {
|
||||
t.append(relay)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
static func from_tag(tag: TagSequence) -> NoteRef? {
|
||||
guard tag.count >= 2 else { return nil }
|
||||
|
||||
var i = tag.makeIterator()
|
||||
|
||||
guard let t0 = i.next(),
|
||||
t0.single_char == "e",
|
||||
let t1 = i.next(),
|
||||
let note_id = t1.id().map(NoteId.init)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var relay: String? = nil
|
||||
var marker: Marker? = nil
|
||||
|
||||
if tag.count >= 3, let r = i.next() {
|
||||
relay = r.string()
|
||||
if tag.count >= 4, let m = i.next() {
|
||||
marker = Marker(m)
|
||||
}
|
||||
}
|
||||
|
||||
return NoteRef(note_id: note_id, relay: relay, marker: marker)
|
||||
}
|
||||
}
|
@ -20,11 +20,11 @@ enum Bech32Object {
|
||||
}
|
||||
|
||||
if decoded.hrp == "npub" {
|
||||
return .npub(hex_encode(decoded.data))
|
||||
return .npub(Pubkey(decoded.data))
|
||||
} else if decoded.hrp == "nsec" {
|
||||
return .nsec(hex_encode(decoded.data))
|
||||
return .nsec(Privkey(decoded.data))
|
||||
} else if decoded.hrp == "note" {
|
||||
return .note(hex_encode(decoded.data))
|
||||
return .note(NoteId(decoded.data))
|
||||
} else if decoded.hrp == "nscript" {
|
||||
return .nscript(decoded.data.bytes)
|
||||
}
|
||||
|
@ -17,8 +17,11 @@ final class CredentialHandler: NSObject, ASAuthorizationControllerDelegate {
|
||||
authorizationController.performRequests()
|
||||
}
|
||||
|
||||
func save_credential(pubkey: String, privkey: String) {
|
||||
SecAddSharedWebCredential("damus.io" as CFString, pubkey as CFString, privkey as CFString, { error in
|
||||
func save_credential(pubkey: Pubkey, privkey: Privkey) {
|
||||
let pub = pubkey.npub
|
||||
let priv = privkey.nsec
|
||||
|
||||
SecAddSharedWebCredential("damus.io" as CFString, pub as CFString, priv as CFString, { error in
|
||||
if let error {
|
||||
print("⚠️ An error occurred while saving credentials: \(error)")
|
||||
}
|
||||
|
@ -60,7 +60,6 @@ func parse_display_name(profile: Profile?, pubkey: Pubkey) -> DisplayName {
|
||||
return .one(abbrev_bech32_pubkey(pubkey: pubkey))
|
||||
}
|
||||
|
||||
func abbrev_bech32_pubkey(pubkey: String) -> String {
|
||||
let pk = bech32_nopre_pubkey(pubkey) ?? pubkey
|
||||
return abbrev_pubkey(pk)
|
||||
func abbrev_bech32_pubkey(pubkey: Pubkey) -> String {
|
||||
return abbrev_pubkey(String(pubkey.npub.dropFirst(4)))
|
||||
}
|
||||
|
@ -87,12 +87,12 @@ class ZapsDataModel: ObservableObject {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func remove(reqid: String) -> Bool {
|
||||
guard zaps.first(where: { z in z.request.ev.id == reqid }) != nil else {
|
||||
func remove(reqid: ZapRequestId) -> Bool {
|
||||
guard zaps.first(where: { z in z.request.id == reqid }) != nil else {
|
||||
return false
|
||||
}
|
||||
|
||||
self.zaps = zaps.filter { z in z.request.ev.id != reqid }
|
||||
self.zaps = zaps.filter { z in z.request.id != reqid }
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -140,10 +140,10 @@ class EventCache {
|
||||
private var events: [NoteId: NostrEvent] = [:]
|
||||
private var replies = ReplyMap()
|
||||
private var cancellable: AnyCancellable?
|
||||
private var image_metadata: [String: ImageMetadataState] = [:]
|
||||
private var video_meta: [String: VideoPlayerModel] = [:]
|
||||
private var event_data: [String: EventData] = [:]
|
||||
|
||||
private var image_metadata: [String: ImageMetadataState] = [:] // lowercased URL key
|
||||
private var video_meta: [URL: VideoPlayerModel] = [:]
|
||||
private var event_data: [NoteId: EventData] = [:]
|
||||
|
||||
//private var thread_latest: [String: Int64]
|
||||
|
||||
init() {
|
||||
@ -174,7 +174,7 @@ class EventCache {
|
||||
|
||||
@discardableResult
|
||||
func store_zap(zap: Zapping) -> Bool {
|
||||
let data = get_cache_data(zap.target.id).zaps_model
|
||||
let data = get_cache_data(NoteId(zap.target.id)).zaps_model
|
||||
if let ev = zap.event {
|
||||
insert(ev)
|
||||
}
|
||||
@ -185,7 +185,7 @@ class EventCache {
|
||||
switch zap.target {
|
||||
case .note(let note_target):
|
||||
let zaps = get_cache_data(note_target.note_id).zaps_model
|
||||
zaps.remove(reqid: zap.request.ev.id)
|
||||
zaps.remove(reqid: zap.request.id)
|
||||
case .profile:
|
||||
// these aren't stored anywhere yet
|
||||
break
|
||||
@ -193,7 +193,7 @@ class EventCache {
|
||||
}
|
||||
|
||||
func lookup_zaps(target: ZapTarget) -> [Zapping] {
|
||||
return get_cache_data(target.id).zaps_model.zaps
|
||||
return get_cache_data(NoteId(target.id)).zaps_model.zaps
|
||||
}
|
||||
|
||||
func store_img_metadata(url: URL, meta: ImageMetadataState) {
|
||||
@ -214,31 +214,29 @@ class EventCache {
|
||||
}
|
||||
|
||||
func store_video_player_model(url: URL, meta: VideoPlayerModel) {
|
||||
video_meta[url.absoluteString] = meta
|
||||
video_meta[url] = meta
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func get_video_player_model(url: URL) -> VideoPlayerModel {
|
||||
if let model = video_meta[url.absoluteString] {
|
||||
if let model = video_meta[url] {
|
||||
return model
|
||||
}
|
||||
|
||||
let model = VideoPlayerModel()
|
||||
video_meta[url.absoluteString] = model
|
||||
video_meta[url] = model
|
||||
return model
|
||||
}
|
||||
|
||||
func parent_events(event: NostrEvent) -> [NostrEvent] {
|
||||
func parent_events(event: NostrEvent, privkey: Privkey?) -> [NostrEvent] {
|
||||
var parents: [NostrEvent] = []
|
||||
|
||||
var ev = event
|
||||
|
||||
while true {
|
||||
guard let direct_reply = ev.direct_replies(nil).last else {
|
||||
break
|
||||
}
|
||||
|
||||
guard let next_ev = lookup(direct_reply.ref_id), next_ev != ev else {
|
||||
guard let direct_reply = ev.direct_replies(privkey).last,
|
||||
let next_ev = lookup(direct_reply), next_ev != ev
|
||||
else {
|
||||
break
|
||||
}
|
||||
|
||||
@ -249,9 +247,9 @@ class EventCache {
|
||||
return parents.reversed()
|
||||
}
|
||||
|
||||
func add_replies(ev: NostrEvent) {
|
||||
for reply in ev.direct_replies(nil) {
|
||||
replies.add(id: reply.ref_id, reply_id: ev.id)
|
||||
func add_replies(ev: NostrEvent, privkey: Privkey?) {
|
||||
for reply in ev.direct_replies(privkey) {
|
||||
replies.add(id: reply, reply_id: ev.id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -360,7 +358,7 @@ func get_preload_plan(evcache: EventCache, ev: NostrEvent, our_keypair: Keypair,
|
||||
}
|
||||
|
||||
// Cached event might not have the note language determined yet, so determine the language here before figuring out if translations should be preloaded.
|
||||
let note_lang = cache.translations_model.note_language ?? ev.note_language(our_keypair.privkey) ?? current_language()
|
||||
let note_lang = cache.translations_model.note_language ?? /*ev.note_language(our_keypair.privkey)*/ current_language()
|
||||
|
||||
let load_translations = should_preload_translation(event: ev, our_keypair: our_keypair, current_status: cache.translations, settings: settings, note_lang: note_lang)
|
||||
if load_translations {
|
||||
@ -459,7 +457,7 @@ func preload_event(plan: PreloadPlan, state: DamusState) async {
|
||||
}
|
||||
|
||||
let note_language = plan.data.translations_model.note_language ?? plan.event.note_language(our_keypair.privkey) ?? current_language()
|
||||
|
||||
|
||||
var translations: TranslateStatus? = nil
|
||||
// We have to recheck should_translate here now that we have note_language
|
||||
if plan.load_translations && should_translate(event: plan.event, our_keypair: our_keypair, settings: settings, note_lang: note_language) && settings.auto_translate
|
||||
|
@ -9,7 +9,7 @@ import Foundation
|
||||
|
||||
/// Used for holding back events until they're ready to be displayed
|
||||
class EventHolder: ObservableObject, ScrollQueue {
|
||||
private var has_event = Set<String>()
|
||||
private var has_event = Set<NoteId>()
|
||||
@Published var events: [NostrEvent]
|
||||
var incoming: [NostrEvent]
|
||||
var should_queue = false
|
||||
|
@ -9,7 +9,14 @@ import Foundation
|
||||
import secp256k1
|
||||
|
||||
let PUBKEY_HRP = "npub"
|
||||
let ANON_PUBKEY = "anon"
|
||||
|
||||
// some random pubkey
|
||||
let ANON_PUBKEY = Pubkey(Data([
|
||||
0x85, 0x41, 0x5d, 0x63, 0x5c, 0x2b, 0xaf, 0x55,
|
||||
0xf5, 0xb9, 0xa1, 0xa6, 0xce, 0xb7, 0x75, 0xcc,
|
||||
0x5c, 0x45, 0x4a, 0x3a, 0x61, 0xb5, 0x3f, 0xe8,
|
||||
0x50, 0x42, 0xdc, 0x42, 0xac, 0xe1, 0x7f, 0x12
|
||||
]))
|
||||
|
||||
struct FullKeypair: Equatable {
|
||||
let pubkey: Pubkey
|
||||
@ -41,8 +48,8 @@ struct Keypair {
|
||||
init(pubkey: Pubkey, privkey: Privkey?) {
|
||||
self.pubkey = pubkey
|
||||
self.privkey = privkey
|
||||
self.pubkey_bech32 = bech32_pubkey(pubkey) ?? pubkey
|
||||
self.privkey_bech32 = privkey.flatMap { bech32_privkey($0) }
|
||||
self.pubkey_bech32 = pubkey.npub
|
||||
self.privkey_bech32 = privkey?.nsec
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,60 +59,52 @@ enum Bech32Key {
|
||||
}
|
||||
|
||||
func decode_bech32_key(_ key: String) -> Bech32Key? {
|
||||
guard let decoded = try? bech32_decode(key) else {
|
||||
guard let decoded = try? bech32_decode(key),
|
||||
decoded.data.count == 32
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let hexed = hex_encode(decoded.data)
|
||||
|
||||
if decoded.hrp == "npub" {
|
||||
return .pub(hexed)
|
||||
return .pub(Pubkey(decoded.data))
|
||||
} else if decoded.hrp == "nsec" {
|
||||
return .sec(hexed)
|
||||
return .sec(Privkey(decoded.data))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func bech32_privkey(_ privkey: String) -> String? {
|
||||
guard let bytes = hex_decode(privkey) else {
|
||||
return nil
|
||||
}
|
||||
return bech32_encode(hrp: "nsec", bytes)
|
||||
func bech32_privkey(_ privkey: Privkey) -> String {
|
||||
return bech32_encode(hrp: "nsec", privkey.bytes)
|
||||
}
|
||||
|
||||
func bech32_pubkey(_ pubkey: String) -> String? {
|
||||
guard let bytes = hex_decode(pubkey) else {
|
||||
return nil
|
||||
}
|
||||
return bech32_encode(hrp: "npub", bytes)
|
||||
func bech32_pubkey(_ pubkey: Pubkey) -> String {
|
||||
return bech32_encode(hrp: "npub", pubkey.bytes)
|
||||
}
|
||||
|
||||
func bech32_pubkey_decode(_ pubkey: String) -> String? {
|
||||
guard let decoded = try? bech32_decode(pubkey), decoded.hrp == "npub" else {
|
||||
func bech32_pubkey_decode(_ pubkey: String) -> Pubkey? {
|
||||
guard let decoded = try? bech32_decode(pubkey),
|
||||
decoded.hrp == "npub",
|
||||
decoded.data.count == 32
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return hex_encode(decoded.data)
|
||||
return Pubkey(decoded.data)
|
||||
}
|
||||
|
||||
func bech32_nopre_pubkey(_ pubkey: String) -> String? {
|
||||
guard let bytes = hex_decode(pubkey) else {
|
||||
return nil
|
||||
}
|
||||
return bech32_encode(hrp: "", bytes)
|
||||
func bech32_nopre_pubkey(_ pubkey: Pubkey) -> String {
|
||||
return bech32_encode(hrp: "", pubkey.bytes)
|
||||
}
|
||||
|
||||
func bech32_note_id(_ evid: String) -> String? {
|
||||
guard let bytes = hex_decode(evid) else {
|
||||
return nil
|
||||
}
|
||||
return bech32_encode(hrp: "note", bytes)
|
||||
func bech32_note_id(_ evid: NoteId) -> String {
|
||||
return bech32_encode(hrp: "note", evid.bytes)
|
||||
}
|
||||
|
||||
func generate_new_keypair() -> FullKeypair {
|
||||
let key = try! secp256k1.Signing.PrivateKey()
|
||||
let privkey = hex_encode(key.rawRepresentation)
|
||||
let pubkey = hex_encode(Data(key.publicKey.xonly.bytes))
|
||||
let privkey = Privkey(key.rawRepresentation)
|
||||
let pubkey = Pubkey(Data(key.publicKey.xonly.bytes))
|
||||
return FullKeypair(pubkey: pubkey, privkey: privkey)
|
||||
}
|
||||
|
||||
@ -113,12 +112,11 @@ func privkey_to_pubkey_raw(sec: [UInt8]) -> Pubkey? {
|
||||
guard let key = try? secp256k1.Signing.PrivateKey(rawRepresentation: sec) else {
|
||||
return nil
|
||||
}
|
||||
return hex_encode(Data(key.publicKey.xonly.bytes))
|
||||
return Pubkey(Data(key.publicKey.xonly.bytes))
|
||||
}
|
||||
|
||||
func privkey_to_pubkey(privkey: String) -> String? {
|
||||
guard let sec = hex_decode(privkey) else { return nil }
|
||||
return privkey_to_pubkey_raw(sec: sec)
|
||||
func privkey_to_pubkey(privkey: Privkey) -> Pubkey? {
|
||||
return privkey_to_pubkey_raw(sec: privkey.bytes)
|
||||
}
|
||||
|
||||
func save_pubkey(pubkey: Pubkey) {
|
||||
@ -155,11 +153,18 @@ func clear_keypair() throws {
|
||||
func get_saved_keypair() -> Keypair? {
|
||||
do {
|
||||
try removePrivateKeyFromUserDefaults()
|
||||
|
||||
return get_saved_pubkey().flatMap { pubkey in
|
||||
let privkey = get_saved_privkey()
|
||||
return Keypair(pubkey: pubkey, privkey: privkey)
|
||||
|
||||
guard let pubkey = get_saved_pubkey(),
|
||||
let pk = hex_decode(pubkey)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let privkey = get_saved_privkey().flatMap { sec in
|
||||
hex_decode(sec).map { Privkey(Data($0)) }
|
||||
}
|
||||
|
||||
return Keypair(pubkey: Pubkey(Data(pk)), privkey: privkey)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
@ -189,7 +194,10 @@ func contentContainsPrivateKey(_ content: String) -> Bool {
|
||||
}
|
||||
|
||||
fileprivate func removePrivateKeyFromUserDefaults() throws {
|
||||
guard let privKey = UserDefaults.standard.string(forKey: "privkey") else { return }
|
||||
try save_privkey(privkey: privKey)
|
||||
guard let privkey_str = UserDefaults.standard.string(forKey: "privkey"),
|
||||
let privkey = hex_decode_privkey(privkey_str)
|
||||
else { return }
|
||||
|
||||
try save_privkey(privkey: privkey)
|
||||
UserDefaults.standard.removeObject(forKey: "privkey")
|
||||
}
|
||||
|
@ -7,64 +7,62 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
func create_or_update_mutelist(keypair: FullKeypair, mprev: NostrEvent?, to_add: String) -> NostrEvent? {
|
||||
func create_or_update_mutelist(keypair: FullKeypair, mprev: NostrEvent?, to_add: RefId) -> NostrEvent? {
|
||||
return create_or_update_list_event(keypair: keypair, mprev: mprev, to_add: to_add, list_name: "mute", list_type: "p")
|
||||
}
|
||||
|
||||
func remove_from_mutelist(keypair: FullKeypair, prev: NostrEvent, to_remove: String) -> NostrEvent? {
|
||||
return remove_from_list_event(keypair: keypair, prev: prev, to_remove: to_remove, tag_type: "p")
|
||||
func remove_from_mutelist(keypair: FullKeypair, prev: NostrEvent, to_remove: RefId) -> NostrEvent? {
|
||||
return remove_from_list_event(keypair: keypair, prev: prev, to_remove: to_remove)
|
||||
}
|
||||
|
||||
func create_or_update_list_event(keypair: FullKeypair, mprev: NostrEvent?, to_add: String, list_name: String, list_type: String) -> NostrEvent? {
|
||||
func create_or_update_list_event(keypair: FullKeypair, mprev: NostrEvent?, to_add: RefId, list_name: String, list_type: String) -> NostrEvent? {
|
||||
if let prev = mprev,
|
||||
prev.pubkey == keypair.pubkey,
|
||||
matches_list_name(tags: prev.tags, name: list_name)
|
||||
{
|
||||
return add_to_list_event(keypair: keypair, prev: prev, to_add: to_add, tag_type: list_type)
|
||||
return add_to_list_event(keypair: keypair, prev: prev, to_add: to_add)
|
||||
}
|
||||
|
||||
let tags = [["d", list_name], [list_type, to_add]]
|
||||
let tags = [["d", list_name], [list_type, to_add.description]]
|
||||
return NostrEvent(content: "", keypair: keypair.to_keypair(), kind: 30000, tags: tags)
|
||||
}
|
||||
|
||||
func remove_from_list_event(keypair: FullKeypair, prev: NostrEvent, to_remove: String, tag_type: String) -> NostrEvent? {
|
||||
var exists = false
|
||||
for tag in prev.tags {
|
||||
if tag.count >= 2 && tag[0] == tag_type && tag[1] == to_remove {
|
||||
exists = true
|
||||
func remove_from_list_event(keypair: FullKeypair, prev: NostrEvent, to_remove: RefId) -> NostrEvent? {
|
||||
var removed = false
|
||||
|
||||
let tags = prev.tags.reduce(into: [[String]](), { acc, tag in
|
||||
if let ref_id = RefId.from_tag(tag: tag), ref_id == to_remove {
|
||||
removed = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// make sure we actually have the pubkey to remove
|
||||
guard exists else {
|
||||
acc.append(tag.strings())
|
||||
})
|
||||
|
||||
guard removed else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let new_tags = prev.tags.filter { tag in
|
||||
!(tag.count >= 2 && tag[0] == tag_type && tag[1] == to_remove)
|
||||
}
|
||||
|
||||
return NostrEvent(content: prev.content, keypair: keypair.to_keypair(), kind: 30000, tags: new_tags)
|
||||
}
|
||||
|
||||
func add_to_list_event(keypair: FullKeypair, prev: NostrEvent, to_add: String, tag_type: String) -> NostrEvent? {
|
||||
for tag in prev.tags {
|
||||
// we are already muting this user
|
||||
if tag.count >= 2 && tag[0] == tag_type && tag[1] == to_add {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var tags = Array(prev.tags)
|
||||
tags.append([tag_type, to_add])
|
||||
|
||||
return NostrEvent(content: prev.content, keypair: keypair.to_keypair(), kind: 30000, tags: tags)
|
||||
}
|
||||
|
||||
func matches_list_name(tags: [[String]], name: String) -> Bool {
|
||||
func add_to_list_event(keypair: FullKeypair, prev: NostrEvent, to_add: RefId) -> NostrEvent? {
|
||||
for tag in prev.tags {
|
||||
// we are already muting this user
|
||||
if let ref = RefId.from_tag(tag: tag), to_add == ref {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var tags = prev.tags.strings()
|
||||
tags.append(to_add.tag)
|
||||
|
||||
return NostrEvent(content: prev.content, keypair: keypair.to_keypair(), kind: 30000, tags: tags)
|
||||
}
|
||||
|
||||
func matches_list_name(tags: Tags, name: String) -> Bool {
|
||||
for tag in tags {
|
||||
if tag.count >= 2 && tag[0] == "d" {
|
||||
return tag[1] == name
|
||||
if tag.count >= 2 && tag[0].matches_char("d") {
|
||||
return tag[1].matches_str(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,21 +9,21 @@ import Foundation
|
||||
|
||||
struct LossyLocalNotification {
|
||||
let type: LocalNotificationType
|
||||
let event_id: String
|
||||
|
||||
let mention: MentionRef
|
||||
|
||||
func to_user_info() -> [AnyHashable: Any] {
|
||||
return [
|
||||
"type": self.type.rawValue,
|
||||
"evid": self.event_id
|
||||
"id": self.mention.bech32
|
||||
]
|
||||
}
|
||||
|
||||
static func from_user_info(user_info: [AnyHashable: Any]) -> LossyLocalNotification {
|
||||
let target_id = user_info["evid"] as! String
|
||||
let target_id = MentionRef.from_bech32(str: user_info["id"] as! String)!
|
||||
let typestr = user_info["type"] as! String
|
||||
let type = LocalNotificationType(rawValue: typestr)!
|
||||
|
||||
return LossyLocalNotification(type: type, event_id: target_id)
|
||||
return LossyLocalNotification(type: type, mention: target_id)
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ struct LocalNotification {
|
||||
let content: String
|
||||
|
||||
func to_lossy() -> LossyLocalNotification {
|
||||
return LossyLocalNotification(type: self.type, event_id: self.target.id)
|
||||
return LossyLocalNotification(type: self.type, mention: .note(self.target.id))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ class ReplyCounter {
|
||||
return replies[evid] ?? 0
|
||||
}
|
||||
|
||||
func count_replies(_ event: NostrEvent) {
|
||||
func count_replies(_ event: NostrEvent, privkey: Privkey?) {
|
||||
guard event.is_textlike else {
|
||||
return
|
||||
}
|
||||
@ -39,15 +39,15 @@ class ReplyCounter {
|
||||
|
||||
counted.insert(event.id)
|
||||
|
||||
for reply in event.direct_replies(nil) {
|
||||
for reply in event.direct_replies(privkey) {
|
||||
if event.pubkey == our_pubkey {
|
||||
self.our_replies[reply.ref_id] = event
|
||||
self.our_replies[reply] = event
|
||||
}
|
||||
|
||||
if replies[reply.ref_id] != nil {
|
||||
replies[reply.ref_id] = replies[reply.ref_id]! + 1
|
||||
if replies[reply] != nil {
|
||||
replies[reply] = replies[reply]! + 1
|
||||
} else {
|
||||
replies[reply.ref_id] = 1
|
||||
replies[reply] = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,21 +38,25 @@ struct WalletConnectURL: Equatable {
|
||||
init?(str: String) {
|
||||
guard let url = URL(string: str),
|
||||
url.scheme == "nostrwalletconnect" || url.scheme == "nostr+walletconnect",
|
||||
let pk = url.host, pk.utf8.count == 64,
|
||||
let pkhost = url.host,
|
||||
let pubkey = hex_decode_pubkey(pkhost),
|
||||
let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
||||
let items = components.queryItems,
|
||||
let relay = items.first(where: { qi in qi.name == "relay" })?.value,
|
||||
let relay_url = RelayURL(relay),
|
||||
let secret = items.first(where: { qi in qi.name == "secret" })?.value,
|
||||
secret.utf8.count == 64,
|
||||
let our_pk = privkey_to_pubkey(privkey: secret)
|
||||
let decoded = hex_decode(secret)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
let privkey = Privkey(Data(decoded))
|
||||
guard let our_pk = privkey_to_pubkey(privkey: privkey) else { return nil }
|
||||
|
||||
let lud16 = items.first(where: { qi in qi.name == "lud16" })?.value
|
||||
let keypair = FullKeypair(pubkey: our_pk, privkey: secret)
|
||||
self = WalletConnectURL(pubkey: pk, relay: relay_url, keypair: keypair, lud16: lud16)
|
||||
let keypair = FullKeypair(pubkey: our_pk, privkey: privkey)
|
||||
self = WalletConnectURL(pubkey: pubkey, relay: relay_url, keypair: keypair, lud16: lud16)
|
||||
}
|
||||
|
||||
init(pubkey: Pubkey, relay: RelayURL, keypair: FullKeypair, lud16: String?) {
|
||||
@ -90,11 +94,11 @@ struct FullWalletResponse {
|
||||
let response: WalletResponse
|
||||
|
||||
init?(from: NostrEvent, nwc: WalletConnectURL) async {
|
||||
guard let req_id = from.referenced_ids.first else {
|
||||
guard let note_id = from.referenced_ids.first else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.req_id = req_id.ref_id.string()
|
||||
|
||||
self.req_id = note_id
|
||||
|
||||
let ares = Task {
|
||||
guard let json = decrypt_dm(nwc.keypair.privkey, pubkey: nwc.pubkey, content: from.content, encoding: .base64),
|
||||
@ -166,7 +170,7 @@ struct PayInvoiceRequest: Codable {
|
||||
}
|
||||
|
||||
func make_wallet_connect_request<T>(req: WalletRequest<T>, to_pk: Pubkey, keypair: FullKeypair) -> NostrEvent? {
|
||||
let tags = [["p", to_pk]]
|
||||
let tags = [to_pk.tag]
|
||||
let created_at = UInt32(Date().timeIntervalSince1970)
|
||||
guard let content = encode_json(req) else {
|
||||
return nil
|
||||
@ -213,7 +217,7 @@ func nwc_success(state: DamusState, resp: FullWalletResponse) {
|
||||
|
||||
if nwc_state.update_state(state: .confirmed) {
|
||||
// notify the zaps model of an update so it can mark them as paid
|
||||
state.events.get_cache_data(pzap.target.id).zaps_model.objectWillChange.send()
|
||||
state.events.get_cache_data(NoteId(pzap.target.id)).zaps_model.objectWillChange.send()
|
||||
print("NWC success confirmed")
|
||||
}
|
||||
|
||||
|
@ -28,13 +28,22 @@ enum ZapTarget: Equatable, Hashable {
|
||||
return note_target.author
|
||||
}
|
||||
}
|
||||
|
||||
var id: String {
|
||||
|
||||
var note_id: NoteId? {
|
||||
switch self {
|
||||
case .note(let note_target):
|
||||
return note_target.note_id
|
||||
case .profile(let pk):
|
||||
return pk
|
||||
case .profile:
|
||||
return nil
|
||||
case .note(let noteZapTarget):
|
||||
return noteZapTarget.note_id
|
||||
}
|
||||
}
|
||||
|
||||
var id: Data {
|
||||
switch self {
|
||||
case .profile(let pubkey):
|
||||
return pubkey.id
|
||||
case .note(let noteZapTarget):
|
||||
return noteZapTarget.note_id.id
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -42,7 +51,11 @@ enum ZapTarget: Equatable, Hashable {
|
||||
struct ZapRequest {
|
||||
let ev: NostrEvent
|
||||
let marked_hidden: Bool
|
||||
|
||||
|
||||
var id: ZapRequestId {
|
||||
ZapRequestId(from_zap_request: self)
|
||||
}
|
||||
|
||||
var is_in_thread: Bool {
|
||||
return !self.ev.content.isEmpty && !marked_hidden
|
||||
}
|
||||
@ -134,9 +147,13 @@ class PendingZap {
|
||||
}
|
||||
}
|
||||
|
||||
struct ZapRequestId: Equatable {
|
||||
let reqid: String
|
||||
|
||||
struct ZapRequestId: Equatable, Hashable {
|
||||
let reqid: NoteId
|
||||
|
||||
init(from_zap_request: ZapRequest) {
|
||||
self.reqid = from_zap_request.ev.id
|
||||
}
|
||||
|
||||
init(from_zap: Zapping) {
|
||||
self.reqid = from_zap.request.ev.id
|
||||
}
|
||||
@ -348,11 +365,11 @@ func invoice_to_zap_invoice(_ invoice: Invoice) -> ZapInvoice? {
|
||||
}
|
||||
|
||||
func determine_zap_target(_ ev: NostrEvent) -> ZapTarget? {
|
||||
guard let ptag = event_tag(ev, name: "p") else {
|
||||
guard let ptag = ev.referenced_pubkeys.first else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let etag = event_tag(ev, name: "e") {
|
||||
if let etag = ev.referenced_ids.first {
|
||||
return ZapTarget.note(id: etag, author: ptag)
|
||||
}
|
||||
|
||||
@ -376,7 +393,7 @@ func decode_bolt11(_ s: String) -> Invoice? {
|
||||
|
||||
let block = bs.blocks[0]
|
||||
|
||||
guard let converted = convert_block(block, tags: []) else {
|
||||
guard let converted = convert_block(block, tags: nil) else {
|
||||
blocks_free(&bs)
|
||||
return nil
|
||||
}
|
||||
@ -405,20 +422,16 @@ func decode_nostr_event_json(_ desc: String) -> NostrEvent? {
|
||||
}
|
||||
|
||||
|
||||
func fetch_zapper_from_lnurl(lnurls: LNUrls, pubkey: String, lnurl: String) async -> String? {
|
||||
guard let endpoint = await lnurls.lookup_or_fetch(pubkey: pubkey, lnurl: lnurl) else {
|
||||
func fetch_zapper_from_lnurl(lnurls: LNUrls, pubkey: Pubkey, lnurl: String) async -> Pubkey? {
|
||||
guard let endpoint = await lnurls.lookup_or_fetch(pubkey: pubkey, lnurl: lnurl),
|
||||
let allows = endpoint.allowsNostr, allows,
|
||||
let key = endpoint.nostrPubkey,
|
||||
let pk = hex_decode_pubkey(key)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let allows = endpoint.allowsNostr, allows else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let key = endpoint.nostrPubkey, key.count == 64 else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return endpoint.nostrPubkey
|
||||
return pk
|
||||
}
|
||||
|
||||
func decode_lnurl(_ lnurl: String) -> URL? {
|
||||
|
@ -34,14 +34,17 @@ class Zaps {
|
||||
res = zap
|
||||
|
||||
our_zaps[kv.key] = ours.filter { z in z.request.ev.id != reqid }
|
||||
|
||||
if let count = event_counts[zap.target.id] {
|
||||
event_counts[zap.target.id] = count - 1
|
||||
|
||||
// counts for note zaps
|
||||
if let note_id = zap.target.note_id {
|
||||
if let count = event_counts[note_id] {
|
||||
event_counts[note_id] = count - 1
|
||||
}
|
||||
if let total = event_totals[note_id] {
|
||||
event_totals[note_id] = total - zap.amount
|
||||
}
|
||||
}
|
||||
if let total = event_totals[zap.target.id] {
|
||||
event_totals[zap.target.id] = total - zap.amount
|
||||
}
|
||||
|
||||
|
||||
// we found the request id, we can stop looking
|
||||
break
|
||||
}
|
||||
@ -55,6 +58,7 @@ class Zaps {
|
||||
return
|
||||
}
|
||||
self.zaps[zap.request.ev.id] = zap
|
||||
|
||||
if let zap_id = zap.event?.id {
|
||||
self.zaps[zap_id] = zap
|
||||
}
|
||||
@ -62,11 +66,12 @@ class Zaps {
|
||||
// record our zaps for an event
|
||||
if zap.request.ev.pubkey == our_pubkey {
|
||||
switch zap.target {
|
||||
case .note(let note_target):
|
||||
if our_zaps[note_target.note_id] == nil {
|
||||
our_zaps[note_target.note_id] = [zap]
|
||||
case .note(let note_zap):
|
||||
let note_id = note_zap.note_id
|
||||
if our_zaps[note_id] == nil {
|
||||
our_zaps[note_id] = [zap]
|
||||
} else {
|
||||
insert_uniq_sorted_zap_by_amount(zaps: &(our_zaps[note_target.note_id]!), new_zap: zap)
|
||||
insert_uniq_sorted_zap_by_amount(zaps: &(our_zaps[note_id]!), new_zap: zap)
|
||||
}
|
||||
case .profile:
|
||||
break
|
||||
@ -78,19 +83,20 @@ class Zaps {
|
||||
return
|
||||
}
|
||||
|
||||
let id = zap.target.id
|
||||
if event_counts[id] == nil {
|
||||
event_counts[id] = 0
|
||||
}
|
||||
|
||||
if event_totals[id] == nil {
|
||||
event_totals[id] = 0
|
||||
}
|
||||
|
||||
event_counts[id] = event_counts[id]! + 1
|
||||
event_totals[id] = event_totals[id]! + zap.amount
|
||||
if let note_id = zap.target.note_id {
|
||||
if event_counts[note_id] == nil {
|
||||
event_counts[note_id] = 0
|
||||
}
|
||||
|
||||
notify(.update_stats(note_id: zap.target.id))
|
||||
if event_totals[note_id] == nil {
|
||||
event_totals[note_id] = 0
|
||||
}
|
||||
|
||||
event_counts[note_id] = event_counts[note_id]! + 1
|
||||
event_totals[note_id] = event_totals[note_id]! + zap.amount
|
||||
|
||||
notify(.update_stats(note_id: note_id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,5 +104,5 @@ func remove_zap(reqid: ZapRequestId, zapcache: Zaps, evcache: EventCache) {
|
||||
guard let zap = zapcache.remove_zap(reqid: reqid.reqid) else {
|
||||
return
|
||||
}
|
||||
evcache.get_cache_data(zap.target.id).zaps_model.remove(reqid: reqid.reqid)
|
||||
evcache.get_cache_data(NoteId(zap.target.id)).zaps_model.remove(reqid: reqid)
|
||||
}
|
||||
|
@ -106,11 +106,7 @@ struct EventActionBar: View {
|
||||
}
|
||||
}
|
||||
.sheet(isPresented: $show_share_sheet, onDismiss: { self.show_share_sheet = false }) {
|
||||
if let note_id = bech32_note_id(event.id) {
|
||||
if let url = URL(string: "https://damus.io/" + note_id) {
|
||||
ShareSheet(activityItems: [url])
|
||||
}
|
||||
}
|
||||
ShareSheet(activityItems: [URL(string: "https://damus.io/" + event.id.bech32)!])
|
||||
}
|
||||
.sheet(isPresented: $show_repost_action, onDismiss: { self.show_repost_action = false }) {
|
||||
|
||||
|
@ -38,7 +38,7 @@ struct ShareAction: View {
|
||||
|
||||
ShareActionButton(img: "link", text: NSLocalizedString("Copy Link", comment: "Button to copy link to note")) {
|
||||
dismiss()
|
||||
UIPasteboard.general.string = "https://damus.io/" + (bech32_note_id(event.id) ?? event.id)
|
||||
UIPasteboard.general.string = "https://damus.io/" + event.id.bech32
|
||||
}
|
||||
|
||||
let bookmarkImg = isBookmarked ? "bookmark.fill" : "bookmark"
|
||||
|
@ -36,18 +36,18 @@ struct GradientFollowButton: View {
|
||||
)
|
||||
}
|
||||
.onReceive(handle_notify(.followed)) { ref in
|
||||
guard target.pubkey == ref.ref_id else { return }
|
||||
guard target.follow_ref == ref else { return }
|
||||
self.follow_state = .follows
|
||||
}
|
||||
.onReceive(handle_notify(.unfollowed)) { ref in
|
||||
guard target.pubkey == ref.ref_id else { return }
|
||||
guard target.follow_ref == ref else { return }
|
||||
self.follow_state = .unfollows
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct GradientFollowButtonPreviews: View {
|
||||
let target: FollowTarget = .pubkey("")
|
||||
let target: FollowTarget = .pubkey(.empty)
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text(verbatim: "Unfollows")
|
||||
|
@ -135,8 +135,7 @@ struct CreateAccountView_Previews: PreviewProvider {
|
||||
}
|
||||
|
||||
func KeyText(_ pubkey: Binding<Pubkey>) -> some View {
|
||||
let decoded = hex_decode(pubkey.wrappedValue)!
|
||||
let bechkey = bech32_encode(hrp: PUBKEY_HRP, decoded)
|
||||
let bechkey = bech32_encode(hrp: PUBKEY_HRP, pubkey.wrappedValue.bytes)
|
||||
return Text(bechkey)
|
||||
.textSelection(.enabled)
|
||||
.multilineTextAlignment(.center)
|
||||
|
@ -18,7 +18,7 @@ struct DMView: View {
|
||||
var Mention: some View {
|
||||
Group {
|
||||
if let mention = first_eref_mention(ev: event, privkey: damus_state.keypair.privkey) {
|
||||
BuilderEventView(damus: damus_state, event_id: mention.ref.id)
|
||||
BuilderEventView(damus: damus_state, event_id: mention.ref)
|
||||
} else {
|
||||
EmptyView()
|
||||
}
|
||||
|
@ -72,10 +72,10 @@ func should_show_images(settings: UserSettingsStore, contacts: Contacts, ev: Nos
|
||||
}
|
||||
|
||||
extension View {
|
||||
func pubkey_context_menu(bech32_pubkey: Pubkey) -> some View {
|
||||
func pubkey_context_menu(pubkey: Pubkey) -> some View {
|
||||
return self.contextMenu {
|
||||
Button {
|
||||
UIPasteboard.general.string = bech32_pubkey
|
||||
UIPasteboard.general.string = pubkey.npub
|
||||
} label: {
|
||||
Label(NSLocalizedString("Copy Account ID", comment: "Context menu option for copying the ID of the account that created the note."), image: "copy2")
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ struct EventTop: View {
|
||||
|
||||
func ProfileName(is_anon: Bool) -> some View {
|
||||
let profile = state.profiles.lookup(id: self.pubkey)
|
||||
let pk = is_anon ? "anon" : self.pubkey
|
||||
let pk = is_anon ? ANON_PUBKEY : self.pubkey
|
||||
return EventProfileName(pubkey: pk, profile: profile, damus: state, size: .normal)
|
||||
}
|
||||
|
||||
|
@ -37,9 +37,9 @@ func reply_desc(profiles: Profiles, event: NostrEvent, locale: Locale = Locale.c
|
||||
return NSLocalizedString("Replying to self", bundle: bundle, comment: "Label to indicate that the user is replying to themself.")
|
||||
}
|
||||
|
||||
let names: [String] = pubkeys.map {
|
||||
let prof = profiles.lookup(id: $0)
|
||||
return Profile.displayName(profile: prof, pubkey: $0).username.truncate(maxLength: 50)
|
||||
let names: [String] = pubkeys.map { pk in
|
||||
let prof = profiles.lookup(id: pk)
|
||||
return Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50)
|
||||
}
|
||||
|
||||
let uniqueNames = NSOrderedSet(array: names).array as! [String]
|
||||
|
@ -27,9 +27,7 @@ struct EventMenuContext: View {
|
||||
var body: some View {
|
||||
HStack {
|
||||
Menu {
|
||||
|
||||
MenuItems(event: event, keypair: keypair, target_pubkey: target_pubkey, bookmarks: bookmarks, muted_threads: muted_threads, settings: settings)
|
||||
|
||||
} label: {
|
||||
Label("", systemImage: "ellipsis")
|
||||
.foregroundColor(Color.gray)
|
||||
@ -77,13 +75,13 @@ struct MenuItems: View {
|
||||
}
|
||||
|
||||
Button {
|
||||
UIPasteboard.general.string = bech32_pubkey(target_pubkey)
|
||||
UIPasteboard.general.string = target_pubkey.npub
|
||||
} label: {
|
||||
Label(NSLocalizedString("Copy user public key", comment: "Context menu option for copying the ID of the user who created the note."), image: "user")
|
||||
}
|
||||
|
||||
Button {
|
||||
UIPasteboard.general.string = bech32_note_id(event.id) ?? event.id
|
||||
UIPasteboard.general.string = event.id.bech32
|
||||
} label: {
|
||||
Label(NSLocalizedString("Copy note ID", comment: "Context menu option for copying the ID of the note."), image: "note-book")
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ struct EventShell<Content: View>: View {
|
||||
!options.contains(.no_action_bar)
|
||||
}
|
||||
|
||||
func get_mention() -> Mention? {
|
||||
func get_mention() -> Mention<NoteId>? {
|
||||
if self.options.contains(.nested) || self.options.contains(.no_mentions) {
|
||||
return nil
|
||||
}
|
||||
@ -42,8 +42,8 @@ struct EventShell<Content: View>: View {
|
||||
return first_eref_mention(ev: event, privkey: state.keypair.privkey)
|
||||
}
|
||||
|
||||
func Mention(_ mention: Mention) -> some View {
|
||||
return BuilderEventView(damus: state, event_id: mention.ref.id)
|
||||
func Mention(_ mention: Mention<NoteId>) -> some View {
|
||||
return BuilderEventView(damus: state, event_id: mention.ref)
|
||||
}
|
||||
|
||||
var ActionBar: some View {
|
||||
|
@ -20,12 +20,12 @@ struct LongformEvent {
|
||||
|
||||
for tag in ev.tags {
|
||||
guard tag.count >= 2 else { continue }
|
||||
switch tag[0] {
|
||||
case "title": longform.title = tag[1]
|
||||
case "image": longform.image = URL(string: tag[1])
|
||||
case "summary": longform.summary = tag[1]
|
||||
switch tag[0].string() {
|
||||
case "title": longform.title = tag[1].string()
|
||||
case "image": longform.image = URL(string: tag[1].string())
|
||||
case "summary": longform.summary = tag[1].string()
|
||||
case "published_at":
|
||||
longform.published_at = Double(tag[1]).map { d in Date(timeIntervalSince1970: d) }
|
||||
longform.published_at = Double(tag[1].string()).map { d in Date(timeIntervalSince1970: d) }
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ struct SelectedEventView: View {
|
||||
EventBody(damus_state: damus, event: event, size: size, options: [.wide])
|
||||
|
||||
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) {
|
||||
BuilderEventView(damus: damus, event_id: mention.ref.id)
|
||||
BuilderEventView(damus: damus, event_id: mention.ref)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ struct TextEvent: View {
|
||||
|
||||
func event_has_tag(ev: NostrEvent, tag: String) -> Bool {
|
||||
for t in ev.tags {
|
||||
if t.count >= 1 && t[0] == tag {
|
||||
if t.count >= 1 && t[0].matches_str(tag) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -31,18 +31,16 @@ struct FollowButtonView: View {
|
||||
.stroke(follow_state == .unfollows ? .clear : borderColor(), lineWidth: 1)
|
||||
}
|
||||
}
|
||||
.onReceive(handle_notify(.followed)) { pk in
|
||||
guard pk.key == "p", target.pubkey == pk.ref_id else {
|
||||
return
|
||||
}
|
||||
|
||||
.onReceive(handle_notify(.followed)) { follow in
|
||||
guard case .pubkey(let pk) = follow,
|
||||
pk == target.pubkey else { return }
|
||||
|
||||
self.follow_state = .follows
|
||||
}
|
||||
.onReceive(handle_notify(.unfollowed)) { pk in
|
||||
guard pk.key == "p", target.pubkey == pk.ref_id else {
|
||||
return
|
||||
}
|
||||
|
||||
.onReceive(handle_notify(.unfollowed)) { unfollow in
|
||||
guard case .pubkey(let pk) = unfollow,
|
||||
pk == target.pubkey else { return }
|
||||
|
||||
self.follow_state = .unfollows
|
||||
}
|
||||
}
|
||||
@ -65,7 +63,7 @@ struct FollowButtonView: View {
|
||||
}
|
||||
|
||||
struct FollowButtonPreviews: View {
|
||||
let target: FollowTarget = .pubkey("")
|
||||
let target: FollowTarget = .pubkey(test_pubkey)
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text(verbatim: "Unfollows")
|
||||
|
@ -146,10 +146,8 @@ func parse_key(_ thekey: String) -> ParsedKey? {
|
||||
|
||||
if let bech_key = decode_bech32_key(key) {
|
||||
switch bech_key {
|
||||
case .pub(let pk):
|
||||
return .pub(pk)
|
||||
case .sec(let sec):
|
||||
return .priv(sec)
|
||||
case .pub(let pk): return .pub(pk)
|
||||
case .sec(let sec): return .priv(sec)
|
||||
}
|
||||
}
|
||||
|
||||
@ -195,11 +193,12 @@ func process_login(_ key: ParsedKey, is_pubkey: Bool) async throws {
|
||||
save_pubkey(pubkey: nip05.pubkey)
|
||||
|
||||
case .hex(let hexstr):
|
||||
if is_pubkey {
|
||||
if is_pubkey, let pubkey = hex_decode_pubkey(hexstr) {
|
||||
try clear_saved_privkey()
|
||||
save_pubkey(pubkey: hexstr)
|
||||
} else {
|
||||
try handle_privkey(hexstr)
|
||||
|
||||
save_pubkey(pubkey: pubkey)
|
||||
} else if let privkey = hex_decode_privkey(hexstr) {
|
||||
try handle_privkey(privkey)
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,10 +208,8 @@ func process_login(_ key: ParsedKey, is_pubkey: Bool) async throws {
|
||||
guard let pk = privkey_to_pubkey(privkey: privkey) else {
|
||||
throw LoginError.invalid_key
|
||||
}
|
||||
|
||||
if let pub = bech32_pubkey(pk), let priv = bech32_privkey(privkey) {
|
||||
CredentialHandler().save_credential(pubkey: pub, privkey: priv)
|
||||
}
|
||||
|
||||
CredentialHandler().save_credential(pubkey: pk, privkey: privkey)
|
||||
save_pubkey(pubkey: pk)
|
||||
}
|
||||
|
||||
@ -232,7 +229,7 @@ struct NIP05Result: Decodable {
|
||||
|
||||
struct NIP05User {
|
||||
let pubkey: Pubkey
|
||||
let relays: [String]
|
||||
//let relays: [String]
|
||||
}
|
||||
|
||||
func get_nip05_pubkey(id: String) async -> NIP05User? {
|
||||
@ -245,30 +242,24 @@ func get_nip05_pubkey(id: String) async -> NIP05User? {
|
||||
let user = parts[0]
|
||||
let host = parts[1]
|
||||
|
||||
guard let url = URL(string: "https://\(host)/.well-known/nostr.json?name=\(user)") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let (data, _) = try? await URLSession.shared.data(for: URLRequest(url: url)) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let json: NIP05Result = decode_data(data) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let pubkey = json.names[user] else {
|
||||
guard let url = URL(string: "https://\(host)/.well-known/nostr.json?name=\(user)"),
|
||||
let (data, _) = try? await URLSession.shared.data(for: URLRequest(url: url)),
|
||||
let json: NIP05Result = decode_data(data),
|
||||
let pubkey_hex = json.names[user],
|
||||
let pubkey = hex_decode_pubkey(pubkey_hex)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
var relays: [String] = []
|
||||
if let rs = json.relays {
|
||||
if let rs = rs[pubkey] {
|
||||
relays = rs
|
||||
}
|
||||
}
|
||||
|
||||
return NIP05User(pubkey: pubkey, relays: relays)
|
||||
if let rs = json.relays, let rs = rs[pubkey] {
|
||||
relays = rs
|
||||
}
|
||||
*/
|
||||
|
||||
return NIP05User(pubkey: pubkey/*, relays: relays*/)
|
||||
}
|
||||
|
||||
struct KeyInput: View {
|
||||
|
@ -13,15 +13,12 @@ struct MutelistView: View {
|
||||
|
||||
func RemoveAction(pubkey: Pubkey) -> some View {
|
||||
Button {
|
||||
guard let mutelist = damus_state.contacts.mutelist else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let keypair = damus_state.keypair.to_full() else {
|
||||
return
|
||||
}
|
||||
|
||||
guard let new_ev = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: pubkey) else {
|
||||
guard let mutelist = damus_state.contacts.mutelist,
|
||||
let keypair = damus_state.keypair.to_full(),
|
||||
let new_ev = remove_from_mutelist(keypair: keypair,
|
||||
prev: mutelist,
|
||||
to_remove: .pubkey(pubkey))
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -48,22 +45,15 @@ struct MutelistView: View {
|
||||
}
|
||||
.navigationTitle(NSLocalizedString("Muted Users", comment: "Navigation title of view to see list of muted users."))
|
||||
.onAppear {
|
||||
users = get_mutelist_users(damus_state.contacts.mutelist)
|
||||
users = get_mutelist_users(damus_state.contacts.mutelist)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func get_mutelist_users(_ mlist: NostrEvent?) -> [String] {
|
||||
guard let mutelist = mlist else {
|
||||
return []
|
||||
}
|
||||
|
||||
return mutelist.tags.reduce(into: Array<String>()) { pks, tag in
|
||||
if tag.count >= 2 && tag[0] == "p" {
|
||||
pks.append(tag[1])
|
||||
}
|
||||
}
|
||||
func get_mutelist_users(_ mutelist: NostrEvent?) -> Array<Pubkey> {
|
||||
guard let mutelist else { return [] }
|
||||
return Array(mutelist.referenced_pubkeys)
|
||||
}
|
||||
|
||||
struct MutelistView_Previews: PreviewProvider {
|
||||
|
@ -230,7 +230,7 @@ struct NoteContentView: View {
|
||||
for block in blocks.blocks {
|
||||
switch block {
|
||||
case .mention(let m):
|
||||
if m.type == .pubkey && m.ref.ref_id == profile.pubkey {
|
||||
if case .pubkey(let pk) = m.ref, pk == profile.pubkey {
|
||||
load(force_artifacts: true)
|
||||
return
|
||||
}
|
||||
@ -265,21 +265,21 @@ func url_str(_ url: URL) -> CompatibleText {
|
||||
return CompatibleText(attributed: attributedString)
|
||||
}
|
||||
|
||||
func mention_str(_ m: Mention, profiles: Profiles) -> CompatibleText {
|
||||
switch m.type {
|
||||
case .pubkey:
|
||||
let pk = m.ref.ref_id
|
||||
func mention_str(_ m: Mention<MentionRef>, profiles: Profiles) -> CompatibleText {
|
||||
switch m.ref {
|
||||
case .pubkey(let pk):
|
||||
let npub = bech32_pubkey(pk)
|
||||
let profile = profiles.lookup(id: pk)
|
||||
let disp = Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
|
||||
var attributedString = AttributedString(stringLiteral: "@\(disp)")
|
||||
attributedString.link = URL(string: "damus:\(encode_pubkey_uri(m.ref))")
|
||||
attributedString.link = URL(string: "damus:nostr:\(npub)")
|
||||
attributedString.foregroundColor = DamusColors.purple
|
||||
|
||||
return CompatibleText(attributed: attributedString)
|
||||
case .event:
|
||||
let bevid = bech32_note_id(m.ref.ref_id) ?? m.ref.ref_id
|
||||
case .note(let note_id):
|
||||
let bevid = bech32_note_id(note_id)
|
||||
var attributedString = AttributedString(stringLiteral: "@\(abbrev_pubkey(bevid))")
|
||||
attributedString.link = URL(string: "damus:\(encode_event_id_uri(m.ref))")
|
||||
attributedString.link = URL(string: "damus:nostr:\(bevid)")
|
||||
attributedString.foregroundColor = DamusColors.purple
|
||||
|
||||
return CompatibleText(attributed: attributedString)
|
||||
@ -394,7 +394,7 @@ func note_artifact_is_separated(kind: NostrKind?) -> Bool {
|
||||
|
||||
func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: Privkey?) -> NoteArtifacts {
|
||||
let blocks = ev.blocks(privkey)
|
||||
|
||||
|
||||
if ev.known_kind == .longform {
|
||||
return .longform(LongformContent(ev.content))
|
||||
}
|
||||
@ -427,7 +427,9 @@ func reduce_text_block(blocks: [Block], ind: Int, txt: String, one_note_ref: Boo
|
||||
if let next = blocks[safe: ind+1] {
|
||||
if case .url(let u) = next, classify_url(u).is_media != nil {
|
||||
trimmed = trim_suffix(trimmed)
|
||||
} else if case .mention(let m) = next, m.type == .event, one_note_ref {
|
||||
} else if case .mention(let m) = next,
|
||||
case .note = m.ref,
|
||||
one_note_ref {
|
||||
trimmed = trim_suffix(trimmed)
|
||||
}
|
||||
}
|
||||
@ -450,7 +452,7 @@ func render_blocks(blocks bs: Blocks, profiles: Profiles) -> NoteArtifactsSepara
|
||||
|
||||
switch block {
|
||||
case .mention(let m):
|
||||
if m.type == .event && one_note_ref {
|
||||
if case .note = m.ref, one_note_ref {
|
||||
return str
|
||||
}
|
||||
return str + mention_str(m, profiles: profiles)
|
||||
|
@ -10,10 +10,10 @@ import SwiftUI
|
||||
struct ParticipantsView: View {
|
||||
|
||||
let damus_state: DamusState
|
||||
|
||||
@Binding var references: [ReferencedId]
|
||||
@Binding var originalReferences: [ReferencedId]
|
||||
|
||||
let original_pubkeys: [Pubkey]
|
||||
|
||||
@Binding var filtered_pubkeys: Set<Pubkey>
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Replying to", comment: "Text indicating that the view is used for editing which participants are replied to in a note.")
|
||||
@ -23,7 +23,7 @@ struct ParticipantsView: View {
|
||||
|
||||
Button {
|
||||
// Remove all "p" refs, keep "e" refs
|
||||
references = originalReferences.eRefs
|
||||
filtered_pubkeys = Set(original_pubkeys)
|
||||
} label: {
|
||||
Text("Remove all", comment: "Button label to remove all participants from a note reply.")
|
||||
}
|
||||
@ -34,7 +34,7 @@ struct ParticipantsView: View {
|
||||
.clipShape(Capsule())
|
||||
|
||||
Button {
|
||||
references = originalReferences
|
||||
filtered_pubkeys = []
|
||||
} label: {
|
||||
Text("Add all", comment: "Button label to re-add all original participants as profiles to reply to in a note")
|
||||
}
|
||||
@ -48,26 +48,19 @@ struct ParticipantsView: View {
|
||||
}
|
||||
VStack {
|
||||
ScrollView {
|
||||
ForEach(originalReferences.pRefs) { participant in
|
||||
let pubkey = participant.id
|
||||
ForEach(original_pubkeys) { pubkey in
|
||||
HStack {
|
||||
UserView(damus_state: damus_state, pubkey: pubkey)
|
||||
|
||||
Image("check-circle.fill")
|
||||
.font(.system(size: 30))
|
||||
.foregroundColor(references.contains(participant) ? DamusColors.purple : .gray)
|
||||
.foregroundColor(filtered_pubkeys.contains(pubkey) ? .gray : DamusColors.purple)
|
||||
}
|
||||
.onTapGesture {
|
||||
if references.contains(participant) {
|
||||
references = references.filter {
|
||||
$0 != participant
|
||||
}
|
||||
if filtered_pubkeys.contains(pubkey) {
|
||||
filtered_pubkeys.remove(pubkey)
|
||||
} else {
|
||||
if references.contains(participant) {
|
||||
// Don't add it twice
|
||||
} else {
|
||||
references.append(participant)
|
||||
}
|
||||
filtered_pubkeys.insert(pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -50,8 +50,8 @@ struct PostView: View {
|
||||
@State var error: String? = nil
|
||||
@State var uploadedMedias: [UploadedMedia] = []
|
||||
@State var image_upload_confirm: Bool = false
|
||||
@State var originalReferences: [ReferencedId] = []
|
||||
@State var references: [ReferencedId] = []
|
||||
@State var references: [RefId] = []
|
||||
@State var filtered_pubkeys: Set<Pubkey> = []
|
||||
@State var focusWordAttributes: (String?, NSRange?) = (nil, nil)
|
||||
@State var newCursorIndex: Int?
|
||||
@State var postTextViewCanScroll: Bool = true
|
||||
@ -76,7 +76,13 @@ struct PostView: View {
|
||||
}
|
||||
|
||||
func send_post() {
|
||||
let new_post = build_post(post: self.post, action: action, uploadedMedias: uploadedMedias, references: references)
|
||||
let refs = references.filter { ref in
|
||||
if case .pubkey(let pk) = ref, filtered_pubkeys.contains(pk) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
let new_post = build_post(post: self.post, action: action, uploadedMedias: uploadedMedias, references: refs)
|
||||
|
||||
notify(.post(.post(new_post)))
|
||||
|
||||
@ -155,8 +161,7 @@ struct PostView: View {
|
||||
}
|
||||
|
||||
let profile = damus_state.profiles.lookup(id: pubkey)
|
||||
let bech32_pubkey = bech32_pubkey(pubkey) ?? ""
|
||||
return user_tag_attr_string(profile: profile, pubkey: bech32_pubkey)
|
||||
return user_tag_attr_string(profile: profile, pubkey: pubkey)
|
||||
}
|
||||
|
||||
func clear_draft() {
|
||||
@ -310,7 +315,17 @@ struct PostView: View {
|
||||
self.post = initialString()
|
||||
self.tagModel.diff = post.string.count
|
||||
}
|
||||
|
||||
|
||||
var pubkeys: [Pubkey] {
|
||||
self.references.reduce(into: [Pubkey]()) { pks, ref in
|
||||
guard case .pubkey(let pk) = ref else {
|
||||
return
|
||||
}
|
||||
|
||||
pks.append(pk)
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { (deviceSize: GeometryProxy) in
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
@ -321,7 +336,7 @@ struct PostView: View {
|
||||
ScrollViewReader { scroller in
|
||||
ScrollView {
|
||||
if case .replying_to(let replying_to) = self.action {
|
||||
ReplyView(replying_to: replying_to, damus: damus_state, originalReferences: $originalReferences, references: $references)
|
||||
ReplyView(replying_to: replying_to, damus: damus_state, original_pubkeys: pubkeys, filtered_pubkeys: $filtered_pubkeys)
|
||||
}
|
||||
|
||||
Editor(deviceSize: deviceSize)
|
||||
@ -385,10 +400,8 @@ struct PostView: View {
|
||||
switch action {
|
||||
case .replying_to(let replying_to):
|
||||
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
|
||||
originalReferences = references
|
||||
case .quoting(let quoting):
|
||||
references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting)
|
||||
originalReferences = references
|
||||
case .posting(let target):
|
||||
guard !loaded_draft else { break }
|
||||
|
||||
@ -551,13 +564,7 @@ func load_draft_for_post(drafts: Drafts, action: PostAction) -> DraftArtifacts?
|
||||
}
|
||||
|
||||
|
||||
func build_post(post: NSMutableAttributedString, action: PostAction, uploadedMedias: [UploadedMedia], references: [ReferencedId]) -> NostrPost {
|
||||
var kind: NostrKind = .text
|
||||
|
||||
if case .replying_to(let ev) = action, ev.known_kind == .chat {
|
||||
kind = .chat
|
||||
}
|
||||
|
||||
func build_post(post: NSMutableAttributedString, action: PostAction, uploadedMedias: [UploadedMedia], references: [RefId]) -> NostrPost {
|
||||
post.enumerateAttributes(in: NSRange(location: 0, length: post.length), options: []) { attributes, range, stop in
|
||||
if let link = attributes[.link] as? String {
|
||||
let normalized_link: String
|
||||
@ -586,9 +593,9 @@ func build_post(post: NSMutableAttributedString, action: PostAction, uploadedMed
|
||||
content.append(" " + imagesString + " ")
|
||||
}
|
||||
|
||||
if case .quoting(let ev) = action, let id = bech32_note_id(ev.id) {
|
||||
content.append(" nostr:" + id)
|
||||
if case .quoting(let ev) = action {
|
||||
content.append(" nostr:" + bech32_note_id(ev.id))
|
||||
}
|
||||
|
||||
return NostrPost(content: content, references: references, kind: kind, tags: img_meta_tags)
|
||||
return NostrPost(content: content, references: references, kind: .text, tags: img_meta_tags)
|
||||
}
|
||||
|
@ -31,10 +31,7 @@ struct UserSearch: View {
|
||||
}
|
||||
|
||||
func on_user_tapped(user: SearchedUser) {
|
||||
guard let pk = bech32_pubkey(user.pubkey) else {
|
||||
return
|
||||
}
|
||||
|
||||
let pk = user.pubkey
|
||||
let user_tag = user_tag_attr_string(profile: user.profile, pubkey: pk)
|
||||
|
||||
appendUserTag(withTag: user_tag)
|
||||
@ -159,7 +156,7 @@ func user_tag_attr_string(profile: Profile?, pubkey: Pubkey) -> NSMutableAttribu
|
||||
return NSMutableAttributedString(string: tagString, attributes: [
|
||||
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
|
||||
NSAttributedString.Key.foregroundColor: UIColor.label,
|
||||
NSAttributedString.Key.link: "damus:nostr:\(pubkey)"
|
||||
NSAttributedString.Key.link: "damus:nostr:\(pubkey.npub)"
|
||||
])
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ struct AboutView: View {
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
let blocks = parse_note_content(content: about, tags: [])
|
||||
let blocks = parse_note_content(content: .content(about, nil))
|
||||
about_string = render_blocks(blocks: blocks, profiles: state.profiles).content.attributed
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ struct ProfileNameView: View {
|
||||
Spacer()
|
||||
|
||||
KeyView(pubkey: pubkey)
|
||||
.pubkey_context_menu(bech32_pubkey: pubkey)
|
||||
.pubkey_context_menu(pubkey: pubkey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,7 +42,8 @@ struct EditProfilePictureView: View {
|
||||
private func get_profile_url() -> URL? {
|
||||
if let profile_url {
|
||||
return profile_url
|
||||
} else if let state = damus_state, let picture = state.profiles.lookup(id: pubkey)?.picture {
|
||||
} else if let state = damus_state,
|
||||
let picture = state.profiles.lookup(id: pubkey)?.picture {
|
||||
return URL(string: picture)
|
||||
} else {
|
||||
return profile_url ?? URL(string: robohash(pubkey))
|
||||
|
@ -190,7 +190,7 @@ struct ProfileView: View {
|
||||
return
|
||||
}
|
||||
|
||||
guard let new_ev = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: profile.pubkey) else {
|
||||
guard let new_ev = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: .pubkey(profile.pubkey)) else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -260,10 +260,11 @@ struct ProfileView: View {
|
||||
func actionSection(profile_data: Profile?) -> some View {
|
||||
return Group {
|
||||
|
||||
if let profile = profile_data {
|
||||
if let lnurl = profile.lnurl, lnurl != "" {
|
||||
lnButton(lnurl: lnurl, profile: profile)
|
||||
}
|
||||
if let profile = profile_data,
|
||||
let lnurl = profile.lnurl,
|
||||
lnurl != ""
|
||||
{
|
||||
lnButton(lnurl: lnurl, profile: profile)
|
||||
}
|
||||
|
||||
dmButton
|
||||
@ -353,7 +354,7 @@ struct ProfileView: View {
|
||||
|
||||
HStack {
|
||||
if let contact = profile.contacts {
|
||||
let contacts = contact.referenced_pubkeys.map { $0.ref_id }
|
||||
let contacts = Array(contact.referenced_pubkeys)
|
||||
let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
|
||||
NavigationLink(value: Route.Following(following: following_model)) {
|
||||
HStack {
|
||||
@ -466,11 +467,8 @@ struct ProfileView: View {
|
||||
// our profilemodel needs a bit more help
|
||||
}
|
||||
.sheet(isPresented: $show_share_sheet) {
|
||||
if let npub = bech32_pubkey(profile.pubkey) {
|
||||
if let url = URL(string: "https://damus.io/" + npub) {
|
||||
ShareSheet(activityItems: [url])
|
||||
}
|
||||
}
|
||||
let url = URL(string: "https://damus.io/" + profile.pubkey.npub)!
|
||||
ShareSheet(activityItems: [url])
|
||||
}
|
||||
.fullScreenCover(isPresented: $show_qr_code) {
|
||||
QRCodeView(damus_state: damus_state, pubkey: profile.pubkey)
|
||||
@ -517,7 +515,7 @@ struct KeyView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
let bech32 = bech32_pubkey(pubkey) ?? pubkey
|
||||
let bech32 = pubkey.npub
|
||||
|
||||
HStack {
|
||||
Text(verbatim: "\(abbrev_pubkey(bech32, amount: 16))")
|
||||
|
@ -10,9 +10,13 @@ import CoreImage.CIFilterBuiltins
|
||||
|
||||
struct ProfileScanResult: Equatable {
|
||||
let pubkey: Pubkey
|
||||
|
||||
init(hex: String) {
|
||||
self.pubkey = hex
|
||||
|
||||
init?(hex: String) {
|
||||
guard let pk = hex_decode(hex).map({ bytes in Pubkey(Data(bytes)) }) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.pubkey = pk
|
||||
}
|
||||
|
||||
init?(string: String) {
|
||||
@ -25,14 +29,17 @@ struct ProfileScanResult: Equatable {
|
||||
str.removeFirst("nostr:".count)
|
||||
}
|
||||
|
||||
if let _ = hex_decode(str), str.count == 64 {
|
||||
self = .init(hex: str)
|
||||
if let decoded = hex_decode(str),
|
||||
str.count == 64
|
||||
{
|
||||
self.pubkey = Pubkey(Data(decoded))
|
||||
return
|
||||
}
|
||||
|
||||
if str.starts(with: "npub"), let b32 = try? bech32_decode(str) {
|
||||
let hex = hex_encode(b32.data)
|
||||
self = .init(hex: hex)
|
||||
if str.starts(with: "npub"),
|
||||
let b32 = try? bech32_decode(str)
|
||||
{
|
||||
self.pubkey = Pubkey(b32.data)
|
||||
return
|
||||
}
|
||||
|
||||
@ -56,14 +63,6 @@ struct QRCodeView: View {
|
||||
|
||||
let generator = UIImpactFeedbackGenerator(style: .light)
|
||||
|
||||
var maybe_key: String? {
|
||||
guard let key = bech32_pubkey(pubkey) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
func navImage(systemImage: String) -> some View {
|
||||
Image(systemName: systemImage)
|
||||
@ -143,18 +142,16 @@ struct QRCodeView: View {
|
||||
|
||||
Spacer()
|
||||
|
||||
if let key = maybe_key {
|
||||
Image(uiImage: generateQRCode(pubkey: "nostr:" + key))
|
||||
.interpolation(.none)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 300, height: 300)
|
||||
.cornerRadius(10)
|
||||
.overlay(RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(DamusColors.white, lineWidth: 5.0))
|
||||
.shadow(radius: 10)
|
||||
}
|
||||
|
||||
Image(uiImage: generateQRCode(pubkey: "nostr:" + pubkey.npub))
|
||||
.interpolation(.none)
|
||||
.resizable()
|
||||
.scaledToFit()
|
||||
.frame(width: 300, height: 300)
|
||||
.cornerRadius(10)
|
||||
.overlay(RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(DamusColors.white, lineWidth: 5.0))
|
||||
.shadow(radius: 10)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text("Follow me on Nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user.")
|
||||
|
@ -91,6 +91,7 @@ struct RelayDetailView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let relay_connection {
|
||||
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
|
||||
HStack {
|
||||
@ -101,7 +102,7 @@ struct RelayDetailView: View {
|
||||
}
|
||||
}
|
||||
|
||||
if let nip11 = nip11 {
|
||||
if let nip11 {
|
||||
if nip11.is_paid {
|
||||
Section(content: {
|
||||
RelayPaidDetail(payments_url: nip11.payments_url)
|
||||
@ -172,7 +173,7 @@ struct RelayDetailView: View {
|
||||
|
||||
struct RelayDetailView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let metadata = RelayMetadata(name: "name", description: "desc", pubkey: "pubkey", contact: "contact", supported_nips: [1,2,3], software: "software", version: "version", limitation: Limitations.empty, payments_url: "https://jb55.com")
|
||||
let metadata = RelayMetadata(name: "name", description: "desc", pubkey: test_pubkey, contact: "contact", supported_nips: [1,2,3], software: "software", version: "version", limitation: Limitations.empty, payments_url: "https://jb55.com")
|
||||
RelayDetailView(state: test_damus_state(), relay: "relay", nip11: metadata)
|
||||
}
|
||||
}
|
||||
|
@ -93,7 +93,7 @@ struct RelayView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func RemoveButton(privkey: String, showText: Bool) -> some View {
|
||||
func RemoveButton(privkey: Privkey, showText: Bool) -> some View {
|
||||
Button(action: {
|
||||
guard let ev = state.contacts.event else {
|
||||
return
|
||||
|
@ -10,17 +10,23 @@ import SwiftUI
|
||||
struct ReplyView: View {
|
||||
let replying_to: NostrEvent
|
||||
let damus: DamusState
|
||||
|
||||
@Binding var originalReferences: [ReferencedId]
|
||||
@Binding var references: [ReferencedId]
|
||||
|
||||
let original_pubkeys: [Pubkey]
|
||||
@Binding var filtered_pubkeys: Set<Pubkey>
|
||||
@State var participantsShown: Bool = false
|
||||
|
||||
|
||||
var references: [Pubkey] {
|
||||
original_pubkeys.filter { pk in
|
||||
!filtered_pubkeys.contains(pk)
|
||||
}
|
||||
}
|
||||
|
||||
var ReplyingToSection: some View {
|
||||
HStack {
|
||||
Group {
|
||||
let names = references.pRefs
|
||||
let names = references
|
||||
.map { pubkey in
|
||||
let pk = pubkey.ref_id
|
||||
let pk = pubkey
|
||||
let prof = damus.profiles.lookup(id: pk)
|
||||
return "@" + Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50)
|
||||
}
|
||||
@ -40,11 +46,15 @@ struct ReplyView: View {
|
||||
}
|
||||
.sheet(isPresented: $participantsShown) {
|
||||
if #available(iOS 16.0, *) {
|
||||
ParticipantsView(damus_state: damus, references: $references, originalReferences: $originalReferences)
|
||||
ParticipantsView(damus_state: damus,
|
||||
original_pubkeys: self.original_pubkeys,
|
||||
filtered_pubkeys: $filtered_pubkeys)
|
||||
.presentationDetents([.medium, .large])
|
||||
.presentationDragIndicator(.visible)
|
||||
} else {
|
||||
ParticipantsView(damus_state: damus, references: $references, originalReferences: $originalReferences)
|
||||
ParticipantsView(damus_state: damus,
|
||||
original_pubkeys: self.original_pubkeys,
|
||||
filtered_pubkeys: $filtered_pubkeys)
|
||||
}
|
||||
}
|
||||
.padding(.leading, 75)
|
||||
@ -81,10 +91,16 @@ struct ReplyView: View {
|
||||
struct ReplyView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
VStack {
|
||||
ReplyView(replying_to: test_note, damus: test_damus_state(), originalReferences: .constant([]), references: .constant([]))
|
||||
ReplyView(replying_to: test_note,
|
||||
damus: test_damus_state(),
|
||||
original_pubkeys: [],
|
||||
filtered_pubkeys: .constant([]))
|
||||
.frame(height: 300)
|
||||
|
||||
ReplyView(replying_to: test_longform_event.event, damus: test_damus_state(), originalReferences: .constant([]), references: .constant([]))
|
||||
ReplyView(replying_to: test_longform_event.event,
|
||||
damus: test_damus_state(),
|
||||
original_pubkeys: [],
|
||||
filtered_pubkeys: .constant([]))
|
||||
.frame(height: 300)
|
||||
}
|
||||
}
|
||||
|
@ -51,12 +51,8 @@ struct ReportView: View {
|
||||
return
|
||||
}
|
||||
|
||||
guard let note_id = bech32_note_id(ev.id) else {
|
||||
return
|
||||
}
|
||||
|
||||
report_sent = true
|
||||
report_id = note_id
|
||||
report_id = bech32_note_id(ev.id)
|
||||
}
|
||||
|
||||
var send_report_button_text: String {
|
||||
@ -131,9 +127,9 @@ struct ReportView_Previews: PreviewProvider {
|
||||
let ds = test_damus_state()
|
||||
VStack {
|
||||
|
||||
ReportView(postbox: ds.postbox, target: ReportTarget.user(""), keypair: test_keypair.to_full()!)
|
||||
ReportView(postbox: ds.postbox, target: ReportTarget.user(test_pubkey), keypair: test_keypair.to_full()!)
|
||||
|
||||
ReportView(postbox: ds.postbox, target: ReportTarget.user(""), keypair: test_keypair.to_full()!, report_sent: true, report_id: "report_id")
|
||||
ReportView(postbox: ds.postbox, target: ReportTarget.user(test_pubkey), keypair: test_keypair.to_full()!, report_sent: true, report_id: "report_id")
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ struct SaveKeysView: View {
|
||||
Text("This is your account ID, you can give this to your friends so that they can follow you. Tap to copy.", comment: "Label to describe that a public key is the user's account ID and what they can do with it.")
|
||||
.padding(.bottom, 10)
|
||||
|
||||
SaveKeyView(text: account.pubkey_bech32, textContentType: .username, is_copied: $pub_copied, focus: $pubkey_focused)
|
||||
SaveKeyView(text: account.pubkey.npub, textContentType: .username, is_copied: $pub_copied, focus: $pubkey_focused)
|
||||
.padding(.bottom, 10)
|
||||
|
||||
if pub_copied {
|
||||
@ -49,7 +49,7 @@ struct SaveKeysView: View {
|
||||
Text("This is your secret account key. You need this to access your account. Don't share this with anyone! Save it in a password manager and keep it safe!", comment: "Label to describe that a private key is the user's secret account key and what they should do with it.")
|
||||
.padding(.bottom, 10)
|
||||
|
||||
SaveKeyView(text: account.privkey_bech32, textContentType: .newPassword, is_copied: $priv_copied, focus: $privkey_focused)
|
||||
SaveKeyView(text: account.privkey.nsec, textContentType: .newPassword, is_copied: $priv_copied, focus: $privkey_focused)
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
|
||||
@ -115,8 +115,8 @@ struct SaveKeysView: View {
|
||||
|
||||
self.pool.register_handler(sub_id: "signup", handler: handle_event)
|
||||
|
||||
credential_handler.save_credential(pubkey: account.pubkey_bech32, privkey: account.privkey_bech32)
|
||||
|
||||
credential_handler.save_credential(pubkey: account.pubkey, privkey: account.privkey)
|
||||
|
||||
self.loading = true
|
||||
|
||||
self.pool.connect()
|
||||
|
@ -14,15 +14,14 @@ enum SearchState {
|
||||
case not_found
|
||||
}
|
||||
|
||||
enum SearchType {
|
||||
case event
|
||||
case profile
|
||||
case nip05
|
||||
enum SearchType: Equatable {
|
||||
case event(NoteId)
|
||||
case profile(Pubkey)
|
||||
case nip05(String)
|
||||
}
|
||||
|
||||
struct SearchingEventView: View {
|
||||
let state: DamusState
|
||||
let evid: String
|
||||
let search_type: SearchType
|
||||
|
||||
@State var search_state: SearchState = .searching
|
||||
@ -38,18 +37,18 @@ struct SearchingEventView: View {
|
||||
}
|
||||
}
|
||||
|
||||
func handle_search(_ evid: String) {
|
||||
func handle_search(search: SearchType) {
|
||||
self.search_state = .searching
|
||||
|
||||
switch search_type {
|
||||
case .nip05:
|
||||
if let pk = state.profiles.nip05_pubkey[evid] {
|
||||
switch search {
|
||||
case .nip05(let nip05):
|
||||
if let pk = state.profiles.nip05_pubkey[nip05] {
|
||||
if state.profiles.lookup(id: pk) != nil {
|
||||
self.search_state = .found_profile(pk)
|
||||
}
|
||||
} else {
|
||||
Task {
|
||||
guard let nip05 = NIP05.parse(evid) else {
|
||||
guard let nip05 = NIP05.parse(nip05) else {
|
||||
self.search_state = .not_found
|
||||
return
|
||||
}
|
||||
@ -71,16 +70,16 @@ struct SearchingEventView: View {
|
||||
}
|
||||
}
|
||||
|
||||
case .event:
|
||||
find_event(state: state, query: .event(evid: evid)) { res in
|
||||
case .event(let note_id):
|
||||
find_event(state: state, query: .event(evid: note_id)) { res in
|
||||
guard case .event(let ev) = res else {
|
||||
self.search_state = .not_found
|
||||
return
|
||||
}
|
||||
self.search_state = .found(ev)
|
||||
}
|
||||
case .profile:
|
||||
find_event(state: state, query: .profile(pubkey: evid)) { res in
|
||||
case .profile(let pubkey):
|
||||
find_event(state: state, query: .profile(pubkey: pubkey)) { res in
|
||||
guard case .profile(_, let ev) = res else {
|
||||
self.search_state = .not_found
|
||||
return
|
||||
@ -113,11 +112,11 @@ struct SearchingEventView: View {
|
||||
Text("\(search_name) not found", comment: "When a note or profile is not found when searching for it via its note id")
|
||||
}
|
||||
}
|
||||
.onChange(of: evid, debounceTime: 0.5) { evid in
|
||||
handle_search(evid)
|
||||
.onChange(of: search_type, debounceTime: 0.5) { stype in
|
||||
handle_search(search: stype)
|
||||
}
|
||||
.onAppear {
|
||||
handle_search(evid)
|
||||
handle_search(search: search_type)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -125,6 +124,6 @@ struct SearchingEventView: View {
|
||||
struct SearchingEventView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let state = test_damus_state()
|
||||
SearchingEventView(state: state, evid: test_note.id, search_type: .event)
|
||||
SearchingEventView(state: state, search_type: .event(test_note.id))
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ enum Search: Identifiable {
|
||||
case profile(Pubkey)
|
||||
case note(NoteId)
|
||||
case nip05(String)
|
||||
case hex(String)
|
||||
case hex(Data)
|
||||
case multi(MultiSearch)
|
||||
|
||||
var id: String {
|
||||
@ -67,28 +67,22 @@ struct InnerSearchResults: View {
|
||||
HashtagSearch(ht)
|
||||
|
||||
case .nip05(let addr):
|
||||
SearchingEventView(state: damus_state, evid: addr, search_type: .nip05)
|
||||
|
||||
case .profile(let prof):
|
||||
let decoded = try? bech32_decode(prof)
|
||||
let hex = hex_encode(decoded!.data)
|
||||
|
||||
SearchingEventView(state: damus_state, evid: hex, search_type: .profile)
|
||||
SearchingEventView(state: damus_state, search_type: .nip05(addr))
|
||||
|
||||
case .profile(let pubkey):
|
||||
SearchingEventView(state: damus_state, search_type: .profile(pubkey))
|
||||
|
||||
case .hex(let h):
|
||||
//let prof_view = ProfileView(damus_state: damus_state, pubkey: h)
|
||||
//let ev_view = ThreadView(damus: damus_state, event_id: h)
|
||||
|
||||
|
||||
VStack(spacing: 10) {
|
||||
SearchingEventView(state: damus_state, evid: h, search_type: .event)
|
||||
|
||||
SearchingEventView(state: damus_state, evid: h, search_type: .profile)
|
||||
SearchingEventView(state: damus_state, search_type: .event(NoteId(h)))
|
||||
|
||||
SearchingEventView(state: damus_state, search_type: .profile(Pubkey(h)))
|
||||
}
|
||||
|
||||
case .note(let nid):
|
||||
let decoded = try? bech32_decode(nid)
|
||||
let hex = hex_encode(decoded!.data)
|
||||
|
||||
SearchingEventView(state: damus_state, evid: hex, search_type: .event)
|
||||
SearchingEventView(state: damus_state, search_type: .event(nid))
|
||||
|
||||
case .multi(let multi):
|
||||
VStack {
|
||||
HashtagSearch(multi.hashtag)
|
||||
@ -146,20 +140,18 @@ func search_for_string(profiles: Profiles, _ new: String) -> Search? {
|
||||
return .hashtag(make_hashtagable(new))
|
||||
}
|
||||
|
||||
if hex_decode(new) != nil, new.count == 64 {
|
||||
if let new = hex_decode_id(new) {
|
||||
return .hex(new)
|
||||
}
|
||||
|
||||
|
||||
if new.starts(with: "npub") {
|
||||
if (try? bech32_decode(new)) != nil {
|
||||
return .profile(new)
|
||||
if let decoded = bech32_pubkey_decode(new) {
|
||||
return .profile(decoded)
|
||||
}
|
||||
}
|
||||
|
||||
if new.starts(with: "note") {
|
||||
if (try? bech32_decode(new)) != nil {
|
||||
return .note(new)
|
||||
}
|
||||
if new.starts(with: "note"), let decoded = try? bech32_decode(new) {
|
||||
return .note(NoteId(decoded.data))
|
||||
}
|
||||
|
||||
let multisearch = MultiSearch(hashtag: make_hashtagable(new), profiles: search_profiles(profiles: profiles, search: new))
|
||||
@ -181,13 +173,19 @@ func make_hashtagable(_ str: String) -> String {
|
||||
|
||||
func search_profiles(profiles: Profiles, search: String) -> [SearchedUser] {
|
||||
// Search by hex pubkey.
|
||||
if search.count == 64 && hex_decode(search) != nil, let profile = profiles.lookup(id: search) {
|
||||
return [SearchedUser(profile: profile, pubkey: search)]
|
||||
if let pubkey = hex_decode_pubkey(search),
|
||||
let profile = profiles.lookup(id: pubkey)
|
||||
{
|
||||
return [SearchedUser(profile: profile, pubkey: pubkey)]
|
||||
}
|
||||
|
||||
// Search by npub pubkey.
|
||||
if search.starts(with: "npub"), let bech32_key = decode_bech32_key(search), case Bech32Key.pub(let hex) = bech32_key, let profile = profiles.lookup(id: hex) {
|
||||
return [SearchedUser(profile: profile, pubkey: hex)]
|
||||
if search.starts(with: "npub"),
|
||||
let bech32_key = decode_bech32_key(search),
|
||||
case Bech32Key.pub(let pk) = bech32_key,
|
||||
let profile = profiles.lookup(id: pk)
|
||||
{
|
||||
return [SearchedUser(profile: profile, pubkey: pk)]
|
||||
}
|
||||
|
||||
let new = search.lowercased()
|
||||
|
@ -14,7 +14,7 @@ struct ThreadView: View {
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var parent_events: [NostrEvent] {
|
||||
state.events.parent_events(event: thread.event)
|
||||
state.events.parent_events(event: thread.event, privkey: state.keypair.privkey)
|
||||
}
|
||||
|
||||
var child_events: [NostrEvent] {
|
||||
@ -34,7 +34,7 @@ struct ThreadView: View {
|
||||
selected: false)
|
||||
.padding(.horizontal)
|
||||
.onTapGesture {
|
||||
thread.set_active_event(parent_event)
|
||||
thread.set_active_event(parent_event, privkey: self.state.keypair.privkey)
|
||||
scroll_to_event(scroller: reader, id: parent_event.id, delay: 0.1, animate: false)
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ struct ThreadView: View {
|
||||
)
|
||||
.padding(.horizontal)
|
||||
.onTapGesture {
|
||||
thread.set_active_event(child_event)
|
||||
thread.set_active_event(child_event, privkey: state.keypair.privkey)
|
||||
scroll_to_event(scroller: reader, id: child_event.id, delay: 0.1, animate: false)
|
||||
}
|
||||
|
||||
|
@ -192,7 +192,7 @@ struct WalletView: View {
|
||||
}
|
||||
}
|
||||
|
||||
let test_wallet_connect_url = WalletConnectURL(pubkey: "pk", relay: .init("wss://relay.damus.io")!, keypair: test_damus_state().keypair.to_full()!, lud16: "jb55@sendsats.com")
|
||||
let test_wallet_connect_url = WalletConnectURL(pubkey: test_pubkey, relay: .init("wss://relay.damus.io")!, keypair: test_damus_state().keypair.to_full()!, lud16: "jb55@sendsats.com")
|
||||
|
||||
struct WalletView_Previews: PreviewProvider {
|
||||
static let tds = test_damus_state()
|
||||
|
@ -16,7 +16,7 @@ struct ZapsView: View {
|
||||
init(state: DamusState, target: ZapTarget) {
|
||||
self.state = state
|
||||
self.model = ZapsModel(state: state, target: target)
|
||||
self._zaps = ObservedObject(wrappedValue: state.events.get_cache_data(target.id).zaps_model)
|
||||
self._zaps = ObservedObject(wrappedValue: state.events.get_cache_data(NoteId(target.id)).zaps_model)
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@ -40,6 +40,6 @@ struct ZapsView: View {
|
||||
|
||||
struct ZapsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
ZapsView(state: test_damus_state(), target: .profile("pk"))
|
||||
ZapsView(state: test_damus_state(), target: .profile(test_pubkey))
|
||||
}
|
||||
}
|
||||
|
@ -25,20 +25,14 @@ class Bech32Tests: XCTestCase {
|
||||
// Mark your test throws to produce an unexpected failure when your test encounters an uncaught error.
|
||||
// Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
|
||||
|
||||
let pubkey = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
|
||||
guard let b32_pubkey = bech32_pubkey(pubkey) else {
|
||||
let pubkey = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
|
||||
|
||||
guard let decoded = try? bech32_decode(pubkey.npub) else {
|
||||
XCTAssert(false)
|
||||
return
|
||||
}
|
||||
|
||||
guard let decoded = try? bech32_decode(b32_pubkey) else {
|
||||
XCTAssert(false)
|
||||
return
|
||||
}
|
||||
|
||||
let encoded = hex_encode(decoded.data)
|
||||
|
||||
XCTAssertEqual(encoded, pubkey)
|
||||
|
||||
XCTAssertEqual(decoded.data, pubkey.id)
|
||||
}
|
||||
|
||||
func testPerformanceExample() throws {
|
||||
|
@ -11,96 +11,97 @@ import XCTest
|
||||
final class DMTests: XCTestCase {
|
||||
|
||||
var alice: Keypair {
|
||||
let sec = "494c680d20f202807a116a6915815bd76a27d62802e7585806f6a2e034cb5cdb"
|
||||
let pk = "22d925632551a3299022e98de7f9c1087f79a21209f3413ec24ec219b08bd1e4"
|
||||
let sec = hex_decode_privkey("494c680d20f202807a116a6915815bd76a27d62802e7585806f6a2e034cb5cdb")!
|
||||
let pk = hex_decode_pubkey("22d925632551a3299022e98de7f9c1087f79a21209f3413ec24ec219b08bd1e4")!
|
||||
return Keypair(pubkey: pk, privkey: sec)
|
||||
}
|
||||
|
||||
var bob: Keypair {
|
||||
let sec = "aa8920b05b4bd5c79fce46868ed5ebc82bdb91b211850b14541bfbd13953cfef"
|
||||
let pk = "5a9a277dca94260688ecf7d63053de8c121b7f01f609d7f84a1eb9cff64e4606"
|
||||
let sec = hex_decode_privkey("aa8920b05b4bd5c79fce46868ed5ebc82bdb91b211850b14541bfbd13953cfef")!
|
||||
let pk = hex_decode_pubkey("5a9a277dca94260688ecf7d63053de8c121b7f01f609d7f84a1eb9cff64e4606")!
|
||||
return Keypair(pubkey: pk, privkey: sec)
|
||||
}
|
||||
|
||||
var charlie: Keypair {
|
||||
let sec = "4c79130952c9c3b017dad62f37f285853a9c53f2a1184d94594f5b860f30b5a5"
|
||||
let pk = "51c0d263fbfc4bf850805dccf9a29125071e6fed9619bff3efa9a6b5bbcc54a7"
|
||||
let sec = hex_decode_privkey("4c79130952c9c3b017dad62f37f285853a9c53f2a1184d94594f5b860f30b5a5")!
|
||||
let pk = hex_decode_pubkey("51c0d263fbfc4bf850805dccf9a29125071e6fed9619bff3efa9a6b5bbcc54a7")!
|
||||
return Keypair(pubkey: pk, privkey: sec)
|
||||
}
|
||||
|
||||
var dave: Keypair {
|
||||
let sec = "630ffd518084334cbb9ecb20d9532ce0658b8123f4ba565c236d0cea9a4a2cfe"
|
||||
let pk = "b42e44b555013239a0d5dcdb09ebde0857cd8a5a57efbba5a2b6ac78833cb9f0"
|
||||
let sec = hex_decode_privkey("630ffd518084334cbb9ecb20d9532ce0658b8123f4ba565c236d0cea9a4a2cfe")!
|
||||
let pk = hex_decode_pubkey("b42e44b555013239a0d5dcdb09ebde0857cd8a5a57efbba5a2b6ac78833cb9f0")!
|
||||
return Keypair(pubkey: pk, privkey: sec)
|
||||
}
|
||||
|
||||
var fiatjaf: Keypair {
|
||||
let sec = "5426893eab32191ec17a83a583d5c8f85adaabcab0fa56af277ea0b61f575599"
|
||||
let pub = "e27258d7be6d84038967334bfd0954f05801b1bcd85b2afa4c03cfd16ae4b0ad"
|
||||
let sec = hex_decode_privkey("5426893eab32191ec17a83a583d5c8f85adaabcab0fa56af277ea0b61f575599")!
|
||||
let pub = hex_decode_pubkey("e27258d7be6d84038967334bfd0954f05801b1bcd85b2afa4c03cfd16ae4b0ad")!
|
||||
return Keypair(pubkey: pub, privkey: sec)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
func testDMSortOrder() throws {
|
||||
let notif = NewEventsBits()
|
||||
let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681"
|
||||
let pubkey = hex_decode_pubkey("3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681")!
|
||||
let model = DirectMessagesModel(our_pubkey: pubkey)
|
||||
|
||||
let now = UInt32(Date().timeIntervalSince1970)
|
||||
|
||||
let alice_to_bob = create_dm("hi bob", to_pk: bob.pubkey, tags: [["p", bob.pubkey]], keypair: alice, created_at: now)!
|
||||
let debouncer = Debouncer(interval: 3.0)
|
||||
let alice_to_bob = create_dm("hi bob", to_pk: bob.pubkey, tags: [bob.pubkey.tag], keypair: alice, created_at: now)!
|
||||
handle_incoming_dms(debouncer: debouncer, prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [alice_to_bob])
|
||||
|
||||
|
||||
XCTAssertEqual(model.dms.count, 1)
|
||||
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey)
|
||||
|
||||
let bob_to_alice = create_dm("hi alice", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: bob, created_at: now + 1)!
|
||||
let bob_to_alice = create_dm("hi alice", to_pk: alice.pubkey, tags: [alice.pubkey.tag], keypair: bob, created_at: now + 1)!
|
||||
handle_incoming_dms(debouncer: debouncer, prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [bob_to_alice])
|
||||
|
||||
|
||||
XCTAssertEqual(model.dms.count, 1)
|
||||
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey)
|
||||
|
||||
let alice_to_bob_2 = create_dm("hi bob", to_pk: bob.pubkey, tags: [["p", bob.pubkey]], keypair: alice, created_at: now + 2)!
|
||||
let alice_to_bob_2 = create_dm("hi bob", to_pk: bob.pubkey, tags: [bob.pubkey.tag], keypair: alice, created_at: now + 2)!
|
||||
handle_incoming_dms(debouncer: debouncer, prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [alice_to_bob_2])
|
||||
|
||||
|
||||
XCTAssertEqual(model.dms.count, 1)
|
||||
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey)
|
||||
|
||||
let fiatjaf_to_alice = create_dm("hi alice", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: fiatjaf, created_at: now+5)!
|
||||
let fiatjaf_to_alice = create_dm("hi alice", to_pk: alice.pubkey, tags: [alice.pubkey.tag], keypair: fiatjaf, created_at: now+5)!
|
||||
handle_incoming_dms(debouncer: debouncer, prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [fiatjaf_to_alice])
|
||||
|
||||
|
||||
XCTAssertEqual(model.dms.count, 2)
|
||||
XCTAssertEqual(model.dms[0].pubkey, fiatjaf.pubkey)
|
||||
|
||||
let dave_to_alice = create_dm("hi alice", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: dave, created_at: now + 10)!
|
||||
let dave_to_alice = create_dm("hi alice", to_pk: alice.pubkey, tags: [alice.pubkey.tag], keypair: dave, created_at: now + 10)!
|
||||
handle_incoming_dms(debouncer: debouncer, prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [dave_to_alice])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 3)
|
||||
XCTAssertEqual(model.dms[0].pubkey, dave.pubkey)
|
||||
|
||||
let bob_to_alice_2 = create_dm("hi alice 2", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: bob, created_at: now + 15)!
|
||||
let bob_to_alice_2 = create_dm("hi alice 2", to_pk: alice.pubkey, tags: [alice.pubkey.tag], keypair: bob, created_at: now + 15)!
|
||||
handle_incoming_dms(debouncer: debouncer, prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [bob_to_alice_2])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 3)
|
||||
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey)
|
||||
|
||||
let charlie_to_alice = create_dm("hi alice", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: charlie, created_at: now + 20)!
|
||||
let charlie_to_alice = create_dm("hi alice", to_pk: alice.pubkey, tags: [alice.pubkey.tag], keypair: charlie, created_at: now + 20)!
|
||||
handle_incoming_dms(debouncer: debouncer, prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [charlie_to_alice])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 4)
|
||||
XCTAssertEqual(model.dms[0].pubkey, charlie.pubkey)
|
||||
|
||||
let bob_to_alice_3 = create_dm("hi alice 3", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: bob, created_at: now + 25)!
|
||||
let bob_to_alice_3 = create_dm("hi alice 3", to_pk: alice.pubkey, tags: [alice.pubkey.tag], keypair: bob, created_at: now + 25)!
|
||||
handle_incoming_dms(debouncer: debouncer, prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [bob_to_alice_3])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 4)
|
||||
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey)
|
||||
|
||||
let charlie_to_alice_2 = create_dm("hi alice 2", to_pk: alice.pubkey, tags: [["p", alice.pubkey]], keypair: charlie, created_at: now + 30)!
|
||||
let charlie_to_alice_2 = create_dm("hi alice 2", to_pk: alice.pubkey, tags: [alice.pubkey.tag], keypair: charlie, created_at: now + 30)!
|
||||
handle_incoming_dms(debouncer: debouncer, prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [charlie_to_alice_2])
|
||||
|
||||
XCTAssertEqual(model.dms.count, 4)
|
||||
XCTAssertEqual(model.dms[0].pubkey, charlie.pubkey)
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
@ -20,26 +20,31 @@ final class EventGroupViewTests: XCTestCase {
|
||||
|
||||
func testEventAuthorName() {
|
||||
let damusState = test_damus_state()
|
||||
XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: "pk1"), "pk1:pk1")
|
||||
XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: "pk2"), "pk2:pk2")
|
||||
XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: "anon"), "Anonymous")
|
||||
XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: test_pubkey), "damus")
|
||||
XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: test_pubkey_2), "1rppft3m:4qxhsgnj")
|
||||
XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: ANON_PUBKEY), "Anonymous")
|
||||
}
|
||||
|
||||
func testEventGroupUniquePubkeys() {
|
||||
let damusState = test_damus_state()
|
||||
|
||||
let encodedPost = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
|
||||
let pk1 = Keypair(pubkey: "pk1", privkey: nil)
|
||||
let pk2 = Keypair(pubkey: "pk2", privkey: nil)
|
||||
let pk3 = Keypair(pubkey: "pk3", privkey: nil)
|
||||
let repost1 = NostrEvent(content: encodedPost, keypair: pk1, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
|
||||
let repost2 = NostrEvent(content: encodedPost, keypair: pk2, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
|
||||
let repost3 = NostrEvent(content: encodedPost, keypair: pk3, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
|
||||
|
||||
let pk1 =
|
||||
hex_decode_pubkey("1723a4dcc6596d84472eb74d579114d8c46b533c81a0ac76620a7605d3ff76e0")!
|
||||
let pk2 =
|
||||
hex_decode_pubkey("08c43696702ba1d720e4564b4ad895efdc3716b37468fb288e585368950a428a")!
|
||||
let pk3 =
|
||||
hex_decode_pubkey("4e563600925231e9eb35a61842c2c6c19685aa8eefdfad076d6a3f853453a299")!
|
||||
|
||||
let repost1 = NostrEvent(content: encodedPost, keypair: .just_pubkey(pk1), kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
|
||||
let repost2 = NostrEvent(content: encodedPost, keypair: .just_pubkey(pk2), kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
|
||||
let repost3 = NostrEvent(content: encodedPost, keypair: .just_pubkey(pk3), kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
|
||||
|
||||
XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: []))), [])
|
||||
XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1]))), [pk1.pubkey])
|
||||
XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1, repost2]))), [pk1.pubkey, pk2.pubkey])
|
||||
XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1, repost2, repost3]))), [pk1.pubkey, pk2.pubkey, pk3.pubkey])
|
||||
XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1]))), [pk1])
|
||||
XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1, repost2]))), [pk1, pk2])
|
||||
XCTAssertEqual(event_group_unique_pubkeys(profiles: damusState.profiles, group: .repost(EventGroup(events: [repost1, repost2, repost3]))), [pk1, pk2, pk3])
|
||||
}
|
||||
|
||||
func testReactingToText() throws {
|
||||
@ -48,18 +53,27 @@ final class EventGroupViewTests: XCTestCase {
|
||||
|
||||
let encodedPost = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
|
||||
|
||||
let pk1 = Keypair(pubkey: "pk1", privkey: nil)
|
||||
let pk2 = Keypair(pubkey: "pk2", privkey: nil)
|
||||
let pk3 = Keypair(pubkey: "pk3", privkey: nil)
|
||||
let pk1_pk =
|
||||
hex_decode_pubkey("938afd5f44fdf293546767dcc024b4ec09b9df422fad10d577a846f88f56c8f5")!
|
||||
|
||||
let pk2_pk =
|
||||
hex_decode_pubkey("6b9bb7acbcdf0458a81b9e6d29bb1e23ab9b5d288e9b7fa8cee8dedc9082a466")!
|
||||
|
||||
let pk3_pk =
|
||||
hex_decode_pubkey("b9f00c1f12b0b7a2e3960565af7aba71da9678d90faeb60bc19813f3a28840de")!
|
||||
|
||||
let pk1 = Keypair(pubkey: pk1_pk, privkey: nil)
|
||||
let pk2 = Keypair(pubkey: pk2_pk, privkey: nil)
|
||||
let pk3 = Keypair(pubkey: pk3_pk, privkey: nil)
|
||||
|
||||
let repost1 = NostrEvent(content: encodedPost, keypair: pk1, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
|
||||
let repost2 = NostrEvent(content: encodedPost, keypair: pk2, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
|
||||
let repost3 = NostrEvent(content: encodedPost, keypair: pk3, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)!
|
||||
|
||||
XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [])), ev: test_note, pubkeys: [], locale: enUsLocale), "??")
|
||||
XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_note, pubkeys: [pk1.pubkey], locale: enUsLocale), "pk1:pk1 reposted a note you were tagged in")
|
||||
XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_note, pubkeys: [pk1.pubkey, pk2.pubkey], locale: enUsLocale), "pk1:pk1 and pk2:pk2 reposted a note you were tagged in")
|
||||
XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost2])), ev: test_note, pubkeys: [pk1.pubkey, pk2.pubkey, pk3.pubkey], locale: enUsLocale), "pk1:pk1 and 2 others reposted a note you were tagged in")
|
||||
XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1])), ev: test_note, pubkeys: [pk1.pubkey], locale: enUsLocale), "1jw906h6:6saq3vx4 reposted a note you were tagged in")
|
||||
XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2])), ev: test_note, pubkeys: [pk1.pubkey, pk2.pubkey], locale: enUsLocale), "1jw906h6:6saq3vx4 and 1dwdm0t9:nqtnamhd reposted a note you were tagged in")
|
||||
XCTAssertEqual(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [repost1, repost2, repost2])), ev: test_note, pubkeys: [pk1.pubkey, pk2.pubkey, pk3.pubkey], locale: enUsLocale), "1jw906h6:6saq3vx4 and 2 others reposted a note you were tagged in")
|
||||
|
||||
Bundle.main.localizations.map { Locale(identifier: $0) }.forEach {
|
||||
XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [])), ev: test_note, pubkeys: [], locale: $0), "??")
|
||||
|
@ -9,18 +9,9 @@ import XCTest
|
||||
@testable import damus
|
||||
|
||||
final class HashtagTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testParseHashtag() {
|
||||
let parsed = parse_note_content(content: "some hashtag #bitcoin derp", tags: []).blocks
|
||||
|
||||
let parsed = parse_note_content(content: .content("some hashtag #bitcoin derp",nil)).blocks
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 3)
|
||||
XCTAssertEqual(parsed[0].is_text, "some hashtag ")
|
||||
@ -29,8 +20,8 @@ final class HashtagTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testHashtagWithComma() {
|
||||
let parsed = parse_note_content(content: "some hashtag #bitcoin, cool", tags: []).blocks
|
||||
|
||||
let parsed = parse_note_content(content: .content("some hashtag #bitcoin, cool",nil)).blocks
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 3)
|
||||
XCTAssertEqual(parsed[0].is_text, "some hashtag ")
|
||||
@ -40,7 +31,7 @@ final class HashtagTests: XCTestCase {
|
||||
|
||||
func testHashtagWithEmoji() {
|
||||
let content = "some hashtag #bitcoin☕️ cool"
|
||||
let parsed = parse_note_content(content: content, tags: []).blocks
|
||||
let parsed = parse_note_content(content: .content(content, nil)).blocks
|
||||
let post_blocks = parse_post_blocks(content: content)
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
@ -57,7 +48,7 @@ final class HashtagTests: XCTestCase {
|
||||
|
||||
func testPowHashtag() {
|
||||
let content = "pow! #ぽわ〜"
|
||||
let parsed = parse_note_content(content: content, tags: []).blocks
|
||||
let parsed = parse_note_content(content: .content(content,nil)).blocks
|
||||
let post_blocks = parse_post_blocks(content: content)
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
@ -71,8 +62,8 @@ final class HashtagTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testHashtagWithAccents() {
|
||||
let parsed = parse_note_content(content: "hello from #türkiye", tags: []).blocks
|
||||
|
||||
let parsed = parse_note_content(content: .content("hello from #türkiye",nil)).blocks
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 2)
|
||||
XCTAssertEqual(parsed[0].is_text, "hello from ")
|
||||
@ -80,8 +71,8 @@ final class HashtagTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testHashtagWithNonLatinCharacters() {
|
||||
let parsed = parse_note_content(content: "this is a #시험 hope it works", tags: []).blocks
|
||||
|
||||
let parsed = parse_note_content(content: .content("this is a #시험 hope it works",nil)).blocks
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 3)
|
||||
XCTAssertEqual(parsed[0].is_text, "this is a ")
|
||||
@ -90,8 +81,8 @@ final class HashtagTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testParseHashtagEnd() {
|
||||
let parsed = parse_note_content(content: "some hashtag #bitcoin", tags: []).blocks
|
||||
|
||||
let parsed = parse_note_content(content: .content("some hashtag #bitcoin",nil)).blocks
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 2)
|
||||
XCTAssertEqual(parsed[0].is_text, "some hashtag ")
|
||||
|
@ -20,8 +20,8 @@ final class InvoiceTests: XCTestCase {
|
||||
|
||||
func testParseAnyAmountInvoice() throws {
|
||||
let invstr = "LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C"
|
||||
let parsed = parse_note_content(content: invstr, tags: []).blocks
|
||||
|
||||
let parsed = parse_note_content(content: .content(invstr,nil)).blocks
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 1)
|
||||
XCTAssertNotNil(parsed[0].is_invoice)
|
||||
@ -38,8 +38,8 @@ final class InvoiceTests: XCTestCase {
|
||||
let invstr = """
|
||||
LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C hi there
|
||||
"""
|
||||
let parsed = parse_note_content(content: invstr, tags: []).blocks
|
||||
|
||||
let parsed = parse_note_content(content: .content(invstr,nil)).blocks
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 2)
|
||||
XCTAssertNotNil(parsed[0].is_invoice)
|
||||
@ -54,8 +54,8 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
|
||||
|
||||
func testParseInvoiceUpper() throws {
|
||||
let invstr = "LNBC100N1P357SL0SP5T9N56WDZTUN39LGDQLR30XQWKSG3K69Q4Q2RKR52APLUJW0ESN0QPP5MRQGLJK62Z20Q4NVGR6LZCYN6FHYLZCCWDVU4K77APG3ZMRKUJJQDPZW35XJUEQD9EJQCFQV3JHXCMJD9C8G6T0DCXQYJW5QCQPJRZJQT56H4GVP5YX36U2UZQA6QWCSK3E2DUUNFXPPZJ9VHYPC3WFE2WSWZ607UQQ3XQQQSQQQQQQQQQQQLQQYG9QYYSGQAGX5H20AEULJ3GDWX3KXS8U9F4MCAKDKWUAKASAMM9562FFYR9EN8YG20LG0YGNR9ZPWP68524KMDA0T5XP2WYTEX35PU8HAPYJAJXQPSQL29R"
|
||||
let parsed = parse_note_content(content: invstr, tags: []).blocks
|
||||
|
||||
let parsed = parse_note_content(content: .content(invstr,nil)).blocks
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 1)
|
||||
XCTAssertNotNil(parsed[0].is_invoice)
|
||||
@ -70,8 +70,8 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
|
||||
|
||||
func testParseInvoiceWithPrefix() throws {
|
||||
let invstr = "lightning:lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
|
||||
let parsed = parse_note_content(content: invstr, tags: []).blocks
|
||||
|
||||
let parsed = parse_note_content(content: .content(invstr,nil)).blocks
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 1)
|
||||
XCTAssertNotNil(parsed[0].is_invoice)
|
||||
@ -79,8 +79,8 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
|
||||
|
||||
func testParseInvoiceWithPrefixCapitalized() throws {
|
||||
let invstr = "LIGHTNING:LNBC100N1P357SL0SP5T9N56WDZTUN39LGDQLR30XQWKSG3K69Q4Q2RKR52APLUJW0ESN0QPP5MRQGLJK62Z20Q4NVGR6LZCYN6FHYLZCCWDVU4K77APG3ZMRKUJJQDPZW35XJUEQD9EJQCFQV3JHXCMJD9C8G6T0DCXQYJW5QCQPJRZJQT56H4GVP5YX36U2UZQA6QWCSK3E2DUUNFXPPZJ9VHYPC3WFE2WSWZ607UQQ3XQQQSQQQQQQQQQQQLQQYG9QYYSGQAGX5H20AEULJ3GDWX3KXS8U9F4MCAKDKWUAKASAMM9562FFYR9EN8YG20LG0YGNR9ZPWP68524KMDA0T5XP2WYTEX35PU8HAPYJAJXQPSQL29R"
|
||||
let parsed = parse_note_content(content: invstr, tags: []).blocks
|
||||
|
||||
let parsed = parse_note_content(content: .content(invstr,nil)).blocks
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 1)
|
||||
XCTAssertNotNil(parsed[0].is_invoice)
|
||||
@ -88,8 +88,8 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
|
||||
|
||||
func testParseInvoice() throws {
|
||||
let invstr = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
|
||||
let parsed = parse_note_content(content: invstr, tags: []).blocks
|
||||
|
||||
let parsed = parse_note_content(content: .content(invstr,nil)).blocks
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 1)
|
||||
XCTAssertNotNil(parsed[0].is_invoice)
|
||||
|
@ -19,14 +19,18 @@ class LikeTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testLikeHasNotification() throws {
|
||||
let liked = NostrEvent(content: "awesome #[0] post", keypair: test_keypair, tags: [["p", "cindy"], ["e", "bob"]])!
|
||||
let cindy = Pubkey(hex: "9d9181f0aea6500e1f360e07b9f37e25c72169b5158ae78df53f295272b6b71c")!
|
||||
let bob = Pubkey(hex: "218837fe8c94a66ae33af277bcbda45a0319e7726220cd76171b9dd1a468af91")!
|
||||
let liked = NostrEvent(content: "awesome #[0] post",
|
||||
keypair: test_keypair,
|
||||
tags: [cindy.tag, bob.tag])!
|
||||
let id = liked.id
|
||||
let like_ev = make_like_event(keypair: test_keypair_full, liked: liked)!
|
||||
|
||||
XCTAssertTrue(like_ev.references(id: test_keypair.pubkey, key: "p"))
|
||||
XCTAssertTrue(like_ev.references(id: "cindy", key: "p"))
|
||||
XCTAssertTrue(like_ev.references(id: "bob", key: "e"))
|
||||
XCTAssertEqual(like_ev.last_refid()!.ref_id, id)
|
||||
XCTAssertTrue(like_ev.referenced_pubkeys.contains(test_keypair.pubkey))
|
||||
XCTAssertTrue(like_ev.referenced_pubkeys.contains(cindy))
|
||||
XCTAssertTrue(like_ev.referenced_pubkeys.contains(bob))
|
||||
XCTAssertEqual(like_ev.last_refid()!, id)
|
||||
}
|
||||
|
||||
func testToReactionEmoji() {
|
||||
|
@ -19,53 +19,53 @@ final class ListTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testCreateMuteList() throws {
|
||||
let privkey = "87f313b03f2548e6eaf1c188db47078e08e894252949779b639b28db0891937a"
|
||||
let pubkey = "4b0c29bf96496130c1253102f6870c0eee05db38a257315858272aa43fd19685"
|
||||
let to_mute = "2fa2630fea3d2c188c49f2799fcd92f0e9879ea6a36ae60770a5428ed6c19edd"
|
||||
let privkey = test_keypair_full.privkey
|
||||
let pubkey = test_keypair_full.pubkey
|
||||
let to_mute = test_pubkey
|
||||
let keypair = FullKeypair(pubkey: pubkey, privkey: privkey)
|
||||
let mutelist = create_or_update_mutelist(keypair: keypair, mprev: nil, to_add: to_mute)!
|
||||
|
||||
let mutelist = create_or_update_mutelist(keypair: keypair, mprev: nil, to_add: .pubkey(to_mute))!
|
||||
|
||||
XCTAssertEqual(mutelist.pubkey, pubkey)
|
||||
XCTAssertEqual(mutelist.content, "")
|
||||
XCTAssertEqual(mutelist.tags.count, 2)
|
||||
XCTAssertEqual(mutelist.tags[0][0], "d")
|
||||
XCTAssertEqual(mutelist.tags[0][1], "mute")
|
||||
XCTAssertEqual(mutelist.tags[1][0], "p")
|
||||
XCTAssertEqual(mutelist.tags[1][1], to_mute)
|
||||
XCTAssertEqual(mutelist.tags[0][0].string(), "d")
|
||||
XCTAssertEqual(mutelist.tags[0][1].string(), "mute")
|
||||
XCTAssertEqual(mutelist.tags[1][0].string(), "p")
|
||||
XCTAssertEqual(mutelist.tags[1][1].string(), to_mute.hex())
|
||||
}
|
||||
|
||||
func testCreateAndRemoveMuteList() throws {
|
||||
let privkey = "87f313b03f2548e6eaf1c188db47078e08e894252949779b639b28db0891937a"
|
||||
let pubkey = "4b0c29bf96496130c1253102f6870c0eee05db38a257315858272aa43fd19685"
|
||||
let to_mute = "2fa2630fea3d2c188c49f2799fcd92f0e9879ea6a36ae60770a5428ed6c19edd"
|
||||
let privkey = test_keypair_full.privkey
|
||||
let pubkey = test_keypair_full.pubkey
|
||||
let to_mute = test_pubkey
|
||||
let keypair = FullKeypair(pubkey: pubkey, privkey: privkey)
|
||||
let mutelist = create_or_update_mutelist(keypair: keypair, mprev: nil, to_add: to_mute)!
|
||||
let new = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: to_mute)!
|
||||
|
||||
let mutelist = create_or_update_mutelist(keypair: keypair, mprev: nil, to_add: .pubkey(to_mute))!
|
||||
let new = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: .pubkey(to_mute))!
|
||||
|
||||
XCTAssertEqual(new.pubkey, pubkey)
|
||||
XCTAssertEqual(new.content, "")
|
||||
XCTAssertEqual(new.tags.count, 1)
|
||||
XCTAssertEqual(new.tags[0][0], "d")
|
||||
XCTAssertEqual(new.tags[0][1], "mute")
|
||||
XCTAssertEqual(new.tags[0][0].string(), "d")
|
||||
XCTAssertEqual(new.tags[0][1].string(), "mute")
|
||||
}
|
||||
|
||||
func testAddToExistingMutelist() throws {
|
||||
let privkey = "87f313b03f2548e6eaf1c188db47078e08e894252949779b639b28db0891937a"
|
||||
let pubkey = "4b0c29bf96496130c1253102f6870c0eee05db38a257315858272aa43fd19685"
|
||||
let to_mute = "2fa2630fea3d2c188c49f2799fcd92f0e9879ea6a36ae60770a5428ed6c19edd"
|
||||
let to_mute_2 = "976b4ab41f8634119b4f21f57ef5836a4bef65d0bf72c7ced67b8b170ba4a38d"
|
||||
let privkey = test_keypair_full.privkey
|
||||
let pubkey = test_keypair_full.pubkey
|
||||
let to_mute = test_pubkey
|
||||
let to_mute_2 = test_pubkey_2
|
||||
let keypair = FullKeypair(pubkey: pubkey, privkey: privkey)
|
||||
let mutelist = create_or_update_mutelist(keypair: keypair, mprev: nil, to_add: to_mute)!
|
||||
let new = create_or_update_mutelist(keypair: keypair, mprev: mutelist, to_add: to_mute_2)!
|
||||
|
||||
let mutelist = create_or_update_mutelist(keypair: keypair, mprev: nil, to_add: .pubkey(to_mute))!
|
||||
let new = create_or_update_mutelist(keypair: keypair, mprev: mutelist, to_add: .pubkey(to_mute_2))!
|
||||
|
||||
XCTAssertEqual(new.pubkey, pubkey)
|
||||
XCTAssertEqual(new.content, "")
|
||||
XCTAssertEqual(new.tags.count, 3)
|
||||
XCTAssertEqual(new.tags[0][0], "d")
|
||||
XCTAssertEqual(new.tags[0][1], "mute")
|
||||
XCTAssertEqual(new.tags[1][0], "p")
|
||||
XCTAssertEqual(new.tags[1][1], to_mute)
|
||||
XCTAssertEqual(new.tags[2][0], "p")
|
||||
XCTAssertEqual(new.tags[2][1], to_mute_2)
|
||||
XCTAssertEqual(new.tags[0][0].string(), "d")
|
||||
XCTAssertEqual(new.tags[0][1].string(), "mute")
|
||||
XCTAssertEqual(new.tags[1][0].string(), "p")
|
||||
XCTAssertEqual(new.tags[1][1].string(), to_mute.hex())
|
||||
XCTAssertEqual(new.tags[2][0].string(), "p")
|
||||
XCTAssertEqual(new.tags[2][1].string(), to_mute_2.hex())
|
||||
}
|
||||
}
|
||||
|
@ -24,13 +24,14 @@ class ContentParserTests: XCTestCase {
|
||||
let url = "https://media.tenor.com/5MibLt95scAAAAAC/%ED%98%BC%ED%8C%8C%EB%A7%9D-%ED%94%BC%EC%9E%90.gif"
|
||||
let content = "gm 🤙\(url)"
|
||||
|
||||
let blocks = parse_note_content(content: content, tags: []).blocks
|
||||
let blocks = parse_note_content(content: .content(content,nil)).blocks
|
||||
XCTAssertEqual(blocks.count, 2)
|
||||
XCTAssertEqual(blocks[0], .text("gm 🤙"))
|
||||
XCTAssertEqual(blocks[1], .url(URL(string: url)!))
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
func test_damus_parse_content_can_parse_mention_without_white_space_at_front() throws {
|
||||
var bs = note_blocks()
|
||||
bs.num_blocks = 0;
|
||||
@ -38,7 +39,7 @@ class ContentParserTests: XCTestCase {
|
||||
blocks_init(&bs)
|
||||
|
||||
let content = "#[0], #[1],#[2],#[3]#[4],#[5],#[6],#[7], #[8], \n#[9], #[10], #[11], #[12]"
|
||||
|
||||
|
||||
let tagsString = "[[\"p\",\"82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2\"],[\"p\",\"0339bb0d9d818ba126a39385a5edee5651993af7c21f18d4ceb0ba8c9de0d463\"],[\"p\",\"e7424ad457e512fdf4764a56bf6d428a06a13a1006af1fb8e0fe32f6d03265c7\"],[\"p\",\"520830c334a3f79f88cac934580d26f91a7832c6b21fb9625690ea2ed81b5626\"],[\"p\",\"971615b70ad9ec896f8d5ba0f2d01652f1dfe5f9ced81ac9469ca7facefad68b\"],[\"p\",\"2779f3d9f42c7dee17f0e6bcdcf89a8f9d592d19e3b1bbd27ef1cffd1a7f98d1\"],[\"p\",\"17538dc2a62769d09443f18c37cbe358fab5bbf981173542aa7c5ff171ed77c4\"],[\"p\",\"985a7c6b0e75508ad74c4110b2e52dfba6ce26063d80bca218564bd083a72b99\"],[\"p\",\"7fb2a29bd1a41d9a8ca43a19a7dcf3a8522f1bc09b4086253539190e9c29c51a\"],[\"p\",\"b88c7f007bbf3bc2fcaeff9e513f186bab33782c0baa6a6cc12add78b9110ba3\"],[\"p\",\"2f4fa408d85b962d1fe717daae148a4c98424ab2e10c7dd11927e101ed3257b2\"],[\"p\",\"bd1e19980e2c91e6dc657e92c25762ca882eb9272d2579e221f037f93788de91\"],[\"p\",\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\"]]"
|
||||
|
||||
let tags = try decoder.decode([[String]].self, from: tagsString.data(using: .utf8)!)
|
||||
@ -72,4 +73,5 @@ class ContentParserTests: XCTestCase {
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
@ -18,37 +18,37 @@ final class NIP19Tests: XCTestCase {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
/*
|
||||
func test_parse_nprofile() throws {
|
||||
let res = parse_note_content(content: "nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p", tags: []).blocks
|
||||
let res = parse_note_content(content: .content("nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p")).blocks
|
||||
XCTAssertEqual(res.count, 1)
|
||||
let expected_ref = ReferencedId(ref_id: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", relay_id: "wss://r.x.com", key: "p")
|
||||
let expected_mention = Mention(index: nil, type: .pubkey, ref: expected_ref)
|
||||
XCTAssertEqual(res[0], .mention(expected_mention))
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
func test_parse_npub() throws {
|
||||
let res = parse_note_content(content: "nostr:npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg ", tags: []).blocks
|
||||
let res = parse_note_content(content: .content("nostr:npub10elfcs4fr0l0r8af98jlmgdh9c8tcxjvz9qkw038js35mp4dma8qzvjptg ",nil)).blocks
|
||||
XCTAssertEqual(res.count, 2)
|
||||
let expected_ref = ReferencedId(ref_id: "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e", relay_id: nil, key: "p")
|
||||
let expected_mention = Mention(index: nil, type: .pubkey, ref: expected_ref)
|
||||
let expected_ref = Pubkey(hex: "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e")!
|
||||
let expected_mention: Mention<MentionRef> = Mention(index: nil, ref: .pubkey(expected_ref))
|
||||
XCTAssertEqual(res[0], .mention(expected_mention))
|
||||
}
|
||||
|
||||
func test_parse_note() throws {
|
||||
let res = parse_note_content(content: " nostr:note1s4p70596lv50x0zftuses32t6ck8x6wgd4edwacyetfxwns2jtysux7vep", tags: []).blocks
|
||||
let res = parse_note_content(content: .content(" nostr:note1s4p70596lv50x0zftuses32t6ck8x6wgd4edwacyetfxwns2jtysux7vep",nil)).blocks
|
||||
XCTAssertEqual(res.count, 2)
|
||||
let expected_ref = ReferencedId(ref_id: "8543e7d0bafb28f33c495f2198454bd62c7369c86d72d77704cad2674e0a92c9", relay_id: nil, key: "e")
|
||||
let expected_mention = Mention(index: nil, type: .event, ref: expected_ref)
|
||||
XCTAssertEqual(res[1], .mention(expected_mention))
|
||||
let note_id = NoteId(hex:"8543e7d0bafb28f33c495f2198454bd62c7369c86d72d77704cad2674e0a92c9")!
|
||||
XCTAssertEqual(res[1], .mention(.any(.note(note_id))))
|
||||
}
|
||||
|
||||
func test_mention_with_adjacent() throws {
|
||||
let res = parse_note_content(content: " nostr:note1s4p70596lv50x0zftuses32t6ck8x6wgd4edwacyetfxwns2jtysux7vep?", tags: []).blocks
|
||||
let res = parse_note_content(content: .content(" nostr:note1s4p70596lv50x0zftuses32t6ck8x6wgd4edwacyetfxwns2jtysux7vep?",nil)).blocks
|
||||
XCTAssertEqual(res.count, 3)
|
||||
let expected_ref = ReferencedId(ref_id: "8543e7d0bafb28f33c495f2198454bd62c7369c86d72d77704cad2674e0a92c9", relay_id: nil, key: "e")
|
||||
let expected_mention = Mention(index: nil, type: .event, ref: expected_ref)
|
||||
let note_id = NoteId(hex: "8543e7d0bafb28f33c495f2198454bd62c7369c86d72d77704cad2674e0a92c9")!
|
||||
XCTAssertEqual(res[0], .text(" "))
|
||||
XCTAssertEqual(res[1], .mention(expected_mention))
|
||||
XCTAssertEqual(res[1], .mention(.any(.note(note_id))))
|
||||
XCTAssertEqual(res[2], .text("?"))
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ final class NostrScriptTests: XCTestCase {
|
||||
let data = try load_bool_set_test_wasm().bytes
|
||||
let pool = RelayPool()
|
||||
let script = NostrScript(pool: pool, data: data)
|
||||
let pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
|
||||
let pk = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
|
||||
UserSettingsStore.pubkey = pk
|
||||
let key = pk_setting_key(pk, key: "nozaps")
|
||||
UserDefaults.standard.set(true, forKey: key)
|
||||
@ -103,7 +103,7 @@ final class NostrScriptTests: XCTestCase {
|
||||
|
||||
pool.connect(to: ["wss://cache0.primal.net/cache17"])
|
||||
|
||||
self.wait(for: [resume_expected], timeout: 10.0)
|
||||
self.wait(for: [resume_expected], timeout: 5.0)
|
||||
}
|
||||
*/
|
||||
|
||||
|
@ -10,8 +10,10 @@ import XCTest
|
||||
|
||||
class NoteContentViewTests: XCTestCase {
|
||||
func testRenderBlocksWithNonLatinHashtags() {
|
||||
let parsed: Blocks = parse_note_content(content: "Damusはかっこいいです #cool #かっこいい", tags: [["t", "かっこいい"]])
|
||||
|
||||
let content = "Damusはかっこいいです #cool #かっこいい"
|
||||
let note = NostrEvent(content: content, keypair: test_keypair, tags: [["t", "かっこいい"]])!
|
||||
let parsed: Blocks = parse_note_content(content: .init(note: note, privkey: test_keypair.privkey))
|
||||
|
||||
let testState = test_damus_state()
|
||||
|
||||
let text: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles)
|
||||
|
@ -32,8 +32,8 @@ class ProfileDatabaseTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testStoreAndRetrieveProfile() async throws {
|
||||
let id = "test-id"
|
||||
|
||||
let id = test_pubkey
|
||||
|
||||
let profile = test_profile
|
||||
|
||||
// make sure it's not there yet
|
||||
@ -58,7 +58,7 @@ class ProfileDatabaseTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testRejectOutdatedProfile() async throws {
|
||||
let id = "test-id"
|
||||
let id = test_pubkey
|
||||
|
||||
// store a profile
|
||||
let profile = test_profile
|
||||
@ -80,8 +80,8 @@ class ProfileDatabaseTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testUpdateExistingProfile() async throws {
|
||||
let id = "test-id"
|
||||
|
||||
let id = test_pubkey
|
||||
|
||||
// store a profile
|
||||
let profile = test_profile
|
||||
let profile_last_update = Date.now
|
||||
@ -102,7 +102,7 @@ class ProfileDatabaseTests: XCTestCase {
|
||||
XCTAssertEqual(database.count, 0)
|
||||
|
||||
// store a profile
|
||||
let id = "test-id"
|
||||
let id = test_pubkey
|
||||
let profile = test_profile
|
||||
let profile_last_update = Date.now
|
||||
try await database.upsert(id: id, profile: profile, last_update: profile_last_update)
|
||||
@ -110,7 +110,7 @@ class ProfileDatabaseTests: XCTestCase {
|
||||
XCTAssertEqual(database.count, 1)
|
||||
|
||||
// store another profile
|
||||
let id2 = "test-id-2"
|
||||
let id2 = test_pubkey_2
|
||||
let profile2 = test_profile
|
||||
let profile_last_update2 = Date.now
|
||||
try await database.upsert(id: id2, profile: profile2, last_update: profile_last_update2)
|
||||
|
@ -23,13 +23,19 @@ final class ProfileViewTests: XCTestCase {
|
||||
func testFollowedByString() throws {
|
||||
let profiles = test_damus_state().profiles
|
||||
|
||||
XCTAssertEqual(followedByString(["pk1"], profiles: profiles, locale: enUsLocale), "Followed by pk1:pk1")
|
||||
XCTAssertEqual(followedByString(["pk1", "pk2"], profiles: profiles, locale: enUsLocale), "Followed by pk1:pk1 & pk2:pk2")
|
||||
XCTAssertEqual(followedByString(["pk1", "pk2", "pk3"], profiles: profiles, locale: enUsLocale), "Followed by pk1:pk1, pk2:pk2 & pk3:pk3")
|
||||
XCTAssertEqual(followedByString(["pk1", "pk2", "pk3", "pk4",], profiles: profiles, locale: enUsLocale), "Followed by pk1:pk1, pk2:pk2, pk3:pk3 & 1 other")
|
||||
XCTAssertEqual(followedByString(["pk1", "pk2", "pk3", "pk4", "pk5"], profiles: profiles, locale: enUsLocale), "Followed by pk1:pk1, pk2:pk2, pk3:pk3 & 2 others")
|
||||
let pk1 = test_pubkey
|
||||
let pk2 = test_pubkey_2
|
||||
let pk3 = Pubkey(hex: "b42e44b555013239a0d5dcdb09ebde0857cd8a5a57efbba5a2b6ac78833cb9f0")!
|
||||
let pk4 = Pubkey(hex: "cc590e46363d0fa66bb27081368d01f169b8ffc7c614629d4e9eef6c88b38670")!
|
||||
let pk5 = Pubkey(hex: "f2aa579bb998627e04a8f553842a09446360c9d708c6141dd119c479f6ab9d29")!
|
||||
|
||||
let pubkeys = ["pk1", "pk2", "pk3", "pk4", "pk5", "pk6", "pk7", "pk8", "pk9", "pk10"]
|
||||
XCTAssertEqual(followedByString([pk1], profiles: profiles, locale: enUsLocale), "Followed by damus")
|
||||
XCTAssertEqual(followedByString([pk1, pk2], profiles: profiles, locale: enUsLocale), "Followed by damus & 1rppft3m:4qxhsgnj")
|
||||
XCTAssertEqual(followedByString([pk1, pk2, pk3], profiles: profiles, locale: enUsLocale), "Followed by damus, 1rppft3m:4qxhsgnj & 1kshyfd2:cq04aze0")
|
||||
XCTAssertEqual(followedByString([pk1, pk2, pk3, pk4,], profiles: profiles, locale: enUsLocale), "Followed by damus, 1rppft3m:4qxhsgnj, 1kshyfd2:cq04aze0 & 1 other")
|
||||
XCTAssertEqual(followedByString([pk1, pk2, pk3, pk4, pk5], profiles: profiles, locale: enUsLocale), "Followed by damus, 1rppft3m:4qxhsgnj, 1kshyfd2:cq04aze0 & 2 others")
|
||||
|
||||
let pubkeys = [pk1, pk2, pk3, pk4, pk5, pk1, pk2, pk3, pk4, pk5]
|
||||
Bundle.main.localizations.map { Locale(identifier: $0) }.forEach {
|
||||
for count in 1...10 {
|
||||
XCTAssertNoThrow(followedByString(pubkeys.prefix(count).map { $0 }, profiles: profiles, locale: $0))
|
||||
|
@ -19,11 +19,13 @@ class ReplyTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testMentionIsntReply() throws {
|
||||
let evid = NoteId(hex: "4090a9017a2beac3f17795d1aafb80d9f2b9eda97e4738501082ed5c927be014")!
|
||||
let content = "this is #[0] a mention"
|
||||
let tags = [["e", "event_id"]]
|
||||
let blocks = parse_note_content(content: content, tags: tags).blocks
|
||||
let event_refs = interpret_event_refs(blocks: blocks, tags: tags)
|
||||
|
||||
let tags = [evid.tag]
|
||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: tags)!
|
||||
let blocks = parse_note_content(content: .init(note: ev, privkey: test_keypair.privkey)).blocks
|
||||
let event_refs = interpret_event_refs(blocks: blocks, tags: ev.tags)
|
||||
|
||||
XCTAssertEqual(event_refs.count, 1)
|
||||
|
||||
let ref = event_refs[0]
|
||||
@ -31,14 +33,13 @@ class ReplyTests: XCTestCase {
|
||||
XCTAssertNil(ref.is_reply)
|
||||
XCTAssertNil(ref.is_thread_id)
|
||||
XCTAssertNil(ref.is_direct_reply)
|
||||
XCTAssertEqual(ref.is_mention?.type, .event)
|
||||
XCTAssertEqual(ref.is_mention?.ref.ref_id, "event_id")
|
||||
XCTAssertEqual(ref.is_mention, .some(.init(note_id: evid)))
|
||||
}
|
||||
|
||||
func testUrlAnchorsAreNotHashtags() {
|
||||
let content = "this is my link: https://jb55.com/index.html#buybitcoin this is not a hashtag!"
|
||||
let blocks = parse_post_blocks(content: content)
|
||||
|
||||
|
||||
XCTAssertEqual(blocks.count, 3)
|
||||
XCTAssertEqual(blocks[0].is_text, "this is my link: ")
|
||||
XCTAssertEqual(blocks[1].is_url, URL(string: "https://jb55.com/index.html#buybitcoin")!)
|
||||
@ -93,30 +94,32 @@ class ReplyTests: XCTestCase {
|
||||
|
||||
func testRootReplyWithMention() throws {
|
||||
let content = "this is #[1] a mention"
|
||||
let tags = [["e", "thread_id"], ["e", "mentioned_id"]]
|
||||
let blocks = parse_note_content(content: content, tags: tags).blocks
|
||||
let event_refs = interpret_event_refs(blocks: blocks, tags: tags)
|
||||
|
||||
let thread_id = NoteId(hex: "c75e5cbafbefd5de2275f831c2a2386ea05ec5e5a78a5ccf60d467582db48945")!
|
||||
let mentioned_id = NoteId(hex: "5a534797e8cd3b9f4c1cf63e20e48bd0e8bd7f8c4d6353fbd576df000f6f54d3")!
|
||||
let tags = [thread_id.tag, mentioned_id.tag]
|
||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: tags)!
|
||||
let blocks = parse_note_content(content: .init(note: ev, privkey: test_keypair.privkey)).blocks
|
||||
let event_refs = interpret_event_refs(blocks: blocks, tags: ev.tags)
|
||||
|
||||
XCTAssertEqual(event_refs.count, 2)
|
||||
XCTAssertNotNil(event_refs[0].is_reply)
|
||||
XCTAssertNotNil(event_refs[0].is_thread_id)
|
||||
XCTAssertNotNil(event_refs[0].is_reply)
|
||||
XCTAssertNotNil(event_refs[0].is_direct_reply)
|
||||
XCTAssertEqual(event_refs[0].is_reply?.ref_id, "thread_id")
|
||||
XCTAssertEqual(event_refs[0].is_thread_id?.ref_id, "thread_id")
|
||||
XCTAssertEqual(event_refs[0].is_reply, .some(NoteRef(note_id: thread_id)))
|
||||
XCTAssertEqual(event_refs[0].is_thread_id, .some(NoteRef(note_id: thread_id)))
|
||||
XCTAssertNotNil(event_refs[1].is_mention)
|
||||
XCTAssertEqual(event_refs[1].is_mention?.type, .event)
|
||||
XCTAssertEqual(event_refs[1].is_mention?.ref.ref_id, "mentioned_id")
|
||||
XCTAssertEqual(event_refs[1].is_mention, .some(NoteRef(note_id: mentioned_id)))
|
||||
}
|
||||
|
||||
func testEmptyMention() throws {
|
||||
let content = "this is some & content"
|
||||
let tags: [[String]] = []
|
||||
let blocks = parse_note_content(content: content, tags: tags).blocks
|
||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: [])!
|
||||
let blocks = parse_note_content(content: .init(note: ev, privkey: test_keypair.privkey)).blocks
|
||||
let post_blocks = parse_post_blocks(content: content)
|
||||
let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags)
|
||||
let event_refs = interpret_event_refs(blocks: blocks, tags: tags)
|
||||
|
||||
let post_tags = make_post_tags(post_blocks: post_blocks, tags: [])
|
||||
let event_refs = interpret_event_refs(blocks: blocks, tags: ev.tags)
|
||||
|
||||
XCTAssertEqual(event_refs.count, 0)
|
||||
XCTAssertEqual(post_tags.blocks.count, 1)
|
||||
XCTAssertEqual(post_tags.tags.count, 0)
|
||||
@ -126,16 +129,15 @@ class ReplyTests: XCTestCase {
|
||||
func testManyMentions() throws {
|
||||
let content = "#[10]"
|
||||
let tags: [[String]] = [[],[],[],[],[],[],[],[],[],[],["p", "3e999f94e2cb34ef44a64b351141ac4e51b5121b2d31aed4a6c84602a1144692"]]
|
||||
let blocks = parse_note_content(content: content, tags: tags).blocks
|
||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: tags)!
|
||||
let blocks = parse_note_content(content: .init(note: ev, privkey: test_keypair.privkey)).blocks
|
||||
let mentions = blocks.filter { $0.is_mention != nil }
|
||||
XCTAssertEqual(mentions.count, 1)
|
||||
}
|
||||
|
||||
func testNewlineMentions() throws {
|
||||
let pk = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"
|
||||
guard let hex_pk = bech32_pubkey_decode(pk) else {
|
||||
return
|
||||
}
|
||||
let bech32_pk = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"
|
||||
let pk = bech32_pubkey_decode(bech32_pk)!
|
||||
|
||||
let profile = Profile(name: "jb55")
|
||||
let post = user_tag_attr_string(profile: profile, pubkey: pk)
|
||||
@ -143,50 +145,55 @@ class ReplyTests: XCTestCase {
|
||||
post.append(user_tag_attr_string(profile: profile, pubkey: pk))
|
||||
post.append(.init(string: "\n"))
|
||||
|
||||
let post_note = build_post(post: post, action: .posting(.none), uploadedMedias: [], references: [.p(hex_pk)])
|
||||
let post_note = build_post(post: post, action: .posting(.none), uploadedMedias: [], references: [.pubkey(pk)])
|
||||
|
||||
let expected_render = "nostr:\(pk)\nnostr:\(pk)"
|
||||
let expected_render = "nostr:\(pk.npub)\nnostr:\(pk.npub)"
|
||||
XCTAssertEqual(post_note.content, expected_render)
|
||||
|
||||
let blocks = parse_note_content(content: post_note.content, tags: []).blocks
|
||||
let blocks = parse_note_content(content: .content(post_note.content,nil)).blocks
|
||||
let rendered = render_blocks(blocks: blocks)
|
||||
|
||||
XCTAssertEqual(rendered, expected_render)
|
||||
|
||||
XCTAssertEqual(blocks.count, 3)
|
||||
XCTAssertEqual(blocks[0].is_mention, .pubkey(hex_pk))
|
||||
XCTAssertEqual(blocks[0].is_mention, .any(.pubkey(pk)))
|
||||
XCTAssertEqual(blocks[1].is_text, "\n")
|
||||
XCTAssertEqual(blocks[2].is_mention, .pubkey(hex_pk))
|
||||
XCTAssertEqual(blocks[2].is_mention, .any(.pubkey(pk)))
|
||||
}
|
||||
|
||||
func testThreadedReply() throws {
|
||||
let content = "this is some content"
|
||||
let tags = [["e", "thread_id"], ["e", "reply_id"]]
|
||||
let blocks = parse_note_content(content: content, tags: tags).blocks
|
||||
let event_refs = interpret_event_refs(blocks: blocks, tags: tags)
|
||||
|
||||
let thread_id = NoteId(hex: "da256fb52146dc565c6c6b9ef906117c665864dc02b14a7b853eca244729c2f2")!
|
||||
let reply_id = NoteId(hex: "80093e9bdb495728f54cda2bad4aed096877189552b3d41264e73b9a9595be22")!
|
||||
let tags = [thread_id.tag, reply_id.tag]
|
||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: tags)!
|
||||
let blocks = parse_note_content(content: .init(note: ev, privkey: test_keypair.privkey)).blocks
|
||||
let event_refs = interpret_event_refs(blocks: blocks, tags: ev.tags)
|
||||
|
||||
XCTAssertEqual(event_refs.count, 2)
|
||||
let r1 = event_refs[0]
|
||||
let r2 = event_refs[1]
|
||||
|
||||
XCTAssertEqual(r1.is_thread_id!.ref_id, "thread_id")
|
||||
XCTAssertEqual(r2.is_reply!.ref_id, "reply_id")
|
||||
XCTAssertEqual(r2.is_direct_reply!.ref_id, "reply_id")
|
||||
XCTAssertEqual(r1.is_thread_id, .some(.note_id(thread_id)))
|
||||
XCTAssertEqual(r2.is_reply, .some(.note_id(reply_id)))
|
||||
XCTAssertEqual(r2.is_direct_reply, .some(.note_id(reply_id)))
|
||||
XCTAssertNil(r1.is_direct_reply)
|
||||
}
|
||||
|
||||
func testRootReply() throws {
|
||||
let content = "this is a reply"
|
||||
let tags = [["e", "thread_id"]]
|
||||
let blocks = parse_note_content(content: content, tags: tags).blocks
|
||||
let event_refs = interpret_event_refs(blocks: blocks, tags: tags)
|
||||
|
||||
let thread_id = NoteId(hex: "53f60f5114c06f069ffe9da2bc033e533d09cae44d37a8462154a663771a4ce6")!
|
||||
let tags = [thread_id.tag]
|
||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: tags)!
|
||||
let blocks = parse_note_content(content: .content(ev.content,nil)).blocks
|
||||
let event_refs = interpret_event_refs(blocks: blocks, tags: ev.tags)
|
||||
|
||||
XCTAssertEqual(event_refs.count, 1)
|
||||
let r = event_refs[0]
|
||||
|
||||
XCTAssertEqual(r.is_direct_reply!.ref_id, "thread_id")
|
||||
XCTAssertEqual(r.is_reply!.ref_id, "thread_id")
|
||||
XCTAssertEqual(r.is_thread_id!.ref_id, "thread_id")
|
||||
XCTAssertEqual(r.is_direct_reply, .some(.note_id(thread_id)))
|
||||
XCTAssertEqual(r.is_reply, .some(.note_id(thread_id)))
|
||||
XCTAssertEqual(r.is_thread_id, .some(.note_id(thread_id)))
|
||||
XCTAssertNil(r.is_mention)
|
||||
}
|
||||
|
||||
@ -194,13 +201,13 @@ class ReplyTests: XCTestCase {
|
||||
let content = "cc@jb55"
|
||||
|
||||
let profile = Profile(name: "jb55")
|
||||
let tag = user_tag_attr_string(profile: profile, pubkey: "pk")
|
||||
let tag = user_tag_attr_string(profile: profile, pubkey: test_pubkey)
|
||||
let appended = append_user_tag(tag: tag, post: .init(string: content), word_range: .init(2...6))
|
||||
let new_post = appended.post
|
||||
|
||||
try new_post.testAttributes(conditions: [
|
||||
{ let link = $0[.link] as? String; XCTAssertNil(link) },
|
||||
{ let link = $0[.link] as! String; XCTAssertEqual(link, "damus:nostr:pk") },
|
||||
{ let link = $0[.link] as! String; XCTAssertEqual(link, "damus:nostr:\(test_pubkey.npub)") },
|
||||
{ let link = $0[.link] as? String; XCTAssertNil(link) }
|
||||
])
|
||||
|
||||
@ -211,13 +218,13 @@ class ReplyTests: XCTestCase {
|
||||
let content = "😎@jb55"
|
||||
|
||||
let profile = Profile(name: "jb55")
|
||||
let tag = user_tag_attr_string(profile: profile, pubkey: "pk")
|
||||
let tag = user_tag_attr_string(profile: profile, pubkey: test_pubkey)
|
||||
let appended = append_user_tag(tag: tag, post: .init(string: content), word_range: .init(2...6))
|
||||
let new_post = appended.post
|
||||
|
||||
try new_post.testAttributes(conditions: [
|
||||
{ let link = $0[.link] as? String; XCTAssertNil(link) },
|
||||
{ let link = $0[.link] as! String; XCTAssertEqual(link, "damus:nostr:pk") },
|
||||
{ let link = $0[.link] as! String; XCTAssertEqual(link, "damus:nostr:\(test_pubkey.npub)") },
|
||||
{ let link = $0[.link] as? String; XCTAssertNil(link) }
|
||||
])
|
||||
|
||||
@ -231,13 +238,13 @@ class ReplyTests: XCTestCase {
|
||||
"""
|
||||
|
||||
let profile = Profile(name: "jb55")
|
||||
let tag = user_tag_attr_string(profile: profile, pubkey: "pk")
|
||||
let tag = user_tag_attr_string(profile: profile, pubkey: test_pubkey)
|
||||
let appended = append_user_tag(tag: tag, post: .init(string: content), word_range: .init(1...5))
|
||||
let new_post = appended.post
|
||||
|
||||
try new_post.testAttributes(conditions: [
|
||||
{ let link = $0[.link] as? String; XCTAssertNil(link) },
|
||||
{ let link = $0[.link] as! String; XCTAssertEqual(link, "damus:nostr:pk") },
|
||||
{ let link = $0[.link] as! String; XCTAssertEqual(link, "damus:nostr:\(test_pubkey.npub)") },
|
||||
{ let link = $0[.link] as? String; XCTAssertNil(link) },
|
||||
])
|
||||
|
||||
@ -248,12 +255,12 @@ class ReplyTests: XCTestCase {
|
||||
let content = "@jb55"
|
||||
|
||||
let profile = Profile(name: "jb55")
|
||||
let tag = user_tag_attr_string(profile: profile, pubkey: "pk")
|
||||
let tag = user_tag_attr_string(profile: profile, pubkey: test_pubkey)
|
||||
let appended = append_user_tag(tag: tag, post: .init(string: content), word_range: .init(0...4))
|
||||
let new_post = appended.post
|
||||
|
||||
try new_post.testAttributes(conditions: [
|
||||
{ let link = $0[.link] as! String; XCTAssertEqual(link, "damus:nostr:pk") },
|
||||
{ let link = $0[.link] as! String; XCTAssertEqual(link, "damus:nostr:\(test_pubkey.npub)") },
|
||||
{ let link = $0[.link] as? String; XCTAssertNil(link) },
|
||||
])
|
||||
|
||||
@ -264,13 +271,13 @@ class ReplyTests: XCTestCase {
|
||||
let content = "cc @jb55"
|
||||
|
||||
let profile = Profile(name: "jb55")
|
||||
let tag = user_tag_attr_string(profile: profile, pubkey: "pk")
|
||||
let tag = user_tag_attr_string(profile: profile, pubkey: test_pubkey)
|
||||
let appended = append_user_tag(tag: tag, post: .init(string: content), word_range: .init(3...7))
|
||||
let new_post = appended.post
|
||||
|
||||
try new_post.testAttributes(conditions: [
|
||||
{ let link = $0[.link] as? String; XCTAssertNil(link) },
|
||||
{ let link = $0[.link] as! String; XCTAssertEqual(link, "damus:nostr:pk") },
|
||||
{ let link = $0[.link] as! String; XCTAssertEqual(link, "damus:nostr:\(test_pubkey.npub)") },
|
||||
{ let link = $0[.link] as? String; XCTAssertNil(link) }
|
||||
])
|
||||
|
||||
@ -279,15 +286,19 @@ class ReplyTests: XCTestCase {
|
||||
|
||||
func testNoReply() throws {
|
||||
let content = "this is a #[0] reply"
|
||||
let blocks = parse_note_content(content: content, tags: []).blocks
|
||||
let event_refs = interpret_event_refs(blocks: blocks, tags: [])
|
||||
|
||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: [])!
|
||||
let blocks = parse_note_content(content: .init(note: ev, privkey: test_keypair.privkey)).blocks
|
||||
let event_refs = interpret_event_refs(blocks: blocks, tags:ev.tags)
|
||||
|
||||
XCTAssertEqual(event_refs.count, 0)
|
||||
}
|
||||
|
||||
func testParseMention() throws {
|
||||
let parsed = parse_note_content(content: "this is #[0] a mention", tags: [["e", "event_id"]]).blocks
|
||||
|
||||
let note_id = NoteId(hex: "53f60f5114c06f069ffe9da2bc033e533d09cae44d37a8462154a663771a4ce6")!
|
||||
let tags = [note_id.tag]
|
||||
let ev = NostrEvent(content: "this is #[0] a mention", keypair: test_keypair, tags: tags)!
|
||||
let parsed = parse_note_content(content: .init(note: ev, privkey: test_keypair.privkey)).blocks
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 3)
|
||||
XCTAssertEqual(parsed[0].is_text, "this is ")
|
||||
@ -301,76 +312,71 @@ class ReplyTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testBech32MentionAtStart() throws {
|
||||
let pk = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"
|
||||
let hex_pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
|
||||
let content = "@\(pk) hello there"
|
||||
let pk = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
|
||||
let content = "@\(pk.npub) hello there"
|
||||
let blocks = parse_post_blocks(content: content)
|
||||
|
||||
XCTAssertEqual(blocks.count, 2)
|
||||
XCTAssertEqual(blocks[0].is_mention, .pubkey(hex_pk))
|
||||
XCTAssertEqual(blocks[0].is_mention, .any(.pubkey(pk)))
|
||||
XCTAssertEqual(blocks[1].is_text, " hello there")
|
||||
|
||||
}
|
||||
|
||||
func testBech32MentionAtEnd() throws {
|
||||
let pk = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"
|
||||
let hex_pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
|
||||
let content = "this is a @\(pk)"
|
||||
let pk = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
|
||||
let content = "this is a @\(pk.npub)"
|
||||
let blocks = parse_post_blocks(content: content)
|
||||
|
||||
XCTAssertEqual(blocks.count, 2)
|
||||
XCTAssertEqual(blocks[1].is_mention, .pubkey(hex_pk))
|
||||
XCTAssertEqual(blocks[1].is_mention, .any(.pubkey(pk)))
|
||||
XCTAssertEqual(blocks[0].is_text, "this is a ")
|
||||
}
|
||||
|
||||
func testNpubMention() throws {
|
||||
let evid = "0000000000000000000000000000000000000000000000000000000000000005"
|
||||
let pk = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"
|
||||
let hex_pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"
|
||||
let content = "this is a @\(pk) mention"
|
||||
let reply_ref = ReferencedId(ref_id: evid, relay_id: nil, key: "e")
|
||||
let evid = NoteId(hex: "71ba3e5ddaf48103be294aa370e470fb60b6c8bca3fb01706eecd00054c2f588")!
|
||||
let pk = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
|
||||
let content = "this is a @\(pk.npub) mention"
|
||||
let blocks = parse_post_blocks(content: content)
|
||||
let post = NostrPost(content: content, references: [reply_ref])
|
||||
let post = NostrPost(content: content, references: [.event(evid)])
|
||||
let ev = post_to_event(post: post, keypair: test_keypair_full)!
|
||||
|
||||
XCTAssertEqual(ev.tags.count, 2)
|
||||
XCTAssertEqual(blocks.count, 3)
|
||||
XCTAssertEqual(blocks[1].is_mention, .pubkey(hex_pk))
|
||||
XCTAssertEqual(blocks[1].is_mention, .any(.pubkey(pk)))
|
||||
XCTAssertEqual(ev.content, "this is a nostr:npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s mention")
|
||||
}
|
||||
|
||||
func testNsecMention() throws {
|
||||
let evid = "0000000000000000000000000000000000000000000000000000000000000005"
|
||||
let pk = "nsec1jmzdz7d0ldqctdxwm5fzue277ttng2pk28n2u8wntc2r4a0w96ssnyukg7"
|
||||
let hex_pk = "ccf95d668650178defca5ac503693b6668eb77895f610178ff8ed9fe5cf9482e"
|
||||
let content = "this is a @\(pk) mention"
|
||||
let reply_ref = ReferencedId(ref_id: evid, relay_id: nil, key: "e")
|
||||
let evid = NoteId(hex: "71ba3e5ddaf48103be294aa370e470fb60b6c8bca3fb01706eecd00054c2f588")!
|
||||
let pk = Pubkey(hex: "ccf95d668650178defca5ac503693b6668eb77895f610178ff8ed9fe5cf9482e")!
|
||||
let nsec = "nsec1jmzdz7d0ldqctdxwm5fzue277ttng2pk28n2u8wntc2r4a0w96ssnyukg7"
|
||||
let content = "this is a @\(nsec) mention"
|
||||
let blocks = parse_post_blocks(content: content)
|
||||
let post = NostrPost(content: content, references: [reply_ref])
|
||||
let post = NostrPost(content: content, references: [.event(evid)])
|
||||
let ev = post_to_event(post: post, keypair: test_keypair_full)!
|
||||
|
||||
XCTAssertEqual(ev.tags.count, 2)
|
||||
XCTAssertEqual(blocks.count, 3)
|
||||
XCTAssertEqual(blocks[1].is_mention, .pubkey(hex_pk))
|
||||
XCTAssertEqual(blocks[1].is_mention, .any(.pubkey(pk)))
|
||||
XCTAssertEqual(ev.content, "this is a nostr:npub1enu46e5x2qtcmm72ttzsx6fmve5wkauftassz78l3mvluh8efqhqejf3v4 mention")
|
||||
}
|
||||
|
||||
func testReplyMentions() throws {
|
||||
let privkey = "0fc2092231f958f8d57d66f5e238bb45b6a2571f44c0ce024bbc6f3a9c8a15fe"
|
||||
let pubkey = "30c6d1dc7f7c156794fa15055e651b758a61b99f50fcf759de59386050bf6ae2"
|
||||
let npub = "npub1xrrdrhrl0s2k0986z5z4uegmwk9xrwvl2r70wkw7tyuxq59ldt3qh09eay"
|
||||
let pubkey = Pubkey(hex: "30c6d1dc7f7c156794fa15055e651b758a61b99f50fcf759de59386050bf6ae2")!
|
||||
let thread_id = NoteId(hex: "a250fc93570c3e87f9c9b08d6b3ef7b8e05d346df8a52c69e30ffecdb178fb9e")!
|
||||
let reply_id = NoteId(hex: "9a180a10f16dac9566543ad1fc29616aab272b0cf123ab5d58843e16f4ef03a3")!
|
||||
|
||||
let refs = [
|
||||
ReferencedId(ref_id: "thread_id", relay_id: nil, key: "e"),
|
||||
ReferencedId(ref_id: "reply_id", relay_id: nil, key: "e"),
|
||||
ReferencedId(ref_id: pubkey, relay_id: nil, key: "p"),
|
||||
let refs: [RefId] = [
|
||||
.event(thread_id),
|
||||
.event(reply_id),
|
||||
.pubkey(pubkey)
|
||||
]
|
||||
|
||||
let post = NostrPost(content: "this is a (@\(npub)) mention", references: refs)
|
||||
|
||||
let post = NostrPost(content: "this is a (@\(pubkey.npub)) mention", references: refs)
|
||||
let ev = post_to_event(post: post, keypair: test_keypair_full)!
|
||||
|
||||
XCTAssertEqual(ev.content, "this is a (nostr:\(npub)) mention")
|
||||
XCTAssertEqual(ev.tags[2][1], pubkey)
|
||||
XCTAssertEqual(ev.content, "this is a (nostr:\(pubkey.npub)) mention")
|
||||
XCTAssertEqual(ev.tags[2][1].string(), pubkey.description)
|
||||
}
|
||||
|
||||
func testInvalidPostReference() throws {
|
||||
@ -413,14 +419,13 @@ class ReplyTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testParsePostUriPubkeyReference() throws {
|
||||
let id = "6fec2ee6cfff779fe8560976b3d9df782b74577f0caefa7a77c0ed4c3749b5de"
|
||||
let npub = try XCTUnwrap(bech32_pubkey(id))
|
||||
let parsed = parse_post_blocks(content: "this is a nostr:\(npub) event mention")
|
||||
|
||||
let id = Pubkey(hex: "6fec2ee6cfff779fe8560976b3d9df782b74577f0caefa7a77c0ed4c3749b5de")!
|
||||
let parsed = parse_post_blocks(content: "this is a nostr:\(id.npub) event mention")
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 3)
|
||||
XCTAssertEqual(parsed[0].is_text, "this is a ")
|
||||
XCTAssertEqual(parsed[1].is_mention, .pubkey(id))
|
||||
XCTAssertEqual(parsed[1].is_mention, .any(.pubkey(id)))
|
||||
XCTAssertEqual(parsed[2].is_text, " event mention")
|
||||
|
||||
guard case .text(let t1) = parsed[0] else {
|
||||
@ -437,14 +442,13 @@ class ReplyTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testParsePostUriReference() throws {
|
||||
let id = "6fec2ee6cfff779fe8560976b3d9df782b74577f0caefa7a77c0ed4c3749b5de"
|
||||
let note_id = try XCTUnwrap(bech32_note_id(id))
|
||||
let parsed = parse_post_blocks(content: "this is a nostr:\(note_id) event mention")
|
||||
|
||||
let id = NoteId(hex: "6fec2ee6cfff779fe8560976b3d9df782b74577f0caefa7a77c0ed4c3749b5de")!
|
||||
let parsed = parse_post_blocks(content: "this is a nostr:\(id.bech32) event mention")
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 3)
|
||||
XCTAssertEqual(parsed[0].is_text, "this is a ")
|
||||
XCTAssertEqual(parsed[1].is_mention, .note(id))
|
||||
XCTAssertEqual(parsed[1].is_mention, .any(.note(id)))
|
||||
XCTAssertEqual(parsed[2].is_text, " event mention")
|
||||
|
||||
guard case .text(let t1) = parsed[0] else {
|
||||
@ -461,8 +465,8 @@ class ReplyTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testParseInvalidMention() throws {
|
||||
let parsed = parse_note_content(content: "this is #[0] a mention", tags: []).blocks
|
||||
|
||||
let parsed = parse_note_content(content: .content("this is #[0] a mention",nil)).blocks
|
||||
|
||||
XCTAssertNotNil(parsed)
|
||||
XCTAssertEqual(parsed.count, 3)
|
||||
XCTAssertEqual(parsed[0].is_text, "this is ")
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user