A calm place to write long-form, and publish it to the open social web. skypress.blog/
0

Configure Feed

Select the types of activity you want to include in your feed.

at trunk 3.6 kB View raw
1/** 2 * Reader-side (public, no-auth) resolution of a writer's SkyPress publications (SP10, step F/G). 3 * 4 * Reads `site.standard.publication` over the public `com.atproto.repo` XRPC (`listRecords`, 5 * SSRF-guarded), keeping only publications SkyPress OWNS (`isSkyPressPublicationUrl`) so a 6 * record some other tool wrote into the shared collection never renders under a skypress.blog 7 * URL. The authed CRUD counterpart lives in `../publish/publications.ts`. 8 */ 9import { listRecords } from './records'; 10import { 11 isSkyPressPublicationUrl, 12 publicationSlugFromUrl, 13} from '../publish/records'; 14import { detectProvider, type ProviderId } from '../publish/providers'; 15import type { BlobRefJson } from '../media/blob'; 16import { parseBasicTheme, type BasicTheme } from '../publish/themes'; 17 18export interface ReaderPublication { 19 uri: string; 20 slug: string; 21 name: string; 22 description: string | null; 23 icon: BlobRefJson | null; 24 basicTheme: BasicTheme | null; 25} 26 27/** A publication record another app wrote into the shared collection (Leaflet, …). Read-only, links out. */ 28export interface ReaderForeignPublication { 29 uri: string; 30 name: string; 31 hostname: string; 32 url: string; 33 icon: BlobRefJson | null; 34 /** The app that wrote the record (Leaflet, pckt, …), or null when unrecognised. */ 35 provider: ProviderId | null; 36} 37 38interface RawPublication { 39 url?: string; 40 name?: string; 41 description?: string; 42 icon?: BlobRefJson; 43 basicTheme?: unknown; 44} 45 46function toReaderPublication( record: { uri: string; value: RawPublication } ): ReaderPublication | null { 47 const value = record.value; 48 if ( ! value?.url || ! isSkyPressPublicationUrl( value.url ) ) { 49 return null; 50 } 51 const slug = publicationSlugFromUrl( value.url ); 52 if ( ! slug ) { 53 return null; 54 } 55 return { 56 uri: record.uri, 57 slug, 58 name: value.name ?? slug, 59 description: value.description?.trim() || null, 60 icon: value.icon ?? null, 61 basicTheme: parseBasicTheme( value.basicTheme ), 62 }; 63} 64 65/** Map a raw record to a foreign publication, or null if its url isn't a usable http(s) link. */ 66function toReaderForeignPublication( 67 record: { uri: string; value: RawPublication } 68): ReaderForeignPublication | null { 69 const value = record.value; 70 if ( ! value?.url ) { 71 return null; 72 } 73 let parsed: URL; 74 try { 75 parsed = new URL( value.url ); 76 } catch { 77 return null; 78 } 79 if ( parsed.protocol !== 'http:' && parsed.protocol !== 'https:' ) { 80 return null; 81 } 82 return { 83 uri: record.uri, 84 name: value.name ?? parsed.hostname, 85 hostname: parsed.hostname, 86 url: value.url, 87 icon: value.icon ?? null, 88 provider: detectProvider( value.url, value ), 89 }; 90} 91 92/** 93 * Fetch the writer's publication records once and partition them: those SkyPress OWNS 94 * (rendered under skypress.blog URLs) versus FOREIGN records another app wrote into the 95 * shared collection (Leaflet, …), which the author index links out to (Decision 0010). 96 */ 97export async function listAllReaderPublications( 98 pdsUrl: string, 99 did: string 100): Promise< { owned: ReaderPublication[]; foreign: ReaderForeignPublication[] } > { 101 const records = await listRecords< RawPublication >( 102 pdsUrl, 103 did, 104 'site.standard.publication', 105 100 106 ); 107 const owned: ReaderPublication[] = []; 108 const foreign: ReaderForeignPublication[] = []; 109 for ( const record of records ) { 110 if ( record.value?.url && isSkyPressPublicationUrl( record.value.url ) ) { 111 const pub = toReaderPublication( record ); 112 if ( pub ) { 113 owned.push( pub ); 114 } 115 continue; 116 } 117 const fp = toReaderForeignPublication( record ); 118 if ( fp ) { 119 foreign.push( fp ); 120 } 121 } 122 return { owned, foreign }; 123} 124