feat(swarm,posts): Add NSFW filtering and link preview enrichment
- Add NSFW content filtering based on user's nsfwEnabled session setting - Pass includeNsfw preference to fetchSwarmTimeline in curated and swarm feeds - Remove in-memory caching from swarm API route to respect per-user preferences - Add link preview enrichment for swarm posts with URLs but no preview data - Implement extractFirstUrl utility to parse URLs from post content - Implement fetchLinkPreview to fetch metadata from external URLs with 3s timeout - Implement enrichPostsWithPreviews to enrich posts asynchronously - Skip video URLs (YouTube, Vimeo) from preview enrichment as they use VideoEmbed - Update swarm API documentation to reflect NSFW filtering behavior - Improves content discovery by enriching posts with link metadata while respecting user content preferences
This commit is contained in:
@@ -17,6 +17,85 @@ interface TimelineOptions {
|
||||
includeNsfw?: boolean; // Whether to include NSFW content
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the first URL from post content
|
||||
*/
|
||||
function extractFirstUrl(content: string): string | null {
|
||||
const urlMatch = content.match(/https?:\/\/[^\s<>"{}|\\^`[\]]+/);
|
||||
if (!urlMatch) return null;
|
||||
// Clean trailing punctuation
|
||||
return urlMatch[0].replace(/[)\].,!?;:]+$/, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch link preview for a URL
|
||||
*/
|
||||
async function fetchLinkPreview(url: string): Promise<{
|
||||
url: string;
|
||||
title: string | null;
|
||||
description: string | null;
|
||||
image: string | null;
|
||||
} | null> {
|
||||
try {
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost';
|
||||
const protocol = nodeDomain === 'localhost' ? 'http' : 'https';
|
||||
const previewUrl = `${protocol}://${nodeDomain}/api/media/preview?url=${encodeURIComponent(url)}`;
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 3000); // 3s timeout for previews
|
||||
|
||||
const response = await fetch(previewUrl, {
|
||||
headers: { 'Accept': 'application/json' },
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeout);
|
||||
|
||||
if (!response.ok) return null;
|
||||
|
||||
const data = await response.json();
|
||||
return {
|
||||
url: data.url || url,
|
||||
title: data.title || null,
|
||||
description: data.description || null,
|
||||
image: data.image || null,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enrich swarm posts with link previews if they have URLs but no preview data
|
||||
*/
|
||||
async function enrichPostsWithPreviews(posts: SwarmPost[]): Promise<SwarmPost[]> {
|
||||
const enrichmentPromises = posts.map(async (post) => {
|
||||
// Skip if already has link preview data
|
||||
if (post.linkPreviewUrl) return post;
|
||||
|
||||
// Extract URL from content
|
||||
const url = extractFirstUrl(post.content);
|
||||
if (!url) return post;
|
||||
|
||||
// Skip video URLs (handled by VideoEmbed component)
|
||||
if (url.match(/(youtube\.com|youtu\.be|vimeo\.com)/)) return post;
|
||||
|
||||
// Fetch preview
|
||||
const preview = await fetchLinkPreview(url);
|
||||
if (!preview) return post;
|
||||
|
||||
return {
|
||||
...post,
|
||||
linkPreviewUrl: preview.url,
|
||||
linkPreviewTitle: preview.title || undefined,
|
||||
linkPreviewDescription: preview.description || undefined,
|
||||
linkPreviewImage: preview.image || undefined,
|
||||
};
|
||||
});
|
||||
|
||||
return Promise.all(enrichmentPromises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch timeline from a single node
|
||||
*/
|
||||
@@ -121,8 +200,11 @@ export async function fetchSwarmTimeline(
|
||||
return true;
|
||||
});
|
||||
|
||||
// Enrich posts that have URLs but no link preview data
|
||||
const enrichedPosts = await enrichPostsWithPreviews(uniquePosts);
|
||||
|
||||
return {
|
||||
posts: uniquePosts,
|
||||
posts: enrichedPosts,
|
||||
sources,
|
||||
fetchedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user