From 5cf0d2b13391970bf0f115d3d92b02202197a603 Mon Sep 17 00:00:00 2001 From: Shoggomo Date: Mon, 11 May 2026 18:25:01 +0200 Subject: [PATCH] feat(route/4chan): add filters for sticky threads and reply count (#21964) * feat(route/4chan): add filters for sticky threads and reply count * Revert formatting * Applied suggestion --- lib/routes/4chan/catalog.tsx | 7 +++++-- lib/routes/4chan/utils.tsx | 28 +++++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/lib/routes/4chan/catalog.tsx b/lib/routes/4chan/catalog.tsx index 06faea5c9..2abcc8062 100644 --- a/lib/routes/4chan/catalog.tsx +++ b/lib/routes/4chan/catalog.tsx @@ -41,12 +41,15 @@ export const route: Route = { target: '/:board/catalog', }, ], - description: `Specify options (in the format of query string) in parameter \`routeParams\` to control some extra features for Tweets + description: `Specify options (in the format of query string) in parameter \`routeParams\` to control extra features for threads | Key | Description | Accepts | Defaults to | | ----------------- | ------------------------------------------------ | ---------------------- | ----------- | | \`showReplyCount\` | Show number of replies of each thread in catalog | \`0\`/\`1\`/\`true\`/\`false\` | \`false\` | | \`showLastReplies\` | Show last 5 replies of each thread | \`0\`/\`1\`/\`true\`/\`false\` | \`false\` | -| \`revealSpoilers\` | Don't wrap images tagged as spoilers | \`0\`/\`1\`/\`true\`/\`false\` | \`false\` |`, +| \`revealSpoilers\` | Don't wrap images tagged as spoilers | \`0\`/\`1\`/\`true\`/\`false\` | \`false\` | +| \`excludeSticky\` | Filter out sticky threads | \`0\`/\`1\`/\`true\`/\`false\` | \`false\` | +| \`minReplies\` | Minimum replies per thread | Integer | None | +| \`maxReplies\` | Maximum replies per thread | Integer | None |`, handler, }; diff --git a/lib/routes/4chan/utils.tsx b/lib/routes/4chan/utils.tsx index bc5b3631d..463e4f70a 100644 --- a/lib/routes/4chan/utils.tsx +++ b/lib/routes/4chan/utils.tsx @@ -5,11 +5,18 @@ import sanitizeHtml from 'sanitize-html'; import { parseDate } from '@/utils/parse-date'; import { queryToBoolean } from '@/utils/readable-social'; -const parseParams = (routeParams: string) => { - const parsed = new URLSearchParams(routeParams); +const parseParams = (routeParams?: string) => { + const parsed = new URLSearchParams(routeParams ?? ''); + const minRepliesRaw = parsed.get('minReplies'); + const maxRepliesRaw = parsed.get('maxReplies'); + const minReplies = minRepliesRaw === null ? undefined : Number(minRepliesRaw); + const maxReplies = maxRepliesRaw === null ? undefined : Number(maxRepliesRaw); const viewOptions = { + excludeSticky: !!queryToBoolean(parsed.get('excludeSticky')), includeLastReplies: !!queryToBoolean(parsed.get('showLastReplies')), includeReplyCount: !!queryToBoolean(parsed.get('showReplyCount')), + maxReplies: Number.isFinite(maxReplies) ? maxReplies : undefined, + minReplies: Number.isFinite(minReplies) ? minReplies : undefined, revealSpoilers: !!queryToBoolean(parsed.get('revealSpoilers')), }; return viewOptions; @@ -17,7 +24,22 @@ const parseParams = (routeParams: string) => { const processCatalog = ({ data, board, viewOptions }: { data: CatalogApiReturn; board: string; viewOptions: ViewOptions }) => { const transformedData = data.flatMap((page) => page.threads); - return transformedData.map((thread) => ({ + return transformedData.filter((thread) => { + if (viewOptions.excludeSticky && thread.sticky) { + return false; + } + + const replies = thread.replies ?? 0; + if (viewOptions.minReplies !== undefined && replies < viewOptions.minReplies) { + return false; + } + + if (viewOptions.maxReplies !== undefined && replies > viewOptions.maxReplies) { + return false; + } + + return true; + }).map((thread) => ({ author: `${thread.name} ${thread.trip ?? thread.no}`, description: renderToString(renderPost({ post: thread, board, viewOptions })), link: `https://boards.4chan.org/${board}/thread/${thread.no}`,