a tool for shared writing and social publishing
at 12cf4ffee79fa0f8fab38e1fe7f2ddfa3ec678a2 189 lines 4.7 kB view raw
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}