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 let embed = data.data.links?.player?.[0]; 63 await rep.mutate.assertFact([ 64 { 65 entity: entityID, 66 attribute: "block/type", 67 data: { type: "block-type-union", value: "embed" }, 68 }, 69 { 70 entity: entityID, 71 attribute: "embed/url", 72 data: { 73 type: "string", 74 value: embed.href, 75 }, 76 }, 77 { 78 entity: entityID, 79 attribute: "embed/height", 80 data: { 81 type: "number", 82 value: embed.media?.height || 300, 83 }, 84 }, 85 ]); 86 return; 87 } 88 await rep?.mutate.assertFact([ 89 { 90 entity: entityID, 91 attribute: "link/url", 92 data: { 93 type: "string", 94 value: url, 95 }, 96 }, 97 { 98 entity: entityID, 99 attribute: "block/type", 100 data: { type: "block-type-union", value: "link" }, 101 }, 102 { 103 entity: entityID, 104 attribute: "link/title", 105 data: { 106 type: "string", 107 value: data.data.meta?.title || "", 108 }, 109 }, 110 { 111 entity: entityID, 112 attribute: "link/description", 113 data: { 114 type: "string", 115 value: data.data.meta?.description || "", 116 }, 117 }, 118 ]); 119 let imageRes = await fetch("/api/link_previews", { 120 headers: { "Content-Type": "application/json" }, 121 method: "POST", 122 body: JSON.stringify({ url, type: "image" } as LinkPreviewBody), 123 }); 124 125 let image_data = await (imageRes.json() as LinkPreviewImageResult); 126 127 await rep?.mutate.assertFact({ 128 entity: entityID, 129 attribute: "link/preview", 130 data: { 131 fallback: "", 132 type: "image", 133 src: image_data.url, 134 width: image_data.width, 135 height: image_data.height, 136 }, 137 }); 138} 139 140export async function addBlueskyPostBlock( 141 url: string, 142 entityID: string, 143 rep: Replicache<ReplicacheMutators>, 144) { 145 //construct bsky post uri from url 146 let urlParts = url?.split("/"); 147 let userDidOrHandle = urlParts ? urlParts[4] : ""; // "schlage.town" or "did:plc:jjsc5rflv3cpv6hgtqhn2dcm" 148 let collection = "app.bsky.feed.post"; 149 let postId = urlParts ? urlParts[6] : ""; 150 let uri = `at://${userDidOrHandle}/${collection}/${postId}`; 151 152 let post = await getBlueskyPost(uri); 153 if (!post || post === undefined) return false; 154 155 await rep.mutate.assertFact({ 156 entity: entityID, 157 attribute: "block/type", 158 data: { type: "block-type-union", value: "bluesky-post" }, 159 }); 160 await rep?.mutate.assertFact({ 161 entity: entityID, 162 attribute: "block/bluesky-post", 163 data: { 164 type: "bluesky-post", 165 //TODO: this is a hack to get rid of a nested Array buffer which cannot be frozen, which replicache does on write. 166 value: JSON.parse(JSON.stringify(post.data.thread)), 167 }, 168 }); 169 return true; 170} 171async function getBlueskyPost(uri: string) { 172 const agent = new AtpAgent({ service: "https://public.api.bsky.app" }); 173 try { 174 let blueskyPost = await agent 175 .getPostThread({ 176 uri: uri, 177 depth: 0, 178 parentHeight: 0, 179 }) 180 .then((res) => { 181 return res; 182 }); 183 return blueskyPost; 184 } catch (error) { 185 let rect = document; 186 return; 187 } 188}