Skip to content

Commit d00b91b

Browse files
authored
Merge pull request #1800 from danguyf/feature/1671-bluesky_reply_duplication
Issue 1671: Bluesky reply duplication
2 parents 99da50e + 9628a5b commit d00b91b

File tree

2 files changed

+153
-90
lines changed
  • shared/src/commonMain/kotlin/dev/dimension/flare

2 files changed

+153
-90
lines changed

shared/src/commonMain/kotlin/dev/dimension/flare/data/database/cache/mapper/Bluesky.kt

Lines changed: 144 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -570,102 +570,160 @@ internal suspend fun List<FeedViewPost>.toDbPagingTimeline(
570570
}
571571
}
572572
},
573-
): List<DbPagingTimelineWithStatus> =
574-
this.flatMap {
575-
val reply =
576-
when (val reply = it.reply?.parent) {
577-
is ReplyRefParentUnion.PostView ->
578-
if (reply.value.uri != it.post.uri) {
579-
reply.value
580-
} else {
581-
null
582-
}
573+
): List<DbPagingTimelineWithStatus> {
574+
// Build a map of all posts by URI for quick lookup
575+
val postUriMap = mutableMapOf<String, FeedViewPost>()
576+
for (item in this) {
577+
postUriMap[item.post.uri.atUri] = item
578+
}
583579

580+
// Identify which posts are parents of other posts in this feed
581+
val parentUrisInFeed = mutableSetOf<String>()
582+
for (item in this) {
583+
val parentUri =
584+
when (val parent = item.reply?.parent) {
585+
is ReplyRefParentUnion.PostView -> parent.value.uri.atUri
584586
else -> null
585587
}
588+
if (parentUri != null) {
589+
val inFeed = postUriMap.containsKey(parentUri)
590+
if (inFeed) {
591+
parentUrisInFeed.add(parentUri)
592+
}
593+
}
594+
}
586595

587-
val status =
588-
when (val data = it.reason) {
589-
is FeedViewPostReasonUnion.ReasonRepost -> {
590-
val user = data.value.by.toDbUser(accountKey.host)
591-
DbStatusWithUser(
592-
user = user,
593-
data =
594-
DbStatus(
595-
statusKey =
596-
MicroBlogKey(
597-
it.post.uri.atUri + "_reblog_${user.userKey}",
598-
accountKey.host,
599-
),
600-
userKey =
601-
data.value.by
602-
.toDbUser(accountKey.host)
603-
.userKey,
604-
content = StatusContent.BlueskyReason(data),
605-
accountType = AccountType.Specific(accountKey),
606-
text = null,
607-
createdAt = it.post.indexedAt,
608-
),
609-
)
596+
// Build complete parent chains for each post
597+
val parentChainMap = mutableMapOf<String, List<PostView>>()
598+
for (item in this) {
599+
val parentChain = mutableListOf<PostView>()
600+
var currentPostUri: String? = item.post.uri.atUri
601+
val visitedUris = mutableSetOf<String>() // prevent infinite loops
602+
603+
while (currentPostUri != null && !visitedUris.contains(currentPostUri) && parentChain.size < 1000) {
604+
visitedUris.add(currentPostUri)
605+
val currentFeedItem = postUriMap[currentPostUri]
606+
if (currentFeedItem != null) {
607+
val parentPostView =
608+
when (val reply = currentFeedItem.reply?.parent) {
609+
is ReplyRefParentUnion.PostView -> reply.value
610+
else -> null
611+
}
612+
if (parentPostView != null) {
613+
parentChain.add(parentPostView)
614+
currentPostUri = parentPostView.uri.atUri
615+
} else {
616+
currentPostUri = null
610617
}
618+
} else {
619+
currentPostUri = null
620+
}
621+
}
611622

612-
is FeedViewPostReasonUnion.ReasonPin -> {
613-
val status = it.post.toDbStatusWithUser(accountKey)
614-
DbStatusWithUser(
615-
user = status.user,
616-
data =
617-
DbStatus(
618-
statusKey =
619-
MicroBlogKey(
620-
it.post.uri.atUri + "_pin_${status.user?.userKey}",
621-
accountKey.host,
622-
),
623-
userKey = status.user?.userKey,
624-
content = StatusContent.BlueskyReason(data),
625-
accountType = AccountType.Specific(accountKey),
626-
text = status.data.text,
627-
createdAt = it.post.indexedAt,
628-
),
629-
)
630-
}
623+
if (parentChain.isNotEmpty()) {
624+
parentChainMap[item.post.uri.atUri] = parentChain
625+
}
626+
}
631627

632-
else -> {
633-
// bluesky doesn't have "quote" and "retweet" as the same as the other platforms
634-
it.post.toDbStatusWithUser(accountKey)
635-
}
628+
// Filter out posts that are parents of other posts, keeping only leaves/endpoints
629+
val result =
630+
this.flatMap { item ->
631+
if (parentUrisInFeed.contains(item.post.uri.atUri)) {
632+
emptyList()
633+
} else {
634+
processBlueskyFeedItem(item, accountKey, pagingKey, sortIdProvider, parentChainMap)
636635
}
637-
val references =
638-
listOfNotNull(
639-
if (reply != null) {
640-
ReferenceType.Reply to listOfNotNull(reply.toDbStatusWithUser(accountKey))
641-
} else {
642-
null
643-
},
644-
if (it.reason != null) {
645-
ReferenceType.Retweet to listOfNotNull(it.post.toDbStatusWithUser(accountKey))
636+
}
637+
return result
638+
}
639+
640+
private suspend fun processBlueskyFeedItem(
641+
it: FeedViewPost,
642+
accountKey: MicroBlogKey,
643+
pagingKey: String,
644+
sortIdProvider: suspend (FeedViewPost) -> Long,
645+
parentChainMap: Map<String, List<PostView>>,
646+
): List<DbPagingTimelineWithStatus> {
647+
val parentChain = parentChainMap[it.post.uri.atUri].orEmpty()
648+
649+
val status =
650+
when (val data = it.reason) {
651+
is FeedViewPostReasonUnion.ReasonRepost -> {
652+
val user = data.value.by.toDbUser(accountKey.host)
653+
DbStatusWithUser(
654+
user = user,
655+
data =
656+
DbStatus(
657+
statusKey =
658+
MicroBlogKey(
659+
it.post.uri.atUri + "_reblog_${user.userKey}",
660+
accountKey.host,
661+
),
662+
userKey =
663+
data.value.by
664+
.toDbUser(accountKey.host)
665+
.userKey,
666+
content = StatusContent.BlueskyReason(data),
667+
accountType = AccountType.Specific(accountKey),
668+
text = null,
669+
createdAt = it.post.indexedAt,
670+
),
671+
)
672+
}
673+
674+
is FeedViewPostReasonUnion.ReasonPin -> {
675+
val status = it.post.toDbStatusWithUser(accountKey)
676+
DbStatusWithUser(
677+
user = status.user,
678+
data =
679+
DbStatus(
680+
statusKey =
681+
MicroBlogKey(
682+
it.post.uri.atUri + "_pin_${status.user?.userKey}",
683+
accountKey.host,
684+
),
685+
userKey = status.user?.userKey,
686+
content = StatusContent.BlueskyReason(data),
687+
accountType = AccountType.Specific(accountKey),
688+
text = status.data.text,
689+
createdAt = it.post.indexedAt,
690+
),
691+
)
692+
}
693+
694+
else -> {
695+
// bluesky doesn't have "quote" and "retweet" as the same as the other platforms
696+
it.post.toDbStatusWithUser(accountKey)
697+
}
698+
}
699+
val references =
700+
listOfNotNull(
701+
if (parentChain.isNotEmpty()) {
702+
val convertedParents = parentChain.mapNotNull { it.toDbStatusWithUser(accountKey) }
703+
if (convertedParents.isNotEmpty()) {
704+
ReferenceType.Reply to convertedParents
646705
} else {
647706
null
648-
},
649-
).toMap()
650-
listOfNotNull(
651-
// reply?.let {
652-
// createDbPagingTimelineWithStatus(
653-
// accountKey = accountKey,
654-
// pagingKey = pagingKey,
655-
// sortId = -SnowflakeIdGenerator.nextId(),
656-
// status = it,
657-
// references = references,
658-
// )
659-
// },
660-
createDbPagingTimelineWithStatus(
661-
accountKey = accountKey,
662-
pagingKey = pagingKey,
663-
sortId = sortIdProvider(it),
664-
status = status,
665-
references = references,
666-
),
667-
)
668-
}
707+
}
708+
} else {
709+
null
710+
},
711+
if (it.reason != null) {
712+
ReferenceType.Retweet to listOfNotNull(it.post.toDbStatusWithUser(accountKey))
713+
} else {
714+
null
715+
},
716+
).toMap()
717+
return listOfNotNull(
718+
createDbPagingTimelineWithStatus(
719+
accountKey = accountKey,
720+
pagingKey = pagingKey,
721+
sortId = sortIdProvider(it),
722+
status = status,
723+
references = references,
724+
),
725+
)
726+
}
669727

670728
private fun PostView.toDbStatusWithUser(accountKey: MicroBlogKey): DbStatusWithUser {
671729
val user = author.toDbUser(accountKey.host)

shared/src/commonMain/kotlin/dev/dimension/flare/ui/model/mapper/Bluesky.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,14 @@ internal fun PostView.renderStatus(
529529
append(uri.atUri.substringAfterLast("/"))
530530
}
531531

532+
val parents =
533+
references[ReferenceType.Reply]
534+
?.mapNotNull { it as? StatusContent.Bluesky }
535+
?.reversed()
536+
?.map { it.data.renderStatus(accountKey, event) }
537+
?.toPersistentList()
538+
?: persistentListOf()
539+
532540
return UiTimeline.ItemContent.Status(
533541
platformType = PlatformType.Bluesky,
534542
user = user,
@@ -539,10 +547,7 @@ internal fun PostView.renderStatus(
539547
poll = null,
540548
quote = listOfNotNull(findQuote(accountKey, this, event)).toImmutableList(),
541549
contentWarning = null,
542-
parents =
543-
listOfNotNull(
544-
parent?.renderStatus(accountKey, event),
545-
).toPersistentList(),
550+
parents = parents,
546551
actions =
547552
listOfNotNull(
548553
ActionMenu.Item(

0 commit comments

Comments
 (0)