Thread viewer for Bluesky

use Constellation to load hidden replies instead of blueapi

+18 -45
+5 -2
src/api.ts
··· 1 1 import { AuthenticatedAPI } from "./api/authenticated_api"; 2 - import { BlueskyAPI, HiddenRepliesError, URLError } from "./api/bluesky_api"; 2 + import { BlueskyAPI, URLError } from "./api/bluesky_api"; 3 3 import { APIError, Minisky } from "./api/minisky"; 4 4 import { settings } from "./models/settings.svelte"; 5 5 6 6 export { AuthenticatedAPI, BlueskyAPI, Minisky }; 7 - export { APIError, HiddenRepliesError, URLError }; 7 + export { APIError, URLError }; 8 8 9 9 declare global { 10 10 interface Window { ··· 15 15 api: BlueskyAPI; 16 16 appView: BlueskyAPI; 17 17 blueAPI: BlueskyAPI; 18 + constellationAPI: BlueskyAPI; 18 19 accountAPI: AuthenticatedAPI; 19 20 } 20 21 } 21 22 22 23 export let appView = new BlueskyAPI('api.bsky.app'); 23 24 export let blueAPI = new BlueskyAPI('blue.mackuba.eu'); 25 + export let constellationAPI = new BlueskyAPI('constellation.microcosm.blue'); 24 26 export let accountAPI = new AuthenticatedAPI(); 25 27 export let api: BlueskyAPI; 26 28 ··· 38 40 window.appView = appView; 39 41 window.blueAPI = blueAPI; 40 42 window.accountAPI = accountAPI; 43 + window.constellationAPI = constellationAPI;
+12 -28
src/api/bluesky_api.ts
··· 1 1 import { HandleCache } from './handle_cache.js'; 2 - import { blueAPI, appView } from '../api.js'; 2 + import { appView, constellationAPI } from '../api.js'; 3 3 import { APIError, Minisky, type FetchAllOnPageLoad, type MiniskyConfig, type MiniskyOptions } from './minisky.js'; 4 4 import { atURI, feedPostTime } from '../utils.js'; 5 5 import { Post } from '../models/posts.js'; ··· 20 20 export class URLError extends Error { 21 21 constructor(message: string) { 22 22 super(message); 23 - } 24 - } 25 - 26 - /** 27 - * Thrown when hidden replies couldn't be loaded from the blue.feeds API. 28 - */ 29 - 30 - export class HiddenRepliesError extends Error { 31 - originalError: Error; 32 - 33 - constructor(error: Error) { 34 - super(error.message); 35 - this.originalError = error; 36 23 } 37 24 } 38 25 ··· 130 117 } 131 118 132 119 async getReplies(uri: string): Promise<string[]> { 133 - let json = await this.getRequest('blue.feeds.post.getReplies', { uri }); 134 - return json.replies; 120 + let results = await this.fetchAll('blue.microcosm.links.getBacklinks', { 121 + field: 'records', 122 + params: { 123 + subject: uri, 124 + source: 'app.bsky.feed.post:reply.parent.uri', 125 + limit: 100 126 + } 127 + }); 128 + 129 + return results.map((x: json) => `at://${x.did}/${x.collection}/${x.rkey}`); 135 130 } 136 131 137 132 async getQuoteCount(uri: string): Promise<number> { ··· 170 165 } 171 166 172 167 async loadHiddenReplies(post: Post): Promise<(json | null)[]> { 173 - let expectedReplyURIs: string[]; 174 - 175 - try { 176 - expectedReplyURIs = await blueAPI.getReplies(post.uri); 177 - } catch (error) { 178 - if (error instanceof APIError && error.code == 404) { 179 - throw new HiddenRepliesError(error); 180 - } else { 181 - throw error; 182 - } 183 - } 184 - 168 + let expectedReplyURIs = await constellationAPI.getReplies(post.uri); 185 169 let missingReplyURIs = expectedReplyURIs.filter(r => !post.replies.some(x => x.uri === r)); 186 170 let promises = missingReplyURIs.map(uri => this.loadThreadByAtURI(uri)); 187 171 let responses = await Promise.allSettled(promises);
+1 -15
src/components/posts/PostComponent.svelte
··· 4 4 5 5 <script lang="ts"> 6 6 import { createContext } from 'svelte'; 7 - import { HiddenRepliesError } from '../../api.js'; 8 7 import { settings } from '../../models/settings.svelte.js'; 9 8 import { Post, BlockedPost } from '../../models/posts.js'; 10 9 import { Embed, InlineLinkEmbed } from '../../models/embeds.js'; ··· 45 44 let replies: AnyPost[] = $state(post.replies); 46 45 let repliesLoaded = $state(false); 47 46 let missingHiddenReplies: number | undefined = $state(); 48 - let hiddenRepliesError: Error | undefined = $state(); 49 47 50 48 setPostContext({ post, placement }); 51 49 ··· 96 94 } 97 95 98 96 function onRepliesLoadingError(error: Error) { 99 - repliesLoaded = true; 100 - 101 - if (error instanceof HiddenRepliesError) { 102 - hiddenRepliesError = error; 103 - } else { 104 - setTimeout(() => showError(error), 1); 105 - } 97 + showError(error); 106 98 } 107 99 </script> 108 100 ··· 175 167 Some replies are missing 176 168 {/if} 177 169 (likely taken down by moderation) 178 - </p> 179 - {/if} 180 - 181 - {#if hiddenRepliesError} 182 - <p class="missing-replies-info"> 183 - <i class="fa-solid fa-ban"></i> Hidden replies not available (post too old) 184 170 </p> 185 171 {/if} 186 172 </div>