mirror of
git://jb55.com/damus
synced 2024-09-29 16:30:44 +00:00
Compare commits
14 Commits
23138c5e03
...
2c84184dbd
Author | SHA1 | Date | |
---|---|---|---|
|
2c84184dbd | ||
|
901a6fc98f | ||
|
a0f6bdd8d9 | ||
|
8feb228ea0 | ||
|
b148fb735e | ||
|
0a9bcb6189 | ||
|
5a68cfa448 | ||
|
c99aaea598 | ||
|
46185c55d1 | ||
|
52aefc8d64 | ||
|
8dbdff7ff0 | ||
|
784fb20b4f | ||
|
0d9954290a | ||
|
13a7ee82d0 |
@ -40,15 +40,32 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard should_display_notification(state: state, event: nostr_event) else {
|
// Don't show notification details that match mute list.
|
||||||
|
// TODO: Remove this code block once we get notification suppression entitlement from Apple. It will be covered by the `guard should_display_notification` block
|
||||||
|
if state.mutelist_manager.is_event_muted(nostr_event) {
|
||||||
|
// We cannot really suppress muted notifications until we have the notification supression entitlement.
|
||||||
|
// The best we can do if we ever get those muted notifications (which we generally won't due to server-side processing) is to obscure the details
|
||||||
|
let content = UNMutableNotificationContent()
|
||||||
|
content.title = NSLocalizedString("Muted event", comment: "Title for a push notification which has been muted")
|
||||||
|
content.body = NSLocalizedString("This is an event that has been muted according to your mute list rules. We cannot suppress this notification, but we obscured the details to respect your preferences", comment: "Description for a push notification which has been muted, and explanation that we cannot suppress it")
|
||||||
|
content.sound = UNNotificationSound.default
|
||||||
|
contentHandler(content)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard should_display_notification(state: state, event: nostr_event, mode: .push) else {
|
||||||
// We should not display notification for this event. Suppress notification.
|
// We should not display notification for this event. Suppress notification.
|
||||||
contentHandler(UNNotificationContent())
|
// contentHandler(UNNotificationContent())
|
||||||
|
// TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification
|
||||||
|
contentHandler(request.content)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let notification_object = generate_local_notification_object(from: nostr_event, state: state) else {
|
guard let notification_object = generate_local_notification_object(from: nostr_event, state: state) else {
|
||||||
// We could not process this notification. Probably an unsupported nostr event kind. Suppress.
|
// We could not process this notification. Probably an unsupported nostr event kind. Suppress.
|
||||||
contentHandler(UNNotificationContent())
|
// contentHandler(UNNotificationContent())
|
||||||
|
// TODO: We cannot really suppress until we have the notification supression entitlement. Show the raw notification
|
||||||
|
contentHandler(request.content)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
<string>File Timestamp</string>
|
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
<array>
|
<array>
|
||||||
<string>C617.1</string>
|
<string>C617.1</string>
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSPrivacyAccessedAPIType</key>
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
<string>File Timestamp</string>
|
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
<array>
|
<array>
|
||||||
<string>C617.1</string>
|
<string>C617.1</string>
|
||||||
|
@ -136,7 +136,6 @@
|
|||||||
4C363A94282704FA006E126D /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A93282704FA006E126D /* Post.swift */; };
|
4C363A94282704FA006E126D /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A93282704FA006E126D /* Post.swift */; };
|
||||||
4C363A962827096D006E126D /* PostBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A952827096D006E126D /* PostBlock.swift */; };
|
4C363A962827096D006E126D /* PostBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A952827096D006E126D /* PostBlock.swift */; };
|
||||||
4C363A9A28283854006E126D /* Reply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9928283854006E126D /* Reply.swift */; };
|
4C363A9A28283854006E126D /* Reply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9928283854006E126D /* Reply.swift */; };
|
||||||
4C363A9C282838B9006E126D /* EventRef.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9B282838B9006E126D /* EventRef.swift */; };
|
|
||||||
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9D2828A822006E126D /* ReplyTests.swift */; };
|
4C363A9E2828A822006E126D /* ReplyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9D2828A822006E126D /* ReplyTests.swift */; };
|
||||||
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9F2828A8DD006E126D /* LikeTests.swift */; };
|
4C363AA02828A8DD006E126D /* LikeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9F2828A8DD006E126D /* LikeTests.swift */; };
|
||||||
4C363AA228296A7E006E126D /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA128296A7E006E126D /* SearchView.swift */; };
|
4C363AA228296A7E006E126D /* SearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363AA128296A7E006E126D /* SearchView.swift */; };
|
||||||
@ -465,6 +464,8 @@
|
|||||||
D72A2D022AD9C136002AFF62 /* EventViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */; };
|
D72A2D022AD9C136002AFF62 /* EventViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */; };
|
||||||
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */; };
|
D72A2D052AD9C1B5002AFF62 /* MockDamusState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */; };
|
||||||
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */; };
|
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */; };
|
||||||
|
D72E12782BEED22500F4F781 /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E12772BEED22400F4F781 /* Array.swift */; };
|
||||||
|
D72E127A2BEEEED000F4F781 /* NostrFilterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */; };
|
||||||
D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */; };
|
D7315A2A2ACDF3B70036E30A /* DamusCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */; };
|
||||||
D7315A2C2ACDF4DA0036E30A /* DamusCacheManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */; };
|
D7315A2C2ACDF4DA0036E30A /* DamusCacheManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */; };
|
||||||
D7373BA62B688EA300F7783D /* DamusPurpleTranslationSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */; };
|
D7373BA62B688EA300F7783D /* DamusPurpleTranslationSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */; };
|
||||||
@ -555,7 +556,6 @@
|
|||||||
D7CCFC072B05833200323D86 /* NdbNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90548A2A6AEDEE00811EEC /* NdbNote.swift */; };
|
D7CCFC072B05833200323D86 /* NdbNote.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C90548A2A6AEDEE00811EEC /* NdbNote.swift */; };
|
||||||
D7CCFC082B05834500323D86 /* NoteId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF42A740BB7007AEB17 /* NoteId.swift */; };
|
D7CCFC082B05834500323D86 /* NoteId.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CC14FF42A740BB7007AEB17 /* NoteId.swift */; };
|
||||||
D7CCFC0B2B0585EA00323D86 /* nostrdb.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CE9FBB82A6B3B26007E485C /* nostrdb.c */; settings = {COMPILER_FLAGS = "-w"; }; };
|
D7CCFC0B2B0585EA00323D86 /* nostrdb.c in Sources */ = {isa = PBXBuildFile; fileRef = 4CE9FBB82A6B3B26007E485C /* nostrdb.c */; settings = {COMPILER_FLAGS = "-w"; }; };
|
||||||
D7CCFC0E2B0587C300323D86 /* EventRef.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9B282838B9006E126D /* EventRef.swift */; };
|
|
||||||
D7CCFC0F2B0587F600323D86 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8B28398BC6008A31F1 /* Keys.swift */; };
|
D7CCFC0F2B0587F600323D86 /* Keys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C285C8B28398BC6008A31F1 /* Keys.swift */; };
|
||||||
D7CCFC102B05880F00323D86 /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B7BF12A71B6540049DEE7 /* Id.swift */; };
|
D7CCFC102B05880F00323D86 /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C2B7BF12A71B6540049DEE7 /* Id.swift */; };
|
||||||
D7CCFC112B05884E00323D86 /* AsciiCharacter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5D5C9C2A6B2CB40024563C /* AsciiCharacter.swift */; };
|
D7CCFC112B05884E00323D86 /* AsciiCharacter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C5D5C9C2A6B2CB40024563C /* AsciiCharacter.swift */; };
|
||||||
@ -615,6 +615,7 @@
|
|||||||
D7CE1B472B0BE719002EDAD4 /* NativeObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9462A9AD44700DC3548 /* NativeObject.swift */; };
|
D7CE1B472B0BE719002EDAD4 /* NativeObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B9462A9AD44700DC3548 /* NativeObject.swift */; };
|
||||||
D7CE1B482B0BE719002EDAD4 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93D2A9AD44700DC3548 /* Message.swift */; };
|
D7CE1B482B0BE719002EDAD4 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C32B93D2A9AD44700DC3548 /* Message.swift */; };
|
||||||
D7CE1B492B0BE729002EDAD4 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; };
|
D7CE1B492B0BE729002EDAD4 /* DisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C9BB83029C0ED4F00FC4E37 /* DisplayName.swift */; };
|
||||||
|
D7D2A3812BF815D000E4B42B /* PushNotificationClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */; };
|
||||||
D7DBD41F2B02F15E002A6197 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
|
D7DBD41F2B02F15E002A6197 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
|
||||||
D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; };
|
D7DEEF2F2A8C021E00E0C99F /* NostrEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */; };
|
||||||
D7EDED152B11776B0018B19C /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
|
D7EDED152B11776B0018B19C /* LibreTranslateServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AE45AF5297BB2E700C1D842 /* LibreTranslateServer.swift */; };
|
||||||
@ -928,7 +929,6 @@
|
|||||||
4C363A93282704FA006E126D /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; };
|
4C363A93282704FA006E126D /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; };
|
||||||
4C363A952827096D006E126D /* PostBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostBlock.swift; sourceTree = "<group>"; };
|
4C363A952827096D006E126D /* PostBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostBlock.swift; sourceTree = "<group>"; };
|
||||||
4C363A9928283854006E126D /* Reply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reply.swift; sourceTree = "<group>"; };
|
4C363A9928283854006E126D /* Reply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reply.swift; sourceTree = "<group>"; };
|
||||||
4C363A9B282838B9006E126D /* EventRef.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventRef.swift; sourceTree = "<group>"; };
|
|
||||||
4C363A9D2828A822006E126D /* ReplyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyTests.swift; sourceTree = "<group>"; };
|
4C363A9D2828A822006E126D /* ReplyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyTests.swift; sourceTree = "<group>"; };
|
||||||
4C363A9F2828A8DD006E126D /* LikeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikeTests.swift; sourceTree = "<group>"; };
|
4C363A9F2828A8DD006E126D /* LikeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LikeTests.swift; sourceTree = "<group>"; };
|
||||||
4C363AA128296A7E006E126D /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
|
4C363AA128296A7E006E126D /* SearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchView.swift; sourceTree = "<group>"; };
|
||||||
@ -1394,6 +1394,8 @@
|
|||||||
D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventViewTests.swift; sourceTree = "<group>"; };
|
D72A2CFF2AD9B66B002AFF62 /* EventViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventViewTests.swift; sourceTree = "<group>"; };
|
||||||
D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDamusState.swift; sourceTree = "<group>"; };
|
D72A2D042AD9C1B5002AFF62 /* MockDamusState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDamusState.swift; sourceTree = "<group>"; };
|
||||||
D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProfiles.swift; sourceTree = "<group>"; };
|
D72A2D062AD9C1FB002AFF62 /* MockProfiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockProfiles.swift; sourceTree = "<group>"; };
|
||||||
|
D72E12772BEED22400F4F781 /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = "<group>"; };
|
||||||
|
D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrFilterTests.swift; sourceTree = "<group>"; };
|
||||||
D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManager.swift; sourceTree = "<group>"; };
|
D7315A292ACDF3B70036E30A /* DamusCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManager.swift; sourceTree = "<group>"; };
|
||||||
D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManagerTests.swift; sourceTree = "<group>"; };
|
D7315A2B2ACDF4DA0036E30A /* DamusCacheManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusCacheManagerTests.swift; sourceTree = "<group>"; };
|
||||||
D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleTranslationSetupView.swift; sourceTree = "<group>"; };
|
D7373BA52B688EA200F7783D /* DamusPurpleTranslationSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleTranslationSetupView.swift; sourceTree = "<group>"; };
|
||||||
@ -1436,6 +1438,7 @@
|
|||||||
D7CB5D5E2B11770C00AD4105 /* FollowState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowState.swift; sourceTree = "<group>"; };
|
D7CB5D5E2B11770C00AD4105 /* FollowState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FollowState.swift; sourceTree = "<group>"; };
|
||||||
D7CBD1D32B8D21DC00BFD889 /* DamusPurpleNotificationManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleNotificationManagement.swift; sourceTree = "<group>"; };
|
D7CBD1D32B8D21DC00BFD889 /* DamusPurpleNotificationManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleNotificationManagement.swift; sourceTree = "<group>"; };
|
||||||
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleImpendingExpirationTests.swift; sourceTree = "<group>"; };
|
D7CBD1D52B8D509800BFD889 /* DamusPurpleImpendingExpirationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusPurpleImpendingExpirationTests.swift; sourceTree = "<group>"; };
|
||||||
|
D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationClient.swift; sourceTree = "<group>"; };
|
||||||
D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = "<group>"; };
|
D7DEEF2E2A8C021E00E0C99F /* NostrEventTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrEventTests.swift; sourceTree = "<group>"; };
|
||||||
D7EDED1B2B1178FE0018B19C /* NoteContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContent.swift; sourceTree = "<group>"; };
|
D7EDED1B2B1178FE0018B19C /* NoteContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoteContent.swift; sourceTree = "<group>"; };
|
||||||
D7EDED1D2B11797D0018B19C /* LongformEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongformEvent.swift; sourceTree = "<group>"; };
|
D7EDED1D2B11797D0018B19C /* LongformEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LongformEvent.swift; sourceTree = "<group>"; };
|
||||||
@ -1662,6 +1665,7 @@
|
|||||||
D74AAFC12B153395006CF0F4 /* HeadlessDamusState.swift */,
|
D74AAFC12B153395006CF0F4 /* HeadlessDamusState.swift */,
|
||||||
B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */,
|
B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */,
|
||||||
B533694D2B66D791008A805E /* MutelistManager.swift */,
|
B533694D2B66D791008A805E /* MutelistManager.swift */,
|
||||||
|
D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1793,7 +1797,6 @@
|
|||||||
4C45E5002BED4CE10025A428 /* NIP10 */ = {
|
4C45E5002BED4CE10025A428 /* NIP10 */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
4C363A9B282838B9006E126D /* EventRef.swift */,
|
|
||||||
4C45E5012BED4D000025A428 /* ThreadReply.swift */,
|
4C45E5012BED4D000025A428 /* ThreadReply.swift */,
|
||||||
);
|
);
|
||||||
path = NIP10;
|
path = NIP10;
|
||||||
@ -2575,6 +2578,7 @@
|
|||||||
D72927AC2BAB515C00F93E90 /* RelayURLTests.swift */,
|
D72927AC2BAB515C00F93E90 /* RelayURLTests.swift */,
|
||||||
D753CEA92BE9DE04001C3A5D /* MutingTests.swift */,
|
D753CEA92BE9DE04001C3A5D /* MutingTests.swift */,
|
||||||
4C2D34402BDAF1B300F9FB44 /* NIP10Tests.swift */,
|
4C2D34402BDAF1B300F9FB44 /* NIP10Tests.swift */,
|
||||||
|
D72E12792BEEEED000F4F781 /* NostrFilterTests.swift */,
|
||||||
);
|
);
|
||||||
path = damusTests;
|
path = damusTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -2699,6 +2703,7 @@
|
|||||||
children = (
|
children = (
|
||||||
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */,
|
7C95CAED299DCEF1009DCB67 /* KFOptionSetter+.swift */,
|
||||||
4C7D09752A0AF19E00943473 /* FillAndStroke.swift */,
|
4C7D09752A0AF19E00943473 /* FillAndStroke.swift */,
|
||||||
|
D72E12772BEED22400F4F781 /* Array.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -3255,6 +3260,7 @@
|
|||||||
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
|
4CE8794829941DA700F758CC /* RelayFilters.swift in Sources */,
|
||||||
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
|
4CEE2B02280B39E800AB5EEF /* EventActionBar.swift in Sources */,
|
||||||
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,
|
4C3BEFE0281DE1ED00B3DE84 /* DamusState.swift in Sources */,
|
||||||
|
D72E12782BEED22500F4F781 /* Array.swift in Sources */,
|
||||||
4C198DF529F88D2E004C165C /* ImageMetadata.swift in Sources */,
|
4C198DF529F88D2E004C165C /* ImageMetadata.swift in Sources */,
|
||||||
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */,
|
4CCEB7AE29B53D260078AA28 /* SearchingEventView.swift in Sources */,
|
||||||
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
4CF0ABE929844AF100D66079 /* AnyCodable.swift in Sources */,
|
||||||
@ -3288,6 +3294,7 @@
|
|||||||
5CF2DCCC2AA3AF0B00984B8D /* RelayPicView.swift in Sources */,
|
5CF2DCCC2AA3AF0B00984B8D /* RelayPicView.swift in Sources */,
|
||||||
4C687C242A5FA86D0092C550 /* SearchHeaderView.swift in Sources */,
|
4C687C242A5FA86D0092C550 /* SearchHeaderView.swift in Sources */,
|
||||||
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */,
|
64FBD06F296255C400D9D3B2 /* Theme.swift in Sources */,
|
||||||
|
D7D2A3812BF815D000E4B42B /* PushNotificationClient.swift in Sources */,
|
||||||
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
|
4C1A9A2329DDDB8100516EAC /* IconLabel.swift in Sources */,
|
||||||
4CA352AC2A76C07F003BB08B /* NewUnmutesNotify.swift in Sources */,
|
4CA352AC2A76C07F003BB08B /* NewUnmutesNotify.swift in Sources */,
|
||||||
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
|
4C3EA64928FF597700C48A62 /* bech32.c in Sources */,
|
||||||
@ -3350,7 +3357,6 @@
|
|||||||
4CE1399029F0661A00AC6A0B /* RepostAction.swift in Sources */,
|
4CE1399029F0661A00AC6A0B /* RepostAction.swift in Sources */,
|
||||||
4CE1399229F0666100AC6A0B /* ShareActionButton.swift in Sources */,
|
4CE1399229F0666100AC6A0B /* ShareActionButton.swift in Sources */,
|
||||||
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */,
|
4C42812C298C848200DBF26F /* TranslateView.swift in Sources */,
|
||||||
4C363A9C282838B9006E126D /* EventRef.swift in Sources */,
|
|
||||||
4C5E54032A9522F600FF6E60 /* UserStatus.swift in Sources */,
|
4C5E54032A9522F600FF6E60 /* UserStatus.swift in Sources */,
|
||||||
4C7D095F2A098C5D00943473 /* ConnectWalletView.swift in Sources */,
|
4C7D095F2A098C5D00943473 /* ConnectWalletView.swift in Sources */,
|
||||||
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */,
|
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */,
|
||||||
@ -3555,6 +3561,7 @@
|
|||||||
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */,
|
4C8D00D429E3C5D40036AF10 /* NIP19Tests.swift in Sources */,
|
||||||
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */,
|
3A30410129AB12AA008A0F29 /* EventGroupViewTests.swift in Sources */,
|
||||||
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */,
|
501F8C822A0224EB001AFC1D /* KeychainStorageTests.swift in Sources */,
|
||||||
|
D72E127A2BEEEED000F4F781 /* NostrFilterTests.swift in Sources */,
|
||||||
B5B4D1432B37D47600844320 /* NdbExtensions.swift in Sources */,
|
B5B4D1432B37D47600844320 /* NdbExtensions.swift in Sources */,
|
||||||
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
|
3ACBCB78295FE5C70037388A /* TimeAgoTests.swift in Sources */,
|
||||||
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */,
|
D72A2D072AD9C1FB002AFF62 /* MockProfiles.swift in Sources */,
|
||||||
@ -3606,7 +3613,6 @@
|
|||||||
D798D21F2B0858D600234419 /* MigratedTypes.swift in Sources */,
|
D798D21F2B0858D600234419 /* MigratedTypes.swift in Sources */,
|
||||||
D7CE1B472B0BE719002EDAD4 /* NativeObject.swift in Sources */,
|
D7CE1B472B0BE719002EDAD4 /* NativeObject.swift in Sources */,
|
||||||
D7CB5D552B11758A00AD4105 /* UnmuteThreadNotify.swift in Sources */,
|
D7CB5D552B11758A00AD4105 /* UnmuteThreadNotify.swift in Sources */,
|
||||||
D7CCFC0E2B0587C300323D86 /* EventRef.swift in Sources */,
|
|
||||||
D7CCFC192B058A3F00323D86 /* Block.swift in Sources */,
|
D7CCFC192B058A3F00323D86 /* Block.swift in Sources */,
|
||||||
D7CCFC112B05884E00323D86 /* AsciiCharacter.swift in Sources */,
|
D7CCFC112B05884E00323D86 /* AsciiCharacter.swift in Sources */,
|
||||||
D798D2202B08592000234419 /* NdbTagIterator.swift in Sources */,
|
D798D2202B08592000234419 /* NdbTagIterator.swift in Sources */,
|
||||||
@ -3912,7 +3918,7 @@
|
|||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 2;
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
ENABLE_TESTABILITY = YES;
|
ENABLE_TESTABILITY = YES;
|
||||||
@ -3979,7 +3985,7 @@
|
|||||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
COPY_PHASE_STRIP = NO;
|
COPY_PHASE_STRIP = NO;
|
||||||
CURRENT_PROJECT_VERSION = 2;
|
CURRENT_PROJECT_VERSION = 4;
|
||||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
ENABLE_NS_ASSERTIONS = NO;
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
@ -59,26 +59,24 @@ func parse_note_content(content: NoteContent) -> Blocks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func interpret_event_refs_ndb(blocks: [Block], tags: TagsSequence) -> [EventRef] {
|
func interpret_event_refs(tags: TagsSequence) -> ThreadReply? {
|
||||||
if tags.count == 0 {
|
// migration is long over, lets just do this to fix tests
|
||||||
return []
|
return interpret_event_refs_ndb(tags: tags)
|
||||||
}
|
|
||||||
|
|
||||||
/// build a set of indices for each event mention
|
|
||||||
let mention_indices = build_mention_indices(blocks, type: .e)
|
|
||||||
|
|
||||||
/// simpler case with no mentions
|
|
||||||
if mention_indices.count == 0 {
|
|
||||||
return interp_event_refs_without_mentions_ndb(tags.note.referenced_noterefs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return interp_event_refs_with_mentions_ndb(tags: tags, mention_indices: mention_indices)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func interp_event_refs_without_mentions_ndb(_ ev_tags: References<NoteRef>) -> [EventRef] {
|
func interpret_event_refs_ndb(tags: TagsSequence) -> ThreadReply? {
|
||||||
var evrefs: [EventRef] = []
|
if tags.count == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return interp_event_refs_without_mentions_ndb(References<NoteRef>(tags: tags))
|
||||||
|
}
|
||||||
|
|
||||||
|
func interp_event_refs_without_mentions_ndb(_ ev_tags: References<NoteRef>) -> ThreadReply? {
|
||||||
var first: Bool = true
|
var first: Bool = true
|
||||||
var root_id: NoteRef? = nil
|
var root_id: NoteRef? = nil
|
||||||
|
var reply_id: NoteRef? = nil
|
||||||
|
var mention: NoteRef? = nil
|
||||||
var any_marker: Bool = false
|
var any_marker: Bool = false
|
||||||
|
|
||||||
for ref in ev_tags {
|
for ref in ev_tags {
|
||||||
@ -86,47 +84,32 @@ func interp_event_refs_without_mentions_ndb(_ ev_tags: References<NoteRef>) -> [
|
|||||||
any_marker = true
|
any_marker = true
|
||||||
switch marker {
|
switch marker {
|
||||||
case .root: root_id = ref
|
case .root: root_id = ref
|
||||||
case .reply: evrefs.append(.reply(ref))
|
case .reply: reply_id = ref
|
||||||
case .mention: evrefs.append(.mention(.noteref(ref)))
|
case .mention: mention = ref
|
||||||
}
|
}
|
||||||
} else {
|
// deprecated form, only activate if we don't have any markers set
|
||||||
if !any_marker && first {
|
} else if !any_marker {
|
||||||
|
if first {
|
||||||
root_id = ref
|
root_id = ref
|
||||||
first = false
|
first = false
|
||||||
} else if !any_marker {
|
|
||||||
evrefs.append(.reply(ref))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let root_id {
|
|
||||||
if evrefs.count == 0 {
|
|
||||||
return [.reply_to_root(root_id)]
|
|
||||||
} else {
|
|
||||||
evrefs.insert(.thread_id(root_id), at: 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return evrefs
|
|
||||||
}
|
|
||||||
|
|
||||||
func interp_event_refs_with_mentions_ndb(tags: TagsSequence, mention_indices: Set<Int>) -> [EventRef] {
|
|
||||||
var mentions: [EventRef] = []
|
|
||||||
var ev_refs: [NoteRef] = []
|
|
||||||
var i: Int = 0
|
|
||||||
|
|
||||||
for tag in tags {
|
|
||||||
if let note_id = NoteRef.from_tag(tag: tag) {
|
|
||||||
if mention_indices.contains(i) {
|
|
||||||
mentions.append(.mention(.noteref(note_id, index: i)))
|
|
||||||
} else {
|
} else {
|
||||||
ev_refs.append(note_id)
|
reply_id = ref
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i += 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var replies = interp_event_refs_without_mentions(ev_refs)
|
// If either reply or root_id is blank while the other is not, then this is
|
||||||
replies.append(contentsOf: mentions)
|
// considered reply-to-root. We should always have a root and reply tag, if they
|
||||||
return replies
|
// are equal this is reply-to-root
|
||||||
|
if reply_id == nil && root_id != nil {
|
||||||
|
reply_id = root_id
|
||||||
|
} else if root_id == nil && reply_id != nil {
|
||||||
|
root_id = reply_id
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let reply_id, let root_id else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ThreadReply(root: root_id, reply: reply_id, mention: mention.map { m in .noteref(m) })
|
||||||
}
|
}
|
||||||
|
@ -308,7 +308,7 @@ struct ContentView: View {
|
|||||||
active_sheet = .onboardingSuggestions
|
active_sheet = .onboardingSuggestions
|
||||||
hasSeenOnboardingSuggestions = true
|
hasSeenOnboardingSuggestions = true
|
||||||
}
|
}
|
||||||
self.appDelegate?.settings = damus_state?.settings
|
self.appDelegate?.state = damus_state
|
||||||
}
|
}
|
||||||
.sheet(item: $active_sheet) { item in
|
.sheet(item: $active_sheet) { item in
|
||||||
switch item {
|
switch item {
|
||||||
|
@ -16,7 +16,7 @@ enum FilterState : Int {
|
|||||||
func filter(ev: NostrEvent) -> Bool {
|
func filter(ev: NostrEvent) -> Bool {
|
||||||
switch self {
|
switch self {
|
||||||
case .posts:
|
case .posts:
|
||||||
return ev.known_kind == .boost || !ev.is_reply(.empty)
|
return ev.known_kind == .boost || !ev.is_reply()
|
||||||
case .posts_and_replies:
|
case .posts_and_replies:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ class DamusState: HeadlessDamusState {
|
|||||||
let video: VideoController
|
let video: VideoController
|
||||||
let ndb: Ndb
|
let ndb: Ndb
|
||||||
var purple: DamusPurple
|
var purple: DamusPurple
|
||||||
|
var push_notification_client: PushNotificationClient
|
||||||
|
|
||||||
init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [RelayURL], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter) {
|
init(pool: RelayPool, keypair: Keypair, likes: EventCounter, boosts: EventCounter, contacts: Contacts, mutelist_manager: MutelistManager, profiles: Profiles, dms: DirectMessagesModel, previews: PreviewCache, zaps: Zaps, lnurls: LNUrls, settings: UserSettingsStore, relay_filters: RelayFilters, relay_model_cache: RelayModelCache, drafts: Drafts, events: EventCache, bookmarks: BookmarksManager, postbox: PostBox, bootstrap_relays: [RelayURL], replies: ReplyCounter, wallet: WalletModel, nav: NavigationCoordinator, music: MusicController?, video: VideoController, ndb: Ndb, purple: DamusPurple? = nil, quote_reposts: EventCounter) {
|
||||||
self.pool = pool
|
self.pool = pool
|
||||||
@ -68,6 +69,7 @@ class DamusState: HeadlessDamusState {
|
|||||||
keypair: keypair
|
keypair: keypair
|
||||||
)
|
)
|
||||||
self.quote_reposts = quote_reposts
|
self.quote_reposts = quote_reposts
|
||||||
|
self.push_notification_client = PushNotificationClient(keypair: keypair, settings: settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
@discardableResult
|
@discardableResult
|
||||||
|
@ -42,6 +42,10 @@ enum HomeResubFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class HomeModel: ContactsDelegate {
|
class HomeModel: ContactsDelegate {
|
||||||
|
// The maximum amount of contacts placed on a home feed subscription filter.
|
||||||
|
// If the user has more contacts, chunking or other techniques will be used to avoid sending huge filters
|
||||||
|
let MAX_CONTACTS_ON_FILTER = 500
|
||||||
|
|
||||||
// Don't trigger a user notification for events older than a certain age
|
// Don't trigger a user notification for events older than a certain age
|
||||||
static let event_max_age_for_notification: TimeInterval = EVENT_MAX_AGE_FOR_NOTIFICATION
|
static let event_max_age_for_notification: TimeInterval = EVENT_MAX_AGE_FOR_NOTIFICATION
|
||||||
|
|
||||||
@ -545,7 +549,8 @@ class HomeModel: ContactsDelegate {
|
|||||||
notifications_filter.limit = 500
|
notifications_filter.limit = 500
|
||||||
|
|
||||||
var notifications_filters = [notifications_filter]
|
var notifications_filters = [notifications_filter]
|
||||||
var contacts_filters = [contacts_filter, our_contacts_filter, our_blocklist_filter, our_old_blocklist_filter]
|
let contacts_filter_chunks = contacts_filter.chunked(on: .authors, into: MAX_CONTACTS_ON_FILTER)
|
||||||
|
var contacts_filters = contacts_filter_chunks + [our_contacts_filter, our_blocklist_filter, our_old_blocklist_filter]
|
||||||
var dms_filters = [dms_filter, our_dms_filter]
|
var dms_filters = [dms_filter, our_dms_filter]
|
||||||
let last_of_kind = get_last_of_kind(relay_id: relay_id)
|
let last_of_kind = get_last_of_kind(relay_id: relay_id)
|
||||||
|
|
||||||
@ -598,7 +603,7 @@ class HomeModel: ContactsDelegate {
|
|||||||
home_filter.authors = friends
|
home_filter.authors = friends
|
||||||
home_filter.limit = 500
|
home_filter.limit = 500
|
||||||
|
|
||||||
var home_filters = [home_filter]
|
var home_filters = home_filter.chunked(on: .authors, into: MAX_CONTACTS_ON_FILTER)
|
||||||
|
|
||||||
let followed_hashtags = Array(damus_state.contacts.get_followed_hashtags())
|
let followed_hashtags = Array(damus_state.contacts.get_followed_hashtags())
|
||||||
if followed_hashtags.count != 0 {
|
if followed_hashtags.count != 0 {
|
||||||
@ -728,7 +733,7 @@ class HomeModel: ContactsDelegate {
|
|||||||
func got_new_dm(notifs: NewEventsBits, ev: NostrEvent) {
|
func got_new_dm(notifs: NewEventsBits, ev: NostrEvent) {
|
||||||
notification_status.new_events = notifs
|
notification_status.new_events = notifs
|
||||||
|
|
||||||
guard should_display_notification(state: damus_state, event: ev),
|
guard should_display_notification(state: damus_state, event: ev, mode: .local),
|
||||||
let notification_object = generate_local_notification_object(from: ev, state: damus_state)
|
let notification_object = generate_local_notification_object(from: ev, state: damus_state)
|
||||||
else {
|
else {
|
||||||
return
|
return
|
||||||
|
@ -13,7 +13,7 @@ import UIKit
|
|||||||
let EVENT_MAX_AGE_FOR_NOTIFICATION: TimeInterval = 12 * 60 * 60
|
let EVENT_MAX_AGE_FOR_NOTIFICATION: TimeInterval = 12 * 60 * 60
|
||||||
|
|
||||||
func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent) {
|
func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent) {
|
||||||
guard should_display_notification(state: state, event: ev) else {
|
guard should_display_notification(state: state, event: ev, mode: .local) else {
|
||||||
// We should not display notification. Exit.
|
// We should not display notification. Exit.
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -25,7 +25,12 @@ func process_local_notification(state: HeadlessDamusState, event ev: NostrEvent)
|
|||||||
create_local_notification(profiles: state.profiles, notify: local_notification)
|
create_local_notification(profiles: state.profiles, notify: local_notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent) -> Bool {
|
func should_display_notification(state: HeadlessDamusState, event ev: NostrEvent, mode: UserSettingsStore.NotificationsMode) -> Bool {
|
||||||
|
// Do not show notification if it's coming from a mode different from the one selected by our user
|
||||||
|
guard state.settings.notifications_mode == mode else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if ev.known_kind == nil {
|
if ev.known_kind == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
105
damus/Models/PushNotificationClient.swift
Normal file
105
damus/Models/PushNotificationClient.swift
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
//
|
||||||
|
// PushNotificationClient.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2024-05-17.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct PushNotificationClient {
|
||||||
|
let keypair: Keypair
|
||||||
|
let settings: UserSettingsStore
|
||||||
|
private(set) var device_token: Data? = nil
|
||||||
|
|
||||||
|
mutating func set_device_token(new_device_token: Data) async throws {
|
||||||
|
self.device_token = new_device_token
|
||||||
|
if settings.enable_experimental_push_notifications && settings.notifications_mode == .push {
|
||||||
|
try await self.send_token()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func send_token() async throws {
|
||||||
|
guard let device_token else { return }
|
||||||
|
// Send the device token and pubkey to the server
|
||||||
|
let token = device_token.map { String(format: "%02.2hhx", $0) }.joined()
|
||||||
|
|
||||||
|
Log.info("Sending device token to server: %s", for: .push_notifications, token)
|
||||||
|
|
||||||
|
let pubkey = self.keypair.pubkey
|
||||||
|
|
||||||
|
// Send those as JSON to the server
|
||||||
|
let json: [String: Any] = ["deviceToken": token, "pubkey": pubkey.hex()]
|
||||||
|
|
||||||
|
// create post request
|
||||||
|
let url = self.settings.send_device_token_to_localhost ? Constants.DEVICE_TOKEN_RECEIVER_TEST_URL : Constants.DEVICE_TOKEN_RECEIVER_PRODUCTION_URL
|
||||||
|
let json_data = try JSONSerialization.data(withJSONObject: json)
|
||||||
|
|
||||||
|
|
||||||
|
let (data, response) = try await make_nip98_authenticated_request(
|
||||||
|
method: .post,
|
||||||
|
url: url,
|
||||||
|
payload: json_data,
|
||||||
|
payload_type: .json,
|
||||||
|
auth_keypair: self.keypair
|
||||||
|
)
|
||||||
|
|
||||||
|
if let httpResponse = response as? HTTPURLResponse {
|
||||||
|
switch httpResponse.statusCode {
|
||||||
|
case 200:
|
||||||
|
Log.info("Sent device token to Damus push notification server successfully", for: .push_notifications)
|
||||||
|
default:
|
||||||
|
Log.error("Error in sending device_token to Damus push notification server. HTTP status code: %d; Response: %s", for: .push_notifications, httpResponse.statusCode, String(data: data, encoding: .utf8) ?? "Unknown")
|
||||||
|
throw ClientError.http_response_error(status_code: httpResponse.statusCode, response: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func revoke_token() async throws {
|
||||||
|
guard let device_token else { return }
|
||||||
|
// Send the device token and pubkey to the server
|
||||||
|
let token = device_token.map { String(format: "%02.2hhx", $0) }.joined()
|
||||||
|
|
||||||
|
Log.info("Revoking device token from server: %s", for: .push_notifications, token)
|
||||||
|
|
||||||
|
let pubkey = self.keypair.pubkey
|
||||||
|
|
||||||
|
// Send those as JSON to the server
|
||||||
|
let json: [String: Any] = ["deviceToken": token, "pubkey": pubkey.hex()]
|
||||||
|
|
||||||
|
// create post request
|
||||||
|
let url = self.settings.send_device_token_to_localhost ? Constants.DEVICE_TOKEN_REVOKER_TEST_URL : Constants.DEVICE_TOKEN_REVOKER_PRODUCTION_URL
|
||||||
|
let json_data = try JSONSerialization.data(withJSONObject: json)
|
||||||
|
|
||||||
|
|
||||||
|
let (data, response) = try await make_nip98_authenticated_request(
|
||||||
|
method: .post,
|
||||||
|
url: url,
|
||||||
|
payload: json_data,
|
||||||
|
payload_type: .json,
|
||||||
|
auth_keypair: self.keypair
|
||||||
|
)
|
||||||
|
|
||||||
|
if let httpResponse = response as? HTTPURLResponse {
|
||||||
|
switch httpResponse.statusCode {
|
||||||
|
case 200:
|
||||||
|
Log.info("Sent device token removal request to Damus push notification server successfully", for: .push_notifications)
|
||||||
|
default:
|
||||||
|
Log.error("Error in sending device_token removal to Damus push notification server. HTTP status code: %d; Response: %s", for: .push_notifications, httpResponse.statusCode, String(data: data, encoding: .utf8) ?? "Unknown")
|
||||||
|
throw ClientError.http_response_error(status_code: httpResponse.statusCode, response: data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: Helper structures
|
||||||
|
|
||||||
|
extension PushNotificationClient {
|
||||||
|
enum ClientError: Error {
|
||||||
|
case http_response_error(status_code: Int, response: Data)
|
||||||
|
}
|
||||||
|
}
|
@ -60,7 +60,7 @@ class SearchHomeModel: ObservableObject {
|
|||||||
guard sub_id == self.base_subid || sub_id == self.profiles_subid else {
|
guard sub_id == self.base_subid || sub_id == self.profiles_subid else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply(damus_state.keypair)
|
if ev.is_textlike && should_show_event(state: damus_state, ev: ev) && !ev.is_reply()
|
||||||
{
|
{
|
||||||
if !damus_state.settings.multiple_events_per_pubkey && seen_pubkey.contains(ev.pubkey) {
|
if !damus_state.settings.multiple_events_per_pubkey && seen_pubkey.contains(ev.pubkey) {
|
||||||
return
|
return
|
||||||
|
@ -60,7 +60,7 @@ class ThreadModel: ObservableObject {
|
|||||||
var event_filter = NostrFilter()
|
var event_filter = NostrFilter()
|
||||||
var ref_events = NostrFilter()
|
var ref_events = NostrFilter()
|
||||||
|
|
||||||
let thread_id = event.thread_id(keypair: .empty)
|
let thread_id = event.thread_id()
|
||||||
|
|
||||||
ref_events.referenced_ids = [thread_id, event.id]
|
ref_events.referenced_ids = [thread_id, event.id]
|
||||||
ref_events.kinds = [.text]
|
ref_events.kinds = [.text]
|
||||||
|
@ -155,6 +155,9 @@ class UserSettingsStore: ObservableObject {
|
|||||||
@Setting(key: "like_notification", default_value: true)
|
@Setting(key: "like_notification", default_value: true)
|
||||||
var like_notification: Bool
|
var like_notification: Bool
|
||||||
|
|
||||||
|
@StringSetting(key: "notifications_mode", default_value: .local)
|
||||||
|
var notifications_mode: NotificationsMode
|
||||||
|
|
||||||
@Setting(key: "notification_only_from_following", default_value: false)
|
@Setting(key: "notification_only_from_following", default_value: false)
|
||||||
var notification_only_from_following: Bool
|
var notification_only_from_following: Bool
|
||||||
|
|
||||||
@ -326,6 +329,36 @@ class UserSettingsStore: ObservableObject {
|
|||||||
@Setting(key: "latest_contact_event_id", default_value: nil)
|
@Setting(key: "latest_contact_event_id", default_value: nil)
|
||||||
var latest_contact_event_id_hex: String?
|
var latest_contact_event_id_hex: String?
|
||||||
|
|
||||||
|
|
||||||
|
// MARK: Helper types
|
||||||
|
|
||||||
|
enum NotificationsMode: String, CaseIterable, Identifiable, StringCodable, Equatable {
|
||||||
|
var id: String { self.rawValue }
|
||||||
|
|
||||||
|
func to_string() -> String {
|
||||||
|
return rawValue
|
||||||
|
}
|
||||||
|
|
||||||
|
init?(from string: String) {
|
||||||
|
guard let notifications_mode = NotificationsMode(rawValue: string) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self = notifications_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
func text_description() -> String {
|
||||||
|
switch self {
|
||||||
|
case .local:
|
||||||
|
NSLocalizedString("Local", comment: "Option for notification mode setting: Local notification mode")
|
||||||
|
case .push:
|
||||||
|
NSLocalizedString("Push", comment: "Option for notification mode setting: Push notification mode")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case local
|
||||||
|
case push
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pk_setting_key(_ pubkey: Pubkey, key: String) -> String {
|
func pk_setting_key(_ pubkey: Pubkey, key: String) -> String {
|
||||||
|
@ -1,151 +0,0 @@
|
|||||||
//
|
|
||||||
// EventRef.swift
|
|
||||||
// damus
|
|
||||||
//
|
|
||||||
// Created by William Casarin on 2022-05-08.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
enum EventRef: Equatable {
|
|
||||||
case mention(Mention<NoteRef>)
|
|
||||||
case thread_id(NoteRef)
|
|
||||||
case reply(NoteRef)
|
|
||||||
case reply_to_root(NoteRef)
|
|
||||||
|
|
||||||
var note_ref: NoteRef {
|
|
||||||
switch self {
|
|
||||||
case .mention(let mnref): return mnref.ref
|
|
||||||
case .thread_id(let ref): return ref
|
|
||||||
case .reply(let ref): return ref
|
|
||||||
case .reply_to_root(let ref): return ref
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var is_mention: NoteRef? {
|
|
||||||
if case .mention(let m) = self { return m.ref }
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var is_direct_reply: NoteRef? {
|
|
||||||
switch self {
|
|
||||||
case .mention:
|
|
||||||
return nil
|
|
||||||
case .thread_id:
|
|
||||||
return nil
|
|
||||||
case .reply(let refid):
|
|
||||||
return refid
|
|
||||||
case .reply_to_root(let refid):
|
|
||||||
return refid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var is_thread_id: NoteRef? {
|
|
||||||
switch self {
|
|
||||||
case .mention:
|
|
||||||
return nil
|
|
||||||
case .thread_id(let referencedId):
|
|
||||||
return referencedId
|
|
||||||
case .reply:
|
|
||||||
return nil
|
|
||||||
case .reply_to_root(let referencedId):
|
|
||||||
return referencedId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var is_reply: NoteRef? {
|
|
||||||
switch self {
|
|
||||||
case .mention:
|
|
||||||
return nil
|
|
||||||
case .thread_id:
|
|
||||||
return nil
|
|
||||||
case .reply(let refid):
|
|
||||||
return refid
|
|
||||||
case .reply_to_root(let refid):
|
|
||||||
return refid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func build_mention_indices(_ blocks: [Block], type: MentionType) -> Set<Int> {
|
|
||||||
return blocks.reduce(into: []) { acc, block in
|
|
||||||
switch block {
|
|
||||||
case .mention(let m):
|
|
||||||
if m.ref.key == type, let idx = m.index {
|
|
||||||
acc.insert(idx)
|
|
||||||
}
|
|
||||||
case .relay:
|
|
||||||
return
|
|
||||||
case .text:
|
|
||||||
return
|
|
||||||
case .hashtag:
|
|
||||||
return
|
|
||||||
case .url:
|
|
||||||
return
|
|
||||||
case .invoice:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func interp_event_refs_without_mentions(_ refs: [NoteRef]) -> [EventRef] {
|
|
||||||
if refs.count == 0 {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
if refs.count == 1 {
|
|
||||||
return [.reply_to_root(refs[0])]
|
|
||||||
}
|
|
||||||
|
|
||||||
var evrefs: [EventRef] = []
|
|
||||||
var first: Bool = true
|
|
||||||
for ref in refs {
|
|
||||||
if first {
|
|
||||||
evrefs.append(.thread_id(ref))
|
|
||||||
first = false
|
|
||||||
} else {
|
|
||||||
evrefs.append(.reply(ref))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return evrefs
|
|
||||||
}
|
|
||||||
|
|
||||||
func interp_event_refs_with_mentions(tags: Tags, mention_indices: Set<Int>) -> [EventRef] {
|
|
||||||
var mentions: [EventRef] = []
|
|
||||||
var ev_refs: [NoteRef] = []
|
|
||||||
var i: Int = 0
|
|
||||||
|
|
||||||
for tag in tags {
|
|
||||||
if let ref = NoteRef.from_tag(tag: tag) {
|
|
||||||
if mention_indices.contains(i) {
|
|
||||||
let mention = Mention<NoteRef>(index: i, ref: ref)
|
|
||||||
mentions.append(.mention(mention))
|
|
||||||
} else {
|
|
||||||
ev_refs.append(ref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var replies = interp_event_refs_without_mentions(ev_refs)
|
|
||||||
replies.append(contentsOf: mentions)
|
|
||||||
return replies
|
|
||||||
}
|
|
||||||
|
|
||||||
func interpret_event_refs(blocks: [Block], tags: Tags) -> [EventRef] {
|
|
||||||
if tags.count == 0 {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
/// build a set of indices for each event mention
|
|
||||||
let mention_indices = build_mention_indices(blocks, type: .e)
|
|
||||||
|
|
||||||
/// simpler case with no mentions
|
|
||||||
if mention_indices.count == 0 {
|
|
||||||
return interp_event_refs_without_mentions_ndb(References<NoteRef>(tags: tags))
|
|
||||||
}
|
|
||||||
|
|
||||||
return interp_event_refs_with_mentions(tags: tags, mention_indices: mention_indices)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -10,54 +10,23 @@ import Foundation
|
|||||||
|
|
||||||
struct ThreadReply {
|
struct ThreadReply {
|
||||||
let root: NoteRef
|
let root: NoteRef
|
||||||
let reply: NoteRef?
|
let reply: NoteRef
|
||||||
let mention: Mention<NoteRef>?
|
let mention: Mention<NoteRef>?
|
||||||
|
|
||||||
var is_reply_to_root: Bool {
|
var is_reply_to_root: Bool {
|
||||||
guard let reply else {
|
|
||||||
// if we have no reply and only root then this is reply-to-root,
|
|
||||||
// but it should never really be in this form...
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return root.id == reply.id
|
return root.id == reply.id
|
||||||
}
|
}
|
||||||
|
|
||||||
init(root: NoteRef, reply: NoteRef?, mention: Mention<NoteRef>?) {
|
init(root: NoteRef, reply: NoteRef, mention: Mention<NoteRef>?) {
|
||||||
self.root = root
|
self.root = root
|
||||||
self.reply = reply
|
self.reply = reply
|
||||||
self.mention = mention
|
self.mention = mention
|
||||||
}
|
}
|
||||||
|
|
||||||
init?(event_refs: [EventRef]) {
|
init?(tags: TagsSequence) {
|
||||||
var root: NoteRef? = nil
|
guard let tr = interpret_event_refs_ndb(tags: tags) else {
|
||||||
var reply: NoteRef? = nil
|
|
||||||
var mention: Mention<NoteRef>? = nil
|
|
||||||
|
|
||||||
for evref in event_refs {
|
|
||||||
switch evref {
|
|
||||||
case .mention(let m):
|
|
||||||
mention = m
|
|
||||||
case .thread_id(let r):
|
|
||||||
root = r
|
|
||||||
case .reply(let r):
|
|
||||||
reply = r
|
|
||||||
case .reply_to_root(let r):
|
|
||||||
root = r
|
|
||||||
reply = r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reply with no root should be considered reply-to-root
|
|
||||||
if root == nil && reply != nil {
|
|
||||||
root = reply
|
|
||||||
}
|
|
||||||
|
|
||||||
// nip10 threads must have a root
|
|
||||||
guard let root else {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
self = tr
|
||||||
self = ThreadReply(root: root, reply: reply, mention: mention)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,4 +54,68 @@ struct NostrFilter: Codable, Equatable {
|
|||||||
public static func filter_hashtag(_ htags: [String]) -> NostrFilter {
|
public static func filter_hashtag(_ htags: [String]) -> NostrFilter {
|
||||||
NostrFilter(hashtag: htags.map { $0.lowercased() })
|
NostrFilter(hashtag: htags.map { $0.lowercased() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Splits the filter on a given filter path/axis into chunked filters
|
||||||
|
///
|
||||||
|
/// - Parameter path: The path where chunking should be done
|
||||||
|
/// - Parameter chunk_size: The maximum size of each chunk.
|
||||||
|
/// - Returns: An array of arrays, where each contained array is a chunk of the original array with up to `size` elements.
|
||||||
|
func chunked(on path: ChunkPath, into chunk_size: Int) -> [Self] {
|
||||||
|
let chunked_slices = self.get_slice(from: path).chunked(into: chunk_size)
|
||||||
|
var chunked_filters: [NostrFilter] = []
|
||||||
|
for chunked_slice in chunked_slices {
|
||||||
|
var chunked_filter = self
|
||||||
|
chunked_filter.apply_slice(chunked_slice)
|
||||||
|
chunked_filters.append(chunked_filter)
|
||||||
|
}
|
||||||
|
return chunked_filters
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a slice from a NostrFilter on a given path/axis
|
||||||
|
///
|
||||||
|
/// - Parameter path: The path where chunking should be done
|
||||||
|
/// - Parameter chunk_size: The maximum size of each chunk.
|
||||||
|
/// - Returns: An array of arrays, where each contained array is a chunk of the original array with up to `size` elements.
|
||||||
|
func get_slice(from path: ChunkPath) -> Slice {
|
||||||
|
switch path {
|
||||||
|
case .pubkeys:
|
||||||
|
return .pubkeys(self.pubkeys)
|
||||||
|
case .authors:
|
||||||
|
return .authors(self.authors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Overrides one member/axis of a NostrFilter using a specific slice
|
||||||
|
/// - Parameter slice: The slice to be applied on this NostrFilter
|
||||||
|
mutating func apply_slice(_ slice: Slice) {
|
||||||
|
switch slice {
|
||||||
|
case .pubkeys(let pubkeys):
|
||||||
|
self.pubkeys = pubkeys
|
||||||
|
case .authors(let authors):
|
||||||
|
self.authors = authors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// A path to one of the axes of a NostrFilter.
|
||||||
|
enum ChunkPath {
|
||||||
|
case pubkeys
|
||||||
|
case authors
|
||||||
|
// Other paths/axes not supported yet
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the value of a single axis of a NostrFilter
|
||||||
|
enum Slice {
|
||||||
|
case pubkeys([Pubkey]?)
|
||||||
|
case authors([Pubkey]?)
|
||||||
|
|
||||||
|
func chunked(into chunk_size: Int) -> [Slice] {
|
||||||
|
switch self {
|
||||||
|
case .pubkeys(let array):
|
||||||
|
return (array ?? []).chunked(into: chunk_size).map({ .pubkeys($0) })
|
||||||
|
case .authors(let array):
|
||||||
|
return (array ?? []).chunked(into: chunk_size).map({ .authors($0) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ class Constants {
|
|||||||
static let DAMUS_APP_GROUP_IDENTIFIER: String = "group.com.damus"
|
static let DAMUS_APP_GROUP_IDENTIFIER: String = "group.com.damus"
|
||||||
static let DEVICE_TOKEN_RECEIVER_PRODUCTION_URL: URL = URL(string: "https://notify.damus.io:8000/user-info")!
|
static let DEVICE_TOKEN_RECEIVER_PRODUCTION_URL: URL = URL(string: "https://notify.damus.io:8000/user-info")!
|
||||||
static let DEVICE_TOKEN_RECEIVER_TEST_URL: URL = URL(string: "http://localhost:8000/user-info")!
|
static let DEVICE_TOKEN_RECEIVER_TEST_URL: URL = URL(string: "http://localhost:8000/user-info")!
|
||||||
|
static let DEVICE_TOKEN_REVOKER_PRODUCTION_URL: URL = URL(string: "https://notify.damus.io:8000/user-info/remove")!
|
||||||
|
static let DEVICE_TOKEN_REVOKER_TEST_URL: URL = URL(string: "http://localhost:8000/user-info/remove")!
|
||||||
static let MAIN_APP_BUNDLE_IDENTIFIER: String = "com.jb55.damus2"
|
static let MAIN_APP_BUNDLE_IDENTIFIER: String = "com.jb55.damus2"
|
||||||
static let NOTIFICATION_EXTENSION_BUNDLE_IDENTIFIER: String = "com.jb55.damus2.DamusNotificationService"
|
static let NOTIFICATION_EXTENSION_BUNDLE_IDENTIFIER: String = "com.jb55.damus2.DamusNotificationService"
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ class EventCache {
|
|||||||
var ev = event
|
var ev = event
|
||||||
|
|
||||||
while true {
|
while true {
|
||||||
guard let direct_reply = ev.direct_replies(keypair),
|
guard let direct_reply = ev.direct_replies(),
|
||||||
let next_ev = lookup(direct_reply), next_ev != ev
|
let next_ev = lookup(direct_reply), next_ev != ev
|
||||||
else {
|
else {
|
||||||
break
|
break
|
||||||
@ -183,7 +183,7 @@ class EventCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func add_replies(ev: NostrEvent, keypair: Keypair) {
|
func add_replies(ev: NostrEvent, keypair: Keypair) {
|
||||||
if let reply = ev.direct_replies(keypair) {
|
if let reply = ev.direct_replies() {
|
||||||
replies.add(id: reply, reply_id: ev.id)
|
replies.add(id: reply, reply_id: ev.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
26
damus/Util/Extensions/Array.swift
Normal file
26
damus/Util/Extensions/Array.swift
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// Array.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2024-05-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension Array {
|
||||||
|
/// Splits the array into chunks of the specified size.
|
||||||
|
/// - Parameter size: The maximum size of each chunk.
|
||||||
|
/// - Returns: An array of arrays, where each contained array is a chunk of the original array with up to `size` elements.
|
||||||
|
func chunked(into size: Int) -> [[Element]] {
|
||||||
|
guard size > 0 else { return [self] }
|
||||||
|
return stride(from: 0, to: count, by: size).map {
|
||||||
|
Array(self[$0..<Swift.min($0 + size, count)])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension Array where Element: Equatable {
|
||||||
|
mutating func removeAll(equalTo item: Element) {
|
||||||
|
self.removeAll(where: { $0 == item })
|
||||||
|
}
|
||||||
|
}
|
@ -39,7 +39,7 @@ class ReplyCounter {
|
|||||||
|
|
||||||
counted.insert(event.id)
|
counted.insert(event.id)
|
||||||
|
|
||||||
if let reply = event.direct_replies(keypair) {
|
if let reply = event.direct_replies() {
|
||||||
if event.pubkey == our_pubkey {
|
if event.pubkey == our_pubkey {
|
||||||
self.our_replies[reply] = event
|
self.our_replies[reply] = event
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,7 @@ enum Route: Hashable {
|
|||||||
case .AppearanceSettings(let settings):
|
case .AppearanceSettings(let settings):
|
||||||
AppearanceSettingsView(damus_state: damusState, settings: settings)
|
AppearanceSettingsView(damus_state: damusState, settings: settings)
|
||||||
case .NotificationSettings(let settings):
|
case .NotificationSettings(let settings):
|
||||||
NotificationSettingsView(settings: settings)
|
NotificationSettingsView(damus_state: damusState, settings: settings)
|
||||||
case .ZapSettings(let settings):
|
case .ZapSettings(let settings):
|
||||||
ZapSettingsView(settings: settings)
|
ZapSettingsView(settings: settings)
|
||||||
case .TranslationSettings(let settings):
|
case .TranslationSettings(let settings):
|
||||||
|
@ -15,7 +15,7 @@ struct ReplyPart: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if let reply_ref = event.thread_reply(keypair)?.reply {
|
if let reply_ref = event.thread_reply()?.reply {
|
||||||
ReplyDescription(event: event, replying_to: events.lookup(reply_ref.note_id), ndb: ndb)
|
ReplyDescription(event: event, replying_to: events.lookup(reply_ref.note_id), ndb: ndb)
|
||||||
} else {
|
} else {
|
||||||
EmptyView()
|
EmptyView()
|
||||||
|
@ -111,7 +111,7 @@ struct MenuItems: View {
|
|||||||
if event.known_kind != .dm {
|
if event.known_kind != .dm {
|
||||||
MuteDurationMenu { duration in
|
MuteDurationMenu { duration in
|
||||||
if let full_keypair = self.damus_state.keypair.to_full(),
|
if let full_keypair = self.damus_state.keypair.to_full(),
|
||||||
let new_mutelist_ev = toggle_from_mutelist(keypair: full_keypair, prev: damus_state.mutelist_manager.event, to_toggle: .thread(event.thread_id(keypair: damus_state.keypair), duration?.date_from_now)) {
|
let new_mutelist_ev = toggle_from_mutelist(keypair: full_keypair, prev: damus_state.mutelist_manager.event, to_toggle: .thread(event.thread_id(), duration?.date_from_now)) {
|
||||||
damus_state.mutelist_manager.set_mutelist(new_mutelist_ev)
|
damus_state.mutelist_manager.set_mutelist(new_mutelist_ev)
|
||||||
damus_state.postbox.send(new_mutelist_ev)
|
damus_state.postbox.send(new_mutelist_ev)
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,7 @@ struct SelectedEventView: View {
|
|||||||
.minimumScaleFactor(0.75)
|
.minimumScaleFactor(0.75)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
|
|
||||||
if let reply_ref = event.thread_reply(damus.keypair)?.reply {
|
if let reply_ref = event.thread_reply()?.reply {
|
||||||
ReplyDescription(event: event, replying_to: damus.events.lookup(reply_ref.note_id), ndb: damus.ndb)
|
ReplyDescription(event: event, replying_to: damus.events.lookup(reply_ref.note_id), ndb: damus.ndb)
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
}
|
}
|
||||||
|
@ -616,7 +616,7 @@ private func isAlphanumeric(_ char: Character) -> Bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func nip10_reply_tags(replying_to: NostrEvent, keypair: Keypair) -> [[String]] {
|
func nip10_reply_tags(replying_to: NostrEvent, keypair: Keypair) -> [[String]] {
|
||||||
guard let nip10 = replying_to.thread_reply(keypair) else {
|
guard let nip10 = replying_to.thread_reply() else {
|
||||||
// we're replying to a post that isn't in a thread,
|
// we're replying to a post that isn't in a thread,
|
||||||
// just add a single reply-to-root tag
|
// just add a single reply-to-root tag
|
||||||
return [["e", replying_to.id.hex(), "", "root"]]
|
return [["e", replying_to.id.hex(), "", "root"]]
|
||||||
@ -624,16 +624,11 @@ func nip10_reply_tags(replying_to: NostrEvent, keypair: Keypair) -> [[String]] {
|
|||||||
|
|
||||||
// otherwise use the root tag from the parent's nip10 reply and include the note
|
// otherwise use the root tag from the parent's nip10 reply and include the note
|
||||||
// that we are replying to's note id.
|
// that we are replying to's note id.
|
||||||
var tags = [
|
let tags = [
|
||||||
["e", nip10.root.note_id.hex(), nip10.root.relay ?? "", "root"],
|
["e", nip10.root.note_id.hex(), nip10.root.relay ?? "", "root"],
|
||||||
["e", replying_to.id.hex(), "", "reply"]
|
["e", replying_to.id.hex(), "", "reply"]
|
||||||
]
|
]
|
||||||
|
|
||||||
// we also add the parent's nip10 reply tag as an additional e tag for context
|
|
||||||
if let reply = nip10.reply {
|
|
||||||
tags.append(["e", reply.note_id.hex(), reply.relay ?? ""])
|
|
||||||
}
|
|
||||||
|
|
||||||
return tags
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ struct FirstAidSettingsView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if damus_state.contacts.event != nil {
|
if damus_state.contacts.event != nil {
|
||||||
Text(NSLocalizedString("We did not detect any issues that we can automatically fix for you. If you are having issues, please contact Damus support", comment: "Message indicating that no First Aid actions are available."))
|
Text("We did not detect any issues that we can automatically fix for you. If you are having issues, please contact Damus support: [support@damus.io](mailto:support@damus.io)", comment: "Message indicating that no First Aid actions are available.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.navigationTitle(NSLocalizedString("First Aid", comment: "Navigation title for first aid settings and tools"))
|
.navigationTitle(NSLocalizedString("First Aid", comment: "Navigation title for first aid settings and tools"))
|
||||||
|
@ -8,7 +8,9 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct NotificationSettingsView: View {
|
struct NotificationSettingsView: View {
|
||||||
|
let damus_state: DamusState
|
||||||
@ObservedObject var settings: UserSettingsStore
|
@ObservedObject var settings: UserSettingsStore
|
||||||
|
@State var notification_mode_setting_error: String? = nil
|
||||||
|
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
@ -24,8 +26,60 @@ struct NotificationSettingsView: View {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func try_to_set_notifications_mode(new_value: UserSettingsStore.NotificationsMode) {
|
||||||
|
notification_mode_setting_error = nil
|
||||||
|
if new_value == .push {
|
||||||
|
Task {
|
||||||
|
do {
|
||||||
|
try await damus_state.push_notification_client.send_token()
|
||||||
|
settings.notifications_mode = new_value
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
notification_mode_setting_error = String(format: NSLocalizedString("Error configuring push notifications with the server: %@", comment: "Error label shown when user tries to enable push notifications but something fails"), error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Task {
|
||||||
|
do {
|
||||||
|
try await damus_state.push_notification_client.revoke_token()
|
||||||
|
settings.notifications_mode = new_value
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
notification_mode_setting_error = String(format: NSLocalizedString("Error disabling push notifications with the server: %@", comment: "Error label shown when user tries to disable push notifications but something fails"), error.localizedDescription)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
|
if settings.enable_experimental_push_notifications {
|
||||||
|
Section(
|
||||||
|
header: Text("General", comment: "Section header for general damus notifications user configuration"),
|
||||||
|
footer: VStack {
|
||||||
|
if let notification_mode_setting_error {
|
||||||
|
Text(notification_mode_setting_error)
|
||||||
|
.foregroundStyle(.damusDangerPrimary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Picker(NSLocalizedString("Notifications mode", comment: "Prompt selection of the notification mode (Feature to switch between local notifications (generated from user's own phone) or push notifications (generated by Damus server)."),
|
||||||
|
selection: Binding(
|
||||||
|
get: { settings.notifications_mode },
|
||||||
|
set: { newValue in
|
||||||
|
self.try_to_set_notifications_mode(new_value: newValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
ForEach(UserSettingsStore.NotificationsMode.allCases, id: \.self) { notification_mode in
|
||||||
|
Text(notification_mode.text_description())
|
||||||
|
.tag(notification_mode.rawValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Section(header: Text("Local Notifications", comment: "Section header for damus local notifications user configuration")) {
|
Section(header: Text("Local Notifications", comment: "Section header for damus local notifications user configuration")) {
|
||||||
Toggle(NSLocalizedString("Zaps", comment: "Setting to enable Zap Local Notification"), isOn: $settings.zap_notification)
|
Toggle(NSLocalizedString("Zaps", comment: "Setting to enable Zap Local Notification"), isOn: $settings.zap_notification)
|
||||||
.toggleStyle(.switch)
|
.toggleStyle(.switch)
|
||||||
@ -65,6 +119,6 @@ struct NotificationSettingsView: View {
|
|||||||
|
|
||||||
struct NotificationSettings_Previews: PreviewProvider {
|
struct NotificationSettings_Previews: PreviewProvider {
|
||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
NotificationSettingsView(settings: UserSettingsStore())
|
NotificationSettingsView(damus_state: test_damus_state, settings: UserSettingsStore())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,14 +54,12 @@ struct MainView: View {
|
|||||||
.onAppear {
|
.onAppear {
|
||||||
orientationTracker.setDeviceMajorAxis()
|
orientationTracker.setDeviceMajorAxis()
|
||||||
keypair = get_saved_keypair()
|
keypair = get_saved_keypair()
|
||||||
appDelegate.keypair = keypair
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
|
class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate {
|
||||||
var keypair: Keypair? = nil
|
var state: DamusState? = nil
|
||||||
var settings: UserSettingsStore? = nil
|
|
||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||||
UNUserNotificationCenter.current().delegate = self
|
UNUserNotificationCenter.current().delegate = self
|
||||||
@ -71,51 +69,13 @@ class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDele
|
|||||||
}
|
}
|
||||||
|
|
||||||
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||||
// Return if this feature is disabled
|
guard let state else {
|
||||||
guard let settings = self.settings else { return }
|
|
||||||
if !settings.enable_experimental_push_notifications {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the device token and pubkey to the server
|
Task {
|
||||||
let token = deviceToken.map { String(format: "%02.2hhx", $0) }.joined()
|
try await state.push_notification_client.set_device_token(new_device_token: deviceToken)
|
||||||
|
|
||||||
print("Received device token: \(token)")
|
|
||||||
|
|
||||||
guard let pubkey = keypair?.pubkey else {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send those as JSON to the server
|
|
||||||
let json: [String: Any] = ["deviceToken": token, "pubkey": pubkey.hex()]
|
|
||||||
|
|
||||||
// create post request
|
|
||||||
let url = settings.send_device_token_to_localhost ? Constants.DEVICE_TOKEN_RECEIVER_TEST_URL : Constants.DEVICE_TOKEN_RECEIVER_PRODUCTION_URL
|
|
||||||
var request = URLRequest(url: url)
|
|
||||||
request.httpMethod = "POST"
|
|
||||||
|
|
||||||
// insert json data to the request
|
|
||||||
request.httpBody = try? JSONSerialization.data(withJSONObject: json, options: [])
|
|
||||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
||||||
|
|
||||||
let task = URLSession.shared.dataTask(with: request) { data, response, error in
|
|
||||||
guard let data = data, error == nil else {
|
|
||||||
print(error?.localizedDescription ?? "No data")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if let response = response as? HTTPURLResponse, !(200...299).contains(response.statusCode) {
|
|
||||||
print("Unexpected status code: \(response.statusCode)")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
|
|
||||||
if let responseJSON = responseJSON as? [String: Any] {
|
|
||||||
print(responseJSON)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
task.resume()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the notification in the foreground state
|
// Handle the notification in the foreground state
|
||||||
|
@ -26,36 +26,55 @@ final class NIP10Tests: XCTestCase {
|
|||||||
// 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.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func test_root_with_mention_nip10() {
|
||||||
|
let root_id_hex = "a32d70d331f4bea7a859ac71d85a9b4e0c2d1fa9aaf7237a17f85a6227f52fdb"
|
||||||
|
let root_id = NoteId(hex: root_id_hex)!
|
||||||
|
let mention_hex = "e47b7e156acec6881c89a53f1a9e349a982024245e2c398f8a5b4973b7a89ab3"
|
||||||
|
let mention_id = NoteId(hex: mention_hex)!
|
||||||
|
|
||||||
|
let tags =
|
||||||
|
[["e", root_id_hex,"","root"],
|
||||||
|
["e", mention_hex,"","mention"],
|
||||||
|
["p","c4eabae1be3cf657bc1855ee05e69de9f059cb7a059227168b80b89761cbc4e0"],
|
||||||
|
["p","604e96e099936a104883958b040b47672e0f048c98ac793f37ffe4c720279eb2"],
|
||||||
|
["p","ffd375eb40eb486656a028edbc83825f58ff0d5c4a1ba22fe7745d284529ed08","","mention"],
|
||||||
|
["q","e47b7e156acec6881c89a53f1a9e349a982024245e2c398f8a5b4973b7a89ab3"]
|
||||||
|
]
|
||||||
|
|
||||||
|
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
||||||
|
let thread = ThreadReply(tags: note.tags)
|
||||||
|
|
||||||
|
XCTAssertNotNil(thread)
|
||||||
|
guard let thread else { return }
|
||||||
|
|
||||||
|
XCTAssertEqual(thread.root.note_id, root_id)
|
||||||
|
XCTAssertEqual(thread.reply.note_id, root_id)
|
||||||
|
XCTAssertEqual(thread.mention?.ref.note_id, mention_id)
|
||||||
|
}
|
||||||
|
|
||||||
func test_new_nip10() {
|
func test_new_nip10() {
|
||||||
let root_note_id_hex = "7c7d37bc8c04d2ec65cbc7d9275253e6b5cc34b5d10439f158194a3feefa8d52"
|
let root_note_id_hex = "7c7d37bc8c04d2ec65cbc7d9275253e6b5cc34b5d10439f158194a3feefa8d52"
|
||||||
let direct_reply_hex = "7c7d37bc8c04d2ec65cbc7d9275253e6b5cc34b5d10439f158194a3feefa8d51"
|
let direct_reply_hex = "7c7d37bc8c04d2ec65cbc7d9275253e6b5cc34b5d10439f158194a3feefa8d51"
|
||||||
let reply_hex = "7c7d37bc8c04d2ec65cbc7d9275253e6b5cc34b5d10439f158194a3feefa8d53"
|
let reply_hex = "7c7d37bc8c04d2ec65cbc7d9275253e6b5cc34b5d10439f158194a3feefa8d53"
|
||||||
|
let mention_hex = "7c7d37bc8c04d2ec65cbc7d9275253e6b5cc34b5d10439f158194a3feefa8d54"
|
||||||
|
|
||||||
let tags = [
|
let tags = [
|
||||||
|
["e", mention_hex, "", "mention"],
|
||||||
["e", direct_reply_hex, "", "reply"],
|
["e", direct_reply_hex, "", "reply"],
|
||||||
["e", root_note_id_hex, "", "root"],
|
["e", root_note_id_hex, "", "root"],
|
||||||
["e", reply_hex, "", "reply"],
|
["e", reply_hex, "", "reply"],
|
||||||
["e", "7c7d37bc8c04d2ec65cbc7d9275253e6b5cc34b5d10439f158194a3feefa8d54", "", "mention"],
|
|
||||||
]
|
]
|
||||||
|
|
||||||
let root_note_id = NoteId(hex: root_note_id_hex)!
|
let root_note_id = NoteId(hex: root_note_id_hex)!
|
||||||
let direct_reply_id = NoteId(hex: direct_reply_hex)!
|
|
||||||
let reply_id = NoteId(hex: reply_hex)!
|
let reply_id = NoteId(hex: reply_hex)!
|
||||||
|
let mention_id = NoteId(hex: mention_hex)!
|
||||||
|
|
||||||
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
||||||
let refs = interp_event_refs_without_mentions_ndb(note.referenced_noterefs)
|
let tr = interp_event_refs_without_mentions_ndb(note.referenced_noterefs)
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
XCTAssertEqual(tr?.root.note_id, root_note_id)
|
||||||
if let note_id = r.is_thread_id?.note_id { xs.append(note_id) }
|
XCTAssertEqual(tr?.reply.note_id, reply_id)
|
||||||
}), [root_note_id])
|
XCTAssertEqual(tr?.mention?.ref.note_id, mention_id)
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
|
||||||
if let note_id = r.is_direct_reply?.note_id { xs.append(note_id) }
|
|
||||||
}), [direct_reply_id, reply_id])
|
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
|
||||||
if let note_id = r.is_reply?.note_id { xs.append(note_id) }
|
|
||||||
}), [direct_reply_id, reply_id])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_repost_root() {
|
func test_repost_root() {
|
||||||
@ -66,19 +85,9 @@ final class NIP10Tests: XCTestCase {
|
|||||||
|
|
||||||
let mention_id = NoteId(hex: mention_hex)!
|
let mention_id = NoteId(hex: mention_hex)!
|
||||||
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
||||||
let refs = interp_event_refs_without_mentions_ndb(note.referenced_noterefs)
|
let tr = note.thread_reply()
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
XCTAssertNil(tr)
|
||||||
if let note_id = r.is_thread_id?.note_id { xs.append(note_id) }
|
|
||||||
}), [])
|
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
|
||||||
if let note_id = r.is_direct_reply?.note_id { xs.append(note_id) }
|
|
||||||
}), [])
|
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
|
||||||
if let note_id = r.is_reply?.note_id { xs.append(note_id) }
|
|
||||||
}), [])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_direct_reply_old_nip10() {
|
func test_direct_reply_old_nip10() {
|
||||||
@ -90,19 +99,14 @@ final class NIP10Tests: XCTestCase {
|
|||||||
let root_note_id = NoteId(hex: root_note_id_hex)!
|
let root_note_id = NoteId(hex: root_note_id_hex)!
|
||||||
|
|
||||||
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
||||||
let refs = interp_event_refs_without_mentions_ndb(note.referenced_noterefs)
|
let tr = note.thread_reply()
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
XCTAssertNotNil(tr)
|
||||||
if let note_id = r.is_thread_id?.note_id { xs.append(note_id) }
|
guard let tr else { return }
|
||||||
}), [root_note_id])
|
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
XCTAssertEqual(tr.root.note_id, root_note_id)
|
||||||
if let note_id = r.is_direct_reply?.note_id { xs.append(note_id) }
|
XCTAssertEqual(tr.reply.note_id, root_note_id)
|
||||||
}), [root_note_id])
|
XCTAssertEqual(tr.is_reply_to_root, true)
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
|
||||||
if let note_id = r.is_reply?.note_id { xs.append(note_id) }
|
|
||||||
}), [root_note_id])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_direct_reply_new_nip10() {
|
func test_direct_reply_new_nip10() {
|
||||||
@ -114,24 +118,14 @@ final class NIP10Tests: XCTestCase {
|
|||||||
let root_note_id = NoteId(hex: root_note_id_hex)!
|
let root_note_id = NoteId(hex: root_note_id_hex)!
|
||||||
|
|
||||||
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
||||||
let refs = interp_event_refs_without_mentions_ndb(note.referenced_noterefs)
|
let tr = note.thread_reply()
|
||||||
|
XCTAssertNotNil(tr)
|
||||||
|
guard let tr else { return }
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
XCTAssertEqual(tr.root.note_id, root_note_id)
|
||||||
if let note_id = r.is_thread_id?.note_id { xs.append(note_id) }
|
XCTAssertEqual(tr.reply.note_id, root_note_id)
|
||||||
}), [root_note_id])
|
XCTAssertNil(tr.mention)
|
||||||
|
XCTAssertEqual(tr.is_reply_to_root, true)
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
|
||||||
if let note_id = r.is_direct_reply?.note_id { xs.append(note_id) }
|
|
||||||
}), [root_note_id])
|
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
|
||||||
if let note_id = r.is_reply?.note_id { xs.append(note_id) }
|
|
||||||
}), [root_note_id])
|
|
||||||
|
|
||||||
let nip10 = note.thread_reply(test_keypair)!
|
|
||||||
XCTAssertEqual(nip10.is_reply_to_root, true)
|
|
||||||
XCTAssertEqual(nip10.root.note_id, root_note_id)
|
|
||||||
XCTAssertEqual(nip10.reply!.note_id, root_note_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// seen in the wild by the gleasonator
|
// seen in the wild by the gleasonator
|
||||||
@ -143,13 +137,14 @@ final class NIP10Tests: XCTestCase {
|
|||||||
|
|
||||||
let root_note_id = NoteId(hex: root_note_id_hex)!
|
let root_note_id = NoteId(hex: root_note_id_hex)!
|
||||||
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
||||||
let refs = interp_event_refs_without_mentions_ndb(note.referenced_noterefs)
|
let tr = note.thread_reply()
|
||||||
let thread_reply = ThreadReply(event_refs: refs)!
|
XCTAssertNotNil(tr)
|
||||||
|
guard let tr else { return }
|
||||||
|
|
||||||
XCTAssertEqual(thread_reply.mention, nil)
|
XCTAssertNil(tr.mention)
|
||||||
XCTAssertEqual(thread_reply.root.note_id, root_note_id)
|
XCTAssertEqual(tr.root.note_id, root_note_id)
|
||||||
XCTAssertEqual(thread_reply.reply!.note_id, root_note_id)
|
XCTAssertEqual(tr.reply.note_id, root_note_id)
|
||||||
XCTAssertEqual(thread_reply.is_reply_to_root, true)
|
XCTAssertEqual(tr.is_reply_to_root, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_marker_reply() {
|
func test_marker_reply() {
|
||||||
@ -184,7 +179,7 @@ final class NIP10Tests: XCTestCase {
|
|||||||
|
|
||||||
let replying_to_hex = "a8dc8b74852d7ad114d5d650b2125459c0cba3c1fdcaaf527e03f24082e11ab3"
|
let replying_to_hex = "a8dc8b74852d7ad114d5d650b2125459c0cba3c1fdcaaf527e03f24082e11ab3"
|
||||||
let pk = Pubkey(hex: "5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e")!
|
let pk = Pubkey(hex: "5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e")!
|
||||||
let last_reply_hex = "1bb940ce0ba0d4a3b2a589355d908498dcd7452f941cf520072218f7e6ede75e"
|
//let last_reply_hex = "1bb940ce0ba0d4a3b2a589355d908498dcd7452f941cf520072218f7e6ede75e"
|
||||||
let note = decode_nostr_event_json(json: note_json)!
|
let note = decode_nostr_event_json(json: note_json)!
|
||||||
let reply = build_post(state: test_damus_state, post: .init(string: "hello"), action: .replying_to(note), uploadedMedias: [], pubkeys: [pk] + note.referenced_pubkeys.map({pk in pk}))
|
let reply = build_post(state: test_damus_state, post: .init(string: "hello"), action: .replying_to(note), uploadedMedias: [], pubkeys: [pk] + note.referenced_pubkeys.map({pk in pk}))
|
||||||
let root_hex = "00152d2945459fb394fed2ea95af879c903c4ec42d96327a739fa27c023f20e0"
|
let root_hex = "00152d2945459fb394fed2ea95af879c903c4ec42d96327a739fa27c023f20e0"
|
||||||
@ -193,7 +188,6 @@ final class NIP10Tests: XCTestCase {
|
|||||||
[
|
[
|
||||||
["e", root_hex, "wss://nostr.mutinywallet.com/", "root"],
|
["e", root_hex, "wss://nostr.mutinywallet.com/", "root"],
|
||||||
["e", replying_to_hex, "", "reply"],
|
["e", replying_to_hex, "", "reply"],
|
||||||
["e", last_reply_hex, "wss://relay.nostrplebs.com"],
|
|
||||||
["p", "5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e"],
|
["p", "5b0183ab6c3e322bf4d41c6b3aef98562a144847b7499543727c5539a114563e"],
|
||||||
["p", "6e75f7972397ca3295e0f4ca0fbc6eb9cc79be85bafdd56bd378220ca8eee74e"],
|
["p", "6e75f7972397ca3295e0f4ca0fbc6eb9cc79be85bafdd56bd378220ca8eee74e"],
|
||||||
])
|
])
|
||||||
@ -218,16 +212,13 @@ final class NIP10Tests: XCTestCase {
|
|||||||
let reply_id = NoteId(hex: reply_hex)!
|
let reply_id = NoteId(hex: reply_hex)!
|
||||||
|
|
||||||
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
||||||
let refs = interp_event_refs_without_mentions_ndb(note.referenced_noterefs)
|
let tr = note.thread_reply()
|
||||||
|
XCTAssertNotNil(tr)
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
guard let tr else { return }
|
||||||
if let note_id = r.is_thread_id?.note_id { xs.append(note_id) }
|
|
||||||
}), [root_note_id])
|
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
|
||||||
if let note_id = r.is_reply?.note_id { xs.append(note_id) }
|
|
||||||
}), [reply_id])
|
|
||||||
|
|
||||||
|
XCTAssertEqual(tr.root.note_id, root_note_id)
|
||||||
|
XCTAssertEqual(tr.reply.note_id, reply_id)
|
||||||
|
XCTAssertEqual(tr.is_reply_to_root, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_deprecated_nip10() {
|
func test_deprecated_nip10() {
|
||||||
@ -245,19 +236,13 @@ final class NIP10Tests: XCTestCase {
|
|||||||
let reply_id = NoteId(hex: reply_hex)!
|
let reply_id = NoteId(hex: reply_hex)!
|
||||||
|
|
||||||
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
let note = NdbNote(content: "hi", keypair: test_keypair, kind: 1, tags: tags)!
|
||||||
let refs = interp_event_refs_without_mentions_ndb(note.referenced_noterefs)
|
let tr = note.thread_reply()
|
||||||
|
XCTAssertNotNil(tr)
|
||||||
|
guard let tr else { return }
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
XCTAssertEqual(tr.root.note_id, root_note_id)
|
||||||
if let note_id = r.is_thread_id?.note_id { xs.append(note_id) }
|
XCTAssertEqual(tr.reply.note_id, reply_id)
|
||||||
}), [root_note_id])
|
XCTAssertEqual(tr.is_reply_to_root, false)
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
|
||||||
if let note_id = r.is_direct_reply?.note_id { xs.append(note_id) }
|
|
||||||
}), [direct_reply_id, reply_id])
|
|
||||||
|
|
||||||
XCTAssertEqual(refs.reduce(into: Array<NoteId>(), { xs, r in
|
|
||||||
if let note_id = r.is_reply?.note_id { xs.append(note_id) }
|
|
||||||
}), [direct_reply_id, reply_id])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
77
damusTests/NostrFilterTests.swift
Normal file
77
damusTests/NostrFilterTests.swift
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
//
|
||||||
|
// NostrFilterTests.swift
|
||||||
|
// damusTests
|
||||||
|
//
|
||||||
|
// Created by Daniel D’Aquino on 2024-05-10.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import damus
|
||||||
|
|
||||||
|
final class NostrFilterTests: XCTestCase {
|
||||||
|
func testChunkedWithPubKeys() {
|
||||||
|
// Given a NostrFilter with a list of pubkeys
|
||||||
|
let test_pubkey_1 = Pubkey(hex: "760f108754eb415561239d4079e71766d87e23f7e71c8e5b00d759e54dd8d082")!
|
||||||
|
let test_pubkey_2 = Pubkey(hex: "065eab63e939ea2f2f72f2305886b13e5e301302da67b5fe8a18022b278fe872")!
|
||||||
|
let test_pubkey_3 = Pubkey(hex: "aa146d7c6618ebe993702a74c561f54fc046c8a16e388b828cb2f631a1ed9602")!
|
||||||
|
let test_pubkey_4 = Pubkey(hex: "2f7108dcd33fb484be3e09cea24a1e96868fbc0842e691ca19db63781801089e")!
|
||||||
|
let test_pubkey_5 = Pubkey(hex: "1cc7c458e6b565a856d7c3791f4eb5ca5890b1f2433f452ed7a917f9aa0e5250")!
|
||||||
|
let test_pubkey_6 = Pubkey(hex: "2ee1f46a847b6613c33fd766db1e64c7f727c63774fa3ee952261d2c03b81cf2")!
|
||||||
|
let test_pubkey_7 = Pubkey(hex: "214664a7ca3236b9dd5f76550d322f390fd70cc12908a2e3ff2cdf50085d4ef2")!
|
||||||
|
let test_pubkey_8 = Pubkey(hex: "40255b02f3d8ccd6178d50f5ce1c1ac2867b3d919832176957b021c1816fce2f")!
|
||||||
|
let pubkeys: [Pubkey] = [test_pubkey_1, test_pubkey_2, test_pubkey_3, test_pubkey_4]
|
||||||
|
let authors: [Pubkey] = [test_pubkey_5, test_pubkey_6, test_pubkey_7, test_pubkey_8]
|
||||||
|
let filter = NostrFilter(
|
||||||
|
pubkeys: pubkeys,
|
||||||
|
authors: authors
|
||||||
|
)
|
||||||
|
|
||||||
|
let chunked_pubkeys_filters_size_2 = filter.chunked(on: .pubkeys, into: 2)
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_2.count, 2)
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_2[0].pubkeys, [test_pubkey_1, test_pubkey_2])
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_2[1].pubkeys, [test_pubkey_3, test_pubkey_4])
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_2[0].authors, authors)
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_2[1].authors, authors)
|
||||||
|
|
||||||
|
let chunked_pubkeys_filters_size_3 = filter.chunked(on: .pubkeys, into: 3)
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_3.count, 2)
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_3[0].pubkeys, [test_pubkey_1, test_pubkey_2, test_pubkey_3])
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_3[1].pubkeys, [test_pubkey_4])
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_3[0].authors, authors)
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_3[1].authors, authors)
|
||||||
|
|
||||||
|
let chunked_pubkeys_filters_size_4 = filter.chunked(on: .pubkeys, into: 4)
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_4.count, 1)
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_4[0].pubkeys, [test_pubkey_1, test_pubkey_2, test_pubkey_3, test_pubkey_4])
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_4[0].authors, authors)
|
||||||
|
|
||||||
|
let chunked_pubkeys_filters_size_5 = filter.chunked(on: .pubkeys, into: 5)
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_5.count, 1)
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_5[0].pubkeys, [test_pubkey_1, test_pubkey_2, test_pubkey_3, test_pubkey_4])
|
||||||
|
XCTAssertEqual(chunked_pubkeys_filters_size_5[0].authors, authors)
|
||||||
|
|
||||||
|
let chunked_authors_filters_size_2 = filter.chunked(on: .authors, into: 2)
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_2.count, 2)
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_2[0].authors, [test_pubkey_5, test_pubkey_6])
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_2[1].authors, [test_pubkey_7, test_pubkey_8])
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_2[0].pubkeys, pubkeys)
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_2[1].pubkeys, pubkeys)
|
||||||
|
|
||||||
|
let chunked_authors_filters_size_3 = filter.chunked(on: .authors, into: 3)
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_3.count, 2)
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_3[0].authors, [test_pubkey_5, test_pubkey_6, test_pubkey_7])
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_3[1].authors, [test_pubkey_8])
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_3[0].pubkeys, pubkeys)
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_3[1].pubkeys, pubkeys)
|
||||||
|
|
||||||
|
let chunked_authors_filters_size_4 = filter.chunked(on: .authors, into: 4)
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_4.count, 1)
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_4[0].authors, [test_pubkey_5, test_pubkey_6, test_pubkey_7, test_pubkey_8])
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_4[0].pubkeys, pubkeys)
|
||||||
|
|
||||||
|
let chunked_authors_filters_size_5 = filter.chunked(on: .authors, into: 5)
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_5.count, 1)
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_5[0].authors, [test_pubkey_5, test_pubkey_6, test_pubkey_7, test_pubkey_8])
|
||||||
|
XCTAssertEqual(chunked_authors_filters_size_5[0].pubkeys, pubkeys)
|
||||||
|
}
|
||||||
|
}
|
@ -18,24 +18,6 @@ class ReplyTests: 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 testMentionIsntReply() throws {
|
|
||||||
let evid = NoteId(hex: "4090a9017a2beac3f17795d1aafb80d9f2b9eda97e4738501082ed5c927be014")!
|
|
||||||
let content = "this is #[0] a mention"
|
|
||||||
let tags = [evid.tag]
|
|
||||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: tags)!
|
|
||||||
let blocks = parse_note_content(content: .init(note: ev, keypair: test_keypair)).blocks
|
|
||||||
let event_refs = interpret_event_refs(blocks: blocks, tags: ev.tags)
|
|
||||||
|
|
||||||
XCTAssertEqual(event_refs.count, 1)
|
|
||||||
|
|
||||||
let ref = event_refs[0]
|
|
||||||
|
|
||||||
XCTAssertNil(ref.is_reply)
|
|
||||||
XCTAssertNil(ref.is_thread_id)
|
|
||||||
XCTAssertNil(ref.is_direct_reply)
|
|
||||||
XCTAssertEqual(ref.is_mention, .some(.init(note_id: evid)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testAtAtEnd() {
|
func testAtAtEnd() {
|
||||||
let content = "what @"
|
let content = "what @"
|
||||||
let blocks = parse_post_blocks(content: content)
|
let blocks = parse_post_blocks(content: content)
|
||||||
@ -70,49 +52,20 @@ class ReplyTests: XCTestCase {
|
|||||||
XCTAssertEqual(blocks[2].asHashtag, "nope")
|
XCTAssertEqual(blocks[2].asHashtag, "nope")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRootReplyWithMention() throws {
|
|
||||||
let content = "this is #[1] a mention"
|
|
||||||
let thread_id = NoteId(hex: "c75e5cbafbefd5de2275f831c2a2386ea05ec5e5a78a5ccf60d467582db48945")!
|
|
||||||
let mentioned_id = NoteId(hex: "5a534797e8cd3b9f4c1cf63e20e48bd0e8bd7f8c4d6353fbd576df000f6f54d3")!
|
|
||||||
let tags = [thread_id.tag, mentioned_id.tag]
|
|
||||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: tags)!
|
|
||||||
let blocks = parse_note_content(content: .init(note: ev, keypair: test_keypair)).blocks
|
|
||||||
let event_refs = interpret_event_refs(blocks: blocks, tags: ev.tags)
|
|
||||||
|
|
||||||
XCTAssertEqual(event_refs.count, 2)
|
|
||||||
XCTAssertNotNil(event_refs[0].is_reply)
|
|
||||||
XCTAssertNotNil(event_refs[0].is_thread_id)
|
|
||||||
XCTAssertNotNil(event_refs[0].is_reply)
|
|
||||||
XCTAssertNotNil(event_refs[0].is_direct_reply)
|
|
||||||
XCTAssertEqual(event_refs[0].is_reply, .some(NoteRef(note_id: thread_id)))
|
|
||||||
XCTAssertEqual(event_refs[0].is_thread_id, .some(NoteRef(note_id: thread_id)))
|
|
||||||
XCTAssertNotNil(event_refs[1].is_mention)
|
|
||||||
XCTAssertEqual(event_refs[1].is_mention, .some(NoteRef(note_id: mentioned_id)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func testEmptyMention() throws {
|
func testEmptyMention() throws {
|
||||||
let content = "this is some & content"
|
let content = "this is some & content"
|
||||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: [])!
|
let ev = NostrEvent(content: content, keypair: test_keypair, tags: [])!
|
||||||
let blocks = parse_note_content(content: .init(note: ev, keypair: test_keypair)).blocks
|
let blocks = parse_note_content(content: .init(note: ev, keypair: test_keypair)).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: [])
|
let post_tags = make_post_tags(post_blocks: post_blocks, tags: [])
|
||||||
let event_refs = interpret_event_refs(blocks: blocks, tags: ev.tags)
|
let tr = interpret_event_refs(tags: ev.tags)
|
||||||
|
|
||||||
XCTAssertEqual(event_refs.count, 0)
|
XCTAssertNil(tr)
|
||||||
XCTAssertEqual(post_tags.blocks.count, 1)
|
XCTAssertEqual(post_tags.blocks.count, 1)
|
||||||
XCTAssertEqual(post_tags.tags.count, 0)
|
XCTAssertEqual(post_tags.tags.count, 0)
|
||||||
XCTAssertEqual(post_blocks.count, 1)
|
XCTAssertEqual(post_blocks.count, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testManyMentions() throws {
|
|
||||||
let content = "#[10]"
|
|
||||||
let tags: [[String]] = [[],[],[],[],[],[],[],[],[],[],["p", "3e999f94e2cb34ef44a64b351141ac4e51b5121b2d31aed4a6c84602a1144692"]]
|
|
||||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: tags)!
|
|
||||||
let blocks = parse_note_content(content: .init(note: ev, keypair: test_keypair)).blocks
|
|
||||||
let mentions = blocks.filter { $0.asMention != nil }
|
|
||||||
XCTAssertEqual(mentions.count, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testNewlineMentions() throws {
|
func testNewlineMentions() throws {
|
||||||
let bech32_pk = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"
|
let bech32_pk = "npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s"
|
||||||
let pk = bech32_pubkey_decode(bech32_pk)!
|
let pk = bech32_pubkey_decode(bech32_pk)!
|
||||||
@ -145,17 +98,12 @@ class ReplyTests: XCTestCase {
|
|||||||
let reply_id = NoteId(hex: "80093e9bdb495728f54cda2bad4aed096877189552b3d41264e73b9a9595be22")!
|
let reply_id = NoteId(hex: "80093e9bdb495728f54cda2bad4aed096877189552b3d41264e73b9a9595be22")!
|
||||||
let tags = [thread_id.tag, reply_id.tag]
|
let tags = [thread_id.tag, reply_id.tag]
|
||||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: tags)!
|
let ev = NostrEvent(content: content, keypair: test_keypair, tags: tags)!
|
||||||
let blocks = parse_note_content(content: .init(note: ev, keypair: test_keypair)).blocks
|
let tr = interpret_event_refs(tags: ev.tags)
|
||||||
let event_refs = interpret_event_refs(blocks: blocks, tags: ev.tags)
|
XCTAssertNotNil(tr)
|
||||||
|
guard let tr else { return }
|
||||||
|
|
||||||
XCTAssertEqual(event_refs.count, 2)
|
XCTAssertEqual(tr.root.note_id, thread_id)
|
||||||
let r1 = event_refs[0]
|
XCTAssertEqual(tr.reply.note_id, reply_id)
|
||||||
let r2 = event_refs[1]
|
|
||||||
|
|
||||||
XCTAssertEqual(r1.is_thread_id, .some(.note_id(thread_id)))
|
|
||||||
XCTAssertEqual(r2.is_reply, .some(.note_id(reply_id)))
|
|
||||||
XCTAssertEqual(r2.is_direct_reply, .some(.note_id(reply_id)))
|
|
||||||
XCTAssertNil(r1.is_direct_reply)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testRootReply() throws {
|
func testRootReply() throws {
|
||||||
@ -163,16 +111,14 @@ class ReplyTests: XCTestCase {
|
|||||||
let thread_id = NoteId(hex: "53f60f5114c06f069ffe9da2bc033e533d09cae44d37a8462154a663771a4ce6")!
|
let thread_id = NoteId(hex: "53f60f5114c06f069ffe9da2bc033e533d09cae44d37a8462154a663771a4ce6")!
|
||||||
let tags = [thread_id.tag]
|
let tags = [thread_id.tag]
|
||||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: tags)!
|
let ev = NostrEvent(content: content, keypair: test_keypair, tags: tags)!
|
||||||
let blocks = parse_note_content(content: .content(ev.content,nil)).blocks
|
let tr = interpret_event_refs(tags: ev.tags)
|
||||||
let event_refs = interpret_event_refs(blocks: blocks, tags: ev.tags)
|
|
||||||
|
|
||||||
XCTAssertEqual(event_refs.count, 1)
|
XCTAssertNotNil(tr)
|
||||||
let r = event_refs[0]
|
guard let tr else { return }
|
||||||
|
|
||||||
XCTAssertEqual(r.is_direct_reply, .some(.note_id(thread_id)))
|
XCTAssertEqual(tr.root.note_id, thread_id)
|
||||||
XCTAssertEqual(r.is_reply, .some(.note_id(thread_id)))
|
XCTAssertEqual(tr.reply.note_id, thread_id)
|
||||||
XCTAssertEqual(r.is_thread_id, .some(.note_id(thread_id)))
|
XCTAssertNil(tr.mention)
|
||||||
XCTAssertNil(r.is_mention)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAdjacentComposedMention() throws {
|
func testAdjacentComposedMention() throws {
|
||||||
@ -262,28 +208,6 @@ class ReplyTests: XCTestCase {
|
|||||||
XCTAssertEqual(new_post.string, "cc @jb55 ")
|
XCTAssertEqual(new_post.string, "cc @jb55 ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testNoReply() throws {
|
|
||||||
let content = "this is a #[0] reply"
|
|
||||||
let ev = NostrEvent(content: content, keypair: test_keypair, tags: [])!
|
|
||||||
let blocks = parse_note_content(content: .init(note: ev, keypair: test_keypair)).blocks
|
|
||||||
let event_refs = interpret_event_refs(blocks: blocks, tags:ev.tags)
|
|
||||||
|
|
||||||
XCTAssertEqual(event_refs.count, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func testParseMention() throws {
|
|
||||||
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, keypair: test_keypair)).blocks
|
|
||||||
|
|
||||||
XCTAssertNotNil(parsed)
|
|
||||||
XCTAssertEqual(parsed.count, 3)
|
|
||||||
XCTAssertEqual(parsed[0].asText, "this is ")
|
|
||||||
XCTAssertNotNil(parsed[1].asMention)
|
|
||||||
XCTAssertEqual(parsed[2].asText, " a mention")
|
|
||||||
}
|
|
||||||
|
|
||||||
func testEmptyPostReference() throws {
|
func testEmptyPostReference() throws {
|
||||||
let parsed = parse_post_blocks(content: "")
|
let parsed = parse_post_blocks(content: "")
|
||||||
XCTAssertEqual(parsed.count, 0)
|
XCTAssertEqual(parsed.count, 0)
|
||||||
@ -442,14 +366,4 @@ class ReplyTests: XCTestCase {
|
|||||||
XCTAssertEqual(t2, " event mention")
|
XCTAssertEqual(t2, " event mention")
|
||||||
}
|
}
|
||||||
|
|
||||||
func testParseInvalidMention() throws {
|
|
||||||
let parsed = parse_note_content(content: .content("this is #[0] a mention",nil)).blocks
|
|
||||||
|
|
||||||
XCTAssertNotNil(parsed)
|
|
||||||
XCTAssertEqual(parsed.count, 3)
|
|
||||||
XCTAssertEqual(parsed[0].asText, "this is ")
|
|
||||||
XCTAssertEqual(parsed[1].asText, "#[0]")
|
|
||||||
XCTAssertEqual(parsed[2].asText, " a mention")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -340,8 +340,8 @@ extension NdbNote {
|
|||||||
References<RefId>(tags: self.tags)
|
References<RefId>(tags: self.tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func thread_reply(_ keypair: Keypair) -> ThreadReply? {
|
func thread_reply() -> ThreadReply? {
|
||||||
ThreadReply(event_refs: interpret_event_refs_ndb(blocks: self.blocks(keypair).blocks, tags: self.tags))
|
ThreadReply(tags: self.tags)
|
||||||
}
|
}
|
||||||
|
|
||||||
func get_content(_ keypair: Keypair) -> String {
|
func get_content(_ keypair: Keypair) -> String {
|
||||||
@ -387,13 +387,13 @@ extension NdbNote {
|
|||||||
return dec
|
return dec
|
||||||
}
|
}
|
||||||
|
|
||||||
public func direct_replies(_ keypair: Keypair) -> NoteId? {
|
public func direct_replies() -> NoteId? {
|
||||||
return thread_reply(keypair)?.reply?.note_id
|
return thread_reply()?.reply.note_id
|
||||||
}
|
}
|
||||||
|
|
||||||
// NDBTODO: just use Id
|
// NDBTODO: just use Id
|
||||||
public func thread_id(keypair: Keypair) -> NoteId {
|
public func thread_id() -> NoteId {
|
||||||
guard let root = self.thread_reply(keypair)?.root else {
|
guard let root = self.thread_reply()?.root else {
|
||||||
return self.id
|
return self.id
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -421,8 +421,8 @@ extension NdbNote {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func is_reply(_ keypair: Keypair) -> Bool {
|
func is_reply() -> Bool {
|
||||||
return thread_reply(keypair)?.reply != nil
|
return thread_reply() != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func note_language(_ keypair: Keypair) -> String? {
|
func note_language(_ keypair: Keypair) -> String? {
|
||||||
|
@ -202,52 +202,6 @@ final class NdbTests: XCTestCase {
|
|||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_perf_interp_evrefs_old() {
|
|
||||||
guard let event = decode_nostr_event_json(test_reply_json) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.measure(options: longer_iter()) {
|
|
||||||
let blocks = event.blocks(test_keypair).blocks
|
|
||||||
let xs = interpret_event_refs(blocks: blocks, tags: event.tags)
|
|
||||||
XCTAssertEqual(xs.count, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func test_perf_interp_evrefs_ndb() {
|
|
||||||
guard let note = NdbNote.owned_from_json(json: test_reply_json) else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
self.measure(options: longer_iter()) {
|
|
||||||
let blocks = note.blocks(test_keypair).blocks
|
|
||||||
let xs = interpret_event_refs_ndb(blocks: blocks, tags: note.tags)
|
|
||||||
XCTAssertEqual(xs.count, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func test_decoded_events_are_equal() {
|
|
||||||
let event = decode_nostr_event_json(test_reply_json)
|
|
||||||
let note = NdbNote.owned_from_json(json: test_reply_json)
|
|
||||||
|
|
||||||
XCTAssertNotNil(note)
|
|
||||||
XCTAssertNotNil(event)
|
|
||||||
guard let note else { return }
|
|
||||||
guard let event else { return }
|
|
||||||
|
|
||||||
XCTAssertEqual(note.content_len, UInt32(event.content.utf8.count))
|
|
||||||
XCTAssertEqual(note.pubkey, event.pubkey)
|
|
||||||
XCTAssertEqual(note.id, event.id)
|
|
||||||
|
|
||||||
let ev_blocks = event.blocks(test_keypair)
|
|
||||||
let note_blocks = note.blocks(test_keypair)
|
|
||||||
|
|
||||||
XCTAssertEqual(ev_blocks, note_blocks)
|
|
||||||
|
|
||||||
let event_refs = interpret_event_refs(blocks: ev_blocks.blocks, tags: event.tags)
|
|
||||||
let note_refs = interpret_event_refs_ndb(blocks: note_blocks.blocks, tags: note.tags)
|
|
||||||
|
|
||||||
XCTAssertEqual(event_refs, note_refs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func test_iteration_perf() throws {
|
func test_iteration_perf() throws {
|
||||||
guard let note = NdbNote.owned_from_json(json: test_contact_list_json) else {
|
guard let note = NdbNote.owned_from_json(json: test_contact_list_json) else {
|
||||||
XCTAssert(false)
|
XCTAssert(false)
|
||||||
|
Loading…
Reference in New Issue
Block a user