BlueSky & more on desktop
lazurite.stormlightlabs.org/
tauri
rust
typescript
bluesky
appview
atproto
solid
1import type { NormalizedEmbed } from "../feeds";
2import { asRecord } from "../type-guards";
3import type {
4 BlockedPost,
5 EmbedView,
6 FeedReplyNode,
7 FeedViewPost,
8 Maybe,
9 NotFoundPost,
10 PostView,
11 ProfileViewBasic,
12 ThreadNode,
13 ThreadViewPost,
14} from "../types";
15
16export function isPostView(value: unknown): value is PostView {
17 const record = asRecord(value);
18 const author = asRecord(record?.author);
19 const postRecord = asRecord(record?.record);
20
21 return !!record
22 && !!author
23 && !!postRecord
24 && typeof record.cid === "string"
25 && typeof record.indexedAt === "string"
26 && typeof record.uri === "string"
27 && isProfileViewBasic(author);
28}
29
30export function isFeedViewPost(value: unknown): value is FeedViewPost {
31 const record = asRecord(value);
32 return !!record && isPostView(record.post);
33}
34
35export function isThreadNode(value: unknown): value is ThreadNode {
36 const record = asRecord(value);
37 if (!record || typeof record.$type !== "string") {
38 return false;
39 }
40
41 if (record.$type === "app.bsky.feed.defs#threadViewPost") {
42 return isPostView(record.post);
43 }
44
45 return record.$type === "app.bsky.feed.defs#blockedPost" || record.$type === "app.bsky.feed.defs#notFoundPost";
46}
47
48export function isRepostReason(item: FeedViewPost) {
49 return item.reason?.$type === "app.bsky.feed.defs#reasonRepost";
50}
51
52export function isQuoteEmbed(embed: Maybe<EmbedView>) {
53 return embed?.$type === "app.bsky.embed.record#view" || embed?.$type === "app.bsky.embed.recordWithMedia#view";
54}
55
56export function isReplyItem(item: FeedViewPost) {
57 if (item.reply) {
58 return true;
59 }
60
61 const record = asRecord(item.post.record);
62 return !!asRecord(record?.reply);
63}
64
65export function isReplyByUnfollowed(item: FeedViewPost) {
66 return isReplyItem(item) && !item.post.author.viewer?.following;
67}
68
69export function isThreadViewPost(node: Maybe<ThreadNode>): node is ThreadViewPost {
70 return !!node && node.$type === "app.bsky.feed.defs#threadViewPost";
71}
72
73export function isBlockedNode(node: Maybe<ThreadNode | FeedReplyNode>): node is BlockedPost {
74 return !!node && node.$type === "app.bsky.feed.defs#blockedPost";
75}
76
77export function isNotFoundNode(node: Maybe<ThreadNode | FeedReplyNode>): node is NotFoundPost {
78 return !!node && node.$type === "app.bsky.feed.defs#notFoundPost";
79}
80
81export function isProfileViewBasic(value: unknown): value is ProfileViewBasic {
82 const record = asRecord(value);
83 return !!record && typeof record.did === "string" && typeof record.handle === "string";
84}
85
86export function isNormalizedEmbed(value: unknown): value is NormalizedEmbed {
87 if (!value || typeof value !== "object") {
88 return false;
89 }
90
91 const kind = (value as { kind?: unknown }).kind;
92 return kind === "external"
93 || kind === "images"
94 || kind === "record"
95 || kind === "recordWithMedia"
96 || kind === "recognized-unrenderable"
97 || kind === "unknown"
98 || kind === "video";
99}