fix(zsxq): correct feed handling

Combine the reviewed route corrections and their follow-up adjustments.

Affected files:
- lib/routes/zsxq/group.ts
- lib/routes/zsxq/user.ts
- lib/routes/zsxq/utils.ts
This commit is contained in:
DIYgod
2026-10-14 15:09:15 +08:00
parent 0af54f07c8
commit 3799ba267a
3 changed files with 48 additions and 22 deletions
+2 -4
View File
@@ -3,6 +3,7 @@ import type { Context } from 'hono';
import { config } from '@/config';
import ConfigNotFoundError from '@/errors/types/config-not-found';
import type { Data, Route } from '@/types';
import { getRouteLimit } from '@/utils/route-limit';
import type { GroupInfoResponse, TopicsResponse } from './types';
import { customFetch, generateTopicDataItem } from './utils';
@@ -46,10 +47,7 @@ async function handler(ctx: Context): Promise<Data> {
throw new ConfigNotFoundError('该 RSS 源由于配置不正确而被禁用:令牌丢失。');
}
let count = Number(ctx.req.query('limit')) || 20;
if (count > 30) {
count = 30;
}
const count = Math.min(getRouteLimit(ctx, 20), 30);
const { group } = await customFetch<GroupInfoResponse>(`/groups/${groupId}`);
+2 -4
View File
@@ -3,6 +3,7 @@ import type { Context } from 'hono';
import { config } from '@/config';
import ConfigNotFoundError from '@/errors/types/config-not-found';
import type { Data, Route } from '@/types';
import { getRouteLimit } from '@/utils/route-limit';
import type { TopicsResponse, UserInfoResponse } from './types';
import { customFetch, generateTopicDataItem } from './utils';
@@ -41,10 +42,7 @@ async function handler(ctx: Context): Promise<Data> {
throw new ConfigNotFoundError('该 RSS 源由于配置不正确而被禁用:令牌丢失。');
}
let count = Number(ctx.req.query('limit')) || 20;
if (count > 30) {
count = 30;
}
const count = Math.min(getRouteLimit(ctx, 20), 30);
const userInfo = await customFetch<UserInfoResponse>(`/users/${uid}`);
+44 -14
View File
@@ -1,3 +1,5 @@
import { html } from 'hono/html';
import { config } from '@/config';
import type { DataItem } from '@/types';
import got from '@/utils/got';
@@ -21,53 +23,81 @@ export async function customFetch<T extends BasicResponse<ResponseData>>(path: s
if (code === 1059 && retryCount < 3) {
return customFetch(path, retryCount + 1);
}
throw new Error('something wrong');
throw new Error(`Knowledge Planet API request failed (code ${code ?? 'unknown'}). Check the access token and permission for this group or user.`);
}
function decodeTopicText(text: string) {
try {
return decodeURIComponent(text);
} catch {
return text;
}
}
function renderTopicLink(href: string, title: string) {
const label = decodeTopicText(title);
try {
const url = new URL(decodeTopicText(href));
if (url.protocol === 'https:' || url.protocol === 'http:') {
return String(html`<a href="${url.href}">${label}</a>`);
}
} catch {
// Malformed encoded links retain their readable label.
}
return String(html`${label}`);
}
function parseTopicContent(text: string = '', images: TopicImage[] = []) {
let result = text.replaceAll('\n', '<br>');
result = result.replaceAll(/<e type="web" href="(.*?)" title="(.*?)" style="(.*?)" \/>/g, (_, p1, p2) => `<a href=${decodeURIComponent(p1)}>${decodeURIComponent(p2)}</a>`);
result = result.replaceAll(/<e type="hashtag".*?title="(.*?)" \/>/g, (_, p1) => {
const title = decodeURIComponent(p1);
return `<span>${title}</span>`;
});
result += images.map((image) => `<img src="${image.original?.url ?? image.large?.url ?? image.thumbnail?.url}">`).join('<br>');
result = result.replaceAll(/<e type="web" href="(.*?)" title="(.*?)" style="(.*?)" \/>/g, (_, href, title) => renderTopicLink(href, title));
result = result.replaceAll(/<e type="hashtag".*?title="(.*?)" \/>/g, (_, title) => String(html`<span>${decodeTopicText(title)}</span>`));
result += images
.map((image) => {
const url = image.original?.url ?? image.large?.url ?? image.thumbnail?.url;
return url ? String(html`<img src="${url}" />`) : '';
})
.filter(Boolean)
.join('<br>');
return result;
}
export function generateTopicDataItem(topics: Topic[]): DataItem[] {
return topics.map((topic) => {
if (topic.topic_id === undefined || topic.topic_id === null) {
throw new Error('Missing Knowledge Planet topic ID; no unique article URL is available.');
}
let description: string | undefined;
let title = '';
const url = `https://wx.zsxq.com/topic/${topic.topic_id}`;
switch (topic.type) {
case 'talk':
title = topic.talk?.text?.split('\n', 1)[0] ?? '文章';
title = topic.talk?.text ?? '文章';
description = parseTopicContent(topic.talk?.text, topic.talk?.images);
break;
case 'q&a':
title = topic.question?.text?.split('\n', 1)[0] ?? '问答';
title = topic.question?.text ?? '问答';
description = parseTopicContent(topic.question?.text, topic.question?.images);
description = `<blockquote>${topic.question?.owner?.name ?? '匿名用户'} 提问:${description}</blockquote>`;
description = `<blockquote>${String(html`${topic.question?.owner?.name ?? '匿名用户'}`)} 提问:${description}</blockquote>`;
if (topic.answered) {
description += '<br>' + topic.answer?.owner.name + ' 回答:<br><br>';
description += '<br>' + String(html`${topic.answer?.owner?.name ?? '匿名用户'}`) + ' 回答:<br><br>';
description += parseTopicContent(topic.answer?.text, topic.answer?.images);
}
break;
case 'task':
title = topic.task?.text?.split('\n', 1)[0] ?? '作业';
title = topic.task?.text ?? '作业';
description = parseTopicContent(topic.task?.text, topic.task?.images);
break;
case 'solution':
title = topic.solution?.text?.split('\n', 1)[0] ?? '写作业';
title = topic.solution?.text ?? '写作业';
description = parseTopicContent(topic.solution?.text, topic.solution?.images);
break;
default:
throw new Error('Unsupported Knowledge Planet topic type; update RSSHub or report this topic format.');
}
return {
title: topic.title ?? title,
description,
pubDate: parseDate(topic.create_time),
pubDate: topic.create_time ? parseDate(topic.create_time) : undefined,
link: url,
};
});