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:
William Casarin
2023-07-26 08:46:44 -07:00
parent 55bbe8f855
commit cebd1f48ca
110 changed files with 2153 additions and 1799 deletions
+39 -6
View File
@@ -95,6 +95,7 @@
4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */; }; 4C285C8E28399BFE008A31F1 /* SaveKeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8D28399BFD008A31F1 /* SaveKeysView.swift */; };
4C28A4122A6D03D200C1A7A5 /* ReferencedId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C28A4112A6D03D200C1A7A5 /* ReferencedId.swift */; }; 4C28A4122A6D03D200C1A7A5 /* ReferencedId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C28A4112A6D03D200C1A7A5 /* ReferencedId.swift */; };
4C2B10282A7B0F5C008AA43E /* Log.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B10272A7B0F5C008AA43E /* Log.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 */; }; 4C2CDDF7299D4A5E00879FD5 /* Debouncer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2CDDF6299D4A5E00879FD5 /* Debouncer.swift */; };
4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */; }; 4C30AC7229A5677A00E2BD5A /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7129A5677A00E2BD5A /* NotificationsView.swift */; };
4C30AC7429A5680900E2BD5A /* EventGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C30AC7329A5680900E2BD5A /* EventGroupView.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 */; }; 4CA352A02A76AE80003BB08B /* Notify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA3529F2A76AE80003BB08B /* Notify.swift */; };
4CA352A22A76AEC5003BB08B /* LikedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A12A76AEC5003BB08B /* LikedNotify.swift */; }; 4CA352A22A76AEC5003BB08B /* LikedNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A12A76AEC5003BB08B /* LikedNotify.swift */; };
4CA352A42A76AFF3003BB08B /* UpdateStatsNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A32A76AFF3003BB08B /* UpdateStatsNotify.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 */; }; 4CA352A82A76B37E003BB08B /* NewMutesNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A72A76B37E003BB08B /* NewMutesNotify.swift */; };
4CA352AA2A76BF3A003BB08B /* LocalNotificationNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A92A76BF3A003BB08B /* LocalNotificationNotify.swift */; }; 4CA352AA2A76BF3A003BB08B /* LocalNotificationNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352A92A76BF3A003BB08B /* LocalNotificationNotify.swift */; };
4CA352AC2A76C07F003BB08B /* NewUnmutesNotify.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CA352AB2A76C07F003BB08B /* NewUnmutesNotify.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 */; }; 4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A62992D02B00A9A7E4 /* ProfileNameView.swift */; };
4CB9D4A92992D2F400A9A7E4 /* FollowsYou.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */; }; 4CB9D4A92992D2F400A9A7E4 /* FollowsYou.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CB9D4A82992D2F400A9A7E4 /* FollowsYou.swift */; };
4CBCA930297DB57F00EC6B2F /* WebsiteLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CBCA92F297DB57F00EC6B2F /* WebsiteLink.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 */; }; 4CC6193A29DC777C006A86D1 /* RelayBootstrap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC6193929DC777C006A86D1 /* RelayBootstrap.swift */; };
4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAE6297EFA7B00430951 /* Zap.swift */; }; 4CC7AAE7297EFA7B00430951 /* Zap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAE6297EFA7B00430951 /* Zap.swift */; };
4CC7AAEB297F0AEC00430951 /* BuilderEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC7AAEA297F0AEC00430951 /* BuilderEventView.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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 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>"; }; 4CC7AAEA297F0AEC00430951 /* BuilderEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuilderEventView.swift; sourceTree = "<group>"; };
@@ -1309,7 +1317,7 @@
4C363A8F28247A1D006E126D /* NostrLink.swift */, 4C363A8F28247A1D006E126D /* NostrLink.swift */,
50088DA029E8271A008A1FDF /* WebSocket.swift */, 50088DA029E8271A008A1FDF /* WebSocket.swift */,
4C28A4112A6D03D200C1A7A5 /* ReferencedId.swift */, 4C28A4112A6D03D200C1A7A5 /* ReferencedId.swift */,
4CA352A52A76B020003BB08B /* Pubkey.swift */, 4C2B7BF12A71B6540049DEE7 /* Id.swift */,
); );
path = Nostr; path = Nostr;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1555,6 +1563,25 @@
path = Profile; path = Profile;
sourceTree = "<group>"; 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 */ = { 4CC7AAEE297F11B300430951 /* Events */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -1652,6 +1679,7 @@
children = ( children = (
4C1D4FB32A7967990024F453 /* build-git-hash.txt */, 4C1D4FB32A7967990024F453 /* build-git-hash.txt */,
4CA3529C2A76AE47003BB08B /* Notify */, 4CA3529C2A76AE47003BB08B /* Notify */,
4CC14FEC2A73FC9A007AEB17 /* Types */,
F7F0BA23297892AE009531F3 /* Modifiers */, F7F0BA23297892AE009531F3 /* Modifiers */,
4C4A3A5A288A1B2200453788 /* damus.entitlements */, 4C4A3A5A288A1B2200453788 /* damus.entitlements */,
4CE4F9DF285287A000C00DD9 /* Components */, 4CE4F9DF285287A000C00DD9 /* Components */,
@@ -1684,6 +1712,7 @@
4CE6DEF627F7A08200C66700 /* damusTests */ = { 4CE6DEF627F7A08200C66700 /* damusTests */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
4C0379642A7BFE8E0037B8C4 /* Events */,
4C9B0DEC2A65A74000CBDA21 /* Util */, 4C9B0DEC2A65A74000CBDA21 /* Util */,
4C0C03962A61E2670098B3B8 /* Fixtures */, 4C0C03962A61E2670098B3B8 /* Fixtures */,
4C7D097D2A0C58B900943473 /* WalletConnectTests.swift */, 4C7D097D2A0C58B900943473 /* WalletConnectTests.swift */,
@@ -2087,6 +2116,7 @@
4C3EA67728FF7A9800C48A62 /* talstr.c in Sources */, 4C3EA67728FF7A9800C48A62 /* talstr.c in Sources */,
4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */, 4CE6DEE927F7A08100C66700 /* ContentView.swift in Sources */,
4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */, 4CEE2AF5280B29E600AB5EEF /* TimeAgo.swift in Sources */,
4CC14FF12A73FCDB007AEB17 /* Pubkey.swift in Sources */,
4CA9275D2A28FF630098A105 /* LongformView.swift in Sources */, 4CA9275D2A28FF630098A105 /* LongformView.swift in Sources */,
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */, 4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */,
504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */, 504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */,
@@ -2124,7 +2154,6 @@
4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */, 4CE0E2AF29A2E82100DB4CA2 /* EventHolder.swift in Sources */,
4C5D5C992A6AF8F80024563C /* NdbNote.swift in Sources */, 4C5D5C992A6AF8F80024563C /* NdbNote.swift in Sources */,
4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */, 4CF0ABF029857E9200D66079 /* Bech32Object.swift in Sources */,
4CA352A62A76B020003BB08B /* Pubkey.swift in Sources */,
4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */, 4C3D52B8298DB5C6001C5831 /* TextEvent.swift in Sources */,
4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */, 4C216F362870A9A700040376 /* InputDismissKeyboard.swift in Sources */,
4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */, 4C8D1A6F29F31E5000ACDF75 /* FriendsButton.swift in Sources */,
@@ -2180,6 +2209,7 @@
4C285C8228385570008A31F1 /* CarouselView.swift in Sources */, 4C285C8228385570008A31F1 /* CarouselView.swift in Sources */,
3A3040F129A8FF97008A0F29 /* LocalizationUtil.swift in Sources */, 3A3040F129A8FF97008A0F29 /* LocalizationUtil.swift in Sources */,
F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */, F75BA12D29A1855400E10810 /* BookmarksManager.swift in Sources */,
4CC14FEF2A73FCCB007AEB17 /* IdType.swift in Sources */,
4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */, 4C3EA67F28FFC01D00C48A62 /* InvoiceView.swift in Sources */,
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */, 4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */, 4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
@@ -2197,6 +2227,7 @@
4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */, 4C7D09682A0AE9B200943473 /* NWCScannerView.swift in Sources */,
4CA352A42A76AFF3003BB08B /* UpdateStatsNotify.swift in Sources */, 4CA352A42A76AFF3003BB08B /* UpdateStatsNotify.swift in Sources */,
4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */, 4C0A3F93280F66F5000448DE /* ReplyMap.swift in Sources */,
4C2B7BF22A71B6540049DEE7 /* Id.swift in Sources */,
7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */, 7C95CAEE299DCEF1009DCB67 /* KFOptionSetter+.swift in Sources */,
4C7D09722A0AEF5E00943473 /* DamusGradient.swift in Sources */, 4C7D09722A0AEF5E00943473 /* DamusGradient.swift in Sources */,
4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */, 4CCEB7B029B5415A0078AA28 /* SearchingProfileView.swift in Sources */,
@@ -2271,6 +2302,7 @@
4C3EA64128FF553900C48A62 /* hash_u5.c in Sources */, 4C3EA64128FF553900C48A62 /* hash_u5.c in Sources */,
4C1253542A76C7D60004F4B8 /* LogoutNotify.swift in Sources */, 4C1253542A76C7D60004F4B8 /* LogoutNotify.swift in Sources */,
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */, 5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */,
4CC14FF52A740BB7007AEB17 /* NoteId.swift in Sources */,
4C19AE512A5CEF7C00C90DB7 /* NostrScript.swift in Sources */, 4C19AE512A5CEF7C00C90DB7 /* NostrScript.swift in Sources */,
4C3EA64F28FF59F200C48A62 /* tal.c in Sources */, 4C3EA64F28FF59F200C48A62 /* tal.c in Sources */,
4CB88393296F798300DC99E7 /* ReactionsModel.swift in Sources */, 4CB88393296F798300DC99E7 /* ReactionsModel.swift in Sources */,
@@ -2294,6 +2326,7 @@
3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */, 3A4647CF2A413ADC00386AD8 /* CondensedProfilePicturesView.swift in Sources */,
D2277EEA2A089BD5006C3807 /* Router.swift in Sources */, D2277EEA2A089BD5006C3807 /* Router.swift in Sources */,
3A90B1812A4EA3AF00000D94 /* UserSearchCache.swift in Sources */, 3A90B1812A4EA3AF00000D94 /* UserSearchCache.swift in Sources */,
4CC14FF92A741939007AEB17 /* Referenced.swift in Sources */,
4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */, 4C5C7E6A284EDE2E00A22DF5 /* SearchResultsView.swift in Sources */,
4CE1399429F0669900AC6A0B /* BigButton.swift in Sources */, 4CE1399429F0669900AC6A0B /* BigButton.swift in Sources */,
7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */, 7C60CAEF298471A1009C80D6 /* CoreSVG.swift in Sources */,
@@ -2701,7 +2734,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements; CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 7; CURRENT_PROJECT_VERSION = 8;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D; DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@@ -2750,7 +2783,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements; CODE_SIGN_ENTITLEMENTS = damus/damus.entitlements;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 7; CURRENT_PROJECT_VERSION = 8;
DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\""; DEVELOPMENT_ASSET_PATHS = "\"damus/Preview Content\"";
DEVELOPMENT_TEAM = XK7H4JAB3D; DEVELOPMENT_TEAM = XK7H4JAB3D;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
+11 -6
View File
@@ -51,12 +51,12 @@ struct SearchHeaderView: View {
func unfollow(_ hashtag: String) { func unfollow(_ hashtag: String) {
is_following = false is_following = false
handle_unfollow(state: state, unfollow: .t(hashtag)) handle_unfollow(state: state, unfollow: FollowRef.hashtag(hashtag))
} }
func follow(_ hashtag: String) { func follow(_ hashtag: String) {
is_following = true is_following = true
handle_follow(state: state, follow: .t(hashtag)) handle_follow(state: state, follow: .hashtag(hashtag))
} }
func FollowButton(_ ht: String) -> some View { func FollowButton(_ ht: String) -> some View {
@@ -104,15 +104,20 @@ struct SearchHeaderView: View {
} }
} }
func hashtag_matches_search(desc: DescribedSearch, ref: ReferencedId) -> Bool { func hashtag_matches_search(desc: DescribedSearch, ref: FollowRef) -> Bool {
guard let ht = desc.is_hashtag, ref.key == "t" && ref.ref_id == ht guard case .hashtag(let follow_ht) = ref,
else { return false } case .hashtag(let search_ht) = desc,
follow_ht == search_ht
else {
return false
}
return true return true
} }
func is_following_hashtag(contacts: NostrEvent?, hashtag: String) -> Bool { func is_following_hashtag(contacts: NostrEvent?, hashtag: String) -> Bool {
guard let contacts else { return false } guard let contacts else { return false }
return is_already_following(contacts: contacts, follow: .t(hashtag)) return is_already_following(contacts: contacts, follow: .hashtag(hashtag))
} }
+1 -1
View File
@@ -143,7 +143,7 @@ func translate_note(profiles: Profiles, privkey: Privkey?, event: NostrEvent, se
} }
// Render translated note // 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) let artifacts = render_blocks(blocks: translated_blocks, profiles: profiles)
// and cache it // and cache it
+47 -71
View File
@@ -7,75 +7,56 @@
import Foundation import Foundation
func tag_to_refid_ndb(_ tag: TagSequence) -> ReferencedId? { enum NoteContent {
guard tag.count >= 2 else { return nil } case note(NostrEvent)
case content(String, TagsSequence?)
let key = tag[0].string() init(note: NostrEvent, privkey: Privkey?) {
let ref_id = tag[1].string() if note.known_kind == .dm {
self = .content(note.get_content(privkey), note.tags)
var relay_id: String? = nil } else {
if tag.count >= 3 { self = .note(note)
relay_id = tag[2].string() }
} }
return ReferencedId(ref_id: ref_id, relay_id: relay_id, key: key)
} }
func convert_mention_index_block_ndb(ind: Int, tags: TagsSequence) -> Block? { func parsed_blocks_finish(bs: inout note_blocks, tags: TagsSequence?) -> Blocks {
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 {
var out: [Block] = [] var out: [Block] = []
var bs = note_blocks()
bs.num_blocks = 0;
blocks_init(&bs)
damus_parse_content(&bs, note.content_raw)
var i = 0 var i = 0
while (i < bs.num_blocks) { while (i < bs.num_blocks) {
let block = bs.blocks[i] 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) out.append(converted)
} }
i += 1 i += 1
} }
let words = Int(bs.words) let words = Int(bs.words)
blocks_free(&bs) blocks_free(&bs)
return Blocks(words: words, blocks: out) 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] { 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 /// 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 /// simpler case with no mentions
if mention_indices.count == 0 { if mention_indices.count == 0 {
let ev_refs = References.ids(tags: tags) return interp_event_refs_without_mentions_ndb(tags.note.referenced_noterefs)
return interp_event_refs_without_mentions_ndb(ev_refs)
} }
return interp_event_refs_with_mentions_ndb(tags: tags, mention_indices: mention_indices) 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 count = 0
var evrefs: [EventRef] = [] var evrefs: [EventRef] = []
var first: Bool = true var first: Bool = true
var first_ref: Reference? = nil var first_ref: NoteRef? = nil
for ref in ev_tags { for ref in ev_tags {
if first { if first {
first_ref = ref first_ref = ref
evrefs.append(.thread_id(ref.to_referenced_id())) evrefs.append(.thread_id(ref))
first = false first = false
} else { } else {
evrefs.append(.reply(ref.to_referenced_id())) evrefs.append(.reply(ref))
} }
count += 1 count += 1
} }
if let first_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)] 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] { func interp_event_refs_with_mentions_ndb(tags: TagsSequence, mention_indices: Set<Int>) -> [EventRef] {
var mentions: [EventRef] = [] var mentions: [EventRef] = []
var ev_refs: [ReferencedId] = [] var ev_refs: [NoteRef] = []
var i: Int = 0 var i: Int = 0
for tag in tags { for tag in tags {
if tag.count >= 2, if let note_id = NoteRef.from_tag(tag: tag) {
tag[0].matches_char("e"),
let ref = tag_to_refid_ndb(tag)
{
if mention_indices.contains(i) { if mention_indices.contains(i) {
let mention = Mention(index: i, type: .event, ref: ref) mentions.append(.mention(.noteref(note_id, index: i)))
mentions.append(.mention(mention))
} else { } else {
ev_refs.append(ref) ev_refs.append(note_id)
} }
} }
i += 1 i += 1
+59 -53
View File
@@ -43,9 +43,9 @@ enum Sheets: Identifiable {
var id: String { var id: String {
switch self { switch self {
case .report: return "report" case .report: return "report"
case .post(let action): return "post-" + (action.ev?.id ?? "") case .post(let action): return "post-" + (action.ev?.id.hex() ?? "")
case .event(let ev): return "event-" + ev.id case .event(let ev): return "event-" + ev.id.hex()
case .zap(let sheet): return "zap-" + sheet.target.id case .zap(let sheet): return "zap-" + hex_encode(sheet.target.id)
case .select_wallet: return "select-wallet" case .select_wallet: return "select-wallet"
case .filter: return "filter" case .filter: return "filter"
case .suggestedUsers: return "suggested-users" case .suggestedUsers: return "suggested-users"
@@ -397,14 +397,14 @@ struct ContentView: View {
} }
.onReceive(handle_notify(.unfollow)) { target in .onReceive(handle_notify(.unfollow)) { target in
guard let state = self.damus_state else { return } 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 .onReceive(handle_notify(.unfollowed)) { unfollow in
home.resubscribe(.unfollowing(unfollow)) home.resubscribe(.unfollowing(unfollow))
} }
.onReceive(handle_notify(.follow)) { target in .onReceive(handle_notify(.follow)) { target in
guard let state = self.damus_state else { return } 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 .onReceive(handle_notify(.followed)) { _ in
home.resubscribe(.following) home.resubscribe(.following)
@@ -469,26 +469,29 @@ struct ContentView: View {
.onReceive(handle_notify(.local_notification)) { local in .onReceive(handle_notify(.local_notification)) { local in
guard let damus_state else { return } guard let damus_state else { return }
if local.type == .profile_zap { switch local.mention {
open_profile(pubkey: local.event_id) case .pubkey(let pubkey):
return open_profile(pubkey: pubkey)
}
case .note(let noteId):
guard let target = damus_state.events.lookup(local.event_id) else { guard let target = damus_state.events.lookup(noteId) else {
return return
} }
switch local.type { switch local.type {
case .dm: case .dm:
selected_timeline = .dms selected_timeline = .dms
damus_state.dms.set_active_dm(target.pubkey) damus_state.dms.set_active_dm(target.pubkey)
navigationCoordinator.push(route: Route.DMChat(dms: damus_state.dms.active_model)) navigationCoordinator.push(route: Route.DMChat(dms: damus_state.dms.active_model))
case .like, .zap, .mention, .repost: case .like, .zap, .mention, .repost:
open_event(ev: target) open_event(ev: target)
case .profile_zap: case .profile_zap:
// Handled separately above. // Handled separately above.
break break
}
} }
} }
.onReceive(handle_notify(.onlyzaps_mode)) { hide in .onReceive(handle_notify(.onlyzaps_mode)) { hide in
home.filter_events() home.filter_events()
@@ -529,7 +532,7 @@ struct ContentView: View {
guard let ds = damus_state, guard let ds = damus_state,
let keypair = ds.keypair.to_full(), let keypair = ds.keypair.to_full(),
let pubkey = muting, 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 { else {
return return
} }
@@ -556,16 +559,16 @@ struct ContentView: View {
if ds.contacts.mutelist == nil { if ds.contacts.mutelist == nil {
confirm_overwrite_mutelist = true confirm_overwrite_mutelist = true
} else { } else {
guard let keypair = ds.keypair.to_full() else { guard let keypair = ds.keypair.to_full(),
return let pubkey = muting
} else {
guard let pubkey = muting else {
return 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 return
} }
damus_state?.contacts.set_mutelist(ev) damus_state?.contacts.set_mutelist(ev)
ds.postbox.send(ev) ds.postbox.send(ev)
} }
@@ -871,7 +874,7 @@ func timeline_name(_ timeline: Timeline?) -> String {
} }
@discardableResult @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 { guard let keypair = state.keypair.to_full() else {
return false return false
} }
@@ -887,27 +890,20 @@ func handle_unfollow(state: DamusState, unfollow: ReferencedId) -> Bool {
state.contacts.event = ev state.contacts.event = ev
if unfollow.key == "p" { switch unfollow {
state.contacts.remove_friend(unfollow.ref_id) case .pubkey(let pk):
state.contacts.remove_friend(pk)
state.user_search_cache.updateOwnContactsPetnames(id: state.pubkey, oldEvent: old_contacts, newEvent: ev) state.user_search_cache.updateOwnContactsPetnames(id: state.pubkey, oldEvent: old_contacts, newEvent: ev)
case .hashtag:
// nothing to handle here really
break
} }
return true 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 @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 { guard let keypair = state.keypair.to_full() else {
return false return false
} }
@@ -920,8 +916,12 @@ func handle_follow(state: DamusState, follow: ReferencedId) -> Bool {
notify(.followed(follow)) notify(.followed(follow))
state.contacts.event = ev state.contacts.event = ev
if follow.key == "p" { switch follow {
state.contacts.add_friend_pubkey(follow.ref_id) case .pubkey(let pubkey):
state.contacts.add_friend_pubkey(pubkey)
case .hashtag:
// nothing to do
break
} }
return true return true
@@ -936,7 +936,7 @@ func handle_follow_notif(state: DamusState, target: FollowTarget) -> Bool {
state.contacts.add_friend_contact(ev) 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 { 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) postbox.send(new_ev)
for eref in new_ev.referenced_ids.prefix(3) { for eref in new_ev.referenced_ids.prefix(3) {
// also broadcast at most 3 referenced events // also broadcast at most 3 referenced events
if let ev = events.lookup(eref.ref_id) { if let ev = events.lookup(eref) {
postbox.send(ev) postbox.send(ev)
} }
} }
@@ -984,13 +984,19 @@ func on_open_url(state: DamusState, url: URL, result: @escaping (OpenResult?) ->
switch link { switch link {
case .ref(let ref): case .ref(let ref):
if ref.key == "p" { switch ref {
result(.profile(ref.ref_id)) case .pubkey(let pk):
} else if ref.key == "e" { result(.profile(pk))
find_event(state: state, query: .event(evid: ref.ref_id)) { res in case .event(let noteid):
find_event(state: state, query: .event(evid: noteid)) { res in
guard let res, case .event(let ev) = res else { return } guard let res, case .event(let ev) = res else { return }
result(.event(ev)) 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): case .filter(let filt):
result(.filter(filt)) result(.filter(filt))
+27 -25
View File
@@ -30,9 +30,9 @@ class Contacts {
func set_mutelist(_ ev: NostrEvent) { func set_mutelist(_ ev: NostrEvent) {
let oldlist = self.mutelist let oldlist = self.mutelist
self.mutelist = ev self.mutelist = ev
let old = Set(oldlist?.referenced_pubkeys.map({ $0.ref_id }) ?? []) let old = oldlist.map({ ev in Set(ev.referenced_pubkeys) }) ?? Set<Pubkey>()
let new = Set(ev.referenced_pubkeys.map({ $0.ref_id })) let new = Set(ev.referenced_pubkeys)
let diff = old.symmetricDifference(new) let diff = old.symmetricDifference(new)
var new_mutes = Set<Pubkey>() var new_mutes = Set<Pubkey>()
@@ -40,14 +40,14 @@ class Contacts {
for d in diff { for d in diff {
if new.contains(d) { if new.contains(d) {
new_mutes.insert(d.string()) new_mutes.insert(d)
} else { } else {
new_unmutes.insert(d.string()) new_unmutes.insert(d)
} }
} }
// TODO: set local mutelist here // 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 { if new_mutes.count > 0 {
notify(.new_mutes(new_mutes)) notify(.new_mutes(new_mutes))
@@ -72,7 +72,7 @@ class Contacts {
func get_followed_hashtags() -> Set<String> { func get_followed_hashtags() -> Set<String> {
guard let ev = self.event else { return Set() } 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) { func add_friend_pubkey(_ pubkey: Pubkey) {
@@ -81,8 +81,7 @@ class Contacts {
func add_friend_contact(_ contact: NostrEvent) { func add_friend_contact(_ contact: NostrEvent) {
friends.insert(contact.pubkey) friends.insert(contact.pubkey)
for tag in contact.referenced_pubkeys { for pk in contact.referenced_pubkeys {
let pk = tag.id.string()
friend_of_friends.insert(pk) friend_of_friends.insert(pk)
// Exclude themself and us. // 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 { guard let ev = follow_user_event(our_contacts: our_contacts, keypair: keypair, follow: follow) else {
return nil return nil
} }
@@ -132,7 +131,7 @@ func follow_reference(box: PostBox, our_contacts: NostrEvent?, keypair: FullKeyp
return ev 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 { guard let cs = our_contacts else {
return nil return nil
} }
@@ -146,14 +145,9 @@ func unfollow_reference(postbox: PostBox, our_contacts: NostrEvent?, keypair: Fu
return ev 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 let tags = our_contacts.tags.reduce(into: [[String]]()) { ts, tag in
if tag.count >= 2, if let tag = FollowRef.from_tag(tag: tag), tag == unfollow {
let fst = unfollow.key.first,
let afst = AsciiCharacter(fst),
tag[0].matches_char(afst),
tag[1].matches_str(unfollow.ref_id)
{
return 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)) 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 { guard let cs = our_contacts else {
// don't create contacts for now so we don't nuke our contact list due to connectivity issues // 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 // 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 return relay_info
} }
func is_already_following(contacts: NostrEvent, follow: ReferencedId) -> Bool { func is_already_following(contacts: NostrEvent, follow: FollowRef) -> Bool {
guard let key = follow.key.first_char() else { return false } return contacts.references.contains { ref in
return contacts.references(id: follow.ref_id, key: key) 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: FollowRef) -> NostrEvent? {
func follow_with_existing_contacts(keypair: FullKeypair, our_contacts: NostrEvent, follow: ReferencedId) -> NostrEvent? {
// don't update if we're already following // don't update if we're already following
if is_already_following(contacts: our_contacts, follow: follow) { if is_already_following(contacts: our_contacts, follow: follow) {
return nil return nil
@@ -233,7 +235,7 @@ func follow_with_existing_contacts(keypair: FullKeypair, our_contacts: NostrEven
let kind = NostrKind.contacts.rawValue let kind = NostrKind.contacts.rawValue
var tags = our_contacts.tags.strings() 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) return NostrEvent(content: our_contacts.content, keypair: keypair.to_keypair(), kind: kind, tags: tags)
} }
-8
View File
@@ -16,14 +16,6 @@ class CreateAccountModel: ObservableObject {
@Published var privkey: Privkey = .empty @Published var privkey: Privkey = .empty
@Published var profile_image: URL? = nil @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 { var rendered_name: String {
if real_name.isEmpty { if real_name.isEmpty {
return nick_name return nick_name
+2 -2
View File
@@ -42,8 +42,8 @@ struct DamusState {
// thread zaps // thread zaps
if let ev = zap.event, !settings.nozaps, zap.is_in_thread { if let ev = zap.event, !settings.nozaps, zap.is_in_thread {
// [nozaps]: thread zaps are only available outside of the app store // [nozaps]: thread zaps are only available outside of the app store
replies.count_replies(ev) replies.count_replies(ev, privkey: self.keypair.privkey)
events.add_replies(ev: ev) events.add_replies(ev: ev, privkey: self.keypair.privkey)
} }
// associate with events as well // associate with events as well
+22 -28
View File
@@ -8,19 +8,17 @@
import Foundation import Foundation
enum EventRef: Equatable { enum EventRef: Equatable {
case mention(Mention) case mention(Mention<NoteRef>)
case thread_id(ReferencedId) case thread_id(NoteRef)
case reply(ReferencedId) case reply(NoteRef)
case reply_to_root(ReferencedId) case reply_to_root(NoteRef)
var is_mention: Mention? { var is_mention: NoteRef? {
if case .mention(let m) = self { if case .mention(let m) = self { return m.ref }
return m
}
return nil return nil
} }
var is_direct_reply: ReferencedId? { var is_direct_reply: NoteRef? {
switch self { switch self {
case .mention: case .mention:
return nil return nil
@@ -33,7 +31,7 @@ enum EventRef: Equatable {
} }
} }
var is_thread_id: ReferencedId? { var is_thread_id: NoteRef? {
switch self { switch self {
case .mention: case .mention:
return nil return nil
@@ -46,7 +44,7 @@ enum EventRef: Equatable {
} }
} }
var is_reply: ReferencedId? { var is_reply: NoteRef? {
switch self { switch self {
case .mention: case .mention:
return nil return nil
@@ -64,10 +62,8 @@ func build_mention_indices(_ blocks: [Block], type: MentionType) -> Set<Int> {
return blocks.reduce(into: []) { acc, block in return blocks.reduce(into: []) { acc, block in
switch block { switch block {
case .mention(let m): case .mention(let m):
if m.type == type { if m.ref.key == type, let idx = m.index {
if let idx = m.index { acc.insert(idx)
acc.insert(idx)
}
} }
case .relay: case .relay:
return 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 { if refs.count == 0 {
return [] return []
} }
@@ -105,16 +101,15 @@ func interp_event_refs_without_mentions(_ refs: [ReferencedId]) -> [EventRef] {
return evrefs 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 mentions: [EventRef] = []
var ev_refs: [ReferencedId] = [] var ev_refs: [NoteRef] = []
var i: Int = 0 var i: Int = 0
for tag in tags { for tag in tags {
if tag.count >= 2 && tag[0] == "e" { if let ref = NoteRef.from_tag(tag: tag) {
let ref = tag_to_refid(tag)!
if mention_indices.contains(i) { 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)) mentions.append(.mention(mention))
} else { } else {
ev_refs.append(ref) ev_refs.append(ref)
@@ -128,18 +123,17 @@ func interp_event_refs_with_mentions(tags: [[String]], mention_indices: Set<Int>
return replies return replies
} }
func interpret_event_refs(blocks: [Block], tags: [[String]]) -> [EventRef] { func interpret_event_refs(blocks: [Block], tags: Tags) -> [EventRef] {
if tags.count == 0 { if tags.count == 0 {
return [] return []
} }
/// build a set of indices for each event mention /// 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 /// simpler case with no mentions
if mention_indices.count == 0 { if mention_indices.count == 0 {
let ev_refs = get_referenced_ids(tags: tags, key: "e") return interp_event_refs_without_mentions_ndb(References<NoteRef>(tags: tags))
return interp_event_refs_without_mentions(ev_refs)
} }
return interp_event_refs_with_mentions(tags: tags, mention_indices: mention_indices) return interp_event_refs_with_mentions(tags: tags, mention_indices: mention_indices)
+3 -6
View File
@@ -41,14 +41,11 @@ class EventsModel: ObservableObject {
} }
private func handle_event(relay_id: String, ev: NostrEvent) { 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 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 } ) { if insert_uniq_sorted_event(events: &self.events, new_ev: ev, cmp: { a, b in a.created_at < b.created_at } ) {
objectWillChange.send() objectWillChange.send()
} }
+5 -1
View File
@@ -10,7 +10,11 @@ import Foundation
enum FollowTarget { enum FollowTarget {
case pubkey(Pubkey) case pubkey(Pubkey)
case contact(NostrEvent) case contact(NostrEvent)
var follow_ref: FollowRef {
FollowRef.pubkey(pubkey)
}
var pubkey: Pubkey { var pubkey: Pubkey {
switch self { switch self {
case .pubkey(let pk): return pk case .pubkey(let pk): return pk
+1 -1
View File
@@ -36,7 +36,7 @@ class FollowersModel: ObservableObject {
func subscribe() { func subscribe() {
let filter = get_filter() let filter = get_filter()
let filters = [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) self.damus_state.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event)
} }
+1 -1
View File
@@ -39,7 +39,7 @@ class FollowingModel {
return return
} }
let filters = [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) self.damus_state.pool.subscribe(sub_id: sub_id, filters: filters, handler: handle_event)
} }
+68 -75
View File
@@ -25,20 +25,17 @@ struct NewEventsBits: OptionSet {
enum Resubscribe { enum Resubscribe {
case following case following
case unfollowing(ReferencedId) case unfollowing(FollowRef)
} }
enum HomeResubFilter { enum HomeResubFilter {
case pubkey(Pubkey) case pubkey(Pubkey)
case hashtag(String) case hashtag(String)
init?(from: ReferencedId) { init?(from: FollowRef) {
if from.key == "p" { switch from {
self = .pubkey(from.ref_id) case .hashtag(let ht): self = .hashtag(ht.string())
return case .pubkey(let pk): self = .pubkey(pk)
} else if from.key == "t" {
self = .hashtag(from.ref_id)
return
} }
return nil return nil
@@ -52,7 +49,9 @@ enum HomeResubFilter {
if contacts.is_friend(ev.pubkey) { if contacts.is_friend(ev.pubkey) {
return false 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 damus_state: DamusState
var channels: [String: NostrEvent] = [:]
// NDBTODO: let's get rid of this entirely, let nostrdb handle it // NDBTODO: let's get rid of this entirely, let nostrdb handle it
var has_event: [String: Set<NoteId>] = [:] var has_event: [String: Set<NoteId>] = [:]
var deleted_events: Set<NoteId> = Set() var deleted_events: Set<NoteId> = Set()
@@ -183,10 +181,6 @@ class HomeModel {
handle_dm(ev) handle_dm(ev)
case .delete: case .delete:
handle_delete_event(ev) handle_delete_event(ev)
case .channel_create:
handle_channel_create(ev)
case .channel_meta:
break
case .zap: case .zap:
handle_zap_event(ev) handle_zap_event(ev)
case .zap_request: case .zap_request:
@@ -262,10 +256,6 @@ class HomeModel {
} }
func handle_channel_create(_ ev: NostrEvent) {
self.channels[ev.id] = ev
}
func filter_events() { func filter_events() {
events.filter { ev in events.filter { ev in
!damus_state.contacts.is_muted(ev.pubkey) !damus_state.contacts.is_muted(ev.pubkey)
@@ -301,12 +291,11 @@ class HomeModel {
} }
func handle_boost_event(sub_id: String, _ ev: NostrEvent) { 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) { if let inner_ev = ev.get_inner_event(cache: damus_state.events) {
boost_ev_id = inner_ev.id boost_ev_id = inner_ev.id
Task { Task {
guard validate_event(ev: inner_ev) == .ok else { guard validate_event(ev: inner_ev) == .ok else {
return return
@@ -318,7 +307,6 @@ class HomeModel {
} }
} }
} }
} }
guard let e = boost_ev_id else { guard let e = boost_ev_id else {
@@ -345,14 +333,14 @@ class HomeModel {
return return
} }
switch damus_state.likes.add_event(ev, target: e.ref_id) { switch damus_state.likes.add_event(ev, target: e) {
case .already_counted: case .already_counted:
break break
case .success(let n): case .success(let n):
handle_notification(ev: ev) 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(.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 return
} }
guard name.ref_id == "mute" else {
return
}
damus_state.contacts.set_mutelist(ev) 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? // TODO: will we need to process this in other places like zap request contents, etc?
process_image_metadatas(cache: damus_state.events, ev: ev) 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) damus_state.events.insert(ev)
if sub_id == home_subid { 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) { func load_our_contacts(state: DamusState, m_old_ev: NostrEvent?, ev: NostrEvent) {
let contacts = state.contacts let contacts = state.contacts
var new_refs = Set<ReferencedId>() let new_refs = Set<FollowRef>(ev.referenced_follows)
// our contacts let old_refs = m_old_ev.map({ old_ev in Set(old_ev.referenced_follows) }) ?? Set()
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 diff = new_refs.symmetricDifference(old_refs) let diff = new_refs.symmetricDifference(old_refs)
for ref in diff { for ref in diff {
if new_refs.contains(ref) { if new_refs.contains(ref) {
notify(.followed(ref)) notify(.followed(ref))
if ref.key == "p" { switch ref {
contacts.add_friend_pubkey(ref.ref_id) case .pubkey(let pk):
contacts.add_friend_pubkey(pk)
case .hashtag:
// I guess I could cache followed hashtags here... whatever
break
} }
} else { } else {
notify(.unfollowed(ref)) notify(.unfollowed(ref))
if ref.key == "p" { switch ref {
contacts.remove_friend(ref.ref_id) 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))" return "\(n): \(abbrev_ids(ids))"
} }
/*
func print_filter(_ f: NostrFilter) { func print_filter(_ f: NostrFilter) {
let fmt = [ let fmt = [
abbrev_ids_field("ids", f.ids), abbrev_ids_field("ids", f.ids),
@@ -783,6 +761,7 @@ func print_filters(relay_id: String?, filters groups: [[NostrFilter]]) {
} }
print("-----") print("-----")
} }
*/
func process_metadata_profile(our_pubkey: Pubkey, profiles: Profiles, profile: Profile, ev: NostrEvent) { func process_metadata_profile(our_pubkey: Pubkey, profiles: Profiles, profile: Profile, ev: NostrEvent) {
var old_nip05: String? = nil 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 var the_pk = ev.pubkey
if ours { if ours {
if let ref_pk = ev.referenced_pubkeys.first { if let ref_pk = ev.referenced_pubkeys.first {
the_pk = ref_pk.ref_id the_pk = ref_pk
} else { } else {
// self dm!? // self dm!?
print("TODO: handle 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 /// 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 { func event_has_our_pubkey(_ ev: NostrEvent, our_pubkey: Pubkey) -> Bool {
for tag in ev.tags { return ev.referenced_pubkeys.contains(our_pubkey)
if tag.count >= 2 && tag[0] == "p" && tag[1] == our_pubkey {
return true
}
}
return false
} }
@@ -1185,7 +1158,7 @@ func create_in_app_profile_zap_notification(profiles: Profiles, zap: Zap, locale
content.title = zap_notification_title(zap) content.title = zap_notification_title(zap)
content.body = zap_notification_body(profiles: profiles, zap: zap, locale: locale) content.body = zap_notification_body(profiles: profiles, zap: zap, locale: locale)
content.sound = UNNotificationSound.default 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) 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.title = zap_notification_title(zap)
content.body = zap_notification_body(profiles: profiles, zap: zap, locale: locale) content.body = zap_notification_body(profiles: profiles, zap: zap, locale: locale)
content.sound = UNNotificationSound.default 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) let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
@@ -1264,19 +1237,26 @@ func process_local_notification(damus_state: DamusState, event ev: NostrEvent) {
return 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 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 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) let notify = LocalNotification(type: .mention, event: ev, target: ev, content: content_preview)
create_local_notification(profiles: damus_state.profiles, notify: notify ) 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 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) let notify = LocalNotification(type: .repost, event: ev, target: inner_ev, content: content_preview)
create_local_notification(profiles: damus_state.profiles, notify: notify) create_local_notification(profiles: damus_state.profiles, notify: notify)
} else if type == .like && damus_state.settings.like_notification, } else if type == .like,
let evid = ev.referenced_ids.last?.ref_id, damus_state.settings.like_notification,
let evid = ev.referenced_ids.last,
let liked_event = damus_state.events.lookup(evid) 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) 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 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 // securely get the zap target's pubkey. this can be faked so we need to be
// careful // careful
func get_zap_target_pubkey(ev: NostrEvent, events: EventCache) -> Pubkey? { 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 { guard let etag = etags.first else {
// no etags, ptag-only case // no etags, ptag-only case
let ptags = ev.referenced_pubkeys guard let a = ev.referenced_pubkeys.just_one() else {
// ensure that there is only 1 ptag to stop fake profile zap attacks
guard ptags.count == 1 else {
return nil return nil
} }
return ptags.first?.id // TODO: just return data here
return a
} }
// we have an e-tag // 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 // 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) { func process_zap_event(damus_state: DamusState, ev: NostrEvent, completion: @escaping (ProcessZapResult) -> Void) {
+115 -118
View File
@@ -7,31 +7,93 @@
import Foundation import Foundation
enum MentionType { enum MentionType: AsciiCharacter, TagKey {
case pubkey case p
case event 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 { switch self {
case .pubkey: case .pubkey: return .p
return "p" case .note: return .e
case .event: }
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 index: Int?
let type: MentionType let ref: T
let ref: ReferencedId
static func note(_ id: String) -> Mention { static func any(_ mention_id: MentionRef, index: Int? = nil) -> Mention<MentionRef> {
return Mention(index: nil, type: .event, ref: .e(id)) return Mention<MentionRef>(index: index, ref: mention_id)
} }
static func pubkey(_ pubkey: String) -> Mention { static func noteref(_ id: NoteRef, index: Int? = nil) -> Mention<NoteRef> {
return Mention(index: nil, type: .pubkey, ref: .p(pubkey)) 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 text(String)
case mention(Mention) case mention(Mention<MentionRef>)
case hashtag(String) case hashtag(String)
case url(URL) case url(URL)
case invoice(Invoice) case invoice(Invoice)
@@ -116,14 +178,14 @@ enum Block: Equatable {
} }
var is_note_mention: Bool { var is_note_mention: Bool {
guard case .mention(let mention) = self else { if case .mention(let mention) = self,
return false case .note = mention.ref {
return true
} }
return false
return mention.type == .event
} }
var is_mention: Mention? { var is_mention: Mention<MentionRef>? {
if case .mention(let m) = self { if case .mention(let m) = self {
return m return m
} }
@@ -137,12 +199,11 @@ func render_blocks(blocks: [Block]) -> String {
case .mention(let m): case .mention(let m):
if let idx = m.index { if let idx = m.index {
return str + "#[\(idx)]" 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) { switch m.ref {
return str + "nostr:\(note_id)" case .pubkey(let pk): return str + "nostr:\(pk.npub)"
} else { case .note(let note_id): return str + "nostr:\(note_id.bech32)"
return str + m.ref.ref_id
} }
case .relay(let relay): case .relay(let relay):
return str + relay return str + relay
@@ -163,43 +224,13 @@ struct Blocks: Equatable {
let blocks: [Block] 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? { func strblock_to_string(_ s: str_block_t) -> String? {
let len = s.end - s.start let len = s.end - s.start
let bytes = Data(bytes: s.start, count: len) let bytes = Data(bytes: s.start, count: len)
return String(bytes: bytes, encoding: .utf8) 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 { if b.type == BLOCK_HASHTAG {
guard let str = strblock_to_string(b.block.str) else { guard let str = strblock_to_string(b.block.str) else {
return nil return nil
@@ -211,7 +242,7 @@ func convert_block(_ b: block_t, tags: [[String]]) -> Block? {
} }
return .text(str) return .text(str)
} else if b.type == BLOCK_MENTION_INDEX { } 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 { } else if b.type == BLOCK_URL {
return convert_url_block(b.block.str) return convert_url_block(b.block.str)
} else if b.type == BLOCK_INVOICE { } else if b.type == BLOCK_INVOICE {
@@ -321,41 +352,29 @@ func convert_mention_bech32_block(_ b: mention_bech32_block) -> Block?
switch b.bech32.type { switch b.bech32.type {
case NOSTR_BECH32_NOTE: case NOSTR_BECH32_NOTE:
let note = b.bech32.data.note; let note = b.bech32.data.note;
let event_id = hex_encode(Data(bytes: note.event_id, count: 32)) let note_id = NoteId(Data(bytes: note.event_id, count: 32))
let event_id_ref = ReferencedId(ref_id: event_id, relay_id: nil, key: "e") return .mention(.any(.note(note_id)))
return .mention(Mention(index: nil, type: .event, ref: event_id_ref))
case NOSTR_BECH32_NEVENT: case NOSTR_BECH32_NEVENT:
let nevent = b.bech32.data.nevent; let nevent = b.bech32.data.nevent;
let event_id = hex_encode(Data(bytes: nevent.event_id, count: 32)) let note_id = NoteId(Data(bytes: nevent.event_id, count: 32))
var relay_id: String? = nil return .mention(.any(.note(note_id)))
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))
case NOSTR_BECH32_NPUB: case NOSTR_BECH32_NPUB:
let npub = b.bech32.data.npub let npub = b.bech32.data.npub
let pubkey = hex_encode(Data(bytes: npub.pubkey, count: 32)) let pubkey = Pubkey(Data(bytes: npub.pubkey, count: 32))
let pubkey_ref = ReferencedId(ref_id: pubkey, relay_id: nil, key: "p") return .mention(.any(.pubkey(pubkey)))
return .mention(Mention(index: nil, type: .pubkey, ref: pubkey_ref))
case NOSTR_BECH32_NSEC: case NOSTR_BECH32_NSEC:
let nsec = b.bech32.data.nsec let nsec = b.bech32.data.nsec
let nsec_bytes = Data(bytes: nsec.nsec, count: 32) let privkey = Privkey(Data(bytes: nsec.nsec, count: 32))
let pubkey = privkey_to_pubkey_raw(sec: nsec_bytes.bytes) ?? hex_encode(nsec_bytes) guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
return .mention(.pubkey(pubkey)) return .mention(.any(.pubkey(pubkey)))
case NOSTR_BECH32_NPROFILE: case NOSTR_BECH32_NPROFILE:
let nprofile = b.bech32.data.nprofile let nprofile = b.bech32.data.nprofile
let pubkey = hex_encode(Data(bytes: nprofile.pubkey, count: 32)) let pubkey = Pubkey(Data(bytes: nprofile.pubkey, count: 32))
var relay_id: String? = nil return .mention(.any(.pubkey(pubkey)))
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))
case NOSTR_BECH32_NRELAY: case NOSTR_BECH32_NRELAY:
let nrelay = b.bech32.data.nrelay let nrelay = b.bech32.data.nrelay
@@ -388,24 +407,22 @@ func convert_invoice_description(b11: bolt11) -> InvoiceDescription? {
return nil 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) guard let tags,
ind >= 0,
if ind < 0 || (ind + 1 > tags.count) || tags[ind].count < 2 { ind + 1 <= tags.count
else {
return .text("#[\(ind)]") return .text("#[\(ind)]")
} }
let tag = tags[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)]") return .text("#[\(ind)]")
} }
guard let ref = tag_to_refid(tag) else { return .mention(.any(mention, index: ind))
return .text("#[\(ind)]")
}
return .mention(Mention(index: ind, type: mention_type, ref: ref))
} }
func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? { func find_tag_ref(type: String, id: String, tags: [[String]]) -> Int? {
@@ -427,25 +444,6 @@ struct PostTags {
let tags: [[String]] 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 /// Convert
func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags { func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags {
var new_tags = tags var new_tags = tags
@@ -453,12 +451,11 @@ func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags {
for post_block in post_blocks { for post_block in post_blocks {
switch post_block { switch post_block {
case .mention(let mention): case .mention(let mention):
let mention_type = mention.type if case .note = mention.ref {
if mention_type == .event {
continue continue
} }
new_tags.append(refid_to_tag(mention.ref)) new_tags.append(mention.ref.tag)
case .hashtag(let hashtag): case .hashtag(let hashtag):
new_tags.append(["t", hashtag.lowercased()]) new_tags.append(["t", hashtag.lowercased()])
case .text: break 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? { 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_blocks = parse_post_blocks(content: post.content)
let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags) let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags)
let content = render_blocks(blocks: post_tags.blocks) let content = render_blocks(blocks: post_tags.blocks)
+5 -1
View File
@@ -13,7 +13,11 @@ fileprivate func getMutedThreadsKey(pubkey: Pubkey) -> String {
func loadMutedThreads(pubkey: Pubkey) -> [NoteId] { func loadMutedThreads(pubkey: Pubkey) -> [NoteId] {
let key = getMutedThreadsKey(pubkey: pubkey) 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 { func saveMutedThreads(pubkey: Pubkey, currentValue: [NoteId], value: [NoteId]) -> Bool {
+4 -6
View File
@@ -193,17 +193,15 @@ class NotificationsModel: ObservableObject, ScrollQueue {
} }
private func insert_reaction(_ ev: NostrEvent) -> Bool { 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 return false
} }
let id = ref_id.id if let evgrp = self.reactions[id] {
if let evgrp = self.reactions[id.string()] {
return evgrp.insert(ev) return evgrp.insert(ev)
} else { } else {
let evgrp = EventGroup() let evgrp = EventGroup()
self.reactions[id.string()] = evgrp self.reactions[id] = evgrp
return evgrp.insert(ev) return evgrp.insert(ev)
} }
} }
+4 -91
View File
@@ -10,10 +10,10 @@ import Foundation
struct NostrPost { struct NostrPost {
let kind: NostrKind let kind: NostrKind
let content: String let content: String
let references: [ReferencedId] let references: [RefId]
let tags: [[String]] 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.content = content
self.references = references self.references = references
self.kind = kind 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 /// Return a list of tags
func parse_post_blocks(content: String) -> [Block] { func parse_post_blocks(content: String) -> [Block] {
return parse_note_content(content: content, tags: []).blocks return parse_note_content(content: .content(content, nil)).blocks
} }
-31
View File
@@ -6,34 +6,3 @@
// //
import Foundation 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)))
}
+3 -12
View File
@@ -33,17 +33,8 @@ class ProfileModel: ObservableObject, Equatable {
guard let contacts = self.contacts else { guard let contacts = self.contacts else {
return false return false
} }
for tag in contacts.tags { return contacts.referenced_pubkeys.contains(pubkey)
guard tag.count >= 2,
tag[0].matches_char("p"),
tag[1].matches_str(pubkey)
else {
continue
}
}
return false
} }
func get_follow_target() -> FollowTarget { func get_follow_target() -> FollowTarget {
@@ -77,7 +68,7 @@ class ProfileModel: ObservableObject, Equatable {
text_filter.limit = 500 text_filter.limit = 500
print("subscribing to profile \(pubkey) with sub_id \(sub_id)") 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: sub_id, filters: [text_filter], handler: handle_event)
damus.pool.subscribe(sub_id: prof_subid, filters: [profile_filter], handler: handle_event) damus.pool.subscribe(sub_id: prof_subid, filters: [profile_filter], handler: handle_event)
} }
+7 -8
View File
@@ -12,21 +12,20 @@ struct ReplyDesc {
let others: Int let others: Int
} }
func make_reply_description(_ tags: [[String]]) -> ReplyDesc { func make_reply_description(_ tags: Tags) -> ReplyDesc {
var c = 0 var c = 0
var ns: [Pubkey] = [] var ns: [Pubkey] = []
var i = tags.count - 1 var i = tags.count
while i >= 0 { for tag in tags {
let tag = tags[i] if let pk = Pubkey.from_tag(tag: tag) {
if tag.count >= 2 && tag[0] == "p" {
c += 1 c += 1
if ns.count < 2 { if ns.count < 2 {
ns.append(tag[1]) ns.append(pk)
} }
} }
i -= 1 i -= 1
} }
return ReplyDesc(pubkeys: ns, others: c) return ReplyDesc(pubkeys: ns, others: c)
} }
+3 -7
View File
@@ -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 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 { if ev.is_textlike && ev.should_show_event {
self.add_event(ev) 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 { func event_matches_hashtag(_ ev: NostrEvent, hashtags: [String]) -> Bool {
for tag in ev.tags { 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 true
} }
} }
return false return false
} }
func tag_is_hashtag(_ tag: [String]) -> Bool { func tag_is_hashtag(_ tag: Tag) -> Bool {
// "hashtag" is deprecated, will remove in the future // "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 { func event_matches_filter(_ ev: NostrEvent, filter: NostrFilter) -> Bool {
+9 -9
View File
@@ -18,7 +18,7 @@ class ThreadModel: ObservableObject {
self.event_map = Set() self.event_map = Set()
self.event = event self.event = event
self.original_event = event self.original_event = event
add_event(event) add_event(event, privkey: damus_state.keypair.privkey)
} }
var is_original: Bool { var is_original: Bool {
@@ -46,10 +46,10 @@ class ThreadModel: ObservableObject {
} }
@discardableResult @discardableResult
func set_active_event(_ ev: NostrEvent) -> Bool { func set_active_event(_ ev: NostrEvent, privkey: Privkey?) -> Bool {
self.event = ev self.event = ev
add_event(ev) add_event(ev, privkey: privkey)
//self.objectWillChange.send() //self.objectWillChange.send()
return false return false
} }
@@ -85,15 +85,15 @@ class ThreadModel: ObservableObject {
damus_state.pool.subscribe(sub_id: meta_subid, filters: meta_filters, handler: handle_event) 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) { if event_map.contains(ev) {
return return
} }
let the_ev = damus_state.events.upsert(ev) let the_ev = damus_state.events.upsert(ev)
damus_state.replies.count_replies(the_ev) damus_state.replies.count_replies(the_ev, privkey: privkey)
damus_state.events.add_replies(ev: the_ev) damus_state.events.add_replies(ev: the_ev, privkey: privkey)
event_map.insert(ev) event_map.insert(ev)
objectWillChange.send() objectWillChange.send()
} }
@@ -112,7 +112,7 @@ class ThreadModel: ObservableObject {
} }
} else if ev.is_textlike { } else if ev.is_textlike {
self.add_event(ev) self.add_event(ev, privkey: damus_state.keypair.privkey)
} }
} }
+17 -13
View File
@@ -61,31 +61,35 @@ class UserSearchCache {
return return
} }
var petnames: [String: String] = [:] var petnames: [Pubkey: String] = [:]
for tag in newEvent.tags {
// Gets all petnames from our new contacts list. guard tag.count > 3,
newEvent.tags.forEach { tag in let chr = tag[0].single_char, chr == "p",
guard tag.count >= 4 && tag[0] == "p" else { let id = tag[1].id()
else {
return return
} }
let pubkey = tag[1] let pubkey = Pubkey(id)
let petname = tag[3]
petnames[pubkey] = petname petnames[pubkey] = tag[3].string()
} }
// Compute the diff with the old contacts list, if it exists, // 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, // 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. // and remove the old ones that are different from the user search cache.
if let oldEvent, oldEvent.known_kind == .contacts && oldEvent.pubkey == id { if let oldEvent, oldEvent.known_kind == .contacts, oldEvent.pubkey == id {
oldEvent.tags.forEach { tag in for tag in oldEvent.tags {
guard tag.count >= 4 && tag[0] == "p" else { guard tag.count >= 4,
tag[0].matches_char("p"),
let id = tag[1].id()
else {
return return
} }
let pubkey = tag[1] let pubkey = Pubkey(id)
let oldPetname = tag[3]
let oldPetname = tag[3].string()
if let newPetname = petnames[pubkey] { if let newPetname = petnames[pubkey] {
if newPetname.caseInsensitiveCompare(oldPetname) == .orderedSame { if newPetname.caseInsensitiveCompare(oldPetname) == .orderedSame {
+4 -9
View File
@@ -56,15 +56,10 @@ class ZapsModel: ObservableObject {
let events = state.events.lookup_zaps(target: target).map { $0.request.ev } 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) load_profiles(profiles_subid: profiles_subid, relay_id: relay_id, load: .from_events(events), damus_state: state)
case .event(_, let ev): case .event(_, let ev):
guard ev.kind == 9735 else { guard ev.kind == 9735,
return 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 {
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 {
return return
} }
+116
View 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
}
}
+96 -135
View File
@@ -20,16 +20,20 @@ enum ValidationResult: Decodable {
case bad_sig case bad_sig
} }
//typealias NostrEvent = NdbNote typealias NostrEvent = NdbNote
//typealias Tags = TagsSequence typealias TagElem = NdbTagElem
typealias Tags = [[String]] typealias Tag = TagSequence
typealias NostrEvent = NostrEventOld typealias Tags = TagsSequence
//typealias TagElem = String
//typealias Tag = [TagElem]
//typealias Tags = [Tag]
//typealias NostrEvent = NostrEventOld
let MAX_NOTE_SIZE: Int = 2 << 18 let MAX_NOTE_SIZE: Int = 2 << 18
/*
class NostrEventOld: Codable, Identifiable, CustomStringConvertible, Equatable, Hashable, Comparable { class NostrEventOld: Codable, Identifiable, CustomStringConvertible, Equatable, Hashable, Comparable {
// TODO: memory mapped db events // TODO: memory mapped db events
/*
private var note_data: UnsafeMutablePointer<ndb_note> private var note_data: UnsafeMutablePointer<ndb_note>
init(data: UnsafeMutablePointer<ndb_note>) { init(data: UnsafeMutablePointer<ndb_note>) {
@@ -51,7 +55,6 @@ class NostrEventOld: Codable, Identifiable, CustomStringConvertible, Equatable,
} }
var tags: TagIterator var tags: TagIterator
*/
let id: String let id: String
let content: String let content: String
@@ -90,12 +93,16 @@ class NostrEventOld: Codable, Identifiable, CustomStringConvertible, Equatable,
hasher.combine(id) 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() let decoder = JSONDecoder()
guard let dat = json.data(using: .utf8) else { guard let dat = json.data(using: .utf8) else {
return nil 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 return nil
} }
@@ -214,10 +221,6 @@ extension NostrEventOld {
return NostrKind.init(rawValue: kind) 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] { private func get_referenced_ids(key: String) -> [ReferencedId] {
return damus.get_referenced_ids(tags: self.tags, key: key) return damus.get_referenced_ids(tags: self.tags, key: key)
} }
@@ -309,6 +312,7 @@ extension NostrEventOld {
return Date.now.timeIntervalSince(event_date) return Date.now.timeIntervalSince(event_date)
} }
} }
*/
func sign_id(privkey: String, id: String) -> String { func sign_id(privkey: String, id: String) -> String {
let priv_key_bytes = try! privkey.bytes let priv_key_bytes = try! privkey.bytes
@@ -316,7 +320,7 @@ func sign_id(privkey: String, id: String) -> String {
// Extra params for custom signing // 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 var digest = try! id.bytes
// API allows for signing variable length messages // 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? { 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? { 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? { 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_data = try! tags_encoder.encode(tags)
let tags = String(decoding: tags_data, as: UTF8.self) 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 { 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)! 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) 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 return str
} }
func random_bytes(count: Int) -> Data { func random_bytes(count: Int) -> Data {
var bytes = [Int8](repeating: 0, count: count) var bytes = [Int8](repeating: 0, count: count)
guard guard
@@ -448,42 +450,6 @@ func random_bytes(count: Int) -> Data {
return Data(bytes: bytes, count: count) 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? { func make_first_contact_event(keypair: Keypair) -> NostrEvent? {
let bootstrap_relays = load_bootstrap_relays(pubkey: keypair.pubkey) let bootstrap_relays = load_bootstrap_relays(pubkey: keypair.pubkey)
let rw_relay_info = RelayInfo(read: true, write: true) 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? { 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") } 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 {
tags.append(["e", boosted.id, "", "root"]) return
}
ts.append(tag.strings())
}
tags.append(["e", boosted.id.hex(), "", "root"])
tags.append(["p", boosted.pubkey.hex()]) 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? { 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(["e", liked.id.hex()])
tags.append(["p", liked.pubkey.hex()]) tags.append(["p", liked.pubkey.hex()])
return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: 7, tags: tags) 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? { 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 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) 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 // check to see if the private note was from us
if note == nil { 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 return nil
} }
// use our private keypair and their pubkey to get the shared secret // 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 return note
} }
func generate_private_keypair(our_privkey: String, id: String, created_at: UInt32) -> FullKeypair? { func generate_private_keypair(our_privkey: Privkey, id: NoteId, created_at: UInt32) -> FullKeypair? {
let to_hash = our_privkey + id + String(created_at) let to_hash = our_privkey.hex() + id.hex() + String(created_at)
guard let dat = to_hash.data(using: .utf8) else { guard let dat = to_hash.data(using: .utf8) else {
return nil return nil
} }
let privkey_bytes = sha256(dat) let privkey_bytes = sha256(dat)
let privkey = hex_encode(privkey_bytes) let privkey = Privkey(privkey_bytes)
guard let pubkey = privkey_to_pubkey(privkey: privkey) else { guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
return nil
}
return FullKeypair(pubkey: pubkey, privkey: privkey) return FullKeypair(pubkey: pubkey, privkey: privkey)
} }
@@ -661,7 +642,7 @@ func make_zap_request_event(keypair: FullKeypair, content: String, relays: [Rela
tags.append(["anon"]) tags.append(["anon"])
kp = generate_new_keypair() kp = generate_new_keypair()
case .priv: 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 return nil
} }
kp = priv_kp kp = priv_kp
@@ -699,27 +680,36 @@ func uniq<T: Hashable>(_ xs: [T]) -> [T] {
return ys return ys
} }
func gather_reply_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] { func gather_reply_ids(our_pubkey: Pubkey, from: NostrEvent) -> [RefId] {
var ids = get_referenced_ids(tags: from.tags, key: "e").first.map { [$0] } ?? [] var ids: [RefId] = from.referenced_ids.first.map({ ref in [ .event(ref) ] }) ?? []
ids.append(.e(from.id)) let pks = from.referenced_pubkeys.reduce(into: [RefId]()) { rs, pk in
ids.append(contentsOf: uniq(from.referenced_pubkeys.filter { $0.ref_id != our_pubkey })) if pk == our_pubkey {
if from.pubkey != our_pubkey { return
ids.append(ReferencedId(ref_id: from.pubkey, relay_id: nil, key: "p")) }
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 return ids
} }
func gather_quote_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] { func gather_quote_ids(our_pubkey: Pubkey, from: NostrEvent) -> [RefId] {
var ids: [ReferencedId] = [.q(from.id)] var ids: [RefId] = [.quote(from.id.quote_id)]
if from.pubkey != our_pubkey { if from.pubkey != our_pubkey {
ids.append(ReferencedId(ref_id: from.pubkey, relay_id: nil, key: "p")) ids.append(.pubkey(from.pubkey))
} }
return ids return ids
} }
func event_from_json(dat: String) -> NostrEvent? { 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 { 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) return decode_nostr_event_json(json: dec)
} }
func get_shared_secret(privkey: String, pubkey: String) -> [UInt8]? { func get_shared_secret(privkey: Privkey, pubkey: Pubkey) -> [UInt8]? {
guard let privkey_bytes = try? privkey.bytes else { let privkey_bytes = privkey.bytes
return nil var pk_bytes = pubkey.bytes
}
guard var pk_bytes = try? pubkey.bytes else {
return nil
}
pk_bytes.insert(2, at: 0) pk_bytes.insert(2, at: 0)
var publicKey = secp256k1_pubkey() 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 { 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 = calculate_event_id(pubkey: ev.pubkey, created_at: ev.created_at, kind: ev.kind, tags: ev.tags.strings(), content: ev.content)
let id = hex_encode(raw_id)
if id != ev.id { if id != ev.id {
return .bad_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 let ctx = secp256k1.Context.raw
var xonly_pubkey = secp256k1_xonly_pubkey.init() 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 var ok = secp256k1_xonly_pubkey_parse(ctx, &xonly_pubkey, &ev_pubkey) != 0
if !ok { if !ok {
return .bad_sig return .bad_sig
} }
var raw_id_bytes = raw_id.bytes
var sig = ev.sig.data.bytes
ok = secp256k1_schnorrsig_verify(ctx, &sig64, &raw_id_bytes, raw_id.count, &xonly_pubkey) > 0 var idbytes = id.id.bytes
ok = secp256k1_schnorrsig_verify(ctx, &sig, &idbytes, 32, &xonly_pubkey) > 0
return ok ? .ok : .bad_sig 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 let blocks = ev.blocks(privkey).blocks.filter { block in
guard case .mention(let mention) = block else { guard case .mention(let mention) = block,
return false case .note = mention.ref else {
}
guard case .event = mention.type else {
return false
}
if mention.ref.key != "e" {
return false return false
} }
@@ -970,10 +945,13 @@ func first_eref_mention(ev: NostrEvent, privkey: String?) -> Mention? {
} }
/// MARK: - Preview /// MARK: - Preview
if let firstBlock = blocks.first, case .mention(let mention) = firstBlock, mention.ref.key == "e" { if let firstBlock = blocks.first,
return mention case .mention(let mention) = firstBlock,
case .note(let note_id) = mention.ref
{
return .note(note_id)
} }
return nil 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"
}
}
}
}
-2
View File
@@ -16,8 +16,6 @@ enum NostrKind: UInt32, Codable {
case delete = 5 case delete = 5
case boost = 6 case boost = 6
case like = 7 case like = 7
case channel_create = 40
case channel_meta = 41
case chat = 42 case chat = 42
case list = 30000 case list = 30000
case longform = 30023 case longform = 30023
+19 -40
View File
@@ -9,18 +9,18 @@ import Foundation
enum NostrLink: Equatable { enum NostrLink: Equatable {
case ref(ReferencedId) case ref(RefId)
case filter(NostrFilter) case filter(NostrFilter)
case script([UInt8]) case script([UInt8])
} }
func encode_pubkey_uri(_ ref: ReferencedId) -> String { func encode_pubkey_uri(_ pubkey: Pubkey) -> String {
return "p:" + ref.ref_id return "p:" + pubkey.hex()
} }
// TODO: bech32 and relay hints // TODO: bech32 and relay hints
func encode_event_id_uri(_ ref: ReferencedId) -> String { func encode_event_id_uri(_ noteid: NoteId) -> String {
return "e:" + ref.ref_id return "e:" + noteid.hex()
} }
func parse_nostr_ref_uri_type(_ p: Parser) -> String? { 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)) 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? { func decode_universal_link(_ s: String) -> NostrLink? {
var uri = s.replacingOccurrences(of: "https://damus.io/r/", with: "") var uri = s.replacingOccurrences(of: "https://damus.io/r/", with: "")
uri = uri.replacingOccurrences(of: "https://damus.io/", with: "") uri = uri.replacingOccurrences(of: "https://damus.io/", with: "")
uri = uri.replacingOccurrences(of: "/", 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 return nil
} }
let h = hex_encode(decoded.data)
if decoded.hrp == "note" { 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" { } 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 // TODO: handle nprofile, etc
@@ -98,14 +83,12 @@ func decode_nostr_bech32_uri(_ s: String) -> NostrLink? {
switch obj { switch obj {
case .nsec(let privkey): case .nsec(let privkey):
guard let pubkey = privkey_to_pubkey(privkey: privkey) else { guard let pubkey = privkey_to_pubkey(privkey: privkey) else { return nil }
return nil return .ref(.pubkey(pubkey))
}
return .ref(ReferencedId(ref_id: pubkey, relay_id: nil, key: "p"))
case .npub(let pubkey): case .npub(let pubkey):
return .ref(ReferencedId(ref_id: pubkey, relay_id: nil, key: "p")) return .ref(.pubkey(pubkey))
case .note(let id): case .note(let id):
return .ref(ReferencedId(ref_id: id, relay_id: nil, key: "e")) return .ref(.event(id))
case .nscript(let data): case .nscript(let data):
return .script(data) return .script(data)
} }
@@ -134,19 +117,15 @@ func decode_nostr_uri(_ s: String) -> NostrLink? {
acc.append(decoded) acc.append(decoded)
return return
} }
if tag_is_hashtag(parts) { if parts.count >= 2 && parts[0] == "t" {
return .filter(NostrFilter(hashtag: [parts[1].lowercased()])) return .filter(NostrFilter(hashtag: [parts[1].lowercased()]))
} }
if let rid = tag_to_refid(parts) {
return .ref(rid)
}
guard parts.count == 1 else { guard parts.count == 1 else {
return nil return nil
} }
let part = parts[0] let part = parts[0]
return decode_nostr_bech32_uri(part) return decode_nostr_bech32_uri(part)
+69 -39
View File
@@ -13,7 +13,12 @@ struct CommandResult {
let msg: String let msg: String
} }
enum NostrResponse: Decodable { enum MaybeResponse {
case bad
case ok(NostrResponse)
}
enum NostrResponse {
case event(String, NostrEvent) case event(String, NostrEvent)
case notice(String) case notice(String)
case eose(String) case eose(String)
@@ -32,48 +37,73 @@ enum NostrResponse: Decodable {
} }
} }
init(from decoder: Decoder) throws { static func owned_from_json(json: String) -> NostrResponse? {
var container = try decoder.unkeyedContainer() 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 if data == nil {
let typ = try container.decode(String.self) let r: NostrResponse? = nil
if typ == "EVENT" { return r
let sub_id = try container.decode(String.self)
var ev: NostrEvent
do {
ev = try container.decode(NostrEvent.self)
} catch {
print(error)
throw error
} }
//ev.pow = count_hash_leading_zero_bits(ev.id) //guard var json_cstr = json.cString(using: .utf8) else { return nil }
self = .event(sub_id, ev)
return //json_cs
} else if typ == "NOTICE" { var tce = ndb_tce()
let msg = try container.decode(String.self)
self = .notice(msg) let len = ndb_ws_event_from_json(cstr, Int32(json.utf8.count), &tce, data, Int32(bufsize))
return if len <= 0 {
} else if typ == "EOSE" { free(data)
let sub_id = try container.decode(String.self) return nil
self = .eose(sub_id) }
return
} else if typ == "OK" { switch tce.evtype {
var cr: CommandResult case NDB_TCE_OK:
do { defer { free(data) }
let event_id = try container.decode(String.self)
let ok = try container.decode(Bool.self) guard let evid_str = sized_cstr(cstr: tce.subid, len: tce.subid_len),
let msg = try container.decode(String.self) let evid = hex_decode_noteid(evid_str),
cr = CommandResult(event_id: event_id, ok: ok, msg: msg) let msg = sized_cstr(cstr: tce.command_result.msg, len: tce.command_result.msglen) else {
} catch { return nil
print(error) }
throw error 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)
}
+19
View File
@@ -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)
}
-29
View File
@@ -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
}
}
+127 -74
View File
@@ -7,79 +7,44 @@
import Foundation 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 { 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 let tags: TagsSequence
var tags_iter: TagsIterator 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) { init(tags: TagsSequence) {
self.tags = tags self.tags = tags
self.tags_iter = tags.makeIterator() self.tags_iter = tags.makeIterator()
} }
}
// TagsSequence transition helpers mutating func next() -> T? {
extension [[String]] { while let tag = tags_iter.next() {
func strings() -> [[String]] { guard let evref = T.from_tag(tag: tag) else { continue }
return self return evref
}
return nil
} }
} }
// TagsSequence transition helpers extension References {
extension [String] { var first: T? {
func strings() -> [String] { self.first(where: { _ in true })
return self }
var last: T? {
var last: T? = nil
for t in self {
last = t
}
return last
} }
} }
// NdbTagElem transition helpers // NdbTagElem transition helpers
extension String { extension String {
func string() -> String { func string() -> String {
@@ -99,29 +64,117 @@ extension String {
} }
} }
struct ReferencedId: Identifiable, Hashable, Equatable { enum FollowRef: TagKeys, Hashable, TagConvertible, Equatable {
let ref_id: String
let relay_id: String?
let key: String
var id: String { // NOTE: When adding cases make sure to update key and from_tag
return ref_id case pubkey(Pubkey)
} case hashtag(String)
static func q(_ id: String, relay_id: String? = nil) -> ReferencedId { var key: FollowKeys {
return ReferencedId(ref_id: id, relay_id: relay_id, key: "q") switch self {
} case .hashtag: return .t
case .pubkey: return .p
static func e(_ id: String, relay_id: String? = nil) -> ReferencedId { }
return ReferencedId(ref_id: id, relay_id: relay_id, key: "e")
} }
static func p(_ pk: String, relay_id: String? = nil) -> ReferencedId { enum FollowKeys: AsciiCharacter, TagKey, CustomStringConvertible {
return ReferencedId(ref_id: pk, relay_id: relay_id, key: "p") case p, t
var keychar: AsciiCharacter { self.rawValue }
var description: String { self.rawValue.description }
} }
static func t(_ hashtag: String, relay_id: String? = nil) -> ReferencedId { static func from_tag(tag: TagSequence) -> FollowRef? {
return ReferencedId(ref_id: hashtag, relay_id: relay_id, key: "t") 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)
}
} }
} }
+1
View File
@@ -200,6 +200,7 @@ final class RelayConnection: ObservableObject {
} }
return return
} }
print("failed to decode event \(messageString)")
case .data(let messageData): case .data(let messageData):
if let messageString = String(data: messageData, encoding: .utf8) { if let messageString = String(data: messageData, encoding: .utf8) {
receive(message: .string(messageString)) receive(message: .string(messageString))
+8 -2
View File
@@ -7,8 +7,14 @@
import Foundation 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 = Keypair(pubkey: test_pubkey, privkey: test_seckey)
let test_keypair_full = test_keypair.to_full()! let test_keypair_full = test_keypair.to_full()!
+62
View 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
View 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
View 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
View 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)
}
}
+3 -3
View File
@@ -20,11 +20,11 @@ enum Bech32Object {
} }
if decoded.hrp == "npub" { if decoded.hrp == "npub" {
return .npub(hex_encode(decoded.data)) return .npub(Pubkey(decoded.data))
} else if decoded.hrp == "nsec" { } else if decoded.hrp == "nsec" {
return .nsec(hex_encode(decoded.data)) return .nsec(Privkey(decoded.data))
} else if decoded.hrp == "note" { } else if decoded.hrp == "note" {
return .note(hex_encode(decoded.data)) return .note(NoteId(decoded.data))
} else if decoded.hrp == "nscript" { } else if decoded.hrp == "nscript" {
return .nscript(decoded.data.bytes) return .nscript(decoded.data.bytes)
} }
+5 -2
View File
@@ -17,8 +17,11 @@ final class CredentialHandler: NSObject, ASAuthorizationControllerDelegate {
authorizationController.performRequests() authorizationController.performRequests()
} }
func save_credential(pubkey: String, privkey: String) { func save_credential(pubkey: Pubkey, privkey: Privkey) {
SecAddSharedWebCredential("damus.io" as CFString, pubkey as CFString, privkey as CFString, { error in let pub = pubkey.npub
let priv = privkey.nsec
SecAddSharedWebCredential("damus.io" as CFString, pub as CFString, priv as CFString, { error in
if let error { if let error {
print("⚠️ An error occurred while saving credentials: \(error)") print("⚠️ An error occurred while saving credentials: \(error)")
} }
+2 -3
View File
@@ -60,7 +60,6 @@ func parse_display_name(profile: Profile?, pubkey: Pubkey) -> DisplayName {
return .one(abbrev_bech32_pubkey(pubkey: pubkey)) return .one(abbrev_bech32_pubkey(pubkey: pubkey))
} }
func abbrev_bech32_pubkey(pubkey: String) -> String { func abbrev_bech32_pubkey(pubkey: Pubkey) -> String {
let pk = bech32_nopre_pubkey(pubkey) ?? pubkey return abbrev_pubkey(String(pubkey.npub.dropFirst(4)))
return abbrev_pubkey(pk)
} }
+22 -24
View File
@@ -87,12 +87,12 @@ class ZapsDataModel: ObservableObject {
} }
@discardableResult @discardableResult
func remove(reqid: String) -> Bool { func remove(reqid: ZapRequestId) -> Bool {
guard zaps.first(where: { z in z.request.ev.id == reqid }) != nil else { guard zaps.first(where: { z in z.request.id == reqid }) != nil else {
return false 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 return true
} }
} }
@@ -140,10 +140,10 @@ class EventCache {
private var events: [NoteId: NostrEvent] = [:] private var events: [NoteId: NostrEvent] = [:]
private var replies = ReplyMap() private var replies = ReplyMap()
private var cancellable: AnyCancellable? private var cancellable: AnyCancellable?
private var image_metadata: [String: ImageMetadataState] = [:] private var image_metadata: [String: ImageMetadataState] = [:] // lowercased URL key
private var video_meta: [String: VideoPlayerModel] = [:] private var video_meta: [URL: VideoPlayerModel] = [:]
private var event_data: [String: EventData] = [:] private var event_data: [NoteId: EventData] = [:]
//private var thread_latest: [String: Int64] //private var thread_latest: [String: Int64]
init() { init() {
@@ -174,7 +174,7 @@ class EventCache {
@discardableResult @discardableResult
func store_zap(zap: Zapping) -> Bool { 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 { if let ev = zap.event {
insert(ev) insert(ev)
} }
@@ -185,7 +185,7 @@ class EventCache {
switch zap.target { switch zap.target {
case .note(let note_target): case .note(let note_target):
let zaps = get_cache_data(note_target.note_id).zaps_model 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: case .profile:
// these aren't stored anywhere yet // these aren't stored anywhere yet
break break
@@ -193,7 +193,7 @@ class EventCache {
} }
func lookup_zaps(target: ZapTarget) -> [Zapping] { 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) { func store_img_metadata(url: URL, meta: ImageMetadataState) {
@@ -214,31 +214,29 @@ class EventCache {
} }
func store_video_player_model(url: URL, meta: VideoPlayerModel) { func store_video_player_model(url: URL, meta: VideoPlayerModel) {
video_meta[url.absoluteString] = meta video_meta[url] = meta
} }
@MainActor @MainActor
func get_video_player_model(url: URL) -> VideoPlayerModel { func get_video_player_model(url: URL) -> VideoPlayerModel {
if let model = video_meta[url.absoluteString] { if let model = video_meta[url] {
return model return model
} }
let model = VideoPlayerModel() let model = VideoPlayerModel()
video_meta[url.absoluteString] = model video_meta[url] = model
return model return model
} }
func parent_events(event: NostrEvent) -> [NostrEvent] { func parent_events(event: NostrEvent, privkey: Privkey?) -> [NostrEvent] {
var parents: [NostrEvent] = [] var parents: [NostrEvent] = []
var ev = event var ev = event
while true { while true {
guard let direct_reply = ev.direct_replies(nil).last else { guard let direct_reply = ev.direct_replies(privkey).last,
break let next_ev = lookup(direct_reply), next_ev != ev
} else {
guard let next_ev = lookup(direct_reply.ref_id), next_ev != ev else {
break break
} }
@@ -249,9 +247,9 @@ class EventCache {
return parents.reversed() return parents.reversed()
} }
func add_replies(ev: NostrEvent) { func add_replies(ev: NostrEvent, privkey: Privkey?) {
for reply in ev.direct_replies(nil) { for reply in ev.direct_replies(privkey) {
replies.add(id: reply.ref_id, reply_id: ev.id) 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. // 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) 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 { 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() let note_language = plan.data.translations_model.note_language ?? plan.event.note_language(our_keypair.privkey) ?? current_language()
var translations: TranslateStatus? = nil var translations: TranslateStatus? = nil
// We have to recheck should_translate here now that we have note_language // 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 if plan.load_translations && should_translate(event: plan.event, our_keypair: our_keypair, settings: settings, note_lang: note_language) && settings.auto_translate
+1 -1
View File
@@ -9,7 +9,7 @@ import Foundation
/// Used for holding back events until they're ready to be displayed /// Used for holding back events until they're ready to be displayed
class EventHolder: ObservableObject, ScrollQueue { class EventHolder: ObservableObject, ScrollQueue {
private var has_event = Set<String>() private var has_event = Set<NoteId>()
@Published var events: [NostrEvent] @Published var events: [NostrEvent]
var incoming: [NostrEvent] var incoming: [NostrEvent]
var should_queue = false var should_queue = false
+51 -43
View File
@@ -9,7 +9,14 @@ import Foundation
import secp256k1 import secp256k1
let PUBKEY_HRP = "npub" 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 { struct FullKeypair: Equatable {
let pubkey: Pubkey let pubkey: Pubkey
@@ -41,8 +48,8 @@ struct Keypair {
init(pubkey: Pubkey, privkey: Privkey?) { init(pubkey: Pubkey, privkey: Privkey?) {
self.pubkey = pubkey self.pubkey = pubkey
self.privkey = privkey self.privkey = privkey
self.pubkey_bech32 = bech32_pubkey(pubkey) ?? pubkey self.pubkey_bech32 = pubkey.npub
self.privkey_bech32 = privkey.flatMap { bech32_privkey($0) } self.privkey_bech32 = privkey?.nsec
} }
} }
@@ -52,60 +59,52 @@ enum Bech32Key {
} }
func decode_bech32_key(_ key: String) -> 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 return nil
} }
let hexed = hex_encode(decoded.data)
if decoded.hrp == "npub" { if decoded.hrp == "npub" {
return .pub(hexed) return .pub(Pubkey(decoded.data))
} else if decoded.hrp == "nsec" { } else if decoded.hrp == "nsec" {
return .sec(hexed) return .sec(Privkey(decoded.data))
} }
return nil return nil
} }
func bech32_privkey(_ privkey: String) -> String? { func bech32_privkey(_ privkey: Privkey) -> String {
guard let bytes = hex_decode(privkey) else { return bech32_encode(hrp: "nsec", privkey.bytes)
return nil
}
return bech32_encode(hrp: "nsec", bytes)
} }
func bech32_pubkey(_ pubkey: String) -> String? { func bech32_pubkey(_ pubkey: Pubkey) -> String {
guard let bytes = hex_decode(pubkey) else { return bech32_encode(hrp: "npub", pubkey.bytes)
return nil
}
return bech32_encode(hrp: "npub", bytes)
} }
func bech32_pubkey_decode(_ pubkey: String) -> String? { func bech32_pubkey_decode(_ pubkey: String) -> Pubkey? {
guard let decoded = try? bech32_decode(pubkey), decoded.hrp == "npub" else { guard let decoded = try? bech32_decode(pubkey),
decoded.hrp == "npub",
decoded.data.count == 32
else {
return nil return nil
} }
return hex_encode(decoded.data) return Pubkey(decoded.data)
} }
func bech32_nopre_pubkey(_ pubkey: String) -> String? { func bech32_nopre_pubkey(_ pubkey: Pubkey) -> String {
guard let bytes = hex_decode(pubkey) else { return bech32_encode(hrp: "", pubkey.bytes)
return nil
}
return bech32_encode(hrp: "", bytes)
} }
func bech32_note_id(_ evid: String) -> String? { func bech32_note_id(_ evid: NoteId) -> String {
guard let bytes = hex_decode(evid) else { return bech32_encode(hrp: "note", evid.bytes)
return nil
}
return bech32_encode(hrp: "note", bytes)
} }
func generate_new_keypair() -> FullKeypair { func generate_new_keypair() -> FullKeypair {
let key = try! secp256k1.Signing.PrivateKey() let key = try! secp256k1.Signing.PrivateKey()
let privkey = hex_encode(key.rawRepresentation) let privkey = Privkey(key.rawRepresentation)
let pubkey = hex_encode(Data(key.publicKey.xonly.bytes)) let pubkey = Pubkey(Data(key.publicKey.xonly.bytes))
return FullKeypair(pubkey: pubkey, privkey: privkey) 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 { guard let key = try? secp256k1.Signing.PrivateKey(rawRepresentation: sec) else {
return nil return nil
} }
return hex_encode(Data(key.publicKey.xonly.bytes)) return Pubkey(Data(key.publicKey.xonly.bytes))
} }
func privkey_to_pubkey(privkey: String) -> String? { func privkey_to_pubkey(privkey: Privkey) -> Pubkey? {
guard let sec = hex_decode(privkey) else { return nil } return privkey_to_pubkey_raw(sec: privkey.bytes)
return privkey_to_pubkey_raw(sec: sec)
} }
func save_pubkey(pubkey: Pubkey) { func save_pubkey(pubkey: Pubkey) {
@@ -155,11 +153,18 @@ func clear_keypair() throws {
func get_saved_keypair() -> Keypair? { func get_saved_keypair() -> Keypair? {
do { do {
try removePrivateKeyFromUserDefaults() try removePrivateKeyFromUserDefaults()
return get_saved_pubkey().flatMap { pubkey in guard let pubkey = get_saved_pubkey(),
let privkey = get_saved_privkey() let pk = hex_decode(pubkey)
return Keypair(pubkey: pubkey, privkey: privkey) 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 { } catch {
return nil return nil
} }
@@ -189,7 +194,10 @@ func contentContainsPrivateKey(_ content: String) -> Bool {
} }
fileprivate func removePrivateKeyFromUserDefaults() throws { fileprivate func removePrivateKeyFromUserDefaults() throws {
guard let privKey = UserDefaults.standard.string(forKey: "privkey") else { return } guard let privkey_str = UserDefaults.standard.string(forKey: "privkey"),
try save_privkey(privkey: privKey) let privkey = hex_decode_privkey(privkey_str)
else { return }
try save_privkey(privkey: privkey)
UserDefaults.standard.removeObject(forKey: "privkey") UserDefaults.standard.removeObject(forKey: "privkey")
} }
+34 -36
View File
@@ -7,64 +7,62 @@
import Foundation 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") 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? { 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, tag_type: "p") 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, if let prev = mprev,
prev.pubkey == keypair.pubkey, prev.pubkey == keypair.pubkey,
matches_list_name(tags: prev.tags, name: list_name) 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) 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? { func remove_from_list_event(keypair: FullKeypair, prev: NostrEvent, to_remove: RefId) -> NostrEvent? {
var exists = false var removed = false
for tag in prev.tags {
if tag.count >= 2 && tag[0] == tag_type && tag[1] == to_remove { let tags = prev.tags.reduce(into: [[String]](), { acc, tag in
exists = true if let ref_id = RefId.from_tag(tag: tag), ref_id == to_remove {
removed = true
return
} }
} acc.append(tag.strings())
})
// make sure we actually have the pubkey to remove
guard exists else { guard removed else {
return nil 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) 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 { for tag in tags {
if tag.count >= 2 && tag[0] == "d" { if tag.count >= 2 && tag[0].matches_char("d") {
return tag[1] == name return tag[1].matches_str(name)
} }
} }
+6 -6
View File
@@ -9,21 +9,21 @@ import Foundation
struct LossyLocalNotification { struct LossyLocalNotification {
let type: LocalNotificationType let type: LocalNotificationType
let event_id: String let mention: MentionRef
func to_user_info() -> [AnyHashable: Any] { func to_user_info() -> [AnyHashable: Any] {
return [ return [
"type": self.type.rawValue, "type": self.type.rawValue,
"evid": self.event_id "id": self.mention.bech32
] ]
} }
static func from_user_info(user_info: [AnyHashable: Any]) -> LossyLocalNotification { 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 typestr = user_info["type"] as! String
let type = LocalNotificationType(rawValue: typestr)! 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 let content: String
func to_lossy() -> LossyLocalNotification { func to_lossy() -> LossyLocalNotification {
return LossyLocalNotification(type: self.type, event_id: self.target.id) return LossyLocalNotification(type: self.type, mention: .note(self.target.id))
} }
} }
+6 -6
View File
@@ -28,7 +28,7 @@ class ReplyCounter {
return replies[evid] ?? 0 return replies[evid] ?? 0
} }
func count_replies(_ event: NostrEvent) { func count_replies(_ event: NostrEvent, privkey: Privkey?) {
guard event.is_textlike else { guard event.is_textlike else {
return return
} }
@@ -39,15 +39,15 @@ class ReplyCounter {
counted.insert(event.id) counted.insert(event.id)
for reply in event.direct_replies(nil) { for reply in event.direct_replies(privkey) {
if event.pubkey == our_pubkey { if event.pubkey == our_pubkey {
self.our_replies[reply.ref_id] = event self.our_replies[reply] = event
} }
if replies[reply.ref_id] != nil { if replies[reply] != nil {
replies[reply.ref_id] = replies[reply.ref_id]! + 1 replies[reply] = replies[reply]! + 1
} else { } else {
replies[reply.ref_id] = 1 replies[reply] = 1
} }
} }
} }
+14 -10
View File
@@ -38,21 +38,25 @@ struct WalletConnectURL: Equatable {
init?(str: String) { init?(str: String) {
guard let url = URL(string: str), guard let url = URL(string: str),
url.scheme == "nostrwalletconnect" || url.scheme == "nostr+walletconnect", 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 components = URLComponents(url: url, resolvingAgainstBaseURL: true),
let items = components.queryItems, let items = components.queryItems,
let relay = items.first(where: { qi in qi.name == "relay" })?.value, let relay = items.first(where: { qi in qi.name == "relay" })?.value,
let relay_url = RelayURL(relay), let relay_url = RelayURL(relay),
let secret = items.first(where: { qi in qi.name == "secret" })?.value, let secret = items.first(where: { qi in qi.name == "secret" })?.value,
secret.utf8.count == 64, secret.utf8.count == 64,
let our_pk = privkey_to_pubkey(privkey: secret) let decoded = hex_decode(secret)
else { else {
return nil 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 lud16 = items.first(where: { qi in qi.name == "lud16" })?.value
let keypair = FullKeypair(pubkey: our_pk, privkey: secret) let keypair = FullKeypair(pubkey: our_pk, privkey: privkey)
self = WalletConnectURL(pubkey: pk, relay: relay_url, keypair: keypair, lud16: lud16) self = WalletConnectURL(pubkey: pubkey, relay: relay_url, keypair: keypair, lud16: lud16)
} }
init(pubkey: Pubkey, relay: RelayURL, keypair: FullKeypair, lud16: String?) { init(pubkey: Pubkey, relay: RelayURL, keypair: FullKeypair, lud16: String?) {
@@ -90,11 +94,11 @@ struct FullWalletResponse {
let response: WalletResponse let response: WalletResponse
init?(from: NostrEvent, nwc: WalletConnectURL) async { 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 return nil
} }
self.req_id = req_id.ref_id.string() self.req_id = note_id
let ares = Task { let ares = Task {
guard let json = decrypt_dm(nwc.keypair.privkey, pubkey: nwc.pubkey, content: from.content, encoding: .base64), 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? { 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) let created_at = UInt32(Date().timeIntervalSince1970)
guard let content = encode_json(req) else { guard let content = encode_json(req) else {
return nil return nil
@@ -213,7 +217,7 @@ func nwc_success(state: DamusState, resp: FullWalletResponse) {
if nwc_state.update_state(state: .confirmed) { if nwc_state.update_state(state: .confirmed) {
// notify the zaps model of an update so it can mark them as paid // 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") print("NWC success confirmed")
} }
+37 -24
View File
@@ -28,13 +28,22 @@ enum ZapTarget: Equatable, Hashable {
return note_target.author return note_target.author
} }
} }
var id: String { var note_id: NoteId? {
switch self { switch self {
case .note(let note_target): case .profile:
return note_target.note_id return nil
case .profile(let pk): case .note(let noteZapTarget):
return pk 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 { struct ZapRequest {
let ev: NostrEvent let ev: NostrEvent
let marked_hidden: Bool let marked_hidden: Bool
var id: ZapRequestId {
ZapRequestId(from_zap_request: self)
}
var is_in_thread: Bool { var is_in_thread: Bool {
return !self.ev.content.isEmpty && !marked_hidden return !self.ev.content.isEmpty && !marked_hidden
} }
@@ -134,9 +147,13 @@ class PendingZap {
} }
} }
struct ZapRequestId: Equatable { struct ZapRequestId: Equatable, Hashable {
let reqid: String let reqid: NoteId
init(from_zap_request: ZapRequest) {
self.reqid = from_zap_request.ev.id
}
init(from_zap: Zapping) { init(from_zap: Zapping) {
self.reqid = from_zap.request.ev.id 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? { 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 return nil
} }
if let etag = event_tag(ev, name: "e") { if let etag = ev.referenced_ids.first {
return ZapTarget.note(id: etag, author: ptag) return ZapTarget.note(id: etag, author: ptag)
} }
@@ -376,7 +393,7 @@ func decode_bolt11(_ s: String) -> Invoice? {
let block = bs.blocks[0] 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) blocks_free(&bs)
return nil 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? { 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) else { 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 return nil
} }
guard let allows = endpoint.allowsNostr, allows else { return pk
return nil
}
guard let key = endpoint.nostrPubkey, key.count == 64 else {
return nil
}
return endpoint.nostrPubkey
} }
func decode_lnurl(_ lnurl: String) -> URL? { func decode_lnurl(_ lnurl: String) -> URL? {
+30 -24
View File
@@ -34,14 +34,17 @@ class Zaps {
res = zap res = zap
our_zaps[kv.key] = ours.filter { z in z.request.ev.id != reqid } our_zaps[kv.key] = ours.filter { z in z.request.ev.id != reqid }
if let count = event_counts[zap.target.id] { // counts for note zaps
event_counts[zap.target.id] = count - 1 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 // we found the request id, we can stop looking
break break
} }
@@ -55,6 +58,7 @@ class Zaps {
return return
} }
self.zaps[zap.request.ev.id] = zap self.zaps[zap.request.ev.id] = zap
if let zap_id = zap.event?.id { if let zap_id = zap.event?.id {
self.zaps[zap_id] = zap self.zaps[zap_id] = zap
} }
@@ -62,11 +66,12 @@ class Zaps {
// record our zaps for an event // record our zaps for an event
if zap.request.ev.pubkey == our_pubkey { if zap.request.ev.pubkey == our_pubkey {
switch zap.target { switch zap.target {
case .note(let note_target): case .note(let note_zap):
if our_zaps[note_target.note_id] == nil { let note_id = note_zap.note_id
our_zaps[note_target.note_id] = [zap] if our_zaps[note_id] == nil {
our_zaps[note_id] = [zap]
} else { } 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: case .profile:
break break
@@ -78,19 +83,20 @@ class Zaps {
return return
} }
let id = zap.target.id if let note_id = zap.target.note_id {
if event_counts[id] == nil { if event_counts[note_id] == nil {
event_counts[id] = 0 event_counts[note_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
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 { guard let zap = zapcache.remove_zap(reqid: reqid.reqid) else {
return 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)
} }
+1 -5
View File
@@ -106,11 +106,7 @@ struct EventActionBar: View {
} }
} }
.sheet(isPresented: $show_share_sheet, onDismiss: { self.show_share_sheet = false }) { .sheet(isPresented: $show_share_sheet, onDismiss: { self.show_share_sheet = false }) {
if let note_id = bech32_note_id(event.id) { ShareSheet(activityItems: [URL(string: "https://damus.io/" + event.id.bech32)!])
if let url = URL(string: "https://damus.io/" + note_id) {
ShareSheet(activityItems: [url])
}
}
} }
.sheet(isPresented: $show_repost_action, onDismiss: { self.show_repost_action = false }) { .sheet(isPresented: $show_repost_action, onDismiss: { self.show_repost_action = false }) {
+1 -1
View File
@@ -38,7 +38,7 @@ struct ShareAction: View {
ShareActionButton(img: "link", text: NSLocalizedString("Copy Link", comment: "Button to copy link to note")) { ShareActionButton(img: "link", text: NSLocalizedString("Copy Link", comment: "Button to copy link to note")) {
dismiss() 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" let bookmarkImg = isBookmarked ? "bookmark.fill" : "bookmark"
@@ -36,18 +36,18 @@ struct GradientFollowButton: View {
) )
} }
.onReceive(handle_notify(.followed)) { ref in .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 self.follow_state = .follows
} }
.onReceive(handle_notify(.unfollowed)) { ref in .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 self.follow_state = .unfollows
} }
} }
} }
struct GradientFollowButtonPreviews: View { struct GradientFollowButtonPreviews: View {
let target: FollowTarget = .pubkey("") let target: FollowTarget = .pubkey(.empty)
var body: some View { var body: some View {
VStack { VStack {
Text(verbatim: "Unfollows") Text(verbatim: "Unfollows")
+1 -2
View File
@@ -135,8 +135,7 @@ struct CreateAccountView_Previews: PreviewProvider {
} }
func KeyText(_ pubkey: Binding<Pubkey>) -> some View { func KeyText(_ pubkey: Binding<Pubkey>) -> some View {
let decoded = hex_decode(pubkey.wrappedValue)! let bechkey = bech32_encode(hrp: PUBKEY_HRP, pubkey.wrappedValue.bytes)
let bechkey = bech32_encode(hrp: PUBKEY_HRP, decoded)
return Text(bechkey) return Text(bechkey)
.textSelection(.enabled) .textSelection(.enabled)
.multilineTextAlignment(.center) .multilineTextAlignment(.center)
+1 -1
View File
@@ -18,7 +18,7 @@ struct DMView: View {
var Mention: some View { var Mention: some View {
Group { Group {
if let mention = first_eref_mention(ev: event, privkey: damus_state.keypair.privkey) { 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 { } else {
EmptyView() EmptyView()
} }
+2 -2
View File
@@ -72,10 +72,10 @@ func should_show_images(settings: UserSettingsStore, contacts: Contacts, ev: Nos
} }
extension View { extension View {
func pubkey_context_menu(bech32_pubkey: Pubkey) -> some View { func pubkey_context_menu(pubkey: Pubkey) -> some View {
return self.contextMenu { return self.contextMenu {
Button { Button {
UIPasteboard.general.string = bech32_pubkey UIPasteboard.general.string = pubkey.npub
} label: { } label: {
Label(NSLocalizedString("Copy Account ID", comment: "Context menu option for copying the ID of the account that created the note."), image: "copy2") Label(NSLocalizedString("Copy Account ID", comment: "Context menu option for copying the ID of the account that created the note."), image: "copy2")
} }
+1 -1
View File
@@ -22,7 +22,7 @@ struct EventTop: View {
func ProfileName(is_anon: Bool) -> some View { func ProfileName(is_anon: Bool) -> some View {
let profile = state.profiles.lookup(id: self.pubkey) 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) 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.") return NSLocalizedString("Replying to self", bundle: bundle, comment: "Label to indicate that the user is replying to themself.")
} }
let names: [String] = pubkeys.map { let names: [String] = pubkeys.map { pk in
let prof = profiles.lookup(id: $0) let prof = profiles.lookup(id: pk)
return Profile.displayName(profile: prof, pubkey: $0).username.truncate(maxLength: 50) return Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50)
} }
let uniqueNames = NSOrderedSet(array: names).array as! [String] let uniqueNames = NSOrderedSet(array: names).array as! [String]
+2 -4
View File
@@ -27,9 +27,7 @@ struct EventMenuContext: View {
var body: some View { var body: some View {
HStack { HStack {
Menu { Menu {
MenuItems(event: event, keypair: keypair, target_pubkey: target_pubkey, bookmarks: bookmarks, muted_threads: muted_threads, settings: settings) MenuItems(event: event, keypair: keypair, target_pubkey: target_pubkey, bookmarks: bookmarks, muted_threads: muted_threads, settings: settings)
} label: { } label: {
Label("", systemImage: "ellipsis") Label("", systemImage: "ellipsis")
.foregroundColor(Color.gray) .foregroundColor(Color.gray)
@@ -77,13 +75,13 @@ struct MenuItems: View {
} }
Button { Button {
UIPasteboard.general.string = bech32_pubkey(target_pubkey) UIPasteboard.general.string = target_pubkey.npub
} label: { } label: {
Label(NSLocalizedString("Copy user public key", comment: "Context menu option for copying the ID of the user who created the note."), image: "user") Label(NSLocalizedString("Copy user public key", comment: "Context menu option for copying the ID of the user who created the note."), image: "user")
} }
Button { Button {
UIPasteboard.general.string = bech32_note_id(event.id) ?? event.id UIPasteboard.general.string = event.id.bech32
} label: { } label: {
Label(NSLocalizedString("Copy note ID", comment: "Context menu option for copying the ID of the note."), image: "note-book") Label(NSLocalizedString("Copy note ID", comment: "Context menu option for copying the ID of the note."), image: "note-book")
} }
+3 -3
View File
@@ -34,7 +34,7 @@ struct EventShell<Content: View>: View {
!options.contains(.no_action_bar) !options.contains(.no_action_bar)
} }
func get_mention() -> Mention? { func get_mention() -> Mention<NoteId>? {
if self.options.contains(.nested) || self.options.contains(.no_mentions) { if self.options.contains(.nested) || self.options.contains(.no_mentions) {
return nil return nil
} }
@@ -42,8 +42,8 @@ struct EventShell<Content: View>: View {
return first_eref_mention(ev: event, privkey: state.keypair.privkey) return first_eref_mention(ev: event, privkey: state.keypair.privkey)
} }
func Mention(_ mention: Mention) -> some View { func Mention(_ mention: Mention<NoteId>) -> some View {
return BuilderEventView(damus: state, event_id: mention.ref.id) return BuilderEventView(damus: state, event_id: mention.ref)
} }
var ActionBar: some View { var ActionBar: some View {
@@ -20,12 +20,12 @@ struct LongformEvent {
for tag in ev.tags { for tag in ev.tags {
guard tag.count >= 2 else { continue } guard tag.count >= 2 else { continue }
switch tag[0] { switch tag[0].string() {
case "title": longform.title = tag[1] case "title": longform.title = tag[1].string()
case "image": longform.image = URL(string: tag[1]) case "image": longform.image = URL(string: tag[1].string())
case "summary": longform.summary = tag[1] case "summary": longform.summary = tag[1].string()
case "published_at": 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: default:
break break
} }
+1 -1
View File
@@ -50,7 +50,7 @@ struct SelectedEventView: View {
EventBody(damus_state: damus, event: event, size: size, options: [.wide]) EventBody(damus_state: damus, event: event, size: size, options: [.wide])
if let mention = first_eref_mention(ev: event, privkey: damus.keypair.privkey) { 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) .padding(.horizontal)
} }
+1 -1
View File
@@ -59,7 +59,7 @@ struct TextEvent: View {
func event_has_tag(ev: NostrEvent, tag: String) -> Bool { func event_has_tag(ev: NostrEvent, tag: String) -> Bool {
for t in ev.tags { for t in ev.tags {
if t.count >= 1 && t[0] == tag { if t.count >= 1 && t[0].matches_str(tag) {
return true return true
} }
} }
+9 -11
View File
@@ -31,18 +31,16 @@ struct FollowButtonView: View {
.stroke(follow_state == .unfollows ? .clear : borderColor(), lineWidth: 1) .stroke(follow_state == .unfollows ? .clear : borderColor(), lineWidth: 1)
} }
} }
.onReceive(handle_notify(.followed)) { pk in .onReceive(handle_notify(.followed)) { follow in
guard pk.key == "p", target.pubkey == pk.ref_id else { guard case .pubkey(let pk) = follow,
return pk == target.pubkey else { return }
}
self.follow_state = .follows self.follow_state = .follows
} }
.onReceive(handle_notify(.unfollowed)) { pk in .onReceive(handle_notify(.unfollowed)) { unfollow in
guard pk.key == "p", target.pubkey == pk.ref_id else { guard case .pubkey(let pk) = unfollow,
return pk == target.pubkey else { return }
}
self.follow_state = .unfollows self.follow_state = .unfollows
} }
} }
@@ -65,7 +63,7 @@ struct FollowButtonView: View {
} }
struct FollowButtonPreviews: View { struct FollowButtonPreviews: View {
let target: FollowTarget = .pubkey("") let target: FollowTarget = .pubkey(test_pubkey)
var body: some View { var body: some View {
VStack { VStack {
Text(verbatim: "Unfollows") Text(verbatim: "Unfollows")
+23 -32
View File
@@ -146,10 +146,8 @@ func parse_key(_ thekey: String) -> ParsedKey? {
if let bech_key = decode_bech32_key(key) { if let bech_key = decode_bech32_key(key) {
switch bech_key { switch bech_key {
case .pub(let pk): case .pub(let pk): return .pub(pk)
return .pub(pk) case .sec(let sec): return .priv(sec)
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) save_pubkey(pubkey: nip05.pubkey)
case .hex(let hexstr): case .hex(let hexstr):
if is_pubkey { if is_pubkey, let pubkey = hex_decode_pubkey(hexstr) {
try clear_saved_privkey() try clear_saved_privkey()
save_pubkey(pubkey: hexstr)
} else { save_pubkey(pubkey: pubkey)
try handle_privkey(hexstr) } 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 { guard let pk = privkey_to_pubkey(privkey: privkey) else {
throw LoginError.invalid_key throw LoginError.invalid_key
} }
if let pub = bech32_pubkey(pk), let priv = bech32_privkey(privkey) { CredentialHandler().save_credential(pubkey: pk, privkey: privkey)
CredentialHandler().save_credential(pubkey: pub, privkey: priv)
}
save_pubkey(pubkey: pk) save_pubkey(pubkey: pk)
} }
@@ -232,7 +229,7 @@ struct NIP05Result: Decodable {
struct NIP05User { struct NIP05User {
let pubkey: Pubkey let pubkey: Pubkey
let relays: [String] //let relays: [String]
} }
func get_nip05_pubkey(id: String) async -> NIP05User? { 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 user = parts[0]
let host = parts[1] let host = parts[1]
guard let url = URL(string: "https://\(host)/.well-known/nostr.json?name=\(user)") else { guard let url = URL(string: "https://\(host)/.well-known/nostr.json?name=\(user)"),
return nil let (data, _) = try? await URLSession.shared.data(for: URLRequest(url: url)),
} let json: NIP05Result = decode_data(data),
let pubkey_hex = json.names[user],
guard let (data, _) = try? await URLSession.shared.data(for: URLRequest(url: url)) else { let pubkey = hex_decode_pubkey(pubkey_hex)
return nil else {
}
guard let json: NIP05Result = decode_data(data) else {
return nil
}
guard let pubkey = json.names[user] else {
return nil return nil
} }
/*
var relays: [String] = [] 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 { struct KeyInput: View {
+10 -20
View File
@@ -13,15 +13,12 @@ struct MutelistView: View {
func RemoveAction(pubkey: Pubkey) -> some View { func RemoveAction(pubkey: Pubkey) -> some View {
Button { Button {
guard let mutelist = damus_state.contacts.mutelist else { guard let mutelist = damus_state.contacts.mutelist,
return let keypair = damus_state.keypair.to_full(),
} let new_ev = remove_from_mutelist(keypair: keypair,
prev: mutelist,
guard let keypair = damus_state.keypair.to_full() else { to_remove: .pubkey(pubkey))
return else {
}
guard let new_ev = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: pubkey) else {
return return
} }
@@ -48,22 +45,15 @@ struct MutelistView: View {
} }
.navigationTitle(NSLocalizedString("Muted Users", comment: "Navigation title of view to see list of muted users.")) .navigationTitle(NSLocalizedString("Muted Users", comment: "Navigation title of view to see list of muted users."))
.onAppear { .onAppear {
users = get_mutelist_users(damus_state.contacts.mutelist) users = get_mutelist_users(damus_state.contacts.mutelist)
} }
} }
} }
func get_mutelist_users(_ mlist: NostrEvent?) -> [String] { func get_mutelist_users(_ mutelist: NostrEvent?) -> Array<Pubkey> {
guard let mutelist = mlist else { guard let mutelist else { return [] }
return [] return Array(mutelist.referenced_pubkeys)
}
return mutelist.tags.reduce(into: Array<String>()) { pks, tag in
if tag.count >= 2 && tag[0] == "p" {
pks.append(tag[1])
}
}
} }
struct MutelistView_Previews: PreviewProvider { struct MutelistView_Previews: PreviewProvider {
+14 -12
View File
@@ -230,7 +230,7 @@ struct NoteContentView: View {
for block in blocks.blocks { for block in blocks.blocks {
switch block { switch block {
case .mention(let m): 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) load(force_artifacts: true)
return return
} }
@@ -265,21 +265,21 @@ func url_str(_ url: URL) -> CompatibleText {
return CompatibleText(attributed: attributedString) return CompatibleText(attributed: attributedString)
} }
func mention_str(_ m: Mention, profiles: Profiles) -> CompatibleText { func mention_str(_ m: Mention<MentionRef>, profiles: Profiles) -> CompatibleText {
switch m.type { switch m.ref {
case .pubkey: case .pubkey(let pk):
let pk = m.ref.ref_id let npub = bech32_pubkey(pk)
let profile = profiles.lookup(id: pk) let profile = profiles.lookup(id: pk)
let disp = Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50) let disp = Profile.displayName(profile: profile, pubkey: pk).username.truncate(maxLength: 50)
var attributedString = AttributedString(stringLiteral: "@\(disp)") 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 attributedString.foregroundColor = DamusColors.purple
return CompatibleText(attributed: attributedString) return CompatibleText(attributed: attributedString)
case .event: case .note(let note_id):
let bevid = bech32_note_id(m.ref.ref_id) ?? m.ref.ref_id let bevid = bech32_note_id(note_id)
var attributedString = AttributedString(stringLiteral: "@\(abbrev_pubkey(bevid))") 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 attributedString.foregroundColor = DamusColors.purple
return CompatibleText(attributed: attributedString) 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 { func render_note_content(ev: NostrEvent, profiles: Profiles, privkey: Privkey?) -> NoteArtifacts {
let blocks = ev.blocks(privkey) let blocks = ev.blocks(privkey)
if ev.known_kind == .longform { if ev.known_kind == .longform {
return .longform(LongformContent(ev.content)) 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 let next = blocks[safe: ind+1] {
if case .url(let u) = next, classify_url(u).is_media != nil { if case .url(let u) = next, classify_url(u).is_media != nil {
trimmed = trim_suffix(trimmed) 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) trimmed = trim_suffix(trimmed)
} }
} }
@@ -450,7 +452,7 @@ func render_blocks(blocks bs: Blocks, profiles: Profiles) -> NoteArtifactsSepara
switch block { switch block {
case .mention(let m): case .mention(let m):
if m.type == .event && one_note_ref { if case .note = m.ref, one_note_ref {
return str return str
} }
return str + mention_str(m, profiles: profiles) return str + mention_str(m, profiles: profiles)
+11 -18
View File
@@ -10,10 +10,10 @@ import SwiftUI
struct ParticipantsView: View { struct ParticipantsView: View {
let damus_state: DamusState let damus_state: DamusState
let original_pubkeys: [Pubkey]
@Binding var references: [ReferencedId]
@Binding var originalReferences: [ReferencedId] @Binding var filtered_pubkeys: Set<Pubkey>
var body: some View { var body: some View {
VStack { VStack {
Text("Replying to", comment: "Text indicating that the view is used for editing which participants are replied to in a note.") 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 { Button {
// Remove all "p" refs, keep "e" refs // Remove all "p" refs, keep "e" refs
references = originalReferences.eRefs filtered_pubkeys = Set(original_pubkeys)
} label: { } label: {
Text("Remove all", comment: "Button label to remove all participants from a note reply.") Text("Remove all", comment: "Button label to remove all participants from a note reply.")
} }
@@ -34,7 +34,7 @@ struct ParticipantsView: View {
.clipShape(Capsule()) .clipShape(Capsule())
Button { Button {
references = originalReferences filtered_pubkeys = []
} label: { } label: {
Text("Add all", comment: "Button label to re-add all original participants as profiles to reply to in a note") 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 { VStack {
ScrollView { ScrollView {
ForEach(originalReferences.pRefs) { participant in ForEach(original_pubkeys) { pubkey in
let pubkey = participant.id
HStack { HStack {
UserView(damus_state: damus_state, pubkey: pubkey) UserView(damus_state: damus_state, pubkey: pubkey)
Image("check-circle.fill") Image("check-circle.fill")
.font(.system(size: 30)) .font(.system(size: 30))
.foregroundColor(references.contains(participant) ? DamusColors.purple : .gray) .foregroundColor(filtered_pubkeys.contains(pubkey) ? .gray : DamusColors.purple)
} }
.onTapGesture { .onTapGesture {
if references.contains(participant) { if filtered_pubkeys.contains(pubkey) {
references = references.filter { filtered_pubkeys.remove(pubkey)
$0 != participant
}
} else { } else {
if references.contains(participant) { filtered_pubkeys.insert(pubkey)
// Don't add it twice
} else {
references.append(participant)
}
} }
} }
} }
+26 -19
View File
@@ -50,8 +50,8 @@ struct PostView: View {
@State var error: String? = nil @State var error: String? = nil
@State var uploadedMedias: [UploadedMedia] = [] @State var uploadedMedias: [UploadedMedia] = []
@State var image_upload_confirm: Bool = false @State var image_upload_confirm: Bool = false
@State var originalReferences: [ReferencedId] = [] @State var references: [RefId] = []
@State var references: [ReferencedId] = [] @State var filtered_pubkeys: Set<Pubkey> = []
@State var focusWordAttributes: (String?, NSRange?) = (nil, nil) @State var focusWordAttributes: (String?, NSRange?) = (nil, nil)
@State var newCursorIndex: Int? @State var newCursorIndex: Int?
@State var postTextViewCanScroll: Bool = true @State var postTextViewCanScroll: Bool = true
@@ -76,7 +76,13 @@ struct PostView: View {
} }
func send_post() { 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))) notify(.post(.post(new_post)))
@@ -155,8 +161,7 @@ struct PostView: View {
} }
let profile = damus_state.profiles.lookup(id: pubkey) let profile = damus_state.profiles.lookup(id: pubkey)
let bech32_pubkey = bech32_pubkey(pubkey) ?? "" return user_tag_attr_string(profile: profile, pubkey: pubkey)
return user_tag_attr_string(profile: profile, pubkey: bech32_pubkey)
} }
func clear_draft() { func clear_draft() {
@@ -310,7 +315,17 @@ struct PostView: View {
self.post = initialString() self.post = initialString()
self.tagModel.diff = post.string.count 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 { var body: some View {
GeometryReader { (deviceSize: GeometryProxy) in GeometryReader { (deviceSize: GeometryProxy) in
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {
@@ -321,7 +336,7 @@ struct PostView: View {
ScrollViewReader { scroller in ScrollViewReader { scroller in
ScrollView { ScrollView {
if case .replying_to(let replying_to) = self.action { 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) Editor(deviceSize: deviceSize)
@@ -385,10 +400,8 @@ struct PostView: View {
switch action { switch action {
case .replying_to(let replying_to): case .replying_to(let replying_to):
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to) references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
originalReferences = references
case .quoting(let quoting): case .quoting(let quoting):
references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting) references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting)
originalReferences = references
case .posting(let target): case .posting(let target):
guard !loaded_draft else { break } 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 { func build_post(post: NSMutableAttributedString, action: PostAction, uploadedMedias: [UploadedMedia], references: [RefId]) -> NostrPost {
var kind: NostrKind = .text
if case .replying_to(let ev) = action, ev.known_kind == .chat {
kind = .chat
}
post.enumerateAttributes(in: NSRange(location: 0, length: post.length), options: []) { attributes, range, stop in post.enumerateAttributes(in: NSRange(location: 0, length: post.length), options: []) { attributes, range, stop in
if let link = attributes[.link] as? String { if let link = attributes[.link] as? String {
let normalized_link: String let normalized_link: String
@@ -586,9 +593,9 @@ func build_post(post: NSMutableAttributedString, action: PostAction, uploadedMed
content.append(" " + imagesString + " ") content.append(" " + imagesString + " ")
} }
if case .quoting(let ev) = action, let id = bech32_note_id(ev.id) { if case .quoting(let ev) = action {
content.append(" nostr:" + id) 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)
} }
+2 -5
View File
@@ -31,10 +31,7 @@ struct UserSearch: View {
} }
func on_user_tapped(user: SearchedUser) { func on_user_tapped(user: SearchedUser) {
guard let pk = bech32_pubkey(user.pubkey) else { let pk = user.pubkey
return
}
let user_tag = user_tag_attr_string(profile: user.profile, pubkey: pk) let user_tag = user_tag_attr_string(profile: user.profile, pubkey: pk)
appendUserTag(withTag: user_tag) appendUserTag(withTag: user_tag)
@@ -159,7 +156,7 @@ func user_tag_attr_string(profile: Profile?, pubkey: Pubkey) -> NSMutableAttribu
return NSMutableAttributedString(string: tagString, attributes: [ return NSMutableAttributedString(string: tagString, attributes: [
NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0), NSAttributedString.Key.font: UIFont.systemFont(ofSize: 18.0),
NSAttributedString.Key.foregroundColor: UIColor.label, NSAttributedString.Key.foregroundColor: UIColor.label,
NSAttributedString.Key.link: "damus:nostr:\(pubkey)" NSAttributedString.Key.link: "damus:nostr:\(pubkey.npub)"
]) ])
} }
+1 -1
View File
@@ -39,7 +39,7 @@ struct AboutView: View {
} }
} }
.onAppear { .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 about_string = render_blocks(blocks: blocks, profiles: state.profiles).content.attributed
} }
+1 -1
View File
@@ -45,7 +45,7 @@ struct ProfileNameView: View {
Spacer() Spacer()
KeyView(pubkey: pubkey) 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? { private func get_profile_url() -> URL? {
if let profile_url { if let profile_url {
return 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) return URL(string: picture)
} else { } else {
return profile_url ?? URL(string: robohash(pubkey)) return profile_url ?? URL(string: robohash(pubkey))
+10 -12
View File
@@ -190,7 +190,7 @@ struct ProfileView: View {
return 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 return
} }
@@ -260,10 +260,11 @@ struct ProfileView: View {
func actionSection(profile_data: Profile?) -> some View { func actionSection(profile_data: Profile?) -> some View {
return Group { return Group {
if let profile = profile_data { if let profile = profile_data,
if let lnurl = profile.lnurl, lnurl != "" { let lnurl = profile.lnurl,
lnButton(lnurl: lnurl, profile: profile) lnurl != ""
} {
lnButton(lnurl: lnurl, profile: profile)
} }
dmButton dmButton
@@ -353,7 +354,7 @@ struct ProfileView: View {
HStack { HStack {
if let contact = profile.contacts { 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) let following_model = FollowingModel(damus_state: damus_state, contacts: contacts)
NavigationLink(value: Route.Following(following: following_model)) { NavigationLink(value: Route.Following(following: following_model)) {
HStack { HStack {
@@ -466,11 +467,8 @@ struct ProfileView: View {
// our profilemodel needs a bit more help // our profilemodel needs a bit more help
} }
.sheet(isPresented: $show_share_sheet) { .sheet(isPresented: $show_share_sheet) {
if let npub = bech32_pubkey(profile.pubkey) { let url = URL(string: "https://damus.io/" + profile.pubkey.npub)!
if let url = URL(string: "https://damus.io/" + npub) { ShareSheet(activityItems: [url])
ShareSheet(activityItems: [url])
}
}
} }
.fullScreenCover(isPresented: $show_qr_code) { .fullScreenCover(isPresented: $show_qr_code) {
QRCodeView(damus_state: damus_state, pubkey: profile.pubkey) QRCodeView(damus_state: damus_state, pubkey: profile.pubkey)
@@ -517,7 +515,7 @@ struct KeyView: View {
} }
var body: some View { var body: some View {
let bech32 = bech32_pubkey(pubkey) ?? pubkey let bech32 = pubkey.npub
HStack { HStack {
Text(verbatim: "\(abbrev_pubkey(bech32, amount: 16))") Text(verbatim: "\(abbrev_pubkey(bech32, amount: 16))")
+25 -28
View File
@@ -10,9 +10,13 @@ import CoreImage.CIFilterBuiltins
struct ProfileScanResult: Equatable { struct ProfileScanResult: Equatable {
let pubkey: Pubkey let pubkey: Pubkey
init(hex: String) { init?(hex: String) {
self.pubkey = hex guard let pk = hex_decode(hex).map({ bytes in Pubkey(Data(bytes)) }) else {
return nil
}
self.pubkey = pk
} }
init?(string: String) { init?(string: String) {
@@ -25,14 +29,17 @@ struct ProfileScanResult: Equatable {
str.removeFirst("nostr:".count) str.removeFirst("nostr:".count)
} }
if let _ = hex_decode(str), str.count == 64 { if let decoded = hex_decode(str),
self = .init(hex: str) str.count == 64
{
self.pubkey = Pubkey(Data(decoded))
return return
} }
if str.starts(with: "npub"), let b32 = try? bech32_decode(str) { if str.starts(with: "npub"),
let hex = hex_encode(b32.data) let b32 = try? bech32_decode(str)
self = .init(hex: hex) {
self.pubkey = Pubkey(b32.data)
return return
} }
@@ -56,14 +63,6 @@ struct QRCodeView: View {
let generator = UIImpactFeedbackGenerator(style: .light) let generator = UIImpactFeedbackGenerator(style: .light)
var maybe_key: String? {
guard let key = bech32_pubkey(pubkey) else {
return nil
}
return key
}
@ViewBuilder @ViewBuilder
func navImage(systemImage: String) -> some View { func navImage(systemImage: String) -> some View {
Image(systemName: systemImage) Image(systemName: systemImage)
@@ -143,18 +142,16 @@ struct QRCodeView: View {
Spacer() Spacer()
if let key = maybe_key { Image(uiImage: generateQRCode(pubkey: "nostr:" + pubkey.npub))
Image(uiImage: generateQRCode(pubkey: "nostr:" + key)) .interpolation(.none)
.interpolation(.none) .resizable()
.resizable() .scaledToFit()
.scaledToFit() .frame(width: 300, height: 300)
.frame(width: 300, height: 300) .cornerRadius(10)
.cornerRadius(10) .overlay(RoundedRectangle(cornerRadius: 10)
.overlay(RoundedRectangle(cornerRadius: 10) .stroke(DamusColors.white, lineWidth: 5.0))
.stroke(DamusColors.white, lineWidth: 5.0)) .shadow(radius: 10)
.shadow(radius: 10)
}
Spacer() Spacer()
Text("Follow me on Nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user.") Text("Follow me on Nostr", comment: "Text on QR code view to prompt viewer looking at screen to follow the user.")
+3 -2
View File
@@ -91,6 +91,7 @@ struct RelayDetailView: View {
} }
} }
} }
if let relay_connection { if let relay_connection {
Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) { Section(NSLocalizedString("Relay", comment: "Label to display relay address.")) {
HStack { HStack {
@@ -101,7 +102,7 @@ struct RelayDetailView: View {
} }
} }
if let nip11 = nip11 { if let nip11 {
if nip11.is_paid { if nip11.is_paid {
Section(content: { Section(content: {
RelayPaidDetail(payments_url: nip11.payments_url) RelayPaidDetail(payments_url: nip11.payments_url)
@@ -172,7 +173,7 @@ struct RelayDetailView: View {
struct RelayDetailView_Previews: PreviewProvider { struct RelayDetailView_Previews: PreviewProvider {
static var previews: some View { 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) RelayDetailView(state: test_damus_state(), relay: "relay", nip11: metadata)
} }
} }
+1 -1
View File
@@ -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: { Button(action: {
guard let ev = state.contacts.event else { guard let ev = state.contacts.event else {
return return
+26 -10
View File
@@ -10,17 +10,23 @@ import SwiftUI
struct ReplyView: View { struct ReplyView: View {
let replying_to: NostrEvent let replying_to: NostrEvent
let damus: DamusState let damus: DamusState
@Binding var originalReferences: [ReferencedId] let original_pubkeys: [Pubkey]
@Binding var references: [ReferencedId] @Binding var filtered_pubkeys: Set<Pubkey>
@State var participantsShown: Bool = false @State var participantsShown: Bool = false
var references: [Pubkey] {
original_pubkeys.filter { pk in
!filtered_pubkeys.contains(pk)
}
}
var ReplyingToSection: some View { var ReplyingToSection: some View {
HStack { HStack {
Group { Group {
let names = references.pRefs let names = references
.map { pubkey in .map { pubkey in
let pk = pubkey.ref_id let pk = pubkey
let prof = damus.profiles.lookup(id: pk) let prof = damus.profiles.lookup(id: pk)
return "@" + Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50) return "@" + Profile.displayName(profile: prof, pubkey: pk).username.truncate(maxLength: 50)
} }
@@ -40,11 +46,15 @@ struct ReplyView: View {
} }
.sheet(isPresented: $participantsShown) { .sheet(isPresented: $participantsShown) {
if #available(iOS 16.0, *) { 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]) .presentationDetents([.medium, .large])
.presentationDragIndicator(.visible) .presentationDragIndicator(.visible)
} else { } 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) .padding(.leading, 75)
@@ -81,10 +91,16 @@ struct ReplyView: View {
struct ReplyView_Previews: PreviewProvider { struct ReplyView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
VStack { 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) .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) .frame(height: 300)
} }
} }
+3 -7
View File
@@ -51,12 +51,8 @@ struct ReportView: View {
return return
} }
guard let note_id = bech32_note_id(ev.id) else {
return
}
report_sent = true report_sent = true
report_id = note_id report_id = bech32_note_id(ev.id)
} }
var send_report_button_text: String { var send_report_button_text: String {
@@ -131,9 +127,9 @@ struct ReportView_Previews: PreviewProvider {
let ds = test_damus_state() let ds = test_damus_state()
VStack { 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")
} }
} }
+4 -4
View File
@@ -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.") 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) .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) .padding(.bottom, 10)
if pub_copied { 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.") 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) .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) .padding(.bottom, 10)
} }
@@ -115,8 +115,8 @@ struct SaveKeysView: View {
self.pool.register_handler(sub_id: "signup", handler: handle_event) 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.loading = true
self.pool.connect() self.pool.connect()
+17 -18
View File
@@ -14,15 +14,14 @@ enum SearchState {
case not_found case not_found
} }
enum SearchType { enum SearchType: Equatable {
case event case event(NoteId)
case profile case profile(Pubkey)
case nip05 case nip05(String)
} }
struct SearchingEventView: View { struct SearchingEventView: View {
let state: DamusState let state: DamusState
let evid: String
let search_type: SearchType let search_type: SearchType
@State var search_state: SearchState = .searching @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 self.search_state = .searching
switch search_type { switch search {
case .nip05: case .nip05(let nip05):
if let pk = state.profiles.nip05_pubkey[evid] { if let pk = state.profiles.nip05_pubkey[nip05] {
if state.profiles.lookup(id: pk) != nil { if state.profiles.lookup(id: pk) != nil {
self.search_state = .found_profile(pk) self.search_state = .found_profile(pk)
} }
} else { } else {
Task { Task {
guard let nip05 = NIP05.parse(evid) else { guard let nip05 = NIP05.parse(nip05) else {
self.search_state = .not_found self.search_state = .not_found
return return
} }
@@ -71,16 +70,16 @@ struct SearchingEventView: View {
} }
} }
case .event: case .event(let note_id):
find_event(state: state, query: .event(evid: evid)) { res in find_event(state: state, query: .event(evid: note_id)) { res in
guard case .event(let ev) = res else { guard case .event(let ev) = res else {
self.search_state = .not_found self.search_state = .not_found
return return
} }
self.search_state = .found(ev) self.search_state = .found(ev)
} }
case .profile: case .profile(let pubkey):
find_event(state: state, query: .profile(pubkey: evid)) { res in find_event(state: state, query: .profile(pubkey: pubkey)) { res in
guard case .profile(_, let ev) = res else { guard case .profile(_, let ev) = res else {
self.search_state = .not_found self.search_state = .not_found
return 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") 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 .onChange(of: search_type, debounceTime: 0.5) { stype in
handle_search(evid) handle_search(search: stype)
} }
.onAppear { .onAppear {
handle_search(evid) handle_search(search: search_type)
} }
} }
} }
@@ -125,6 +124,6 @@ struct SearchingEventView: View {
struct SearchingEventView_Previews: PreviewProvider { struct SearchingEventView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
let state = test_damus_state() let state = test_damus_state()
SearchingEventView(state: state, evid: test_note.id, search_type: .event) SearchingEventView(state: state, search_type: .event(test_note.id))
} }
} }
+28 -30
View File
@@ -18,7 +18,7 @@ enum Search: Identifiable {
case profile(Pubkey) case profile(Pubkey)
case note(NoteId) case note(NoteId)
case nip05(String) case nip05(String)
case hex(String) case hex(Data)
case multi(MultiSearch) case multi(MultiSearch)
var id: String { var id: String {
@@ -67,28 +67,22 @@ struct InnerSearchResults: View {
HashtagSearch(ht) HashtagSearch(ht)
case .nip05(let addr): case .nip05(let addr):
SearchingEventView(state: damus_state, evid: addr, search_type: .nip05) SearchingEventView(state: damus_state, search_type: .nip05(addr))
case .profile(let prof): case .profile(let pubkey):
let decoded = try? bech32_decode(prof) SearchingEventView(state: damus_state, search_type: .profile(pubkey))
let hex = hex_encode(decoded!.data)
SearchingEventView(state: damus_state, evid: hex, search_type: .profile)
case .hex(let h): 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) { VStack(spacing: 10) {
SearchingEventView(state: damus_state, evid: h, search_type: .event) SearchingEventView(state: damus_state, search_type: .event(NoteId(h)))
SearchingEventView(state: damus_state, evid: h, search_type: .profile) SearchingEventView(state: damus_state, search_type: .profile(Pubkey(h)))
} }
case .note(let nid): case .note(let nid):
let decoded = try? bech32_decode(nid) SearchingEventView(state: damus_state, search_type: .event(nid))
let hex = hex_encode(decoded!.data)
SearchingEventView(state: damus_state, evid: hex, search_type: .event)
case .multi(let multi): case .multi(let multi):
VStack { VStack {
HashtagSearch(multi.hashtag) HashtagSearch(multi.hashtag)
@@ -146,20 +140,18 @@ func search_for_string(profiles: Profiles, _ new: String) -> Search? {
return .hashtag(make_hashtagable(new)) return .hashtag(make_hashtagable(new))
} }
if hex_decode(new) != nil, new.count == 64 { if let new = hex_decode_id(new) {
return .hex(new) return .hex(new)
} }
if new.starts(with: "npub") { if new.starts(with: "npub") {
if (try? bech32_decode(new)) != nil { if let decoded = bech32_pubkey_decode(new) {
return .profile(new) return .profile(decoded)
} }
} }
if new.starts(with: "note") { if new.starts(with: "note"), let decoded = try? bech32_decode(new) {
if (try? bech32_decode(new)) != nil { return .note(NoteId(decoded.data))
return .note(new)
}
} }
let multisearch = MultiSearch(hashtag: make_hashtagable(new), profiles: search_profiles(profiles: profiles, search: new)) 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] { func search_profiles(profiles: Profiles, search: String) -> [SearchedUser] {
// Search by hex pubkey. // Search by hex pubkey.
if search.count == 64 && hex_decode(search) != nil, let profile = profiles.lookup(id: search) { if let pubkey = hex_decode_pubkey(search),
return [SearchedUser(profile: profile, pubkey: search)] let profile = profiles.lookup(id: pubkey)
{
return [SearchedUser(profile: profile, pubkey: pubkey)]
} }
// Search by npub 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) { if search.starts(with: "npub"),
return [SearchedUser(profile: profile, pubkey: hex)] 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() let new = search.lowercased()
+3 -3
View File
@@ -14,7 +14,7 @@ struct ThreadView: View {
@Environment(\.dismiss) var dismiss @Environment(\.dismiss) var dismiss
var parent_events: [NostrEvent] { 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] { var child_events: [NostrEvent] {
@@ -34,7 +34,7 @@ struct ThreadView: View {
selected: false) selected: false)
.padding(.horizontal) .padding(.horizontal)
.onTapGesture { .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) scroll_to_event(scroller: reader, id: parent_event.id, delay: 0.1, animate: false)
} }
@@ -77,7 +77,7 @@ struct ThreadView: View {
) )
.padding(.horizontal) .padding(.horizontal)
.onTapGesture { .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) scroll_to_event(scroller: reader, id: child_event.id, delay: 0.1, animate: false)
} }
+1 -1
View File
@@ -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 { struct WalletView_Previews: PreviewProvider {
static let tds = test_damus_state() static let tds = test_damus_state()
+2 -2
View File
@@ -16,7 +16,7 @@ struct ZapsView: View {
init(state: DamusState, target: ZapTarget) { init(state: DamusState, target: ZapTarget) {
self.state = state self.state = state
self.model = ZapsModel(state: state, target: target) 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 { var body: some View {
@@ -40,6 +40,6 @@ struct ZapsView: View {
struct ZapsView_Previews: PreviewProvider { struct ZapsView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
ZapsView(state: test_damus_state(), target: .profile("pk")) ZapsView(state: test_damus_state(), target: .profile(test_pubkey))
} }
} }
+5 -11
View File
@@ -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 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. // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards.
let pubkey = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" let pubkey = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
guard let b32_pubkey = bech32_pubkey(pubkey) else {
guard let decoded = try? bech32_decode(pubkey.npub) else {
XCTAssert(false) XCTAssert(false)
return return
} }
guard let decoded = try? bech32_decode(b32_pubkey) else { XCTAssertEqual(decoded.data, pubkey.id)
XCTAssert(false)
return
}
let encoded = hex_encode(decoded.data)
XCTAssertEqual(encoded, pubkey)
} }
func testPerformanceExample() throws { func testPerformanceExample() throws {
+27 -26
View File
@@ -11,96 +11,97 @@ import XCTest
final class DMTests: XCTestCase { final class DMTests: XCTestCase {
var alice: Keypair { var alice: Keypair {
let sec = "494c680d20f202807a116a6915815bd76a27d62802e7585806f6a2e034cb5cdb" let sec = hex_decode_privkey("494c680d20f202807a116a6915815bd76a27d62802e7585806f6a2e034cb5cdb")!
let pk = "22d925632551a3299022e98de7f9c1087f79a21209f3413ec24ec219b08bd1e4" let pk = hex_decode_pubkey("22d925632551a3299022e98de7f9c1087f79a21209f3413ec24ec219b08bd1e4")!
return Keypair(pubkey: pk, privkey: sec) return Keypair(pubkey: pk, privkey: sec)
} }
var bob: Keypair { var bob: Keypair {
let sec = "aa8920b05b4bd5c79fce46868ed5ebc82bdb91b211850b14541bfbd13953cfef" let sec = hex_decode_privkey("aa8920b05b4bd5c79fce46868ed5ebc82bdb91b211850b14541bfbd13953cfef")!
let pk = "5a9a277dca94260688ecf7d63053de8c121b7f01f609d7f84a1eb9cff64e4606" let pk = hex_decode_pubkey("5a9a277dca94260688ecf7d63053de8c121b7f01f609d7f84a1eb9cff64e4606")!
return Keypair(pubkey: pk, privkey: sec) return Keypair(pubkey: pk, privkey: sec)
} }
var charlie: Keypair { var charlie: Keypair {
let sec = "4c79130952c9c3b017dad62f37f285853a9c53f2a1184d94594f5b860f30b5a5" let sec = hex_decode_privkey("4c79130952c9c3b017dad62f37f285853a9c53f2a1184d94594f5b860f30b5a5")!
let pk = "51c0d263fbfc4bf850805dccf9a29125071e6fed9619bff3efa9a6b5bbcc54a7" let pk = hex_decode_pubkey("51c0d263fbfc4bf850805dccf9a29125071e6fed9619bff3efa9a6b5bbcc54a7")!
return Keypair(pubkey: pk, privkey: sec) return Keypair(pubkey: pk, privkey: sec)
} }
var dave: Keypair { var dave: Keypair {
let sec = "630ffd518084334cbb9ecb20d9532ce0658b8123f4ba565c236d0cea9a4a2cfe" let sec = hex_decode_privkey("630ffd518084334cbb9ecb20d9532ce0658b8123f4ba565c236d0cea9a4a2cfe")!
let pk = "b42e44b555013239a0d5dcdb09ebde0857cd8a5a57efbba5a2b6ac78833cb9f0" let pk = hex_decode_pubkey("b42e44b555013239a0d5dcdb09ebde0857cd8a5a57efbba5a2b6ac78833cb9f0")!
return Keypair(pubkey: pk, privkey: sec) return Keypair(pubkey: pk, privkey: sec)
} }
var fiatjaf: Keypair { var fiatjaf: Keypair {
let sec = "5426893eab32191ec17a83a583d5c8f85adaabcab0fa56af277ea0b61f575599" let sec = hex_decode_privkey("5426893eab32191ec17a83a583d5c8f85adaabcab0fa56af277ea0b61f575599")!
let pub = "e27258d7be6d84038967334bfd0954f05801b1bcd85b2afa4c03cfd16ae4b0ad" let pub = hex_decode_pubkey("e27258d7be6d84038967334bfd0954f05801b1bcd85b2afa4c03cfd16ae4b0ad")!
return Keypair(pubkey: pub, privkey: sec) return Keypair(pubkey: pub, privkey: sec)
} }
/*
func testDMSortOrder() throws { func testDMSortOrder() throws {
let notif = NewEventsBits() let notif = NewEventsBits()
let pubkey = "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681" let pubkey = hex_decode_pubkey("3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681")!
let model = DirectMessagesModel(our_pubkey: pubkey) let model = DirectMessagesModel(our_pubkey: pubkey)
let now = UInt32(Date().timeIntervalSince1970) 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 alice_to_bob = create_dm("hi bob", to_pk: bob.pubkey, tags: [bob.pubkey.tag], keypair: alice, created_at: now)!
let debouncer = Debouncer(interval: 3.0)
handle_incoming_dms(debouncer: debouncer, prev_events: notif, dms: model, our_pubkey: alice.pubkey, evs: [alice_to_bob]) 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.count, 1)
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey) 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]) 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.count, 1)
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey) 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]) 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.count, 1)
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey) 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]) 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.count, 2)
XCTAssertEqual(model.dms[0].pubkey, fiatjaf.pubkey) 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]) 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.count, 3)
XCTAssertEqual(model.dms[0].pubkey, dave.pubkey) 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]) 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.count, 3)
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey) 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]) 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.count, 4)
XCTAssertEqual(model.dms[0].pubkey, charlie.pubkey) 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]) 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.count, 4)
XCTAssertEqual(model.dms[0].pubkey, bob.pubkey) 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]) 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.count, 4)
XCTAssertEqual(model.dms[0].pubkey, charlie.pubkey) XCTAssertEqual(model.dms[0].pubkey, charlie.pubkey)
} }
*/
} }
+32 -18
View File
@@ -20,26 +20,31 @@ final class EventGroupViewTests: XCTestCase {
func testEventAuthorName() { func testEventAuthorName() {
let damusState = test_damus_state() let damusState = test_damus_state()
XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: "pk1"), "pk1:pk1") XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: test_pubkey), "damus")
XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: "pk2"), "pk2:pk2") XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: test_pubkey_2), "1rppft3m:4qxhsgnj")
XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: "anon"), "Anonymous") XCTAssertEqual(event_author_name(profiles: damusState.profiles, pubkey: ANON_PUBKEY), "Anonymous")
} }
func testEventGroupUniquePubkeys() { func testEventGroupUniquePubkeys() {
let damusState = test_damus_state() let damusState = test_damus_state()
let encodedPost = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}" 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 pk1 =
let pk3 = Keypair(pubkey: "pk3", privkey: nil) hex_decode_pubkey("1723a4dcc6596d84472eb74d579114d8c46b533c81a0ac76620a7605d3ff76e0")!
let repost1 = NostrEvent(content: encodedPost, keypair: pk1, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)! let pk2 =
let repost2 = NostrEvent(content: encodedPost, keypair: pk2, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)! hex_decode_pubkey("08c43696702ba1d720e4564b4ad895efdc3716b37468fb288e585368950a428a")!
let repost3 = NostrEvent(content: encodedPost, keypair: pk3, kind: NostrKind.boost.rawValue, tags: [], createdAt: 1)! 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: []))), [])
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]))), [pk1])
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]))), [pk1, pk2])
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, repost2, repost3]))), [pk1, pk2, pk3])
} }
func testReactingToText() throws { 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 encodedPost = "{\"id\": \"8ba545ab96959fe0ce7db31bc10f3ac3aa5353bc4428dbf1e56a7be7062516db\",\"pubkey\": \"7e27509ccf1e297e1df164912a43406218f8bd80129424c3ef798ca3ef5c8444\",\"created_at\": 1677013417,\"kind\": 1,\"tags\": [],\"content\": \"hello\",\"sig\": \"93684f15eddf11f42afbdd81828ee9fc35350344d8650c78909099d776e9ad8d959cd5c4bff7045be3b0b255144add43d0feef97940794a1bc9c309791bebe4a\"}"
let pk1 = Keypair(pubkey: "pk1", privkey: nil) let pk1_pk =
let pk2 = Keypair(pubkey: "pk2", privkey: nil) hex_decode_pubkey("938afd5f44fdf293546767dcc024b4ec09b9df422fad10d577a846f88f56c8f5")!
let pk3 = Keypair(pubkey: "pk3", privkey: nil)
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 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 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 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: [])), 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])), 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), "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])), 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), "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, 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 { 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), "??") XCTAssertNoThrow(reacting_to_text(profiles: damusState.profiles, our_pubkey: damusState.pubkey, group: .repost(EventGroup(events: [])), ev: test_note, pubkeys: [], locale: $0), "??")
+12 -21
View File
@@ -9,18 +9,9 @@ import XCTest
@testable import damus @testable import damus
final class HashtagTests: XCTestCase { 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() { 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) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
XCTAssertEqual(parsed[0].is_text, "some hashtag ") XCTAssertEqual(parsed[0].is_text, "some hashtag ")
@@ -29,8 +20,8 @@ final class HashtagTests: XCTestCase {
} }
func testHashtagWithComma() { 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) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
XCTAssertEqual(parsed[0].is_text, "some hashtag ") XCTAssertEqual(parsed[0].is_text, "some hashtag ")
@@ -40,7 +31,7 @@ final class HashtagTests: XCTestCase {
func testHashtagWithEmoji() { func testHashtagWithEmoji() {
let content = "some hashtag #bitcoin☕️ cool" 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) let post_blocks = parse_post_blocks(content: content)
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
@@ -57,7 +48,7 @@ final class HashtagTests: XCTestCase {
func testPowHashtag() { func testPowHashtag() {
let content = "pow! #ぽわ〜" 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) let post_blocks = parse_post_blocks(content: content)
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
@@ -71,8 +62,8 @@ final class HashtagTests: XCTestCase {
} }
func testHashtagWithAccents() { 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) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 2) XCTAssertEqual(parsed.count, 2)
XCTAssertEqual(parsed[0].is_text, "hello from ") XCTAssertEqual(parsed[0].is_text, "hello from ")
@@ -80,8 +71,8 @@ final class HashtagTests: XCTestCase {
} }
func testHashtagWithNonLatinCharacters() { 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) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
XCTAssertEqual(parsed[0].is_text, "this is a ") XCTAssertEqual(parsed[0].is_text, "this is a ")
@@ -90,8 +81,8 @@ final class HashtagTests: XCTestCase {
} }
func testParseHashtagEnd() { 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) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 2) XCTAssertEqual(parsed.count, 2)
XCTAssertEqual(parsed[0].is_text, "some hashtag ") XCTAssertEqual(parsed[0].is_text, "some hashtag ")
+12 -12
View File
@@ -20,8 +20,8 @@ final class InvoiceTests: XCTestCase {
func testParseAnyAmountInvoice() throws { func testParseAnyAmountInvoice() throws {
let invstr = "LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C" let invstr = "LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C"
let parsed = parse_note_content(content: invstr, tags: []).blocks let parsed = parse_note_content(content: .content(invstr,nil)).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 1) XCTAssertEqual(parsed.count, 1)
XCTAssertNotNil(parsed[0].is_invoice) XCTAssertNotNil(parsed[0].is_invoice)
@@ -38,8 +38,8 @@ final class InvoiceTests: XCTestCase {
let invstr = """ let invstr = """
LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C hi there LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN4M4XU59XMJCXKR7YDV29DDP6LVQUT46ZW6CU3KE9GQDQ9V9H8JXQ8P3MYLZJCQPJRZJQF60PZDVNGGQWQDNERZSQN35L8CVQ3QG2Z5NSZYD0D3Q0JW2TL6VUZA7FYQQWKGQQYQQQQLGQQQQXJQQ9Q9QXPQYSGQ39EM4QJMQFKZGJXZVGL7QJMYNSWA8PGDTAGXXRG5Z92M7VLCGKQK2L2THDF8LM0AUKAURH7FVAWDLRNMVF38W4EYJDNVN9V4Z9CRS5CQCV465C hi there
""" """
let parsed = parse_note_content(content: invstr, tags: []).blocks let parsed = parse_note_content(content: .content(invstr,nil)).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 2) XCTAssertEqual(parsed.count, 2)
XCTAssertNotNil(parsed[0].is_invoice) XCTAssertNotNil(parsed[0].is_invoice)
@@ -54,8 +54,8 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
func testParseInvoiceUpper() throws { func testParseInvoiceUpper() throws {
let invstr = "LNBC100N1P357SL0SP5T9N56WDZTUN39LGDQLR30XQWKSG3K69Q4Q2RKR52APLUJW0ESN0QPP5MRQGLJK62Z20Q4NVGR6LZCYN6FHYLZCCWDVU4K77APG3ZMRKUJJQDPZW35XJUEQD9EJQCFQV3JHXCMJD9C8G6T0DCXQYJW5QCQPJRZJQT56H4GVP5YX36U2UZQA6QWCSK3E2DUUNFXPPZJ9VHYPC3WFE2WSWZ607UQQ3XQQQSQQQQQQQQQQQLQQYG9QYYSGQAGX5H20AEULJ3GDWX3KXS8U9F4MCAKDKWUAKASAMM9562FFYR9EN8YG20LG0YGNR9ZPWP68524KMDA0T5XP2WYTEX35PU8HAPYJAJXQPSQL29R" let invstr = "LNBC100N1P357SL0SP5T9N56WDZTUN39LGDQLR30XQWKSG3K69Q4Q2RKR52APLUJW0ESN0QPP5MRQGLJK62Z20Q4NVGR6LZCYN6FHYLZCCWDVU4K77APG3ZMRKUJJQDPZW35XJUEQD9EJQCFQV3JHXCMJD9C8G6T0DCXQYJW5QCQPJRZJQT56H4GVP5YX36U2UZQA6QWCSK3E2DUUNFXPPZJ9VHYPC3WFE2WSWZ607UQQ3XQQQSQQQQQQQQQQQLQQYG9QYYSGQAGX5H20AEULJ3GDWX3KXS8U9F4MCAKDKWUAKASAMM9562FFYR9EN8YG20LG0YGNR9ZPWP68524KMDA0T5XP2WYTEX35PU8HAPYJAJXQPSQL29R"
let parsed = parse_note_content(content: invstr, tags: []).blocks let parsed = parse_note_content(content: .content(invstr,nil)).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 1) XCTAssertEqual(parsed.count, 1)
XCTAssertNotNil(parsed[0].is_invoice) XCTAssertNotNil(parsed[0].is_invoice)
@@ -70,8 +70,8 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
func testParseInvoiceWithPrefix() throws { func testParseInvoiceWithPrefix() throws {
let invstr = "lightning:lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r" 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) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 1) XCTAssertEqual(parsed.count, 1)
XCTAssertNotNil(parsed[0].is_invoice) XCTAssertNotNil(parsed[0].is_invoice)
@@ -79,8 +79,8 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
func testParseInvoiceWithPrefixCapitalized() throws { func testParseInvoiceWithPrefixCapitalized() throws {
let invstr = "LIGHTNING:LNBC100N1P357SL0SP5T9N56WDZTUN39LGDQLR30XQWKSG3K69Q4Q2RKR52APLUJW0ESN0QPP5MRQGLJK62Z20Q4NVGR6LZCYN6FHYLZCCWDVU4K77APG3ZMRKUJJQDPZW35XJUEQD9EJQCFQV3JHXCMJD9C8G6T0DCXQYJW5QCQPJRZJQT56H4GVP5YX36U2UZQA6QWCSK3E2DUUNFXPPZJ9VHYPC3WFE2WSWZ607UQQ3XQQQSQQQQQQQQQQQLQQYG9QYYSGQAGX5H20AEULJ3GDWX3KXS8U9F4MCAKDKWUAKASAMM9562FFYR9EN8YG20LG0YGNR9ZPWP68524KMDA0T5XP2WYTEX35PU8HAPYJAJXQPSQL29R" 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) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 1) XCTAssertEqual(parsed.count, 1)
XCTAssertNotNil(parsed[0].is_invoice) XCTAssertNotNil(parsed[0].is_invoice)
@@ -88,8 +88,8 @@ LNBC1P3MR5UJSP5G7SA48YD4JWTTPCHWMY4QYN4UWZQCJQ8NMWKD6QE3HCRVYTDLH9SPP57YM9TSA9NN
func testParseInvoice() throws { func testParseInvoice() throws {
let invstr = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r" let invstr = "lnbc100n1p357sl0sp5t9n56wdztun39lgdqlr30xqwksg3k69q4q2rkr52aplujw0esn0qpp5mrqgljk62z20q4nvgr6lzcyn6fhylzccwdvu4k77apg3zmrkujjqdpzw35xjueqd9ejqcfqv3jhxcmjd9c8g6t0dcxqyjw5qcqpjrzjqt56h4gvp5yx36u2uzqa6qwcsk3e2duunfxppzj9vhypc3wfe2wswz607uqq3xqqqsqqqqqqqqqqqlqqyg9qyysgqagx5h20aeulj3gdwx3kxs8u9f4mcakdkwuakasamm9562ffyr9en8yg20lg0ygnr9zpwp68524kmda0t5xp2wytex35pu8hapyjajxqpsql29r"
let parsed = parse_note_content(content: invstr, tags: []).blocks let parsed = parse_note_content(content: .content(invstr,nil)).blocks
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 1) XCTAssertEqual(parsed.count, 1)
XCTAssertNotNil(parsed[0].is_invoice) XCTAssertNotNil(parsed[0].is_invoice)
+9 -5
View File
@@ -19,14 +19,18 @@ class LikeTests: XCTestCase {
} }
func testLikeHasNotification() throws { 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 id = liked.id
let like_ev = make_like_event(keypair: test_keypair_full, liked: liked)! 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.referenced_pubkeys.contains(test_keypair.pubkey))
XCTAssertTrue(like_ev.references(id: "cindy", key: "p")) XCTAssertTrue(like_ev.referenced_pubkeys.contains(cindy))
XCTAssertTrue(like_ev.references(id: "bob", key: "e")) XCTAssertTrue(like_ev.referenced_pubkeys.contains(bob))
XCTAssertEqual(like_ev.last_refid()!.ref_id, id) XCTAssertEqual(like_ev.last_refid()!, id)
} }
func testToReactionEmoji() { func testToReactionEmoji() {
+30 -30
View File
@@ -19,53 +19,53 @@ final class ListTests: XCTestCase {
} }
func testCreateMuteList() throws { func testCreateMuteList() throws {
let privkey = "87f313b03f2548e6eaf1c188db47078e08e894252949779b639b28db0891937a" let privkey = test_keypair_full.privkey
let pubkey = "4b0c29bf96496130c1253102f6870c0eee05db38a257315858272aa43fd19685" let pubkey = test_keypair_full.pubkey
let to_mute = "2fa2630fea3d2c188c49f2799fcd92f0e9879ea6a36ae60770a5428ed6c19edd" let to_mute = test_pubkey
let keypair = FullKeypair(pubkey: pubkey, privkey: privkey) 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.pubkey, pubkey)
XCTAssertEqual(mutelist.content, "") XCTAssertEqual(mutelist.content, "")
XCTAssertEqual(mutelist.tags.count, 2) XCTAssertEqual(mutelist.tags.count, 2)
XCTAssertEqual(mutelist.tags[0][0], "d") XCTAssertEqual(mutelist.tags[0][0].string(), "d")
XCTAssertEqual(mutelist.tags[0][1], "mute") XCTAssertEqual(mutelist.tags[0][1].string(), "mute")
XCTAssertEqual(mutelist.tags[1][0], "p") XCTAssertEqual(mutelist.tags[1][0].string(), "p")
XCTAssertEqual(mutelist.tags[1][1], to_mute) XCTAssertEqual(mutelist.tags[1][1].string(), to_mute.hex())
} }
func testCreateAndRemoveMuteList() throws { func testCreateAndRemoveMuteList() throws {
let privkey = "87f313b03f2548e6eaf1c188db47078e08e894252949779b639b28db0891937a" let privkey = test_keypair_full.privkey
let pubkey = "4b0c29bf96496130c1253102f6870c0eee05db38a257315858272aa43fd19685" let pubkey = test_keypair_full.pubkey
let to_mute = "2fa2630fea3d2c188c49f2799fcd92f0e9879ea6a36ae60770a5428ed6c19edd" let to_mute = test_pubkey
let keypair = FullKeypair(pubkey: pubkey, privkey: privkey) 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))!
let new = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: to_mute)! let new = remove_from_mutelist(keypair: keypair, prev: mutelist, to_remove: .pubkey(to_mute))!
XCTAssertEqual(new.pubkey, pubkey) XCTAssertEqual(new.pubkey, pubkey)
XCTAssertEqual(new.content, "") XCTAssertEqual(new.content, "")
XCTAssertEqual(new.tags.count, 1) XCTAssertEqual(new.tags.count, 1)
XCTAssertEqual(new.tags[0][0], "d") XCTAssertEqual(new.tags[0][0].string(), "d")
XCTAssertEqual(new.tags[0][1], "mute") XCTAssertEqual(new.tags[0][1].string(), "mute")
} }
func testAddToExistingMutelist() throws { func testAddToExistingMutelist() throws {
let privkey = "87f313b03f2548e6eaf1c188db47078e08e894252949779b639b28db0891937a" let privkey = test_keypair_full.privkey
let pubkey = "4b0c29bf96496130c1253102f6870c0eee05db38a257315858272aa43fd19685" let pubkey = test_keypair_full.pubkey
let to_mute = "2fa2630fea3d2c188c49f2799fcd92f0e9879ea6a36ae60770a5428ed6c19edd" let to_mute = test_pubkey
let to_mute_2 = "976b4ab41f8634119b4f21f57ef5836a4bef65d0bf72c7ced67b8b170ba4a38d" let to_mute_2 = test_pubkey_2
let keypair = FullKeypair(pubkey: pubkey, privkey: privkey) 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))!
let new = create_or_update_mutelist(keypair: keypair, mprev: mutelist, to_add: to_mute_2)! let new = create_or_update_mutelist(keypair: keypair, mprev: mutelist, to_add: .pubkey(to_mute_2))!
XCTAssertEqual(new.pubkey, pubkey) XCTAssertEqual(new.pubkey, pubkey)
XCTAssertEqual(new.content, "") XCTAssertEqual(new.content, "")
XCTAssertEqual(new.tags.count, 3) XCTAssertEqual(new.tags.count, 3)
XCTAssertEqual(new.tags[0][0], "d") XCTAssertEqual(new.tags[0][0].string(), "d")
XCTAssertEqual(new.tags[0][1], "mute") XCTAssertEqual(new.tags[0][1].string(), "mute")
XCTAssertEqual(new.tags[1][0], "p") XCTAssertEqual(new.tags[1][0].string(), "p")
XCTAssertEqual(new.tags[1][1], to_mute) XCTAssertEqual(new.tags[1][1].string(), to_mute.hex())
XCTAssertEqual(new.tags[2][0], "p") XCTAssertEqual(new.tags[2][0].string(), "p")
XCTAssertEqual(new.tags[2][1], to_mute_2) 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 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 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.count, 2)
XCTAssertEqual(blocks[0], .text("gm 🤙")) XCTAssertEqual(blocks[0], .text("gm 🤙"))
XCTAssertEqual(blocks[1], .url(URL(string: url)!)) XCTAssertEqual(blocks[1], .url(URL(string: url)!))
} }
/*
func test_damus_parse_content_can_parse_mention_without_white_space_at_front() throws { func test_damus_parse_content_can_parse_mention_without_white_space_at_front() throws {
var bs = note_blocks() var bs = note_blocks()
bs.num_blocks = 0; bs.num_blocks = 0;
@@ -38,7 +39,7 @@ class ContentParserTests: XCTestCase {
blocks_init(&bs) blocks_init(&bs)
let content = "#[0], #[1],#[2],#[3]#[4],#[5],#[6],#[7], #[8], \n#[9], #[10], #[11], #[12]" 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 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)!) let tags = try decoder.decode([[String]].self, from: tagsString.data(using: .utf8)!)
@@ -72,4 +73,5 @@ class ContentParserTests: XCTestCase {
i += 1 i += 1
} }
} }
*/
} }
+13 -13
View File
@@ -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. // Put teardown code here. This method is called after the invocation of each test method in the class.
} }
/*
func test_parse_nprofile() throws { 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) XCTAssertEqual(res.count, 1)
let expected_ref = ReferencedId(ref_id: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", relay_id: "wss://r.x.com", key: "p") 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) let expected_mention = Mention(index: nil, type: .pubkey, ref: expected_ref)
XCTAssertEqual(res[0], .mention(expected_mention)) XCTAssertEqual(res[0], .mention(expected_mention))
} }
*/
func test_parse_npub() throws { 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) XCTAssertEqual(res.count, 2)
let expected_ref = ReferencedId(ref_id: "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e", relay_id: nil, key: "p") let expected_ref = Pubkey(hex: "7e7e9c42a91bfef19fa929e5fda1b72e0ebc1a4c1141673e2794234d86addf4e")!
let expected_mention = Mention(index: nil, type: .pubkey, ref: expected_ref) let expected_mention: Mention<MentionRef> = Mention(index: nil, ref: .pubkey(expected_ref))
XCTAssertEqual(res[0], .mention(expected_mention)) XCTAssertEqual(res[0], .mention(expected_mention))
} }
func test_parse_note() throws { 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) XCTAssertEqual(res.count, 2)
let expected_ref = ReferencedId(ref_id: "8543e7d0bafb28f33c495f2198454bd62c7369c86d72d77704cad2674e0a92c9", relay_id: nil, key: "e") let note_id = NoteId(hex:"8543e7d0bafb28f33c495f2198454bd62c7369c86d72d77704cad2674e0a92c9")!
let expected_mention = Mention(index: nil, type: .event, ref: expected_ref) XCTAssertEqual(res[1], .mention(.any(.note(note_id))))
XCTAssertEqual(res[1], .mention(expected_mention))
} }
func test_mention_with_adjacent() throws { 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) XCTAssertEqual(res.count, 3)
let expected_ref = ReferencedId(ref_id: "8543e7d0bafb28f33c495f2198454bd62c7369c86d72d77704cad2674e0a92c9", relay_id: nil, key: "e") let note_id = NoteId(hex: "8543e7d0bafb28f33c495f2198454bd62c7369c86d72d77704cad2674e0a92c9")!
let expected_mention = Mention(index: nil, type: .event, ref: expected_ref)
XCTAssertEqual(res[0], .text(" ")) XCTAssertEqual(res[0], .text(" "))
XCTAssertEqual(res[1], .mention(expected_mention)) XCTAssertEqual(res[1], .mention(.any(.note(note_id))))
XCTAssertEqual(res[2], .text("?")) XCTAssertEqual(res[2], .text("?"))
} }
+2 -2
View File
@@ -39,7 +39,7 @@ final class NostrScriptTests: XCTestCase {
let data = try load_bool_set_test_wasm().bytes let data = try load_bool_set_test_wasm().bytes
let pool = RelayPool() let pool = RelayPool()
let script = NostrScript(pool: pool, data: data) let script = NostrScript(pool: pool, data: data)
let pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" let pk = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
UserSettingsStore.pubkey = pk UserSettingsStore.pubkey = pk
let key = pk_setting_key(pk, key: "nozaps") let key = pk_setting_key(pk, key: "nozaps")
UserDefaults.standard.set(true, forKey: key) UserDefaults.standard.set(true, forKey: key)
@@ -103,7 +103,7 @@ final class NostrScriptTests: XCTestCase {
pool.connect(to: ["wss://cache0.primal.net/cache17"]) pool.connect(to: ["wss://cache0.primal.net/cache17"])
self.wait(for: [resume_expected], timeout: 10.0) self.wait(for: [resume_expected], timeout: 5.0)
} }
*/ */
+4 -2
View File
@@ -10,8 +10,10 @@ import XCTest
class NoteContentViewTests: XCTestCase { class NoteContentViewTests: XCTestCase {
func testRenderBlocksWithNonLatinHashtags() { 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 testState = test_damus_state()
let text: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles) let text: NoteArtifactsSeparated = render_blocks(blocks: parsed, profiles: testState.profiles)
+7 -7
View File
@@ -32,8 +32,8 @@ class ProfileDatabaseTests: XCTestCase {
} }
func testStoreAndRetrieveProfile() async throws { func testStoreAndRetrieveProfile() async throws {
let id = "test-id" let id = test_pubkey
let profile = test_profile let profile = test_profile
// make sure it's not there yet // make sure it's not there yet
@@ -58,7 +58,7 @@ class ProfileDatabaseTests: XCTestCase {
} }
func testRejectOutdatedProfile() async throws { func testRejectOutdatedProfile() async throws {
let id = "test-id" let id = test_pubkey
// store a profile // store a profile
let profile = test_profile let profile = test_profile
@@ -80,8 +80,8 @@ class ProfileDatabaseTests: XCTestCase {
} }
func testUpdateExistingProfile() async throws { func testUpdateExistingProfile() async throws {
let id = "test-id" let id = test_pubkey
// store a profile // store a profile
let profile = test_profile let profile = test_profile
let profile_last_update = Date.now let profile_last_update = Date.now
@@ -102,7 +102,7 @@ class ProfileDatabaseTests: XCTestCase {
XCTAssertEqual(database.count, 0) XCTAssertEqual(database.count, 0)
// store a profile // store a profile
let id = "test-id" let id = test_pubkey
let profile = test_profile let profile = test_profile
let profile_last_update = Date.now let profile_last_update = Date.now
try await database.upsert(id: id, profile: profile, last_update: profile_last_update) try await database.upsert(id: id, profile: profile, last_update: profile_last_update)
@@ -110,7 +110,7 @@ class ProfileDatabaseTests: XCTestCase {
XCTAssertEqual(database.count, 1) XCTAssertEqual(database.count, 1)
// store another profile // store another profile
let id2 = "test-id-2" let id2 = test_pubkey_2
let profile2 = test_profile let profile2 = test_profile
let profile_last_update2 = Date.now let profile_last_update2 = Date.now
try await database.upsert(id: id2, profile: profile2, last_update: profile_last_update2) try await database.upsert(id: id2, profile: profile2, last_update: profile_last_update2)
+12 -6
View File
@@ -23,13 +23,19 @@ final class ProfileViewTests: XCTestCase {
func testFollowedByString() throws { func testFollowedByString() throws {
let profiles = test_damus_state().profiles let profiles = test_damus_state().profiles
XCTAssertEqual(followedByString(["pk1"], profiles: profiles, locale: enUsLocale), "Followed by pk1:pk1") let pk1 = test_pubkey
XCTAssertEqual(followedByString(["pk1", "pk2"], profiles: profiles, locale: enUsLocale), "Followed by pk1:pk1 & pk2:pk2") let pk2 = test_pubkey_2
XCTAssertEqual(followedByString(["pk1", "pk2", "pk3"], profiles: profiles, locale: enUsLocale), "Followed by pk1:pk1, pk2:pk2 & pk3:pk3") let pk3 = Pubkey(hex: "b42e44b555013239a0d5dcdb09ebde0857cd8a5a57efbba5a2b6ac78833cb9f0")!
XCTAssertEqual(followedByString(["pk1", "pk2", "pk3", "pk4",], profiles: profiles, locale: enUsLocale), "Followed by pk1:pk1, pk2:pk2, pk3:pk3 & 1 other") let pk4 = Pubkey(hex: "cc590e46363d0fa66bb27081368d01f169b8ffc7c614629d4e9eef6c88b38670")!
XCTAssertEqual(followedByString(["pk1", "pk2", "pk3", "pk4", "pk5"], profiles: profiles, locale: enUsLocale), "Followed by pk1:pk1, pk2:pk2, pk3:pk3 & 2 others") 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 { Bundle.main.localizations.map { Locale(identifier: $0) }.forEach {
for count in 1...10 { for count in 1...10 {
XCTAssertNoThrow(followedByString(pubkeys.prefix(count).map { $0 }, profiles: profiles, locale: $0)) XCTAssertNoThrow(followedByString(pubkeys.prefix(count).map { $0 }, profiles: profiles, locale: $0))
+108 -104
View File
@@ -19,11 +19,13 @@ class ReplyTests: XCTestCase {
} }
func testMentionIsntReply() throws { func testMentionIsntReply() throws {
let evid = NoteId(hex: "4090a9017a2beac3f17795d1aafb80d9f2b9eda97e4738501082ed5c927be014")!
let content = "this is #[0] a mention" let content = "this is #[0] a mention"
let tags = [["e", "event_id"]] let tags = [evid.tag]
let blocks = parse_note_content(content: content, tags: tags).blocks let ev = NostrEvent(content: content, keypair: test_keypair, tags: tags)!
let event_refs = interpret_event_refs(blocks: blocks, 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) XCTAssertEqual(event_refs.count, 1)
let ref = event_refs[0] let ref = event_refs[0]
@@ -31,14 +33,13 @@ class ReplyTests: XCTestCase {
XCTAssertNil(ref.is_reply) XCTAssertNil(ref.is_reply)
XCTAssertNil(ref.is_thread_id) XCTAssertNil(ref.is_thread_id)
XCTAssertNil(ref.is_direct_reply) XCTAssertNil(ref.is_direct_reply)
XCTAssertEqual(ref.is_mention?.type, .event) XCTAssertEqual(ref.is_mention, .some(.init(note_id: evid)))
XCTAssertEqual(ref.is_mention?.ref.ref_id, "event_id")
} }
func testUrlAnchorsAreNotHashtags() { func testUrlAnchorsAreNotHashtags() {
let content = "this is my link: https://jb55.com/index.html#buybitcoin this is not a hashtag!" let content = "this is my link: https://jb55.com/index.html#buybitcoin this is not a hashtag!"
let blocks = parse_post_blocks(content: content) let blocks = parse_post_blocks(content: content)
XCTAssertEqual(blocks.count, 3) XCTAssertEqual(blocks.count, 3)
XCTAssertEqual(blocks[0].is_text, "this is my link: ") XCTAssertEqual(blocks[0].is_text, "this is my link: ")
XCTAssertEqual(blocks[1].is_url, URL(string: "https://jb55.com/index.html#buybitcoin")!) XCTAssertEqual(blocks[1].is_url, URL(string: "https://jb55.com/index.html#buybitcoin")!)
@@ -93,30 +94,32 @@ class ReplyTests: XCTestCase {
func testRootReplyWithMention() throws { func testRootReplyWithMention() throws {
let content = "this is #[1] a mention" let content = "this is #[1] a mention"
let tags = [["e", "thread_id"], ["e", "mentioned_id"]] let thread_id = NoteId(hex: "c75e5cbafbefd5de2275f831c2a2386ea05ec5e5a78a5ccf60d467582db48945")!
let blocks = parse_note_content(content: content, tags: tags).blocks let mentioned_id = NoteId(hex: "5a534797e8cd3b9f4c1cf63e20e48bd0e8bd7f8c4d6353fbd576df000f6f54d3")!
let event_refs = interpret_event_refs(blocks: blocks, tags: tags) 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) XCTAssertEqual(event_refs.count, 2)
XCTAssertNotNil(event_refs[0].is_reply) XCTAssertNotNil(event_refs[0].is_reply)
XCTAssertNotNil(event_refs[0].is_thread_id) XCTAssertNotNil(event_refs[0].is_thread_id)
XCTAssertNotNil(event_refs[0].is_reply) XCTAssertNotNil(event_refs[0].is_reply)
XCTAssertNotNil(event_refs[0].is_direct_reply) XCTAssertNotNil(event_refs[0].is_direct_reply)
XCTAssertEqual(event_refs[0].is_reply?.ref_id, "thread_id") XCTAssertEqual(event_refs[0].is_reply, .some(NoteRef(note_id: thread_id)))
XCTAssertEqual(event_refs[0].is_thread_id?.ref_id, "thread_id") XCTAssertEqual(event_refs[0].is_thread_id, .some(NoteRef(note_id: thread_id)))
XCTAssertNotNil(event_refs[1].is_mention) XCTAssertNotNil(event_refs[1].is_mention)
XCTAssertEqual(event_refs[1].is_mention?.type, .event) XCTAssertEqual(event_refs[1].is_mention, .some(NoteRef(note_id: mentioned_id)))
XCTAssertEqual(event_refs[1].is_mention?.ref.ref_id, "mentioned_id")
} }
func testEmptyMention() throws { func testEmptyMention() throws {
let content = "this is some & content" let content = "this is some & content"
let tags: [[String]] = [] let ev = NostrEvent(content: content, keypair: test_keypair, tags: [])!
let blocks = parse_note_content(content: content, tags: tags).blocks let blocks = parse_note_content(content: .init(note: ev, privkey: test_keypair.privkey)).blocks
let post_blocks = parse_post_blocks(content: content) let post_blocks = parse_post_blocks(content: content)
let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags) let post_tags = make_post_tags(post_blocks: post_blocks, tags: [])
let event_refs = interpret_event_refs(blocks: blocks, tags: tags) let event_refs = interpret_event_refs(blocks: blocks, tags: ev.tags)
XCTAssertEqual(event_refs.count, 0) XCTAssertEqual(event_refs.count, 0)
XCTAssertEqual(post_tags.blocks.count, 1) XCTAssertEqual(post_tags.blocks.count, 1)
XCTAssertEqual(post_tags.tags.count, 0) XCTAssertEqual(post_tags.tags.count, 0)
@@ -126,16 +129,15 @@ class ReplyTests: XCTestCase {
func testManyMentions() throws { func testManyMentions() throws {
let content = "#[10]" let content = "#[10]"
let tags: [[String]] = [[],[],[],[],[],[],[],[],[],[],["p", "3e999f94e2cb34ef44a64b351141ac4e51b5121b2d31aed4a6c84602a1144692"]] 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 } let mentions = blocks.filter { $0.is_mention != nil }
XCTAssertEqual(mentions.count, 1) XCTAssertEqual(mentions.count, 1)
} }
func testNewlineMentions() throws { func testNewlineMentions() throws {
let pk = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s" let bech32_pk = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"
guard let hex_pk = bech32_pubkey_decode(pk) else { let pk = bech32_pubkey_decode(bech32_pk)!
return
}
let profile = Profile(name: "jb55") let profile = Profile(name: "jb55")
let post = user_tag_attr_string(profile: profile, pubkey: pk) 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(user_tag_attr_string(profile: profile, pubkey: pk))
post.append(.init(string: "\n")) 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) 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) let rendered = render_blocks(blocks: blocks)
XCTAssertEqual(rendered, expected_render) XCTAssertEqual(rendered, expected_render)
XCTAssertEqual(blocks.count, 3) 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[1].is_text, "\n")
XCTAssertEqual(blocks[2].is_mention, .pubkey(hex_pk)) XCTAssertEqual(blocks[2].is_mention, .any(.pubkey(pk)))
} }
func testThreadedReply() throws { func testThreadedReply() throws {
let content = "this is some content" let content = "this is some content"
let tags = [["e", "thread_id"], ["e", "reply_id"]] let thread_id = NoteId(hex: "da256fb52146dc565c6c6b9ef906117c665864dc02b14a7b853eca244729c2f2")!
let blocks = parse_note_content(content: content, tags: tags).blocks let reply_id = NoteId(hex: "80093e9bdb495728f54cda2bad4aed096877189552b3d41264e73b9a9595be22")!
let event_refs = interpret_event_refs(blocks: blocks, tags: tags) 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) XCTAssertEqual(event_refs.count, 2)
let r1 = event_refs[0] let r1 = event_refs[0]
let r2 = event_refs[1] let r2 = event_refs[1]
XCTAssertEqual(r1.is_thread_id!.ref_id, "thread_id") XCTAssertEqual(r1.is_thread_id, .some(.note_id(thread_id)))
XCTAssertEqual(r2.is_reply!.ref_id, "reply_id") XCTAssertEqual(r2.is_reply, .some(.note_id(reply_id)))
XCTAssertEqual(r2.is_direct_reply!.ref_id, "reply_id") XCTAssertEqual(r2.is_direct_reply, .some(.note_id(reply_id)))
XCTAssertNil(r1.is_direct_reply) XCTAssertNil(r1.is_direct_reply)
} }
func testRootReply() throws { func testRootReply() throws {
let content = "this is a reply" let content = "this is a reply"
let tags = [["e", "thread_id"]] let thread_id = NoteId(hex: "53f60f5114c06f069ffe9da2bc033e533d09cae44d37a8462154a663771a4ce6")!
let blocks = parse_note_content(content: content, tags: tags).blocks let tags = [thread_id.tag]
let event_refs = interpret_event_refs(blocks: blocks, tags: tags) 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) XCTAssertEqual(event_refs.count, 1)
let r = event_refs[0] let r = event_refs[0]
XCTAssertEqual(r.is_direct_reply!.ref_id, "thread_id") XCTAssertEqual(r.is_direct_reply, .some(.note_id(thread_id)))
XCTAssertEqual(r.is_reply!.ref_id, "thread_id") XCTAssertEqual(r.is_reply, .some(.note_id(thread_id)))
XCTAssertEqual(r.is_thread_id!.ref_id, "thread_id") XCTAssertEqual(r.is_thread_id, .some(.note_id(thread_id)))
XCTAssertNil(r.is_mention) XCTAssertNil(r.is_mention)
} }
@@ -194,13 +201,13 @@ class ReplyTests: XCTestCase {
let content = "cc@jb55" let content = "cc@jb55"
let profile = Profile(name: "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 appended = append_user_tag(tag: tag, post: .init(string: content), word_range: .init(2...6))
let new_post = appended.post let new_post = appended.post
try new_post.testAttributes(conditions: [ try new_post.testAttributes(conditions: [
{ let link = $0[.link] as? String; XCTAssertNil(link) }, { 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) } { let link = $0[.link] as? String; XCTAssertNil(link) }
]) ])
@@ -211,13 +218,13 @@ class ReplyTests: XCTestCase {
let content = "😎@jb55" let content = "😎@jb55"
let profile = Profile(name: "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 appended = append_user_tag(tag: tag, post: .init(string: content), word_range: .init(2...6))
let new_post = appended.post let new_post = appended.post
try new_post.testAttributes(conditions: [ try new_post.testAttributes(conditions: [
{ let link = $0[.link] as? String; XCTAssertNil(link) }, { 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) } { let link = $0[.link] as? String; XCTAssertNil(link) }
]) ])
@@ -231,13 +238,13 @@ class ReplyTests: XCTestCase {
""" """
let profile = Profile(name: "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(1...5)) let appended = append_user_tag(tag: tag, post: .init(string: content), word_range: .init(1...5))
let new_post = appended.post let new_post = appended.post
try new_post.testAttributes(conditions: [ try new_post.testAttributes(conditions: [
{ let link = $0[.link] as? String; XCTAssertNil(link) }, { 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) }, { let link = $0[.link] as? String; XCTAssertNil(link) },
]) ])
@@ -248,12 +255,12 @@ class ReplyTests: XCTestCase {
let content = "@jb55" let content = "@jb55"
let profile = Profile(name: "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 appended = append_user_tag(tag: tag, post: .init(string: content), word_range: .init(0...4))
let new_post = appended.post let new_post = appended.post
try new_post.testAttributes(conditions: [ 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) }, { let link = $0[.link] as? String; XCTAssertNil(link) },
]) ])
@@ -264,13 +271,13 @@ class ReplyTests: XCTestCase {
let content = "cc @jb55" let content = "cc @jb55"
let profile = Profile(name: "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 appended = append_user_tag(tag: tag, post: .init(string: content), word_range: .init(3...7))
let new_post = appended.post let new_post = appended.post
try new_post.testAttributes(conditions: [ try new_post.testAttributes(conditions: [
{ let link = $0[.link] as? String; XCTAssertNil(link) }, { 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) } { let link = $0[.link] as? String; XCTAssertNil(link) }
]) ])
@@ -279,15 +286,19 @@ class ReplyTests: XCTestCase {
func testNoReply() throws { func testNoReply() throws {
let content = "this is a #[0] reply" let content = "this is a #[0] reply"
let blocks = parse_note_content(content: content, tags: []).blocks let ev = NostrEvent(content: content, keypair: test_keypair, tags: [])!
let event_refs = interpret_event_refs(blocks: blocks, 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) XCTAssertEqual(event_refs.count, 0)
} }
func testParseMention() throws { 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) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
XCTAssertEqual(parsed[0].is_text, "this is ") XCTAssertEqual(parsed[0].is_text, "this is ")
@@ -301,76 +312,71 @@ class ReplyTests: XCTestCase {
} }
func testBech32MentionAtStart() throws { func testBech32MentionAtStart() throws {
let pk = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s" let pk = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
let hex_pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" let content = "@\(pk.npub) hello there"
let content = "@\(pk) hello there"
let blocks = parse_post_blocks(content: content) let blocks = parse_post_blocks(content: content)
XCTAssertEqual(blocks.count, 2) 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") XCTAssertEqual(blocks[1].is_text, " hello there")
} }
func testBech32MentionAtEnd() throws { func testBech32MentionAtEnd() throws {
let pk = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s" let pk = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
let hex_pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" let content = "this is a @\(pk.npub)"
let content = "this is a @\(pk)"
let blocks = parse_post_blocks(content: content) let blocks = parse_post_blocks(content: content)
XCTAssertEqual(blocks.count, 2) 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 ") XCTAssertEqual(blocks[0].is_text, "this is a ")
} }
func testNpubMention() throws { func testNpubMention() throws {
let evid = "0000000000000000000000000000000000000000000000000000000000000005" let evid = NoteId(hex: "71ba3e5ddaf48103be294aa370e470fb60b6c8bca3fb01706eecd00054c2f588")!
let pk = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s" let pk = Pubkey(hex: "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245")!
let hex_pk = "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245" let content = "this is a @\(pk.npub) mention"
let content = "this is a @\(pk) mention"
let reply_ref = ReferencedId(ref_id: evid, relay_id: nil, key: "e")
let blocks = parse_post_blocks(content: content) 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)! let ev = post_to_event(post: post, keypair: test_keypair_full)!
XCTAssertEqual(ev.tags.count, 2) XCTAssertEqual(ev.tags.count, 2)
XCTAssertEqual(blocks.count, 3) 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") XCTAssertEqual(ev.content, "this is a nostr:npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s mention")
} }
func testNsecMention() throws { func testNsecMention() throws {
let evid = "0000000000000000000000000000000000000000000000000000000000000005" let evid = NoteId(hex: "71ba3e5ddaf48103be294aa370e470fb60b6c8bca3fb01706eecd00054c2f588")!
let pk = "nsec1jmzdz7d0ldqctdxwm5fzue277ttng2pk28n2u8wntc2r4a0w96ssnyukg7" let pk = Pubkey(hex: "ccf95d668650178defca5ac503693b6668eb77895f610178ff8ed9fe5cf9482e")!
let hex_pk = "ccf95d668650178defca5ac503693b6668eb77895f610178ff8ed9fe5cf9482e" let nsec = "nsec1jmzdz7d0ldqctdxwm5fzue277ttng2pk28n2u8wntc2r4a0w96ssnyukg7"
let content = "this is a @\(pk) mention" let content = "this is a @\(nsec) mention"
let reply_ref = ReferencedId(ref_id: evid, relay_id: nil, key: "e")
let blocks = parse_post_blocks(content: content) 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)! let ev = post_to_event(post: post, keypair: test_keypair_full)!
XCTAssertEqual(ev.tags.count, 2) XCTAssertEqual(ev.tags.count, 2)
XCTAssertEqual(blocks.count, 3) 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") XCTAssertEqual(ev.content, "this is a nostr:npub1enu46e5x2qtcmm72ttzsx6fmve5wkauftassz78l3mvluh8efqhqejf3v4 mention")
} }
func testReplyMentions() throws { func testReplyMentions() throws {
let privkey = "0fc2092231f958f8d57d66f5e238bb45b6a2571f44c0ce024bbc6f3a9c8a15fe" let pubkey = Pubkey(hex: "30c6d1dc7f7c156794fa15055e651b758a61b99f50fcf759de59386050bf6ae2")!
let pubkey = "30c6d1dc7f7c156794fa15055e651b758a61b99f50fcf759de59386050bf6ae2" let thread_id = NoteId(hex: "a250fc93570c3e87f9c9b08d6b3ef7b8e05d346df8a52c69e30ffecdb178fb9e")!
let npub = "npub1xrrdrhrl0s2k0986z5z4uegmwk9xrwvl2r70wkw7tyuxq59ldt3qh09eay" let reply_id = NoteId(hex: "9a180a10f16dac9566543ad1fc29616aab272b0cf123ab5d58843e16f4ef03a3")!
let refs = [ let refs: [RefId] = [
ReferencedId(ref_id: "thread_id", relay_id: nil, key: "e"), .event(thread_id),
ReferencedId(ref_id: "reply_id", relay_id: nil, key: "e"), .event(reply_id),
ReferencedId(ref_id: pubkey, relay_id: nil, key: "p"), .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)! let ev = post_to_event(post: post, keypair: test_keypair_full)!
XCTAssertEqual(ev.content, "this is a (nostr:\(npub)) mention") XCTAssertEqual(ev.content, "this is a (nostr:\(pubkey.npub)) mention")
XCTAssertEqual(ev.tags[2][1], pubkey) XCTAssertEqual(ev.tags[2][1].string(), pubkey.description)
} }
func testInvalidPostReference() throws { func testInvalidPostReference() throws {
@@ -413,14 +419,13 @@ class ReplyTests: XCTestCase {
} }
func testParsePostUriPubkeyReference() throws { func testParsePostUriPubkeyReference() throws {
let id = "6fec2ee6cfff779fe8560976b3d9df782b74577f0caefa7a77c0ed4c3749b5de" let id = Pubkey(hex: "6fec2ee6cfff779fe8560976b3d9df782b74577f0caefa7a77c0ed4c3749b5de")!
let npub = try XCTUnwrap(bech32_pubkey(id)) let parsed = parse_post_blocks(content: "this is a nostr:\(id.npub) event mention")
let parsed = parse_post_blocks(content: "this is a nostr:\(npub) event mention")
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
XCTAssertEqual(parsed[0].is_text, "this is a ") 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") XCTAssertEqual(parsed[2].is_text, " event mention")
guard case .text(let t1) = parsed[0] else { guard case .text(let t1) = parsed[0] else {
@@ -437,14 +442,13 @@ class ReplyTests: XCTestCase {
} }
func testParsePostUriReference() throws { func testParsePostUriReference() throws {
let id = "6fec2ee6cfff779fe8560976b3d9df782b74577f0caefa7a77c0ed4c3749b5de" let id = NoteId(hex: "6fec2ee6cfff779fe8560976b3d9df782b74577f0caefa7a77c0ed4c3749b5de")!
let note_id = try XCTUnwrap(bech32_note_id(id)) let parsed = parse_post_blocks(content: "this is a nostr:\(id.bech32) event mention")
let parsed = parse_post_blocks(content: "this is a nostr:\(note_id) event mention")
XCTAssertNotNil(parsed) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
XCTAssertEqual(parsed[0].is_text, "this is a ") 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") XCTAssertEqual(parsed[2].is_text, " event mention")
guard case .text(let t1) = parsed[0] else { guard case .text(let t1) = parsed[0] else {
@@ -461,8 +465,8 @@ class ReplyTests: XCTestCase {
} }
func testParseInvalidMention() throws { 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) XCTAssertNotNil(parsed)
XCTAssertEqual(parsed.count, 3) XCTAssertEqual(parsed.count, 3)
XCTAssertEqual(parsed[0].is_text, "this is ") XCTAssertEqual(parsed[0].is_text, "this is ")

Some files were not shown because too many files have changed in this diff Show More