NapCatQQ source snapshot
Build NapCat Artifacts / Type Check & Test (push) Waiting to run
Release Tag / validate-version (push) Waiting to run

This commit is contained in:
TanTan committed 2026-11-13 09:01:47 +08:00
commit f954252a34
1111 files changed
+139867

No files matched your search

@@ -0,0 +1,166 @@
import { Type } from '@sinclair/typebox';
import { Buffer } from 'node:buffer';
import { QZONE_REFERENCE_MAX_BYTES, validateQzoneFeedReference } from '@/napcat-core/data/qzone-rpc';
import { QzoneFeedReferenceExample, QzoneFeedReferenceSchema } from './common';
const NullableString = Type.Union([Type.String(), Type.Null()]);
const NullableNumber = Type.Union([Type.Number(), Type.Null()]);
const NullableBoolean = Type.Union([Type.Boolean(), Type.Null()]);
const RawObject = Type.Record(Type.String(), Type.Unknown());
export const QzoneReadParentCommentSchema = Type.Object({
id: Type.Union([
Type.String({ minLength: 1, maxLength: 8192 }),
Type.Integer({ minimum: 0, maximum: Number.MAX_SAFE_INTEGER }),
], { description: '原始父评论 ID,字符串或安全整数,0 是有效 ID' }),
user: Type.Optional(RawObject),
}, { additionalProperties: Type.Unknown(), description: '原样传回读取结果中的完整 comment 对象;整体不得超过64KiB' });
export const QzoneReplyItemSchema = Type.Object({
reply_id: NullableString,
user_id: NullableString,
uid: NullableString,
nickname: NullableString,
time: NullableNumber,
content: NullableString,
images: Type.Union([Type.Array(Type.String()), Type.Null()]),
like_count: NullableNumber,
liked: NullableBoolean,
target_user_id: NullableString,
target_uid: NullableString,
target_nickname: NullableString,
reply: RawObject,
}, { description: '楼中楼回复;未知字段为 null,完整原始对象保留在 reply' });
export const QzoneCommentItemSchema = Type.Object({
comment_id: NullableString,
user_id: NullableString,
uid: NullableString,
nickname: NullableString,
time: NullableNumber,
content: NullableString,
images: Type.Union([Type.Array(Type.String()), Type.Null()]),
like_count: NullableNumber,
liked: NullableBoolean,
reply_num: NullableNumber,
has_more_replies: NullableBoolean,
replies: Type.Array(QzoneReplyItemSchema),
comment: RawObject,
}, { description: '评论及其内联回复;replies 为空不代表没有尚未读取的历史回复' });
export const QzoneCommentPageSchema = Type.Object({
feed_ref: QzoneFeedReferenceSchema,
comment_num: NullableNumber,
comments: Type.Array(QzoneCommentItemSchema),
has_more: Type.Boolean(),
next_cursor: NullableString,
raw: RawObject,
}, { description: '一页评论;续页时原样传回本页 feed_ref 和 next_cursor' });
export const QzoneReplyPageSchema = Type.Object({
feed_ref: QzoneFeedReferenceSchema,
comment_id: Type.String(),
replies: Type.Array(QzoneReplyItemSchema),
reply_num: NullableNumber,
has_more: Type.Boolean(),
next_cursor: NullableString,
raw: RawObject,
}, { description: '一页追加楼中楼回复;与父评论内联 replys 按 ID 合并,不代表完整历史' });
function record (value: unknown): value is Record<string, unknown> {
if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}
export function validateQzoneCommentReadPayload (payload: unknown, withParent: boolean): void {
const allowed = withParent ? ['feed_ref', 'comment', 'cursor'] : ['feed_ref', 'cursor'];
if (!record(payload) || Object.keys(payload).some(key => !allowed.includes(key))) throw new Error('QQ空间评论读取参数无效');
validateQzoneFeedReference(payload['feed_ref']);
const cursor = payload['cursor'];
if (cursor !== undefined && (typeof cursor !== 'string' || !cursor || cursor.length > 16384)) throw new Error('QQ空间评论游标无效');
if (!withParent) return;
const comment = payload['comment'];
if (!record(comment)) throw new Error('请提供读取结果中的完整父评论 comment');
const id = comment['id'];
if ((typeof id !== 'string' || !id.trim() || id.length > 8192) &&
(typeof id !== 'number' || !Number.isSafeInteger(id) || id < 0)) {
throw new Error('父评论 ID 必须为非空字符串或安全的非负整数');
}
if (comment['user'] !== undefined && !record(comment['user'])) throw new Error('父评论 user 必须为原始JSON对象');
let serialized: string;
try {
serialized = JSON.stringify(comment);
} catch {
throw new Error('父评论必须为可序列化的JSON对象');
}
if (Buffer.byteLength(serialized) > QZONE_REFERENCE_MAX_BYTES) throw new Error('父评论不能超过64KiB');
}
export const QzoneReadParentCommentExample = {
id: 0,
user: { uid: 'u_commenter', uin: '', nick: '评论作者', portrait: 'https://example.com/avatar.png' },
time: 1792926000,
content: [{ type: 0, content: '很有意思' }],
media_items: [],
like: { id: 'comment-like-key', count: 2, status: 1 },
reply_num: 1,
has_more_reply: true,
replys: [],
reply_attach_info: 'server-reply-pointer',
};
export const QzoneCommentPageExample = {
feed_ref: QzoneFeedReferenceExample,
comment_num: 1,
comments: [{
comment_id: '0',
user_id: null,
uid: 'u_commenter',
nickname: '评论作者',
time: 1792926000,
content: '很有意思',
images: [],
like_count: 2,
liked: true,
reply_num: 1,
has_more_replies: true,
replies: [],
comment: QzoneReadParentCommentExample,
}],
has_more: false,
next_cursor: null,
raw: { cell_comment: { num: 1, comments: [QzoneReadParentCommentExample] }, has_more: false },
};
const ReplyExample = {
id: 'reply-1',
user: { uid: 'u_commenter', nick: '回复作者' },
target_user: { uin: '10001', uid: 'u_self', nick: 'Qiao' },
time: 1792926001,
content: [{ type: 0, content: '谢谢' }],
};
export const QzoneReplyPageExample = {
feed_ref: QzoneFeedReferenceExample,
comment_id: '0',
replies: [{
reply_id: 'reply-1',
user_id: null,
uid: 'u_commenter',
nickname: '回复作者',
time: 1792926001,
content: '谢谢',
images: null,
like_count: null,
liked: null,
target_user_id: '10001',
target_uid: 'u_self',
target_nickname: 'Qiao',
reply: ReplyExample,
}],
reply_num: 1,
has_more: false,
next_cursor: null,
raw: { replys: [ReplyExample], has_more: false, reply_attach_info: '' },
};