a tool for shared writing and social publishing
1import {
2 LinkPreviewBody,
3 LinkPreviewImageResult,
4 LinkPreviewMetadataResult,
5} from "app/api/link_previews/route";
6import { Replicache } from "replicache";
7import type { ReplicacheMutators } from "src/replicache";
8import { AtpAgent } from "@atproto/api";
9import { v7 } from "uuid";
10
11export async function addLinkBlock(
12 url: string,
13 entityID: string,
14 rep?: Replicache<ReplicacheMutators> | null,
15) {
16 if (!rep) return;
17
18 let res = await fetch("/api/link_previews", {
19 headers: { "Content-Type": "application/json" },
20 method: "POST",
21 body: JSON.stringify({ url, type: "meta" } as LinkPreviewBody),
22 });
23 if (res.status !== 200) {
24 await rep?.mutate.assertFact([
25 {
26 entity: entityID,
27 attribute: "link/url",
28 data: {
29 type: "string",
30 value: url,
31 },
32 },
33 {
34 entity: entityID,
35 attribute: "block/type",
36 data: { type: "block-type-union", value: "link" },
37 },
38 ]);
39 return;
40 }
41 let data = await (res.json() as LinkPreviewMetadataResult);
42 if (!data.success) {
43 await rep?.mutate.assertFact([
44 {
45 entity: entityID,
46 attribute: "link/url",
47 data: {
48 type: "string",
49 value: url,
50 },
51 },
52 {
53 entity: entityID,
54 attribute: "block/type",
55 data: { type: "block-type-union", value: "link" },
56 },
57 ]);
58 return;
59 }
60
61 if (data.data.links?.player?.[0]) {
62 console.log(data.data.links.player);
63 let embed = data.data.links?.player?.[0];
64 await rep.mutate.assertFact([
65 {
66 entity: entityID,
67 attribute: "block/type",
68 data: { type: "block-type-union", value: "embed" },
69 },
70 {
71 entity: entityID,
72 attribute: "embed/url",
73 data: {
74 type: "string",
75 value: embed.href,
76 },
77 },
78 {
79 entity: entityID,
80 attribute: "embed/height",
81 data: {
82 type: "number",
83 value: embed.media?.height || 300,
84 },
85 },
86 ]);
87 return;
88 }
89 await rep?.mutate.assertFact([
90 {
91 entity: entityID,
92 attribute: "link/url",
93 data: {
94 type: "string",
95 value: url,
96 },
97 },
98 {
99 entity: entityID,
100 attribute: "block/type",
101 data: { type: "block-type-union", value: "link" },
102 },
103 {
104 entity: entityID,
105 attribute: "link/title",
106 data: {
107 type: "string",
108 value: data.data.meta?.title || "",
109 },
110 },
111 {
112 entity: entityID,
113 attribute: "link/description",
114 data: {
115 type: "string",
116 value: data.data.meta?.description || "",
117 },
118 },
119 ]);
120 let imageRes = await fetch("/api/link_previews", {
121 headers: { "Content-Type": "application/json" },
122 method: "POST",
123 body: JSON.stringify({ url, type: "image" } as LinkPreviewBody),
124 });
125
126 let image_data = await (imageRes.json() as LinkPreviewImageResult);
127
128 await rep?.mutate.assertFact({
129 entity: entityID,
130 attribute: "link/preview",
131 data: {
132 fallback: "",
133 type: "image",
134 src: image_data.url,
135 width: image_data.width,
136 height: image_data.height,
137 },
138 });
139}
140
141export async function addBlueskyPostBlock(
142 url: string,
143 entityID: string,
144 rep: Replicache<ReplicacheMutators>,
145) {
146 //construct bsky post uri from url
147 let urlParts = url?.split("/");
148 let userDidOrHandle = urlParts ? urlParts[4] : ""; // "schlage.town" or "did:plc:jjsc5rflv3cpv6hgtqhn2dcm"
149 let collection = "app.bsky.feed.post";
150 let postId = urlParts ? urlParts[6] : "";
151 let uri = `at://${userDidOrHandle}/${collection}/${postId}`;
152
153 let post = await getBlueskyPost(uri);
154 if (!post || post === undefined) return false;
155
156 await rep.mutate.assertFact({
157 entity: entityID,
158 attribute: "block/type",
159 data: { type: "block-type-union", value: "bluesky-post" },
160 });
161 await rep?.mutate.assertFact({
162 entity: entityID,
163 attribute: "block/bluesky-post",
164 data: {
165 type: "bluesky-post",
166 //TODO: this is a hack to get rid of a nested Array buffer which cannot be frozen, which replicache does on write.
167 value: JSON.parse(JSON.stringify(post.data.thread)),
168 },
169 });
170 return true;
171}
172async function getBlueskyPost(uri: string) {
173 const agent = new AtpAgent({ service: "https://public.api.bsky.app" });
174 try {
175 let blueskyPost = await agent
176 .getPostThread({
177 uri: uri,
178 depth: 0,
179 parentHeight: 0,
180 })
181 .then((res) => {
182 return res;
183 });
184 return blueskyPost;
185 } catch (error) {
186 let rect = document;
187 return;
188 }
189}