[READ-ONLY] Mirror of https://github.com/flo-bit/room. tiny 3d rooms saved locally or in your bluesky account, svelte/threlte
flo-bit.dev/room/
1import { RichText } from '@atproto/api';
2
3export type PostEmbedImage = {
4 type: 'images';
5
6 images: {
7 alt: string;
8 thumb: string;
9 fullsize: string;
10 aspectRatio?: {
11 width: number;
12 height: number;
13 };
14 }[];
15};
16
17export type PostEmbedExternal = {
18 type: 'external';
19
20 external: {
21 href: string;
22 thumb: string;
23 title: string;
24 description: string;
25 };
26};
27
28export type PostEmbedVideo = {
29 type: 'video';
30
31 video: {
32 playlist: string;
33
34 thumb: string;
35 alt: string;
36
37 aspectRatio?: {
38 width: number;
39 height: number;
40 };
41 };
42};
43
44export type UnknownEmbed = {
45 type: 'unknown';
46} & Record<string, unknown>;
47
48function blueskyEmbedTypeToEmbedType(type: string) {
49 switch (type) {
50 case 'app.bsky.embed.external#view':
51 case 'app.bsky.embed.external':
52 return 'external';
53 case 'app.bsky.embed.images#view':
54 case 'app.bsky.embed.images':
55 return 'images';
56 case 'app.bsky.embed.video#view':
57 case 'app.bsky.embed.video':
58 return 'video';
59 default:
60 return 'unknown';
61 }
62}
63
64export type PostData = {
65 reposted?: { handle: string; href: string };
66
67 author: {
68 displayName: string;
69 handle: string;
70 avatar: string;
71 href: string;
72 };
73
74 replyCount: number;
75 repostCount: number;
76 likeCount: number;
77
78 createdAt: string;
79
80 embed?: PostEmbedImage | PostEmbedExternal | PostEmbedVideo | UnknownEmbed;
81
82 htmlContent?: string;
83};
84
85// eslint-disable-next-line @typescript-eslint/no-explicit-any
86export function blueskyPostToPostData(post: any): PostData {
87 return {
88 author: {
89 displayName: post.author.displayName,
90 handle: post.author.handle,
91 avatar: post.author.avatar,
92 href: '#'
93 },
94 replyCount: post.replyCount,
95 repostCount: post.repostCount,
96 likeCount: post.likeCount,
97 createdAt: post.createdAt,
98
99 embed: {
100 type: blueskyEmbedTypeToEmbedType(post.embed.$type),
101 // eslint-disable-next-line @typescript-eslint/no-explicit-any
102 images: post.embed.images?.map((image: any) => ({
103 alt: image.alt,
104 thumb: image.thumb,
105 aspectRatio: image.aspectRatio,
106 fullsize: image.fullsize
107 })),
108 external: post.embed.external
109 ? {
110 href: post.embed.external.uri,
111 title: post.embed.external.title,
112 description: post.embed.external.description,
113 thumb: post.embed.external.thumb
114 }
115 : undefined,
116 video: post.embed
117 ? {
118 playlist: post.embed.playlist,
119 thumb: post.embed.thumbnail,
120 alt: post.embed.alt,
121 aspectRatio: post.embed.aspectRatio
122 }
123 : undefined
124 } as PostEmbedExternal | PostEmbedVideo | PostEmbedImage | UnknownEmbed,
125
126 htmlContent: blueskyPostToHTML(post)
127 };
128}
129
130// eslint-disable-next-line @typescript-eslint/no-explicit-any
131export function blueskyPostToHTML(post: any, baseBskyUrl: string = 'https://bsky.app') {
132 if (!post?.record) {
133 return '';
134 }
135 const rt = new RichText(post.record);
136 let html = '';
137
138 const createLink = (href: string, text: string) => {
139 return `<a target="_blank" rel="noopener noreferrer nofollow" href="${encodeURI(href)}">${encodeURI(text)}</a>`;
140 };
141
142 for (const segment of rt.segments()) {
143 if (!segment) continue;
144 if (segment.isLink() && segment.link?.uri) {
145 html += createLink(segment.link?.uri, segment.text);
146 } else if (segment.isMention() && segment.mention?.did) {
147 html += createLink(`${baseBskyUrl}/profile/${segment.mention?.did}`, segment.text);
148 } else if (segment.isTag() && segment.tag?.tag) {
149 html += createLink(`${baseBskyUrl}/hashtag/${segment.tag?.tag}`, segment.text);
150 } else {
151 html += segment.text;
152 }
153 }
154
155 return html.replace(/\n/g, '<br>');
156}
157
158export { default as Post } from './Post.svelte';