about summary refs log tree commit diff
path: root/src/lib
diff options
context:
space:
mode:
authorMinseo Lee <itoupluk427@gmail.com>2024-03-02 13:04:51 +0900
committerGitHub <noreply@github.com>2024-03-02 13:04:51 +0900
commitab2b454be8f15ccd4176edce2d28abdce501274b (patch)
tree41e198f85a4372950ce39a6613d231b2d5932be1 /src/lib
parent537ae578d6501319e07132ea8b12c280e0755fca (diff)
parentb70c404d4b369d6fab0dfbafd6b31390ffd20014 (diff)
downloadvoidsky-ab2b454be8f15ccd4176edce2d28abdce501274b.tar.zst
Merge branch 'bluesky-social:main' into patch-3
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/hooks/useIntentHandler.ts20
-rw-r--r--src/lib/moderatePost_wrapped.ts145
-rw-r--r--src/lib/routes/types.ts3
-rw-r--r--src/lib/strings/url-helpers.ts12
-rw-r--r--src/lib/themes.ts4
5 files changed, 151 insertions, 33 deletions
diff --git a/src/lib/hooks/useIntentHandler.ts b/src/lib/hooks/useIntentHandler.ts
index d1e2de31d..8741530b5 100644
--- a/src/lib/hooks/useIntentHandler.ts
+++ b/src/lib/hooks/useIntentHandler.ts
@@ -15,15 +15,20 @@ export function useIntentHandler() {
 
   React.useEffect(() => {
     const handleIncomingURL = (url: string) => {
+      // We want to be able to support bluesky:// deeplinks. It's unnatural for someone to use a deeplink with three
+      // slashes, like bluesky:///intent/follow. However, supporting just two slashes causes us to have to take care
+      // of two cases when parsing the url. If we ensure there is a third slash, we can always ensure the first
+      // path parameter is in pathname rather than in hostname.
+      if (url.startsWith('bluesky://') && !url.startsWith('bluesky:///')) {
+        url = url.replace('bluesky://', 'bluesky:///')
+      }
+
       const urlp = new URL(url)
-      const [_, intentTypeNative, intentTypeWeb] = urlp.pathname.split('/')
+      const [_, intent, intentType] = urlp.pathname.split('/')
 
       // On native, our links look like bluesky://intent/SomeIntent, so we have to check the hostname for the
       // intent check. On web, we have to check the first part of the path since we have an actual hostname
-      const intentType = isNative ? intentTypeNative : intentTypeWeb
-      const isIntent = isNative
-        ? urlp.hostname === 'intent'
-        : intentTypeNative === 'intent'
+      const isIntent = intent === 'intent'
       const params = urlp.searchParams
 
       if (!isIntent) return
@@ -69,10 +74,7 @@ function useComposeIntent() {
             return false
           }
           // We also should just filter out cases that don't have all the info we need
-          if (!VALID_IMAGE_REGEX.test(part)) {
-            return false
-          }
-          return true
+          return VALID_IMAGE_REGEX.test(part)
         })
         .map(part => {
           const [uri, width, height] = part.split('|')
diff --git a/src/lib/moderatePost_wrapped.ts b/src/lib/moderatePost_wrapped.ts
index 92543b42c..9f6fa9c07 100644
--- a/src/lib/moderatePost_wrapped.ts
+++ b/src/lib/moderatePost_wrapped.ts
@@ -6,6 +6,7 @@ import {
   AppBskyFeedPost,
   AppBskyRichtextFacet,
   AppBskyEmbedImages,
+  AppBskyEmbedExternal,
 } from '@atproto/api'
 
 type ModeratePost = typeof moderatePost
@@ -205,44 +206,151 @@ export function moderatePost_wrapped(
 
   if (subject.embed) {
     let embedHidden = false
+    let embedMuted = false
+    let externalMuted = false
+
     if (AppBskyEmbedRecord.isViewRecord(subject.embed.record)) {
       embedHidden = hiddenPosts.includes(subject.embed.record.uri)
+    }
+    if (
+      AppBskyEmbedRecordWithMedia.isView(subject.embed) &&
+      AppBskyEmbedRecord.isViewRecord(subject.embed.record.record)
+    ) {
+      embedHidden = hiddenPosts.includes(subject.embed.record.record.uri)
+    }
 
+    if (AppBskyEmbedRecord.isViewRecord(subject.embed.record)) {
       if (AppBskyFeedPost.isRecord(subject.embed.record.value)) {
-        embedHidden =
-          embedHidden ||
+        const embeddedPost = subject.embed.record.value
+
+        embedMuted =
+          embedMuted ||
           hasMutedWord({
             mutedWords,
-            text: subject.embed.record.value.text,
-            facets: subject.embed.record.value.facets,
-            outlineTags: subject.embed.record.value.tags,
-            languages: subject.embed.record.value.langs,
+            text: embeddedPost.text,
+            facets: embeddedPost.facets,
+            outlineTags: embeddedPost.tags,
+            languages: embeddedPost.langs,
             isOwnPost,
           })
 
-        if (AppBskyEmbedImages.isMain(subject.embed.record.value.embed)) {
-          for (const image of subject.embed.record.value.embed.images) {
-            embedHidden =
-              embedHidden ||
+        if (AppBskyEmbedImages.isMain(embeddedPost.embed)) {
+          for (const image of embeddedPost.embed.images) {
+            embedMuted =
+              embedMuted ||
               hasMutedWord({
                 mutedWords,
                 text: image.alt,
                 facets: [],
                 outlineTags: [],
-                languages: subject.embed.record.value.langs,
+                languages: embeddedPost.langs,
                 isOwnPost,
               })
           }
         }
+
+        if (AppBskyEmbedExternal.isMain(embeddedPost.embed)) {
+          const {external} = embeddedPost.embed
+
+          embedMuted =
+            embedMuted ||
+            hasMutedWord({
+              mutedWords,
+              text: external.title + ' ' + external.description,
+              facets: [],
+              outlineTags: [],
+              languages: [],
+              isOwnPost,
+            })
+        }
+
+        if (AppBskyEmbedRecordWithMedia.isMain(embeddedPost.embed)) {
+          if (AppBskyEmbedExternal.isMain(embeddedPost.embed.media)) {
+            const {external} = embeddedPost.embed.media
+
+            embedMuted =
+              embedMuted ||
+              hasMutedWord({
+                mutedWords,
+                text: external.title + ' ' + external.description,
+                facets: [],
+                outlineTags: [],
+                languages: [],
+                isOwnPost,
+              })
+          }
+
+          if (AppBskyEmbedImages.isMain(embeddedPost.embed.media)) {
+            for (const image of embeddedPost.embed.media.images) {
+              embedMuted =
+                embedMuted ||
+                hasMutedWord({
+                  mutedWords,
+                  text: image.alt,
+                  facets: [],
+                  outlineTags: [],
+                  languages: AppBskyFeedPost.isRecord(embeddedPost.record)
+                    ? embeddedPost.langs
+                    : [],
+                  isOwnPost,
+                })
+            }
+          }
+        }
       }
     }
+
+    if (AppBskyEmbedExternal.isView(subject.embed)) {
+      const {external} = subject.embed
+
+      externalMuted =
+        externalMuted ||
+        hasMutedWord({
+          mutedWords,
+          text: external.title + ' ' + external.description,
+          facets: [],
+          outlineTags: [],
+          languages: [],
+          isOwnPost,
+        })
+    }
+
     if (
       AppBskyEmbedRecordWithMedia.isView(subject.embed) &&
       AppBskyEmbedRecord.isViewRecord(subject.embed.record.record)
     ) {
-      // TODO what
-      embedHidden = hiddenPosts.includes(subject.embed.record.record.uri)
+      if (AppBskyFeedPost.isRecord(subject.embed.record.record.value)) {
+        const post = subject.embed.record.record.value
+        embedMuted =
+          embedMuted ||
+          hasMutedWord({
+            mutedWords,
+            text: post.text,
+            facets: post.facets,
+            outlineTags: post.tags,
+            languages: post.langs,
+            isOwnPost,
+          })
+      }
+
+      if (AppBskyEmbedImages.isView(subject.embed.media)) {
+        for (const image of subject.embed.media.images) {
+          embedMuted =
+            embedMuted ||
+            hasMutedWord({
+              mutedWords,
+              text: image.alt,
+              facets: [],
+              outlineTags: [],
+              languages: AppBskyFeedPost.isRecord(subject.record)
+                ? subject.record.langs
+                : [],
+              isOwnPost,
+            })
+        }
+      }
     }
+
     if (embedHidden) {
       moderations.embed.filter = true
       moderations.embed.blur = true
@@ -254,6 +362,17 @@ export function moderatePost_wrapped(
           priority: 1,
         }
       }
+    } else if (externalMuted || embedMuted) {
+      moderations.content.filter = true
+      moderations.content.blur = true
+      if (!moderations.content.cause) {
+        moderations.content.cause = {
+          // @ts-ignore Temporary extension to the moderation system -prf
+          type: 'muted-word',
+          source: {type: 'user'},
+          priority: 1,
+        }
+      }
     }
   }
 
diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts
index 0ec09f610..6756a62a6 100644
--- a/src/lib/routes/types.ts
+++ b/src/lib/routes/types.ts
@@ -34,6 +34,7 @@ export type CommonNavigatorParams = {
   PreferencesThreads: undefined
   PreferencesExternalEmbeds: undefined
   Search: {q?: string}
+  Hashtag: {tag: string; author?: string}
 }
 
 export type BottomTabNavigatorParams = CommonNavigatorParams & {
@@ -69,6 +70,7 @@ export type FlatNavigatorParams = CommonNavigatorParams & {
   Search: {q?: string}
   Feeds: undefined
   Notifications: undefined
+  Hashtag: {tag: string; author?: string}
 }
 
 export type AllNavigatorParams = CommonNavigatorParams & {
@@ -81,6 +83,7 @@ export type AllNavigatorParams = CommonNavigatorParams & {
   NotificationsTab: undefined
   Notifications: undefined
   MyProfileTab: undefined
+  Hashtag: {tag: string; author?: string}
 }
 
 // NOTE
diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts
index ef341154d..ba2cdb39b 100644
--- a/src/lib/strings/url-helpers.ts
+++ b/src/lib/strings/url-helpers.ts
@@ -157,17 +157,11 @@ export function linkRequiresWarning(uri: string, label: string) {
 
   const host = urip.hostname.toLowerCase()
 
-  if (host === 'bsky.app') {
+  // Hosts that end with bsky.app or bsky.social should be trusted by default.
+  if (host.endsWith('bsky.app') || host.endsWith('bsky.social')) {
     // if this is a link to internal content,
     // warn if it represents itself as a URL to another app
-    if (
-      labelDomain &&
-      labelDomain !== 'bsky.app' &&
-      isPossiblyAUrl(labelDomain)
-    ) {
-      return true
-    }
-    return false
+    return !!labelDomain && labelDomain !== host && isPossiblyAUrl(labelDomain)
   } else {
     // if this is a link to external content,
     // warn if the label doesnt match the target
diff --git a/src/lib/themes.ts b/src/lib/themes.ts
index 135d50ab6..bd75aabea 100644
--- a/src/lib/themes.ts
+++ b/src/lib/themes.ts
@@ -357,8 +357,8 @@ export const dimTheme: Theme = {
       textVeryLight: dimPalette.contrast_400,
       replyLine: dimPalette.contrast_200,
       replyLineDot: dimPalette.contrast_200,
-      unreadNotifBg: `hsl(211, 48%, 17%)`,
-      unreadNotifBorder: `hsl(211, 48%, 30%)`,
+      unreadNotifBg: dimPalette.primary_975,
+      unreadNotifBorder: dimPalette.primary_900,
       postCtrl: dimPalette.contrast_500,
       brandText: dimPalette.primary_500,
       emptyStateIcon: dimPalette.contrast_300,