wip bsky client for the web & android bbell.vt3e.cat
at main 239 lines 5.7 kB view raw
1import { defineStore } from 'pinia' 2import { shallowRef, reactive } from 'vue' 3import type { ComAtprotoRepoStrongRef } from '@atcute/atproto' 4import { AppBskyFeedDefs, AppBskyFeedPost } from '@atcute/bluesky' 5import { ok } from '@atcute/client' 6 7import { useAuthStore } from './auth' 8 9type PostView = AppBskyFeedDefs.PostView 10 11export const usePostStore = defineStore('posts', () => { 12 const auth = useAuthStore() 13 14 const posts = shallowRef(new Map<string, PostView>()) 15 16 function mergePost(post: PostView): PostView { 17 const existing = posts.value.get(post.uri) 18 19 if (existing) { 20 Object.assign(existing, post) 21 return existing 22 } else { 23 const reactivePost = reactive(post) 24 posts.value.set(post.uri, reactivePost) 25 return reactivePost 26 } 27 } 28 29 async function toggleLike(post: PostView) { 30 if (!auth.isAuthenticated) return 31 if (!auth.session) return 32 33 const uri = post.uri 34 const cid = post.cid 35 36 const originalLike = post.viewer?.like 37 const originalCount = post.likeCount || 0 38 39 if (!post.viewer) post.viewer = {} 40 41 if (originalLike) { 42 post.viewer.like = undefined 43 post.likeCount = originalCount - 1 44 } else { 45 post.viewer.like = 'at://did:plc:pending/like' 46 post.likeCount = originalCount + 1 47 } 48 49 try { 50 const rpc = auth.getRpc() 51 if (originalLike) { 52 await rpc.post('com.atproto.repo.deleteRecord', { 53 input: { 54 collection: 'app.bsky.feed.like', 55 repo: auth.session?.info.sub, 56 rkey: originalLike.split('/').pop()!, 57 }, 58 }) 59 } else { 60 const data = ok( 61 await rpc.post('com.atproto.repo.createRecord', { 62 input: { 63 collection: 'app.bsky.feed.like', 64 repo: auth.session?.info.sub, 65 record: { 66 $type: 'app.bsky.feed.like', 67 subject: { uri, cid }, 68 createdAt: new Date().toISOString(), 69 }, 70 }, 71 }), 72 ) 73 74 const storedPost = posts.value.get(uri) 75 if (storedPost && storedPost.viewer) { 76 storedPost.viewer.like = data.uri 77 } 78 } 79 } catch (err) { 80 console.error('Failed to toggle like', err) 81 const storedPost = posts.value.get(uri) 82 if (storedPost && storedPost.viewer) { 83 storedPost.viewer.like = originalLike 84 storedPost.likeCount = originalCount 85 } 86 } 87 } 88 89 async function toggleBookmark(post: PostView, silent = false) { 90 if (!auth.isAuthenticated) return 91 if (!auth.session) return 92 93 const uri = post.uri 94 const cid = post.cid 95 96 const originalBookmarked = post.viewer?.bookmarked 97 const originalCount = post.bookmarkCount || 0 98 99 if (!post.viewer) post.viewer = {} 100 101 if (originalBookmarked) { 102 post.viewer.bookmarked = false 103 post.bookmarkCount = originalCount - 1 104 } else { 105 post.viewer.bookmarked = true 106 post.bookmarkCount = originalCount + 1 107 } 108 109 try { 110 const rpc = auth.getRpc() 111 if (originalBookmarked) { 112 const res = await rpc.post('app.bsky.bookmark.deleteBookmark', { 113 input: { 114 uri: uri, 115 }, 116 as: null, 117 }) 118 } else { 119 const res = await rpc.post('app.bsky.bookmark.createBookmark', { 120 input: { 121 cid: cid, 122 uri: uri, 123 }, 124 as: null, 125 }) 126 } 127 } catch (err) { 128 console.error('Failed to toggle bookmark', err) 129 const storedPost = posts.value.get(uri) 130 if (storedPost && storedPost.viewer) { 131 storedPost.viewer.bookmarked = originalBookmarked 132 storedPost.bookmarkCount = originalCount 133 } 134 } 135 } 136 137 async function toggleRepost(post: PostView) { 138 if (!auth.isAuthenticated) return 139 140 const uri = post.uri 141 const cid = post.cid 142 143 const originalRepost = post.viewer?.repost 144 const originalCount = post.repostCount || 0 145 146 if (!post.viewer) post.viewer = {} 147 148 if (originalRepost) { 149 post.viewer.repost = undefined 150 post.repostCount = originalCount - 1 151 } else { 152 post.viewer.repost = 'at://did:plc:pending/repost' 153 post.repostCount = originalCount + 1 154 } 155 156 try { 157 const rpc = auth.getRpc() 158 if (originalRepost) { 159 await ok( 160 rpc.post('com.atproto.repo.deleteRecord', { 161 input: { 162 collection: 'app.bsky.feed.repost', 163 repo: auth.session!.info.sub, 164 rkey: originalRepost.split('/').pop()!, 165 }, 166 }), 167 ) 168 } else { 169 const data = ok( 170 await rpc.post('com.atproto.repo.createRecord', { 171 input: { 172 collection: 'app.bsky.feed.repost', 173 repo: auth.session!.info.sub, 174 record: { 175 $type: 'app.bsky.feed.repost', 176 subject: { uri, cid }, 177 createdAt: new Date().toISOString(), 178 }, 179 }, 180 }), 181 ) 182 const storedPost = posts.value.get(uri) 183 if (storedPost && storedPost.viewer) { 184 storedPost.viewer.repost = data.uri 185 } 186 } 187 } catch (err) { 188 console.error('Failed to toggle repost', err) 189 const storedPost = posts.value.get(uri) 190 if (storedPost && storedPost.viewer) { 191 storedPost.viewer.repost = originalRepost 192 storedPost.repostCount = originalCount 193 } 194 } 195 } 196 197 async function createPost(args: { 198 text: string 199 embeds?: AppBskyFeedPost.Main['embed'] 200 reply?: { 201 parent: ComAtprotoRepoStrongRef.Main 202 root: ComAtprotoRepoStrongRef.Main 203 } 204 }) { 205 const { reply, text, embeds } = args 206 if (!auth.isAuthenticated || !auth.session) throw new Error('Not authenticated') 207 208 const rpc = auth.getRpc() 209 const record: AppBskyFeedPost.Main = { 210 $type: 'app.bsky.feed.post', 211 text, 212 createdAt: new Date().toISOString(), 213 embed: embeds, 214 reply: reply, 215 } 216 217 const data = await ok( 218 rpc.post('com.atproto.repo.createRecord', { 219 input: { 220 collection: 'app.bsky.feed.post', 221 repo: auth.session.info.sub, 222 record, 223 }, 224 }), 225 ) 226 227 return data 228 } 229 230 return { 231 posts, 232 mergePost, 233 createPost, 234 235 toggleLike, 236 toggleBookmark, 237 toggleRepost, 238 } 239})