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 { LoaderFunctionArgs, redirect } from 'react-router-dom'
|
||||||
import { appRoutes } from 'routes'
|
import { appRoutes } from 'routes'
|
||||||
import { store } from 'store'
|
import { store } from 'store'
|
||||||
import { BlogPageLoaderResult, FilterOptions, NSFWFilter } from 'types'
|
import {
|
||||||
|
BlogPageLoaderResult,
|
||||||
|
FilterOptions,
|
||||||
|
ModeratedFilter,
|
||||||
|
NSFWFilter
|
||||||
|
} from 'types'
|
||||||
import {
|
import {
|
||||||
DEFAULT_FILTER_OPTIONS,
|
DEFAULT_FILTER_OPTIONS,
|
||||||
getLocalStorageItem,
|
getLocalStorageItem,
|
||||||
@ -59,11 +64,11 @@ export const blogRouteLoader =
|
|||||||
getLocalStorageItem('filter-blog', DEFAULT_FILTER_OPTIONS)
|
getLocalStorageItem('filter-blog', DEFAULT_FILTER_OPTIONS)
|
||||||
) as FilterOptions
|
) 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 = {
|
const latestFilter: NDKFilter = {
|
||||||
authors: [pubkey],
|
authors: [pubkey],
|
||||||
kinds: [kinds.LongFormArticle],
|
kinds: [kinds.LongFormArticle],
|
||||||
limit: 4
|
limit: PROFILE_BLOG_FILTER_LIMIT
|
||||||
}
|
}
|
||||||
// Add source filter
|
// Add source filter
|
||||||
if (filterOptions.source === window.location.host) {
|
if (filterOptions.source === window.location.host) {
|
||||||
@ -75,12 +80,9 @@ export const blogRouteLoader =
|
|||||||
// NSFWFilter.Hide_NSFW -> up the limit and filter after fetch
|
// NSFWFilter.Hide_NSFW -> up the limit and filter after fetch
|
||||||
if (filterOptions.nsfw === NSFWFilter.Only_NSFW) {
|
if (filterOptions.nsfw === NSFWFilter.Only_NSFW) {
|
||||||
latestFilter['#L'] = ['content-warning']
|
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([
|
const settled = await Promise.allSettled([
|
||||||
ndkContext.fetchEvent(filter),
|
ndkContext.fetchEvent(filter),
|
||||||
ndkContext.fetchEvents(latestFilter),
|
ndkContext.fetchEvents(latestFilter),
|
||||||
@ -121,10 +123,6 @@ 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
|
|
||||||
} else if (fetchEventsResult.status === 'rejected') {
|
} else if (fetchEventsResult.status === 'rejected') {
|
||||||
log(
|
log(
|
||||||
true,
|
true,
|
||||||
@ -134,22 +132,48 @@ export const blogRouteLoader =
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const muteList = settled[2]
|
const muteLists = settled[2]
|
||||||
if (muteList.status === 'fulfilled' && muteList.value) {
|
if (muteLists.status === 'fulfilled' && muteLists.value) {
|
||||||
if (muteList && muteList.value) {
|
if (muteLists && muteLists.value) {
|
||||||
if (result.blog && result.blog.aTag) {
|
if (result.blog && result.blog.aTag) {
|
||||||
if (
|
if (
|
||||||
muteList.value.admin.replaceableEvents.includes(
|
muteLists.value.admin.replaceableEvents.includes(
|
||||||
result.blog.aTag
|
result.blog.aTag
|
||||||
) ||
|
) ||
|
||||||
muteList.value.user.replaceableEvents.includes(result.blog.aTag)
|
muteLists.value.user.replaceableEvents.includes(result.blog.aTag)
|
||||||
) {
|
) {
|
||||||
result.isBlocked = true
|
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!)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else if (muteList.status === 'rejected') {
|
|
||||||
log(true, LogType.Error, 'Issue fetching mute list', muteList.reason)
|
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 (muteLists.status === 'rejected') {
|
||||||
|
log(true, LogType.Error, 'Issue fetching mute list', muteLists.reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
const nsfwList = settled[3]
|
const nsfwList = settled[3]
|
||||||
@ -171,22 +195,32 @@ export const blogRouteLoader =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the the latest blogs too
|
// Check the latest blogs too
|
||||||
result.latest = result.latest.map((b) => {
|
result.latest = result.latest.map((b) => {
|
||||||
if (b) {
|
// Add nsfw tag if it's missing
|
||||||
const isMissingNsfwTag =
|
const isMissingNsfwTag =
|
||||||
!b.nsfw && b.aTag && nsfwList.value.includes(b.aTag)
|
!b.nsfw && b.aTag && nsfwList.value.includes(b.aTag)
|
||||||
|
|
||||||
if (isMissingNsfwTag) {
|
if (isMissingNsfwTag) {
|
||||||
b.nsfw = true
|
b.nsfw = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return b
|
return b
|
||||||
})
|
})
|
||||||
} else if (nsfwList.status === 'rejected') {
|
} else if (nsfwList.status === 'rejected') {
|
||||||
log(true, LogType.Error, 'Issue fetching nsfw list', nsfwList.reason)
|
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
|
return result
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
let message = 'An error occurred in fetching blog details from relays'
|
let message = 'An error occurred in fetching blog details from relays'
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { NDKFilter } from '@nostr-dev-kit/ndk'
|
import { NDKFilter } from '@nostr-dev-kit/ndk'
|
||||||
import { NDKContextType } from 'contexts/NDKContext'
|
import { NDKContextType } from 'contexts/NDKContext'
|
||||||
import { kinds } from 'nostr-tools'
|
import { kinds } from 'nostr-tools'
|
||||||
|
import { BlogCardDetails } from 'types'
|
||||||
import { log, LogType, npubToHex } from 'utils'
|
import { log, LogType, npubToHex } from 'utils'
|
||||||
import { extractBlogCardDetails } from 'utils/blog'
|
import { extractBlogCardDetails } from 'utils/blog'
|
||||||
|
|
||||||
@ -15,14 +16,46 @@ export const blogsRouteLoader = (ndkContext: NDKContextType) => async () => {
|
|||||||
authors: blogHexkeys,
|
authors: blogHexkeys,
|
||||||
kinds: [kinds.LongFormArticle]
|
kinds: [kinds.LongFormArticle]
|
||||||
}
|
}
|
||||||
const events = await ndkContext.fetchEvents(filter)
|
|
||||||
|
|
||||||
if (!events) {
|
const settled = await Promise.allSettled([
|
||||||
log(true, LogType.Error, 'Unable to fetch the blog events.')
|
ndkContext.fetchEvents(filter),
|
||||||
return null
|
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) {
|
} catch (error) {
|
||||||
log(
|
log(
|
||||||
true,
|
true,
|
||||||
@ -30,6 +63,6 @@ export const blogsRouteLoader = (ndkContext: NDKContextType) => async () => {
|
|||||||
'An error occurred in fetching blog details from relays',
|
'An error occurred in fetching blog details from relays',
|
||||||
error
|
error
|
||||||
)
|
)
|
||||||
return null
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,18 @@ import { BlogCardDetails, BlogDetails } from 'types'
|
|||||||
import { getFirstTagValue, getFirstTagValueAsInt, getTagValues } from './nostr'
|
import { getFirstTagValue, getFirstTagValueAsInt, getTagValues } from './nostr'
|
||||||
import { kinds, nip19 } from 'nostr-tools'
|
import { kinds, nip19 } from 'nostr-tools'
|
||||||
|
|
||||||
export const extractBlogDetails = (event: NDKEvent): Partial<BlogDetails> => ({
|
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'),
|
title: getFirstTagValue(event, 'title'),
|
||||||
content: event.content,
|
content: event.content,
|
||||||
summary: getFirstTagValue(event, 'summary'),
|
summary: getFirstTagValue(event, 'summary'),
|
||||||
@ -17,10 +28,11 @@ export const extractBlogDetails = (event: NDKEvent): Partial<BlogDetails> => ({
|
|||||||
published_at: getFirstTagValueAsInt(event, 'published_at'),
|
published_at: getFirstTagValueAsInt(event, 'published_at'),
|
||||||
edited_at: event.created_at,
|
edited_at: event.created_at,
|
||||||
rTag: getFirstTagValue(event, 'r') || 'N/A',
|
rTag: getFirstTagValue(event, 'r') || 'N/A',
|
||||||
dTag: getFirstTagValue(event, 'd'),
|
dTag: dTag,
|
||||||
aTag: getFirstTagValue(event, 'a'),
|
aTag: aTag,
|
||||||
tTags: getTagValues(event, 't') || []
|
tTags: getTagValues(event, 't') || []
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const extractBlogCardDetails = (
|
export const extractBlogCardDetails = (
|
||||||
event: NDKEvent
|
event: NDKEvent
|
||||||
|
Loading…
Reference in New Issue
Block a user