fix(blog): nsfw filtering, use L tag instead nsfw
All checks were successful
Release to Staging / build_and_release (push) Successful in 54s
All checks were successful
Release to Staging / build_and_release (push) Successful in 54s
This commit is contained in:
parent
352179f1d9
commit
b49ae9537b
@ -1,4 +1,5 @@
|
|||||||
import { NDKFilter } from '@nostr-dev-kit/ndk'
|
import { NDKFilter } from '@nostr-dev-kit/ndk'
|
||||||
|
import { PROFILE_BLOG_FILTER_LIMIT } from '../../constants'
|
||||||
import { NDKContextType } from 'contexts/NDKContext'
|
import { NDKContextType } from 'contexts/NDKContext'
|
||||||
import { kinds, nip19 } from 'nostr-tools'
|
import { kinds, nip19 } from 'nostr-tools'
|
||||||
import { LoaderFunctionArgs, redirect } from 'react-router-dom'
|
import { LoaderFunctionArgs, redirect } from 'react-router-dom'
|
||||||
@ -59,33 +60,33 @@ export const blogRouteLoader =
|
|||||||
) as FilterOptions
|
) as FilterOptions
|
||||||
|
|
||||||
// Fetch 4 in case the current blog is included in the latest
|
// Fetch 4 in case the current blog is included in the latest
|
||||||
const latestModsFilter: NDKFilter = {
|
const latestFilter: NDKFilter = {
|
||||||
authors: [pubkey],
|
authors: [pubkey],
|
||||||
kinds: [kinds.LongFormArticle],
|
kinds: [kinds.LongFormArticle],
|
||||||
limit: 4
|
limit: 4
|
||||||
}
|
}
|
||||||
// Add source filter
|
// Add source filter
|
||||||
if (filterOptions.source === window.location.host) {
|
if (filterOptions.source === window.location.host) {
|
||||||
latestModsFilter['#r'] = [filterOptions.source]
|
latestFilter['#r'] = [filterOptions.source]
|
||||||
}
|
}
|
||||||
// Filter by NSFW tag
|
// Filter by NSFW tag
|
||||||
|
// NSFWFilter.Only_NSFW -> fetch with content-warning label
|
||||||
// NSFWFilter.Show_NSFW -> filter not needed
|
// NSFWFilter.Show_NSFW -> filter not needed
|
||||||
// NSFWFilter.Only_NSFW -> true
|
// NSFWFilter.Hide_NSFW -> up the limit and filter after fetch
|
||||||
// NSFWFilter.Hide_NSFW -> false
|
if (filterOptions.nsfw === NSFWFilter.Only_NSFW) {
|
||||||
if (filterOptions.nsfw !== NSFWFilter.Show_NSFW) {
|
latestFilter['#L'] = ['content-warning']
|
||||||
latestModsFilter['#nsfw'] = [
|
} else if (filterOptions.nsfw === NSFWFilter.Hide_NSFW) {
|
||||||
(filterOptions.nsfw === NSFWFilter.Only_NSFW).toString()
|
// Up the limit in case we fetch multiple NSFW blogs
|
||||||
]
|
latestFilter.limit = PROFILE_BLOG_FILTER_LIMIT
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parallel fetch blog event, latest events, mute, and nsfw lists in parallel
|
// Parallel fetch blog event, latest events, mute, and nsfw lists in parallel
|
||||||
const settled = await Promise.allSettled([
|
const settled = await Promise.allSettled([
|
||||||
ndkContext.fetchEvent(filter),
|
ndkContext.fetchEvent(filter),
|
||||||
ndkContext.fetchEvents(latestModsFilter),
|
ndkContext.fetchEvents(latestFilter),
|
||||||
ndkContext.getMuteLists(loggedInUserPubkey), // Pass pubkey for logged-in users
|
ndkContext.getMuteLists(loggedInUserPubkey), // Pass pubkey for logged-in users
|
||||||
ndkContext.getNSFWList()
|
ndkContext.getNSFWList()
|
||||||
])
|
])
|
||||||
|
|
||||||
const result: BlogPageLoaderResult = {
|
const result: BlogPageLoaderResult = {
|
||||||
blog: undefined,
|
blog: undefined,
|
||||||
latest: [],
|
latest: [],
|
||||||
@ -120,6 +121,9 @@ export const blogRouteLoader =
|
|||||||
result.latest = fetchEventsResult.value
|
result.latest = fetchEventsResult.value
|
||||||
.map(extractBlogCardDetails)
|
.map(extractBlogCardDetails)
|
||||||
.filter((b) => b.id !== result.blog?.id) // Filter out current blog if present
|
.filter((b) => b.id !== result.blog?.id) // Filter out current blog if present
|
||||||
|
.filter(
|
||||||
|
(b) => !(b.nsfw && filterOptions.nsfw === NSFWFilter.Hide_NSFW)
|
||||||
|
) // Filter out the NSFW if selected
|
||||||
.slice(0, 3) // Take only three
|
.slice(0, 3) // Take only three
|
||||||
} else if (fetchEventsResult.status === 'rejected') {
|
} else if (fetchEventsResult.status === 'rejected') {
|
||||||
log(
|
log(
|
||||||
|
@ -6,7 +6,7 @@ import { Swiper, SwiperSlide } from 'swiper/react'
|
|||||||
import { BlogCard } from '../components/BlogCard'
|
import { BlogCard } from '../components/BlogCard'
|
||||||
import { GameCard } from '../components/GameCard'
|
import { GameCard } from '../components/GameCard'
|
||||||
import { ModCard } from '../components/ModCard'
|
import { ModCard } from '../components/ModCard'
|
||||||
import { LANDING_PAGE_DATA } from '../constants'
|
import { LANDING_PAGE_DATA, PROFILE_BLOG_FILTER_LIMIT } from '../constants'
|
||||||
import {
|
import {
|
||||||
useDidMount,
|
useDidMount,
|
||||||
useGames,
|
useGames,
|
||||||
@ -31,11 +31,7 @@ import '../styles/SimpleSlider.css'
|
|||||||
import '../styles/styles.css'
|
import '../styles/styles.css'
|
||||||
|
|
||||||
// Import Swiper styles
|
// Import Swiper styles
|
||||||
import {
|
import { NDKEvent, NDKFilter } from '@nostr-dev-kit/ndk'
|
||||||
filterForEventsTaggingId,
|
|
||||||
NDKEvent,
|
|
||||||
NDKFilter
|
|
||||||
} from '@nostr-dev-kit/ndk'
|
|
||||||
import 'swiper/css'
|
import 'swiper/css'
|
||||||
import 'swiper/css/navigation'
|
import 'swiper/css/navigation'
|
||||||
import 'swiper/css/pagination'
|
import 'swiper/css/pagination'
|
||||||
@ -332,38 +328,34 @@ const DisplayLatestBlogs = () => {
|
|||||||
// Show maximum of 4 blog posts
|
// Show maximum of 4 blog posts
|
||||||
// 2 should be featured and the most recent 2 from blog npubs
|
// 2 should be featured and the most recent 2 from blog npubs
|
||||||
// Populate the filter from known naddr (constants.ts)
|
// Populate the filter from known naddr (constants.ts)
|
||||||
const filters: NDKFilter[] = []
|
const filter: NDKFilter = {
|
||||||
|
kinds: [kinds.LongFormArticle],
|
||||||
|
authors: [],
|
||||||
|
'#d': []
|
||||||
|
}
|
||||||
for (let i = 0; i < LANDING_PAGE_DATA.featuredBlogPosts.length; i++) {
|
for (let i = 0; i < LANDING_PAGE_DATA.featuredBlogPosts.length; i++) {
|
||||||
try {
|
try {
|
||||||
const naddr = LANDING_PAGE_DATA.featuredBlogPosts[i]
|
const naddr = LANDING_PAGE_DATA.featuredBlogPosts[i]
|
||||||
const filterId = filterForEventsTaggingId(naddr)
|
const decoded = nip19.decode<'naddr'>(naddr as `naddr1${string}`)
|
||||||
if (filterId) {
|
const { pubkey, identifier } = decoded.data
|
||||||
filters.push(filterId)
|
if (!filter.authors?.includes(pubkey)) {
|
||||||
|
filter.authors?.push(pubkey)
|
||||||
|
}
|
||||||
|
if (!filter.authors?.includes(identifier)) {
|
||||||
|
filter['#d']?.push(identifier)
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Silently ignore
|
// Silently ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Create a single filter based on multiple #a's
|
|
||||||
const filter = filters.reduce(
|
|
||||||
(filter, id) => {
|
|
||||||
const a = id['#a']
|
|
||||||
if (a) {
|
|
||||||
filter['#a']?.push(a[0])
|
|
||||||
}
|
|
||||||
return filter
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'#a': []
|
|
||||||
} as NDKFilter
|
|
||||||
)
|
|
||||||
// Prepare filter for the latest
|
// Prepare filter for the latest
|
||||||
const blogNpubs = import.meta.env.VITE_BLOG_NPUBS.split(',')
|
const blogNpubs = import.meta.env.VITE_BLOG_NPUBS.split(',')
|
||||||
const blogHexkeys = blogNpubs
|
const blogHexkeys = blogNpubs
|
||||||
.map(npubToHex)
|
.map(npubToHex)
|
||||||
.filter((hexkey) => hexkey !== null)
|
.filter((hexkey) => hexkey !== null)
|
||||||
|
|
||||||
// We fetch 4 posts in case of duplicates (from featured)
|
// We fetch more posts in case of duplicates (from featured)
|
||||||
const latestFilter: NDKFilter = {
|
const latestFilter: NDKFilter = {
|
||||||
authors: blogHexkeys,
|
authors: blogHexkeys,
|
||||||
kinds: [kinds.LongFormArticle],
|
kinds: [kinds.LongFormArticle],
|
||||||
@ -371,17 +363,15 @@ const DisplayLatestBlogs = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filter by NSFW tag
|
// Filter by NSFW tag
|
||||||
// NSFWFilter.Show_NSFW -> filter not needed
|
if (filterOptions.nsfw === NSFWFilter.Only_NSFW) {
|
||||||
// NSFWFilter.Only_NSFW -> true
|
latestFilter['#L'] = ['content-warning']
|
||||||
// NSFWFilter.Hide_NSFW -> false
|
} else if (filterOptions.nsfw === NSFWFilter.Hide_NSFW) {
|
||||||
if (filterOptions.nsfw !== NSFWFilter.Show_NSFW) {
|
// Up the limit in case we fetch multiple NSFW blogs
|
||||||
latestFilter['#nsfw'] = [
|
latestFilter.limit = PROFILE_BLOG_FILTER_LIMIT
|
||||||
(filterOptions.nsfw === NSFWFilter.Only_NSFW).toString()
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await Promise.allSettled([
|
const results = await Promise.allSettled([
|
||||||
fetchEvents({ ...filter, kinds: [kinds.LongFormArticle] }),
|
fetchEvents(filter),
|
||||||
fetchEvents(latestFilter)
|
fetchEvents(latestFilter)
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -403,9 +393,13 @@ const DisplayLatestBlogs = () => {
|
|||||||
}, new Map())
|
}, new Map())
|
||||||
.values()
|
.values()
|
||||||
)
|
)
|
||||||
const latest = unique.slice(0, 4)
|
.map(extractBlogCardDetails)
|
||||||
|
.filter(
|
||||||
|
(b) => !(b.nsfw && filterOptions.nsfw === NSFWFilter.Hide_NSFW)
|
||||||
|
)
|
||||||
|
|
||||||
setBlogs(latest.map(extractBlogCardDetails))
|
const latest = unique.slice(0, 4)
|
||||||
|
setBlogs(latest)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log(
|
log(
|
||||||
true,
|
true,
|
||||||
|
@ -704,10 +704,8 @@ const ProfileTabBlogs = () => {
|
|||||||
filter['#r'] = [host]
|
filter['#r'] = [host]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filterOptions.nsfw !== NSFWFilter.Show_NSFW) {
|
if (filterOptions.nsfw === NSFWFilter.Only_NSFW) {
|
||||||
filter['#nsfw'] = [
|
filter['#L'] = ['content-warning']
|
||||||
(filterOptions.nsfw === NSFWFilter.Only_NSFW).toString()
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return filter
|
return filter
|
||||||
@ -725,7 +723,7 @@ const ProfileTabBlogs = () => {
|
|||||||
}
|
}
|
||||||
fetchEvents(filter)
|
fetchEvents(filter)
|
||||||
.then((events) => {
|
.then((events) => {
|
||||||
setBlogs(events.map(extractBlogCardDetails).filter((e) => e.naddr))
|
setBlogs(events.map(extractBlogCardDetails).filter((b) => b.naddr))
|
||||||
setHasMore(events.length > PROFILE_BLOG_FILTER_LIMIT)
|
setHasMore(events.length > PROFILE_BLOG_FILTER_LIMIT)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
@ -752,7 +750,7 @@ const ProfileTabBlogs = () => {
|
|||||||
setHasMore(nextBlogs.length > PROFILE_BLOG_FILTER_LIMIT)
|
setHasMore(nextBlogs.length > PROFILE_BLOG_FILTER_LIMIT)
|
||||||
setPage((prev) => prev + 1)
|
setPage((prev) => prev + 1)
|
||||||
setBlogs(
|
setBlogs(
|
||||||
nextBlogs.slice(0, PROFILE_BLOG_FILTER_LIMIT).filter((e) => e.naddr)
|
nextBlogs.slice(0, PROFILE_BLOG_FILTER_LIMIT).filter((b) => b.naddr)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.finally(() => setIsLoading(false))
|
.finally(() => setIsLoading(false))
|
||||||
@ -775,7 +773,7 @@ const ProfileTabBlogs = () => {
|
|||||||
.then((events) => {
|
.then((events) => {
|
||||||
setHasMore(true)
|
setHasMore(true)
|
||||||
setPage((prev) => prev - 1)
|
setPage((prev) => prev - 1)
|
||||||
setBlogs(events.map(extractBlogCardDetails).filter((e) => e.naddr))
|
setBlogs(events.map(extractBlogCardDetails).filter((b) => b.naddr))
|
||||||
})
|
})
|
||||||
.finally(() => setIsLoading(false))
|
.finally(() => setIsLoading(false))
|
||||||
}
|
}
|
||||||
@ -799,6 +797,11 @@ const ProfileTabBlogs = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter nsfw (Hide_NSFW option)
|
||||||
|
_blogs = _blogs.filter(
|
||||||
|
(b) => !(b.nsfw && filterOptions.nsfw === NSFWFilter.Hide_NSFW)
|
||||||
|
)
|
||||||
|
|
||||||
// Only apply filtering if the user is not an admin or the admin has not selected "Unmoderated Fully"
|
// Only apply filtering if the user is not an admin or the admin has not selected "Unmoderated Fully"
|
||||||
// Allow "Unmoderated Fully" when author visits own profile
|
// Allow "Unmoderated Fully" when author visits own profile
|
||||||
if (!((isAdmin || isOwner) && isUnmoderatedFully)) {
|
if (!((isAdmin || isOwner) && isUnmoderatedFully)) {
|
||||||
|
@ -84,22 +84,27 @@ export const writeRouteAction =
|
|||||||
.split(',')
|
.split(',')
|
||||||
.map((t) => ['t', t])
|
.map((t) => ['t', t])
|
||||||
|
|
||||||
|
const tags = [
|
||||||
|
['d', uuid],
|
||||||
|
['a', aTag],
|
||||||
|
['r', rTag],
|
||||||
|
['published_at', published_at.toString()],
|
||||||
|
['title', formSubmit.title!],
|
||||||
|
['image', formSubmit.image!],
|
||||||
|
['summary', formSubmit.summary!],
|
||||||
|
...tTags
|
||||||
|
]
|
||||||
|
|
||||||
|
// Add NSFW tag, L label namespace standardized tag
|
||||||
|
// https://github.com/nostr-protocol/nips/blob/2838e3bd51ac00bd63c4cef1601ae09935e7dd56/README.md#standardized-tags
|
||||||
|
if (formSubmit.nsfw === 'on') tags.push(['L', 'content-warning'])
|
||||||
|
|
||||||
const unsignedEvent: UnsignedEvent = {
|
const unsignedEvent: UnsignedEvent = {
|
||||||
kind: kinds.LongFormArticle,
|
kind: kinds.LongFormArticle,
|
||||||
created_at: currentTimeStamp,
|
created_at: currentTimeStamp,
|
||||||
pubkey: hexPubkey,
|
pubkey: hexPubkey,
|
||||||
content: content,
|
content: content,
|
||||||
tags: [
|
tags: tags
|
||||||
['d', uuid],
|
|
||||||
['a', aTag],
|
|
||||||
['r', rTag],
|
|
||||||
['published_at', published_at.toString()],
|
|
||||||
['title', formSubmit.title!],
|
|
||||||
['image', formSubmit.image!],
|
|
||||||
['summary', formSubmit.summary!],
|
|
||||||
['nsfw', (formSubmit.nsfw === 'on').toString()],
|
|
||||||
...tTags
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -8,8 +8,10 @@ export const extractBlogDetails = (event: NDKEvent): Partial<BlogDetails> => ({
|
|||||||
content: event.content,
|
content: event.content,
|
||||||
summary: getFirstTagValue(event, 'summary'),
|
summary: getFirstTagValue(event, 'summary'),
|
||||||
image: getFirstTagValue(event, 'image'),
|
image: getFirstTagValue(event, 'image'),
|
||||||
nsfw: getFirstTagValue(event, 'nsfw') === 'true',
|
// Check L label namespace for content warning or nsfw (backwards compatibility)
|
||||||
|
nsfw:
|
||||||
|
getFirstTagValue(event, 'L') === 'content-warning' ||
|
||||||
|
getFirstTagValue(event, 'nsfw') === 'true',
|
||||||
id: event.id,
|
id: event.id,
|
||||||
author: event.pubkey,
|
author: event.pubkey,
|
||||||
published_at: getFirstTagValueAsInt(event, 'published_at'),
|
published_at: getFirstTagValueAsInt(event, 'published_at'),
|
||||||
|
Loading…
Reference in New Issue
Block a user