about summary refs log tree commit diff
path: root/src/lib/api
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-03-31 13:17:26 -0500
committerGitHub <noreply@github.com>2023-03-31 13:17:26 -0500
commita3334a01a221877d3e06e02f960fda441f3460bd (patch)
tree64cdbb1232d1a3c00750c346b6e3ae529b51d1b0 /src/lib/api
parent19f3a2fa92a61ddb785fc4e42d73792c1d0e772c (diff)
downloadvoidsky-a3334a01a221877d3e06e02f960fda441f3460bd.tar.zst
Lex refactor (#362)
* Remove the hackcheck for upgrades

* Rename the PostEmbeds folder to match the codebase style

* Updates to latest lex refactor

* Update to use new bsky agent

* Update to use api package's richtext library

* Switch to upsertProfile

* Add TextEncoder/TextDecoder polyfill

* Add Intl.Segmenter polyfill

* Update composer to calculate lengths by grapheme

* Fix detox

* Fix login in e2e

* Create account e2e passing

* Implement an e2e mocking framework

* Don't use private methods on mobx models as mobx can't track them

* Add tooling for e2e-specific builds and add e2e media-picker mock

* Add some tests and fix some bugs around profile editing

* Add shell tests

* Add home screen tests

* Add thread screen tests

* Add tests for other user profile screens

* Add search screen tests

* Implement profile imagery change tools and tests

* Update to new embed behaviors

* Add post tests

* Fix to profile-screen test

* Fix session resumption

* Update web composer to new api

* 1.11.0

* Fix pagination cursor parameters

* Add quote posts to notifications

* Fix embed layouts

* Remove youtube inline player and improve tap handling on link cards

* Reset minimal shell mode on all screen loads and feed swipes (close #299)

* Update podfile.lock

* Improve post notfound UI (close #366)

* Bump atproto packages
Diffstat (limited to 'src/lib/api')
-rw-r--r--src/lib/api/api-polyfill.ts8
-rw-r--r--src/lib/api/api-polyfill.web.ts3
-rw-r--r--src/lib/api/build-suggested-posts.ts22
-rw-r--r--src/lib/api/feed-manip.ts8
-rw-r--r--src/lib/api/index.ts176
5 files changed, 96 insertions, 121 deletions
diff --git a/src/lib/api/api-polyfill.ts b/src/lib/api/api-polyfill.ts
index b7be6913a..7c38625a2 100644
--- a/src/lib/api/api-polyfill.ts
+++ b/src/lib/api/api-polyfill.ts
@@ -1,11 +1,11 @@
-import AtpAgent from '@atproto/api'
+import {BskyAgent, stringifyLex, jsonToLex} from '@atproto/api'
 import RNFS from 'react-native-fs'
 
 const GET_TIMEOUT = 15e3 // 15s
 const POST_TIMEOUT = 60e3 // 60s
 
 export function doPolyfill() {
-  AtpAgent.configure({fetch: fetchHandler})
+  BskyAgent.configure({fetch: fetchHandler})
 }
 
 interface FetchHandlerResponse {
@@ -22,7 +22,7 @@ async function fetchHandler(
 ): Promise<FetchHandlerResponse> {
   const reqMimeType = reqHeaders['Content-Type'] || reqHeaders['content-type']
   if (reqMimeType && reqMimeType.startsWith('application/json')) {
-    reqBody = JSON.stringify(reqBody)
+    reqBody = stringifyLex(reqBody)
   } else if (
     typeof reqBody === 'string' &&
     (reqBody.startsWith('/') || reqBody.startsWith('file:'))
@@ -65,7 +65,7 @@ async function fetchHandler(
   let resBody
   if (resMimeType) {
     if (resMimeType.startsWith('application/json')) {
-      resBody = await res.json()
+      resBody = jsonToLex(await res.json())
     } else if (resMimeType.startsWith('text/')) {
       resBody = await res.text()
     } else {
diff --git a/src/lib/api/api-polyfill.web.ts b/src/lib/api/api-polyfill.web.ts
index 1469cf905..1ad22b3d0 100644
--- a/src/lib/api/api-polyfill.web.ts
+++ b/src/lib/api/api-polyfill.web.ts
@@ -1,4 +1,3 @@
 export function doPolyfill() {
-  // TODO needed? native fetch may work fine -prf
-  // AtpApi.xrpc.fetch = fetchHandler
+  // no polyfill is needed on web
 }
diff --git a/src/lib/api/build-suggested-posts.ts b/src/lib/api/build-suggested-posts.ts
index defa45311..b9feefc72 100644
--- a/src/lib/api/build-suggested-posts.ts
+++ b/src/lib/api/build-suggested-posts.ts
@@ -1,9 +1,9 @@
 import {RootStoreModel} from 'state/index'
 import {
-  AppBskyFeedFeedViewPost,
+  AppBskyFeedDefs,
   AppBskyFeedGetAuthorFeed as GetAuthorFeed,
 } from '@atproto/api'
-type ReasonRepost = AppBskyFeedFeedViewPost.ReasonRepost
+type ReasonRepost = AppBskyFeedDefs.ReasonRepost
 
 async function getMultipleAuthorsPosts(
   rootStore: RootStoreModel,
@@ -12,12 +12,12 @@ async function getMultipleAuthorsPosts(
   limit: number = 10,
 ) {
   const responses = await Promise.all(
-    authors.map((author, index) =>
-      rootStore.api.app.bsky.feed
+    authors.map((actor, index) =>
+      rootStore.agent
         .getAuthorFeed({
-          author,
+          actor,
           limit,
-          before: cursor ? cursor.split(',')[index] : undefined,
+          cursor: cursor ? cursor.split(',')[index] : undefined,
         })
         .catch(_err => ({success: false, headers: {}, data: {feed: []}})),
     ),
@@ -29,14 +29,14 @@ function mergePosts(
   responses: GetAuthorFeed.Response[],
   {repostsOnly, bestOfOnly}: {repostsOnly?: boolean; bestOfOnly?: boolean},
 ) {
-  let posts: AppBskyFeedFeedViewPost.Main[] = []
+  let posts: AppBskyFeedDefs.FeedViewPost[] = []
 
   if (bestOfOnly) {
     for (const res of responses) {
       if (res.success) {
-        // filter the feed down to the post with the most upvotes
+        // filter the feed down to the post with the most likes
         res.data.feed = res.data.feed.reduce(
-          (acc: AppBskyFeedFeedViewPost.Main[], v) => {
+          (acc: AppBskyFeedDefs.FeedViewPost[], v) => {
             if (
               !acc?.[0] &&
               !v.reason &&
@@ -49,7 +49,7 @@ function mergePosts(
               acc &&
               !v.reason &&
               !v.reply &&
-              v.post.upvoteCount > acc[0]?.post.upvoteCount &&
+              (v.post.likeCount || 0) > (acc[0]?.post.likeCount || 0) &&
               isRecentEnough(v.post.indexedAt)
             ) {
               return [v]
@@ -92,7 +92,7 @@ function mergePosts(
   return posts
 }
 
-function isARepostOfSomeoneElse(post: AppBskyFeedFeedViewPost.Main): boolean {
+function isARepostOfSomeoneElse(post: AppBskyFeedDefs.FeedViewPost): boolean {
   return (
     post.reason?.$type === 'app.bsky.feed.feedViewPost#reasonRepost' &&
     post.post.author.did !== (post.reason as ReasonRepost).by.did
diff --git a/src/lib/api/feed-manip.ts b/src/lib/api/feed-manip.ts
index e9a32b7a6..6fdc9a48f 100644
--- a/src/lib/api/feed-manip.ts
+++ b/src/lib/api/feed-manip.ts
@@ -1,8 +1,8 @@
-import {AppBskyFeedFeedViewPost} from '@atproto/api'
+import {AppBskyFeedDefs} from '@atproto/api'
 import lande from 'lande'
-type FeedViewPost = AppBskyFeedFeedViewPost.Main
-import {hasProp} from '@atproto/lexicon'
+import {hasProp} from 'lib/type-guards'
 import {LANGUAGES_MAP_CODE2} from '../../locale/languages'
+type FeedViewPost = AppBskyFeedDefs.FeedViewPost
 
 export type FeedTunerFn = (
   tuner: FeedTuner,
@@ -174,7 +174,7 @@ export class FeedTuner {
       }
       const item = slices[i].rootItem
       const isRepost = Boolean(item.reason)
-      if (!isRepost && item.post.upvoteCount < 2) {
+      if (!isRepost && (item.post.likeCount || 0) < 2) {
         slices.splice(i, 1)
       }
     }
diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts
index 85eca4a61..a5aa916df 100644
--- a/src/lib/api/index.ts
+++ b/src/lib/api/index.ts
@@ -1,16 +1,16 @@
 import {
   AppBskyEmbedImages,
   AppBskyEmbedExternal,
-  ComAtprotoBlobUpload,
   AppBskyEmbedRecord,
+  AppBskyEmbedRecordWithMedia,
+  ComAtprotoRepoUploadBlob,
+  RichText,
 } from '@atproto/api'
 import {AtUri} from '../../third-party/uri'
 import {RootStoreModel} from 'state/models/root-store'
-import {extractEntities} from 'lib/strings/rich-text-detection'
 import {isNetworkError} from 'lib/strings/errors'
 import {LinkMeta} from '../link-meta/link-meta'
 import {Image} from '../media/manip'
-import {RichText} from '../strings/rich-text'
 import {isWeb} from 'platform/detection'
 
 export interface ExternalEmbedDraft {
@@ -27,7 +27,7 @@ export async function resolveName(store: RootStoreModel, didOrHandle: string) {
   if (didOrHandle.startsWith('did:')) {
     return didOrHandle
   }
-  const res = await store.api.com.atproto.handle.resolve({
+  const res = await store.agent.resolveHandle({
     handle: didOrHandle,
   })
   return res.data.did
@@ -37,15 +37,15 @@ export async function uploadBlob(
   store: RootStoreModel,
   blob: string,
   encoding: string,
-): Promise<ComAtprotoBlobUpload.Response> {
+): Promise<ComAtprotoRepoUploadBlob.Response> {
   if (isWeb) {
     // `blob` should be a data uri
-    return store.api.com.atproto.blob.upload(convertDataURIToUint8Array(blob), {
+    return store.agent.uploadBlob(convertDataURIToUint8Array(blob), {
       encoding,
     })
   } else {
     // `blob` should be a path to a file in the local FS
-    return store.api.com.atproto.blob.upload(
+    return store.agent.uploadBlob(
       blob, // this will be special-cased by the fetch monkeypatch in /src/state/lib/api.ts
       {encoding},
     )
@@ -70,22 +70,18 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
     | AppBskyEmbedImages.Main
     | AppBskyEmbedExternal.Main
     | AppBskyEmbedRecord.Main
+    | AppBskyEmbedRecordWithMedia.Main
     | undefined
   let reply
-  const text = new RichText(opts.rawText, undefined, {
-    cleanNewlines: true,
-  }).text.trim()
+  const rt = new RichText(
+    {text: opts.rawText.trim()},
+    {
+      cleanNewlines: true,
+    },
+  )
 
   opts.onStateChange?.('Processing...')
-  const entities = extractEntities(text, opts.knownHandles)
-  if (entities) {
-    for (const ent of entities) {
-      if (ent.type === 'mention') {
-        const prof = await store.profiles.getProfile(ent.value)
-        ent.value = prof.data.did
-      }
-    }
-  }
+  await rt.detectFacets(store.agent)
 
   if (opts.quote) {
     embed = {
@@ -95,24 +91,37 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
         cid: opts.quote.cid,
       },
     } as AppBskyEmbedRecord.Main
-  } else if (opts.images?.length) {
-    embed = {
-      $type: 'app.bsky.embed.images',
-      images: [],
-    } as AppBskyEmbedImages.Main
-    let i = 1
+  }
+
+  if (opts.images?.length) {
+    const images: AppBskyEmbedImages.Image[] = []
     for (const image of opts.images) {
-      opts.onStateChange?.(`Uploading image #${i++}...`)
+      opts.onStateChange?.(`Uploading image #${images.length + 1}...`)
       const res = await uploadBlob(store, image, 'image/jpeg')
-      embed.images.push({
-        image: {
-          cid: res.data.cid,
-          mimeType: 'image/jpeg',
-        },
+      images.push({
+        image: res.data.blob,
         alt: '', // TODO supply alt text
       })
     }
-  } else if (opts.extLink) {
+
+    if (opts.quote) {
+      embed = {
+        $type: 'app.bsky.embed.recordWithMedia',
+        record: embed,
+        media: {
+          $type: 'app.bsky.embed.images',
+          images,
+        },
+      } as AppBskyEmbedRecordWithMedia.Main
+    } else {
+      embed = {
+        $type: 'app.bsky.embed.images',
+        images,
+      } as AppBskyEmbedImages.Main
+    }
+  }
+
+  if (opts.extLink && !opts.images?.length) {
     let thumb
     if (opts.extLink.localThumb) {
       opts.onStateChange?.('Uploading link thumbnail...')
@@ -138,27 +147,41 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
           opts.extLink.localThumb.path,
           encoding,
         )
-        thumb = {
-          cid: thumbUploadRes.data.cid,
-          mimeType: encoding,
-        }
+        thumb = thumbUploadRes.data.blob
       }
     }
-    embed = {
-      $type: 'app.bsky.embed.external',
-      external: {
-        uri: opts.extLink.uri,
-        title: opts.extLink.meta?.title || '',
-        description: opts.extLink.meta?.description || '',
-        thumb,
-      },
-    } as AppBskyEmbedExternal.Main
+
+    if (opts.quote) {
+      embed = {
+        $type: 'app.bsky.embed.recordWithMedia',
+        record: embed,
+        media: {
+          $type: 'app.bsky.embed.external',
+          external: {
+            uri: opts.extLink.uri,
+            title: opts.extLink.meta?.title || '',
+            description: opts.extLink.meta?.description || '',
+            thumb,
+          },
+        } as AppBskyEmbedExternal.Main,
+      } as AppBskyEmbedRecordWithMedia.Main
+    } else {
+      embed = {
+        $type: 'app.bsky.embed.external',
+        external: {
+          uri: opts.extLink.uri,
+          title: opts.extLink.meta?.title || '',
+          description: opts.extLink.meta?.description || '',
+          thumb,
+        },
+      } as AppBskyEmbedExternal.Main
+    }
   }
 
   if (opts.replyTo) {
     const replyToUrip = new AtUri(opts.replyTo)
-    const parentPost = await store.api.app.bsky.feed.post.get({
-      user: replyToUrip.host,
+    const parentPost = await store.agent.getPost({
+      repo: replyToUrip.host,
       rkey: replyToUrip.rkey,
     })
     if (parentPost) {
@@ -175,16 +198,12 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
 
   try {
     opts.onStateChange?.('Posting...')
-    return await store.api.app.bsky.feed.post.create(
-      {did: store.me.did || ''},
-      {
-        text,
-        reply,
-        embed,
-        entities,
-        createdAt: new Date().toISOString(),
-      },
-    )
+    return await store.agent.post({
+      text: rt.text,
+      facets: rt.facets,
+      reply,
+      embed,
+    })
   } catch (e: any) {
     console.error(`Failed to create post: ${e.toString()}`)
     if (isNetworkError(e)) {
@@ -197,49 +216,6 @@ export async function post(store: RootStoreModel, opts: PostOpts) {
   }
 }
 
-export async function repost(store: RootStoreModel, uri: string, cid: string) {
-  return await store.api.app.bsky.feed.repost.create(
-    {did: store.me.did || ''},
-    {
-      subject: {uri, cid},
-      createdAt: new Date().toISOString(),
-    },
-  )
-}
-
-export async function unrepost(store: RootStoreModel, repostUri: string) {
-  const repostUrip = new AtUri(repostUri)
-  return await store.api.app.bsky.feed.repost.delete({
-    did: repostUrip.hostname,
-    rkey: repostUrip.rkey,
-  })
-}
-
-export async function follow(
-  store: RootStoreModel,
-  subjectDid: string,
-  subjectDeclarationCid: string,
-) {
-  return await store.api.app.bsky.graph.follow.create(
-    {did: store.me.did || ''},
-    {
-      subject: {
-        did: subjectDid,
-        declarationCid: subjectDeclarationCid,
-      },
-      createdAt: new Date().toISOString(),
-    },
-  )
-}
-
-export async function unfollow(store: RootStoreModel, followUri: string) {
-  const followUrip = new AtUri(followUri)
-  return await store.api.app.bsky.graph.follow.delete({
-    did: followUrip.hostname,
-    rkey: followUrip.rkey,
-  })
-}
-
 // helpers
 // =