fix(blogs): moderation and missing aTag
All checks were successful
Release to Staging / build_and_release (push) Successful in 45s
All checks were successful
Release to Staging / build_and_release (push) Successful in 45s
This commit is contained in:
parent
3ee9e313de
commit
718350d2bc
@ -5,7 +5,12 @@ import { kinds, nip19 } from 'nostr-tools'
|
||||
import { LoaderFunctionArgs, redirect } from 'react-router-dom'
|
||||
import { appRoutes } from 'routes'
|
||||
import { store } from 'store'
|
||||
import { BlogPageLoaderResult, FilterOptions, NSFWFilter } from 'types'
|
||||
import {
|
||||
BlogPageLoaderResult,
|
||||
FilterOptions,
|
||||
ModeratedFilter,
|
||||
NSFWFilter
|
||||
} from 'types'
|
||||
import {
|
||||
DEFAULT_FILTER_OPTIONS,
|
||||
getLocalStorageItem,
|
||||
@ -59,11 +64,11 @@ export const blogRouteLoader =
|
||||
getLocalStorageItem('filter-blog', DEFAULT_FILTER_OPTIONS)
|
||||
) as FilterOptions
|
||||
|
||||
// Fetch 4 in case the current blog is included in the latest
|
||||
// Fetch more in case the current blog is included in the latest and filters remove some
|
||||
const latestFilter: NDKFilter = {
|
||||
authors: [pubkey],
|
||||
kinds: [kinds.LongFormArticle],
|
||||
limit: 4
|
||||
limit: PROFILE_BLOG_FILTER_LIMIT
|
||||
}
|
||||
// Add source filter
|
||||
if (filterOptions.source === window.location.host) {
|
||||
@ -75,12 +80,9 @@ export const blogRouteLoader =
|
||||
// NSFWFilter.Hide_NSFW -> up the limit and filter after fetch
|
||||
if (filterOptions.nsfw === NSFWFilter.Only_NSFW) {
|
||||
latestFilter['#L'] = ['content-warning']
|
||||
} else if (filterOptions.nsfw === NSFWFilter.Hide_NSFW) {
|
||||
// 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
|
||||
const settled = await Promise.allSettled([
|
||||
ndkContext.fetchEvent(filter),
|
||||
ndkContext.fetchEvents(latestFilter),
|
||||
@ -121,10 +123,6 @@ export const blogRouteLoader =
|
||||
result.latest = fetchEventsResult.value
|
||||
.map(extractBlogCardDetails)
|
||||
.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
|
||||
} else if (fetchEventsResult.status === 'rejected') {
|
||||
log(
|
||||
true,
|
||||
@ -134,22 +132,48 @@ export const blogRouteLoader =
|
||||
)
|
||||
}
|
||||
|
||||
const muteList = settled[2]
|
||||
if (muteList.status === 'fulfilled' && muteList.value) {
|
||||
if (muteList && muteList.value) {
|
||||
const muteLists = settled[2]
|
||||
if (muteLists.status === 'fulfilled' && muteLists.value) {
|
||||
if (muteLists && muteLists.value) {
|
||||
if (result.blog && result.blog.aTag) {
|
||||
if (
|
||||
muteList.value.admin.replaceableEvents.includes(
|
||||
muteLists.value.admin.replaceableEvents.includes(
|
||||
result.blog.aTag
|
||||
) ||
|
||||
muteList.value.user.replaceableEvents.includes(result.blog.aTag)
|
||||
muteLists.value.user.replaceableEvents.includes(result.blog.aTag)
|
||||
) {
|
||||
result.isBlocked = true
|
||||
}
|
||||
}
|
||||
|
||||
// Moderate the latest
|
||||
const isAdmin =
|
||||
userState.user?.npub === import.meta.env.VITE_REPORTING_NPUB
|
||||
const isOwner =
|
||||
userState.user?.pubkey && userState.user.pubkey === pubkey
|
||||
const isUnmoderatedFully =
|
||||
filterOptions.moderated === ModeratedFilter.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
|
||||
if (!((isAdmin || isOwner) && isUnmoderatedFully)) {
|
||||
result.latest = result.latest.filter(
|
||||
(b) =>
|
||||
!muteLists.value.admin.authors.includes(b.author!) &&
|
||||
!muteLists.value.admin.replaceableEvents.includes(b.aTag!)
|
||||
)
|
||||
}
|
||||
|
||||
if (filterOptions.moderated === ModeratedFilter.Moderated) {
|
||||
result.latest = result.latest.filter(
|
||||
(b) =>
|
||||
!muteLists.value.user.authors.includes(b.author!) &&
|
||||
!muteLists.value.user.replaceableEvents.includes(b.aTag!)
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (muteList.status === 'rejected') {
|
||||
log(true, LogType.Error, 'Issue fetching mute list', muteList.reason)
|
||||
} else if (muteLists.status === 'rejected') {
|
||||
log(true, LogType.Error, 'Issue fetching mute list', muteLists.reason)
|
||||
}
|
||||
|
||||
const nsfwList = settled[3]
|
||||
@ -171,15 +195,14 @@ export const blogRouteLoader =
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the the latest blogs too
|
||||
// Check the latest blogs too
|
||||
result.latest = result.latest.map((b) => {
|
||||
if (b) {
|
||||
const isMissingNsfwTag =
|
||||
!b.nsfw && b.aTag && nsfwList.value.includes(b.aTag)
|
||||
// Add nsfw tag if it's missing
|
||||
const isMissingNsfwTag =
|
||||
!b.nsfw && b.aTag && nsfwList.value.includes(b.aTag)
|
||||
|
||||
if (isMissingNsfwTag) {
|
||||
b.nsfw = true
|
||||
}
|
||||
if (isMissingNsfwTag) {
|
||||
b.nsfw = true
|
||||
}
|
||||
return b
|
||||
})
|
||||
@ -187,6 +210,17 @@ export const blogRouteLoader =
|
||||
log(true, LogType.Error, 'Issue fetching nsfw list', nsfwList.reason)
|
||||
}
|
||||
|
||||
// Filter latest, sort and take only three
|
||||
result.latest = result.latest
|
||||
.filter(
|
||||
// Filter out the NSFW if selected
|
||||
(b) => !(b.nsfw && filterOptions.nsfw === NSFWFilter.Hide_NSFW)
|
||||
)
|
||||
.sort((a, b) =>
|
||||
a.published_at && b.published_at ? b.published_at - a.published_at : 0
|
||||
)
|
||||
.slice(0, 3)
|
||||
|
||||
return result
|
||||
} catch (error) {
|
||||
let message = 'An error occurred in fetching blog details from relays'
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { NDKFilter } from '@nostr-dev-kit/ndk'
|
||||
import { NDKContextType } from 'contexts/NDKContext'
|
||||
import { kinds } from 'nostr-tools'
|
||||
import { BlogCardDetails } from 'types'
|
||||
import { log, LogType, npubToHex } from 'utils'
|
||||
import { extractBlogCardDetails } from 'utils/blog'
|
||||
|
||||
@ -15,14 +16,46 @@ export const blogsRouteLoader = (ndkContext: NDKContextType) => async () => {
|
||||
authors: blogHexkeys,
|
||||
kinds: [kinds.LongFormArticle]
|
||||
}
|
||||
const events = await ndkContext.fetchEvents(filter)
|
||||
|
||||
if (!events) {
|
||||
log(true, LogType.Error, 'Unable to fetch the blog events.')
|
||||
return null
|
||||
const settled = await Promise.allSettled([
|
||||
ndkContext.fetchEvents(filter),
|
||||
ndkContext.getMuteLists()
|
||||
])
|
||||
|
||||
let blogs: Partial<BlogCardDetails>[] = []
|
||||
const fetchEventsResult = settled[0]
|
||||
if (fetchEventsResult.status === 'fulfilled' && fetchEventsResult.value) {
|
||||
// Extract the blog card details from the events
|
||||
blogs = fetchEventsResult.value
|
||||
.map(extractBlogCardDetails)
|
||||
.filter((b) => b.naddr)
|
||||
} else if (fetchEventsResult.status === 'rejected') {
|
||||
log(
|
||||
true,
|
||||
LogType.Error,
|
||||
'Unable to fetch the blog events.',
|
||||
fetchEventsResult.reason
|
||||
)
|
||||
return []
|
||||
}
|
||||
|
||||
return events.map(extractBlogCardDetails).filter((e) => e.naddr)
|
||||
const muteListResult = settled[1]
|
||||
if (muteListResult.status === 'fulfilled' && muteListResult.value) {
|
||||
// Filter out the blocked events
|
||||
blogs = blogs.filter(
|
||||
(b) =>
|
||||
b.aTag &&
|
||||
!muteListResult.value.admin.replaceableEvents.includes(b.aTag)
|
||||
)
|
||||
} else if (muteListResult.status === 'rejected') {
|
||||
log(
|
||||
true,
|
||||
LogType.Error,
|
||||
'Failed to fetch mutelists.',
|
||||
muteListResult.reason
|
||||
)
|
||||
}
|
||||
return blogs
|
||||
} catch (error) {
|
||||
log(
|
||||
true,
|
||||
@ -30,6 +63,6 @@ export const blogsRouteLoader = (ndkContext: NDKContextType) => async () => {
|
||||
'An error occurred in fetching blog details from relays',
|
||||
error
|
||||
)
|
||||
return null
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
@ -3,24 +3,36 @@ import { BlogCardDetails, BlogDetails } from 'types'
|
||||
import { getFirstTagValue, getFirstTagValueAsInt, getTagValues } from './nostr'
|
||||
import { kinds, nip19 } from 'nostr-tools'
|
||||
|
||||
export const extractBlogDetails = (event: NDKEvent): Partial<BlogDetails> => ({
|
||||
title: getFirstTagValue(event, 'title'),
|
||||
content: event.content,
|
||||
summary: getFirstTagValue(event, 'summary'),
|
||||
image: getFirstTagValue(event, 'image'),
|
||||
// Check L label namespace for content warning or nsfw (backwards compatibility)
|
||||
nsfw:
|
||||
getFirstTagValue(event, 'L') === 'content-warning' ||
|
||||
getFirstTagValue(event, 'nsfw') === 'true',
|
||||
id: event.id,
|
||||
author: event.pubkey,
|
||||
published_at: getFirstTagValueAsInt(event, 'published_at'),
|
||||
edited_at: event.created_at,
|
||||
rTag: getFirstTagValue(event, 'r') || 'N/A',
|
||||
dTag: getFirstTagValue(event, 'd'),
|
||||
aTag: getFirstTagValue(event, 'a'),
|
||||
tTags: getTagValues(event, 't') || []
|
||||
})
|
||||
export const extractBlogDetails = (event: NDKEvent): Partial<BlogDetails> => {
|
||||
const dTag = getFirstTagValue(event, 'd')
|
||||
|
||||
// Check if the aTag exists on the blog
|
||||
let aTag = getFirstTagValue(event, 'a')
|
||||
|
||||
// Create aTag from components if aTag is not included
|
||||
if (typeof aTag === 'undefined' && event.pubkey && dTag) {
|
||||
aTag = `${kinds.LongFormArticle}:${event.pubkey}:${dTag}`
|
||||
}
|
||||
|
||||
return {
|
||||
title: getFirstTagValue(event, 'title'),
|
||||
content: event.content,
|
||||
summary: getFirstTagValue(event, 'summary'),
|
||||
image: getFirstTagValue(event, 'image'),
|
||||
// Check L label namespace for content warning or nsfw (backwards compatibility)
|
||||
nsfw:
|
||||
getFirstTagValue(event, 'L') === 'content-warning' ||
|
||||
getFirstTagValue(event, 'nsfw') === 'true',
|
||||
id: event.id,
|
||||
author: event.pubkey,
|
||||
published_at: getFirstTagValueAsInt(event, 'published_at'),
|
||||
edited_at: event.created_at,
|
||||
rTag: getFirstTagValue(event, 'r') || 'N/A',
|
||||
dTag: dTag,
|
||||
aTag: aTag,
|
||||
tTags: getTagValues(event, 't') || []
|
||||
}
|
||||
}
|
||||
|
||||
export const extractBlogCardDetails = (
|
||||
event: NDKEvent
|
||||
|
Loading…
Reference in New Issue
Block a user