@@ -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
670728private fun PostView.toDbStatusWithUser (accountKey : MicroBlogKey ): DbStatusWithUser {
671729 val user = author.toDbUser(accountKey.host)
0 commit comments