-
Notifications
You must be signed in to change notification settings - Fork 0
Fix/nostr quote reference display #142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
68d83c3
7ec7bb4
5eeb7d7
61b250b
b437980
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,7 @@ import work.socialhub.knostr.social.model.NostrNote | |
| import work.socialhub.knostr.social.model.NostrReaction | ||
| import work.socialhub.knostr.social.model.NostrRelationship | ||
| import work.socialhub.knostr.social.model.NostrUser as KnostrUser | ||
| import work.socialhub.knostr.util.Nip21 | ||
| import work.socialhub.planetlink.define.MediaType | ||
| import work.socialhub.planetlink.model.Comment | ||
| import work.socialhub.planetlink.model.ID | ||
|
|
@@ -25,6 +26,9 @@ import work.socialhub.planetlink.nostr.model.NostrUser | |
| /** Nostr エンティティのマッピング */ | ||
| object NostrMapper { | ||
|
|
||
| private val NOSTR_EVENT_REFERENCE = | ||
| Regex("nostr:(?:note|nevent)1[ac-hj-np-z02-9]+", RegexOption.IGNORE_CASE) | ||
|
|
||
| /** ユーザーマッピング */ | ||
| fun user( | ||
| knostrUser: KnostrUser, | ||
|
|
@@ -184,7 +188,7 @@ object NostrMapper { | |
| .map { it[1].lowercase() } | ||
| .toSet() | ||
|
|
||
| val attributed = AttributedString.plain(note.content) | ||
| val attributed = AttributedString.plain(displayContent(note)) | ||
|
|
||
| val validated = attributed.elements.map { elem -> | ||
| if (elem.kind == AttributedKind.HASH_TAG && elem is AttributedItem) { | ||
|
|
@@ -199,4 +203,24 @@ object NostrMapper { | |
| } | ||
| return AttributedString(validated) | ||
| } | ||
|
|
||
| private fun displayContent(note: NostrNote): String { | ||
| val quotedEventId = note.quotedNote?.event?.id ?: return note.content | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When the quoted note is not cached, this returns the original content because Useful? React with 👍 / 👎. |
||
| return stripQuoteReference(note.content, quotedEventId) | ||
| } | ||
|
|
||
| internal fun stripQuoteReference( | ||
| content: String, | ||
| quotedEventId: String, | ||
| ): String { | ||
| val stripped = NOSTR_EVENT_REFERENCE.replace(content) { match -> | ||
| val referencedEventId = Nip21.extractEventIds(match.value.lowercase()).singleOrNull() | ||
| if (referencedEventId.equals(quotedEventId, ignoreCase = true)) { | ||
| "" | ||
| } else { | ||
| match.value | ||
| } | ||
| } | ||
| return if (stripped == content) content else stripped.trimEnd() | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,153 @@ | ||
| package work.socialhub.planetlink.nostr.action | ||
|
|
||
| import work.socialhub.knostr.entity.NostrEvent | ||
| import work.socialhub.knostr.social.model.NostrNote | ||
| import work.socialhub.knostr.util.Bech32 | ||
| import work.socialhub.knostr.util.Hex | ||
| import work.socialhub.planetlink.model.Account | ||
| import work.socialhub.planetlink.model.Service | ||
| import kotlin.test.Test | ||
| import kotlin.test.assertEquals | ||
| import kotlin.test.assertNotNull | ||
|
|
||
| class NostrMapperTest { | ||
|
|
||
| @Test | ||
| fun removesResolvedQuoteReferenceFromDisplayText() { | ||
| val quotedEventId = "22".repeat(32) | ||
| val quotedNote = note( | ||
| eventId = quotedEventId, | ||
| content = "Quoted content", | ||
| ) | ||
| val reference = nevent(quotedEventId) | ||
| val source = note( | ||
| eventId = "11".repeat(32), | ||
| content = "Additional comment\n\nnostr:$reference", | ||
| ).apply { | ||
| this.quotedEventId = quotedEventId | ||
| this.quotedNote = quotedNote | ||
| } | ||
|
|
||
| val comment = NostrMapper.comment(source, service()) | ||
|
|
||
| assertEquals("Additional comment", comment.text?.displayText) | ||
| assertNotNull(comment.sharedComment) | ||
| } | ||
|
|
||
| @Test | ||
| fun preservesReferenceToAnotherEvent() { | ||
| val quotedEventId = "22".repeat(32) | ||
| val otherEventId = "33".repeat(32) | ||
| val otherReference = nevent(otherEventId) | ||
| val source = note( | ||
| eventId = "11".repeat(32), | ||
| content = "See also nostr:$otherReference", | ||
| ).apply { | ||
| this.quotedEventId = quotedEventId | ||
| this.quotedNote = note(quotedEventId, "Quoted content") | ||
| } | ||
|
|
||
| val comment = NostrMapper.comment(source, service()) | ||
|
|
||
| assertEquals("See also nostr:$otherReference", comment.text?.displayText) | ||
| } | ||
|
|
||
| @Test | ||
| fun removesResolvedNote1QuoteReferenceFromDisplayText() { | ||
| val quotedEventId = "22".repeat(32) | ||
| val quotedNote = note( | ||
| eventId = quotedEventId, | ||
| content = "Quoted content", | ||
| ) | ||
| val reference = note1(quotedEventId) | ||
| val source = note( | ||
| eventId = "11".repeat(32), | ||
| content = "Additional comment\n\nnostr:$reference", | ||
| ).apply { | ||
| this.quotedEventId = quotedEventId | ||
| this.quotedNote = quotedNote | ||
| } | ||
|
|
||
| val comment = NostrMapper.comment(source, service()) | ||
|
|
||
| assertEquals("Additional comment", comment.text?.displayText) | ||
| assertNotNull(comment.sharedComment) | ||
| } | ||
|
|
||
| @Test | ||
| fun removesResolvedUppercaseQuoteReferenceFromDisplayText() { | ||
| val quotedEventId = "22".repeat(32) | ||
| val quotedNote = note( | ||
| eventId = quotedEventId, | ||
| content = "Quoted content", | ||
| ) | ||
| val reference = nevent(quotedEventId).uppercase() | ||
| val source = note( | ||
| eventId = "11".repeat(32), | ||
| content = "Additional comment\n\nnostr:$reference", | ||
| ).apply { | ||
| this.quotedEventId = quotedEventId | ||
| this.quotedNote = quotedNote | ||
| } | ||
|
|
||
| val comment = NostrMapper.comment(source, service()) | ||
|
|
||
| assertEquals("Additional comment", comment.text?.displayText) | ||
| assertNotNull(comment.sharedComment) | ||
| } | ||
|
|
||
| @Test | ||
| fun preservesQuoteReferenceUntilQuoteIsResolved() { | ||
| val quotedEventId = "22".repeat(32) | ||
| val reference = nevent(quotedEventId) | ||
| val source = note( | ||
| eventId = "11".repeat(32), | ||
| content = "Additional comment\n\nnostr:$reference", | ||
| ).apply { | ||
| this.quotedEventId = quotedEventId | ||
| } | ||
|
|
||
| val comment = NostrMapper.comment(source, service()) | ||
|
|
||
| assertEquals( | ||
| "Additional comment\n\nnostr:$reference", | ||
| comment.text?.displayText, | ||
| ) | ||
| } | ||
|
|
||
| private fun note( | ||
| eventId: String, | ||
| content: String, | ||
| ): NostrNote { | ||
| return NostrNote().apply { | ||
| event = NostrEvent( | ||
| id = eventId, | ||
| pubkey = "44".repeat(32), | ||
| createdAt = 1_000, | ||
| kind = 1, | ||
| tags = emptyList(), | ||
| content = content, | ||
| sig = "55".repeat(64), | ||
| ) | ||
| this.content = content | ||
| createdAt = event.createdAt | ||
| noteId = Bech32.encode("note", Hex.decode(eventId)) | ||
| } | ||
| } | ||
|
|
||
| private fun nevent(eventId: String): String { | ||
| val tlv = byteArrayOf(0, 32) + Hex.decode(eventId) | ||
| return Bech32.encode("nevent", tlv) | ||
| } | ||
|
|
||
| private fun note1(eventId: String): String { | ||
| return Bech32.encode("note", Hex.decode(eventId)) | ||
| } | ||
|
|
||
| private fun service(): Service { | ||
| val account = Account() | ||
| return Service("nostr", account).also { | ||
| account.service = it | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.