about summary refs log tree commit diff
path: root/src/lib/moderatePost_wrapped.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/moderatePost_wrapped.ts')
-rw-r--r--src/lib/moderatePost_wrapped.ts384
1 files changed, 17 insertions, 367 deletions
diff --git a/src/lib/moderatePost_wrapped.ts b/src/lib/moderatePost_wrapped.ts
index 9f6fa9c07..0ce01368a 100644
--- a/src/lib/moderatePost_wrapped.ts
+++ b/src/lib/moderatePost_wrapped.ts
@@ -1,380 +1,30 @@
-import {
-  AppBskyEmbedRecord,
-  AppBskyEmbedRecordWithMedia,
-  moderatePost,
-  AppBskyActorDefs,
-  AppBskyFeedPost,
-  AppBskyRichtextFacet,
-  AppBskyEmbedImages,
-  AppBskyEmbedExternal,
-} from '@atproto/api'
+import {moderatePost, BSKY_LABELER_DID} from '@atproto/api'
 
 type ModeratePost = typeof moderatePost
-type Options = Parameters<ModeratePost>[1] & {
-  hiddenPosts?: string[]
-  mutedWords?: AppBskyActorDefs.MutedWord[]
-}
-
-const REGEX = {
-  LEADING_TRAILING_PUNCTUATION: /(?:^\p{P}+|\p{P}+$)/gu,
-  ESCAPE: /[[\]{}()*+?.\\^$|\s]/g,
-  SEPARATORS: /[\/\-\–\—\(\)\[\]\_]+/g,
-  WORD_BOUNDARY: /[\s\n\t\r\f\v]+?/g,
-}
-
-/**
- * List of 2-letter lang codes for languages that either don't use spaces, or
- * don't use spaces in a way conducive to word-based filtering.
- *
- * For these, we use a simple `String.includes` to check for a match.
- */
-const LANGUAGE_EXCEPTIONS = [
-  'ja', // Japanese
-  'zh', // Chinese
-  'ko', // Korean
-  'th', // Thai
-  'vi', // Vietnamese
-]
-
-export function hasMutedWord({
-  mutedWords,
-  text,
-  facets,
-  outlineTags,
-  languages,
-  isOwnPost,
-}: {
-  mutedWords: AppBskyActorDefs.MutedWord[]
-  text: string
-  facets?: AppBskyRichtextFacet.Main[]
-  outlineTags?: string[]
-  languages?: string[]
-  isOwnPost: boolean
-}) {
-  if (isOwnPost) return false
-
-  const exception = LANGUAGE_EXCEPTIONS.includes(languages?.[0] || '')
-  const tags = ([] as string[])
-    .concat(outlineTags || [])
-    .concat(
-      facets
-        ?.filter(facet => {
-          return facet.features.find(feature =>
-            AppBskyRichtextFacet.isTag(feature),
-          )
-        })
-        .map(t => t.features[0].tag as string) || [],
-    )
-    .map(t => t.toLowerCase())
-
-  for (const mute of mutedWords) {
-    const mutedWord = mute.value.toLowerCase()
-    const postText = text.toLowerCase()
-
-    // `content` applies to tags as well
-    if (tags.includes(mutedWord)) return true
-    // rest of the checks are for `content` only
-    if (!mute.targets.includes('content')) continue
-    // single character or other exception, has to use includes
-    if ((mutedWord.length === 1 || exception) && postText.includes(mutedWord))
-      return true
-    // too long
-    if (mutedWord.length > postText.length) continue
-    // exact match
-    if (mutedWord === postText) return true
-    // any muted phrase with space or punctuation
-    if (/(?:\s|\p{P})+?/u.test(mutedWord) && postText.includes(mutedWord))
-      return true
-
-    // check individual character groups
-    const words = postText.split(REGEX.WORD_BOUNDARY)
-    for (const word of words) {
-      if (word === mutedWord) return true
-
-      // compare word without leading/trailing punctuation, but allow internal
-      // punctuation (such as `s@ssy`)
-      const wordTrimmedPunctuation = word.replace(
-        REGEX.LEADING_TRAILING_PUNCTUATION,
-        '',
-      )
-
-      if (mutedWord === wordTrimmedPunctuation) return true
-      if (mutedWord.length > wordTrimmedPunctuation.length) continue
-
-      // handle hyphenated, slash separated words, etc
-      if (REGEX.SEPARATORS.test(wordTrimmedPunctuation)) {
-        // check against full normalized phrase
-        const wordNormalizedSeparators = wordTrimmedPunctuation.replace(
-          REGEX.SEPARATORS,
-          ' ',
-        )
-        const mutedWordNormalizedSeparators = mutedWord.replace(
-          REGEX.SEPARATORS,
-          ' ',
-        )
-        // hyphenated (or other sep) to spaced words
-        if (wordNormalizedSeparators === mutedWordNormalizedSeparators)
-          return true
-
-        /* Disabled for now e.g. `super-cool` to `supercool`
-        const wordNormalizedCompressed = wordNormalizedSeparators.replace(
-          REGEX.WORD_BOUNDARY,
-          '',
-        )
-        const mutedWordNormalizedCompressed =
-          mutedWordNormalizedSeparators.replace(/\s+?/g, '')
-        // hyphenated (or other sep) to non-hyphenated contiguous word
-        if (mutedWordNormalizedCompressed === wordNormalizedCompressed)
-          return true
-        */
-
-        // then individual parts of separated phrases/words
-        const wordParts = wordTrimmedPunctuation.split(REGEX.SEPARATORS)
-        for (const wp of wordParts) {
-          // still retain internal punctuation
-          if (wp === mutedWord) return true
-        }
-      }
-    }
-  }
-
-  return false
-}
+type Options = Parameters<ModeratePost>[1]
 
 export function moderatePost_wrapped(
   subject: Parameters<ModeratePost>[0],
   opts: Options,
 ) {
-  const {hiddenPosts = [], mutedWords = [], ...options} = opts
-  const moderations = moderatePost(subject, options)
-  const isOwnPost = subject.author.did === opts.userDid
-
-  if (hiddenPosts.includes(subject.uri)) {
-    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: 'post-hidden',
-        source: {type: 'user'},
-        priority: 1,
-      }
-    }
-  }
+  // HACK
+  // temporarily translate 'gore' into 'graphic-media' during the transition period
+  // can remove this in a few months
+  // -prf
+  translateOldLabels(subject)
 
-  if (AppBskyFeedPost.isRecord(subject.record)) {
-    let muted = hasMutedWord({
-      mutedWords,
-      text: subject.record.text,
-      facets: subject.record.facets || [],
-      outlineTags: subject.record.tags || [],
-      languages: subject.record.langs,
-      isOwnPost,
-    })
-
-    if (
-      subject.record.embed &&
-      AppBskyEmbedImages.isMain(subject.record.embed)
-    ) {
-      for (const image of subject.record.embed.images) {
-        muted =
-          muted ||
-          hasMutedWord({
-            mutedWords,
-            text: image.alt,
-            facets: [],
-            outlineTags: [],
-            languages: subject.record.langs,
-            isOwnPost,
-          })
-      }
-    }
-
-    if (muted) {
-      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,
-        }
-      }
-    }
-  }
-
-  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)) {
-        const embeddedPost = subject.embed.record.value
-
-        embedMuted =
-          embedMuted ||
-          hasMutedWord({
-            mutedWords,
-            text: embeddedPost.text,
-            facets: embeddedPost.facets,
-            outlineTags: embeddedPost.tags,
-            languages: embeddedPost.langs,
-            isOwnPost,
-          })
-
-        if (AppBskyEmbedImages.isMain(embeddedPost.embed)) {
-          for (const image of embeddedPost.embed.images) {
-            embedMuted =
-              embedMuted ||
-              hasMutedWord({
-                mutedWords,
-                text: image.alt,
-                facets: [],
-                outlineTags: [],
-                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)
-    ) {
-      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,
-            })
-        }
-      }
-    }
+  return moderatePost(subject, opts)
+}
 
-    if (embedHidden) {
-      moderations.embed.filter = true
-      moderations.embed.blur = true
-      if (!moderations.embed.cause) {
-        moderations.embed.cause = {
-          // @ts-ignore Temporary extension to the moderation system -prf
-          type: 'post-hidden',
-          source: {type: 'user'},
-          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,
-        }
+function translateOldLabels(subject: Parameters<ModeratePost>[0]) {
+  if (subject.labels) {
+    for (const label of subject.labels) {
+      if (
+        label.val === 'gore' &&
+        (!label.src || label.src === BSKY_LABELER_DID)
+      ) {
+        label.val = 'graphic-media'
       }
     }
   }
-
-  return moderations
 }