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,18 @@
import { Static, Type } from '@sinclair/typebox';
import { ActionName } from '@/napcat-onebot/action/router';
import { QzonePublicAction, QzonePublicContentSchema, QzonePublicFeedExample, QzonePublicFeedSchema } from './common';
const PayloadSchema = Type.Object({ feed: QzonePublicFeedSchema, content: QzonePublicContentSchema });
type PayloadType = Static<typeof PayloadSchema>;
export class CommentQzonePublicFeed extends QzonePublicAction<PayloadType> {
override actionName = ActionName.CommentQzonePublicFeed;
override actionSummary = '评论空友爱看动态';
override actionDescription = '对完整 feed 发表评论;content 接受非空文本或富文本对象数组,富文本字段原样保留';
override payloadSchema = PayloadSchema;
override payloadExample = { feed: QzonePublicFeedExample, content: '很有意思' };
async _handle (payload: PayloadType) {
return this.core.apis.QzonePublicApi.addComment(payload.feed, payload.content);
}
}
@@ -0,0 +1,18 @@
import { Static, Type } from '@sinclair/typebox';
import { ActionName } from '@/napcat-onebot/action/router';
import { QzonePublicAction, QzonePublicCommentExample, QzonePublicCommentSchema, QzonePublicFeedExample, QzonePublicFeedSchema } from './common';
const PayloadSchema = Type.Object({ feed: QzonePublicFeedSchema, comment: QzonePublicCommentSchema });
type PayloadType = Static<typeof PayloadSchema>;
export class DeleteQzonePublicComment extends QzonePublicAction<PayloadType> {
override actionName = ActionName.DeleteQzonePublicComment;
override actionSummary = '删除空友爱看评论';
override actionDescription = '传入完整 feed 及 comment 删除评论,操作权限由上游校验';
override payloadSchema = PayloadSchema;
override payloadExample = { feed: QzonePublicFeedExample, comment: QzonePublicCommentExample };
async _handle (payload: PayloadType) {
return this.core.apis.QzonePublicApi.deleteComment(payload.feed, payload.comment);
}
}
@@ -0,0 +1,26 @@
import { Static, Type } from '@sinclair/typebox';
import { ActionName } from '@/napcat-onebot/action/router';
import {
QzonePublicAction,
QzonePublicCommentExample,
QzonePublicCommentSchema,
QzonePublicFeedExample,
QzonePublicFeedSchema,
QzonePublicReplyExample,
QzonePublicReplySchema,
} from './common';
const PayloadSchema = Type.Object({ feed: QzonePublicFeedSchema, comment: QzonePublicCommentSchema, reply: QzonePublicReplySchema });
type PayloadType = Static<typeof PayloadSchema>;
export class DeleteQzonePublicReply extends QzonePublicAction<PayloadType> {
override actionName = ActionName.DeleteQzonePublicReply;
override actionSummary = '删除空友爱看回复';
override actionDescription = '传入完整 feed、父 comment 和目标 reply 删除回复,操作权限由上游校验';
override payloadSchema = PayloadSchema;
override payloadExample = { feed: QzonePublicFeedExample, comment: QzonePublicCommentExample, reply: QzonePublicReplyExample };
async _handle (payload: PayloadType) {
return this.core.apis.QzonePublicApi.deleteReply(payload.feed, payload.comment, payload.reply);
}
}
@@ -0,0 +1,21 @@
import { Static, Type } from '@sinclair/typebox';
import { ActionName } from '@/napcat-onebot/action/router';
import { QzonePublicAction, QzonePublicFeedExample, QzonePublicFeedSchema } from './common';
const PayloadSchema = Type.Object({
feed: QzonePublicFeedSchema,
reason: Type.Optional(Type.String({ default: '', description: '转发附言,默认空字符串;数字或布尔值会转为文本' })),
});
type PayloadType = Static<typeof PayloadSchema>;
export class ForwardQzonePublicFeed extends QzonePublicAction<PayloadType> {
override actionName = ActionName.ForwardQzonePublicFeed;
override actionSummary = '转发空友爱看动态';
override actionDescription = '将完整 feed 转发到自己的空间,可通过 reason 添加转发附言';
override payloadSchema = PayloadSchema;
override payloadExample = { feed: QzonePublicFeedExample, reason: '分享一条动态' };
async _handle (payload: PayloadType) {
return this.core.apis.QzonePublicApi.forwardFeed(payload.feed, payload.reason ?? '');
}
}
@@ -0,0 +1,18 @@
import { Static, Type } from '@sinclair/typebox';
import { ActionName } from '@/napcat-onebot/action/router';
import { QzonePublicAction } from './common';
const PayloadSchema = Type.Object({});
type PayloadType = Static<typeof PayloadSchema>;
export class GetQzonePublicCapabilities extends QzonePublicAction<PayloadType> {
override actionName = ActionName.GetQzonePublicCapabilities;
override actionSummary = '获取空友爱看接口能力';
override actionDescription = '获取当前 QQ 版本和账号可用的空友爱看接口能力,不会执行写操作';
override payloadSchema = PayloadSchema;
override payloadExample = {};
async _handle () {
return this.core.apis.QzonePublicApi.getCapabilities();
}
}
@@ -0,0 +1,21 @@
import { Static, Type } from '@sinclair/typebox';
import { ActionName } from '@/napcat-onebot/action/router';
import { QzonePublicAction, QzonePublicFeedExample, QzonePublicFeedSchema } from './common';
const PayloadSchema = Type.Object({
feed: QzonePublicFeedSchema,
cursor: Type.String({ minLength: 1, description: '动态详情或上一评论页返回的非空 attach_info;数字或布尔值会转为文本' }),
});
type PayloadType = Static<typeof PayloadSchema>;
export class GetQzonePublicComments extends QzonePublicAction<PayloadType> {
override actionName = ActionName.GetQzonePublicComments;
override actionSummary = '获取空友爱看评论';
override actionDescription = '读取动态的后续评论;cursor 为必填非空字符串,使用动态详情或上一评论页的 attach_info,原始评论对象应完整保留';
override payloadSchema = PayloadSchema;
override payloadExample = { feed: QzonePublicFeedExample, cursor: 'detail-attach-info' };
async _handle (payload: PayloadType) {
return this.core.apis.QzonePublicApi.getComments(payload.feed, payload.cursor);
}
}
@@ -0,0 +1,18 @@
import { Static, Type } from '@sinclair/typebox';
import { ActionName } from '@/napcat-onebot/action/router';
import { QzonePublicAction, QzonePublicFeedExample, QzonePublicFeedSchema } from './common';
const PayloadSchema = Type.Object({ feed: QzonePublicFeedSchema });
type PayloadType = Static<typeof PayloadSchema>;
export class GetQzonePublicFeedDetail extends QzonePublicAction<PayloadType> {
override actionName = ActionName.GetQzonePublicFeedDetail;
override actionSummary = '获取空友爱看动态详情';
override actionDescription = '传入列表中的完整 feed 对象获取详情;继续读取评论时,将详情返回的 attach_info 作为 cursor';
override payloadSchema = PayloadSchema;
override payloadExample = { feed: QzonePublicFeedExample };
async _handle (payload: PayloadType) {
return this.core.apis.QzonePublicApi.getFeedDetail(payload.feed);
}
}
@@ -0,0 +1,29 @@
import { Static, Type } from '@sinclair/typebox';
import type { QzonePublicFeedsPage } from '@/napcat-core/data/qzone-public';
import { ActionName } from '@/napcat-onebot/action/router';
import { QzonePublicAction, QzonePublicFeedExample, QzonePublicObjectSchema } from './common';
const PayloadSchema = Type.Object({
cursor: Type.Optional(Type.String({ minLength: 1, maxLength: 65536, description: '上一页返回的非空 next_cursor,首次请求可省略;数字或布尔值会转为文本' })),
});
type PayloadType = Static<typeof PayloadSchema>;
const ReturnSchema = Type.Object({
feeds: Type.Array(QzonePublicObjectSchema, { description: '完整动态对象,可直接传给详情和互动接口' }),
has_more: Type.Boolean({ description: '是否还有下一页' }),
next_cursor: Type.Union([Type.String(), Type.Null()], { description: '下一页游标,没有下一页时为 null' }),
}, { additionalProperties: true, description: '动态列表分页结果,保留上游返回的额外字段' });
export class GetQzonePublicFeeds extends QzonePublicAction<PayloadType, QzonePublicFeedsPage> {
override actionName = ActionName.GetQzonePublicFeeds;
override actionSummary = '获取空友爱看动态';
override actionDescription = '获取空友爱看动态列表;后续操作应直接传入 feeds 中的完整对象,翻页使用 next_cursor';
override payloadSchema = PayloadSchema;
override returnSchema = ReturnSchema;
override payloadExample = {};
override returnExample = { feeds: [QzonePublicFeedExample], has_more: false, next_cursor: null };
async _handle (payload: PayloadType): Promise<QzonePublicFeedsPage> {
return this.core.apis.QzonePublicApi.getFeeds(payload.cursor);
}
}
@@ -0,0 +1,32 @@
import { Static, Type } from '@sinclair/typebox';
import { ActionName } from '@/napcat-onebot/action/router';
import {
parseQzonePublicPageSize,
QzonePublicAction,
QzonePublicCommentExample,
QzonePublicCommentSchema,
QzonePublicFeedExample,
QzonePublicFeedSchema,
} from './common';
const PayloadSchema = Type.Object({
feed: QzonePublicFeedSchema,
comment: QzonePublicCommentSchema,
cursor: Type.Optional(Type.String({ default: '', description: '回复分页游标,默认为空字符串;空字符串时使用 comment.reply_attach_info;数字或布尔值会转为文本' })),
page_size: Type.Optional(Type.Union([
Type.Number({ minimum: 1 }), Type.String(),
], { default: 20, description: '每页回复数量,默认 20;有限数字或非空数值字符串,向零截断后应大于等于 1' })),
});
type PayloadType = Static<typeof PayloadSchema>;
export class GetQzonePublicReplies extends QzonePublicAction<PayloadType> {
override actionName = ActionName.GetQzonePublicReplies;
override actionSummary = '获取空友爱看评论回复';
override actionDescription = '传入完整动态及父评论读取回复分页;省略 cursor 时使用父评论中的 reply_attach_info';
override payloadSchema = PayloadSchema;
override payloadExample = { feed: QzonePublicFeedExample, comment: QzonePublicCommentExample, page_size: 20 };
async _handle (payload: PayloadType) {
return this.core.apis.QzonePublicApi.getReplies(payload.feed, payload.comment, payload.cursor ?? '', parseQzonePublicPageSize(payload.page_size));
}
}
@@ -0,0 +1,31 @@
import { Static, Type } from '@sinclair/typebox';
import { ActionName } from '@/napcat-onebot/action/router';
import {
QzonePublicAction,
QzonePublicCommentExample,
QzonePublicCommentSchema,
QzonePublicContentSchema,
QzonePublicFeedExample,
QzonePublicFeedSchema,
QzonePublicReplySchema,
} from './common';
const PayloadSchema = Type.Object({
feed: QzonePublicFeedSchema,
comment: QzonePublicCommentSchema,
content: QzonePublicContentSchema,
reply: Type.Optional(QzonePublicReplySchema),
});
type PayloadType = Static<typeof PayloadSchema>;
export class ReplyQzonePublicFeed extends QzonePublicAction<PayloadType> {
override actionName = ActionName.ReplyQzonePublicFeed;
override actionSummary = '回复空友爱看评论';
override actionDescription = '传入完整 feed、父 comment 及非空 content 发表回复;需要回复已有回复时一并传入完整 reply';
override payloadSchema = PayloadSchema;
override payloadExample = { feed: QzonePublicFeedExample, comment: QzonePublicCommentExample, content: '谢谢分享' };
async _handle (payload: PayloadType) {
return this.core.apis.QzonePublicApi.addReply(payload.feed, payload.comment, payload.content, payload.reply);
}
}
@@ -0,0 +1,32 @@
import { Static, Type } from '@sinclair/typebox';
import { ActionName } from '@/napcat-onebot/action/router';
import {
parseQzonePublicEnable,
QzonePublicAction,
QzonePublicCommentExample,
QzonePublicCommentSchema,
QzonePublicEnableSchema,
QzonePublicFeedExample,
QzonePublicFeedSchema,
QzonePublicReplySchema,
} from './common';
const PayloadSchema = Type.Object({
feed: QzonePublicFeedSchema,
comment: QzonePublicCommentSchema,
reply: Type.Optional(QzonePublicReplySchema),
enable: QzonePublicEnableSchema,
});
type PayloadType = Static<typeof PayloadSchema>;
export class SetQzonePublicCommentLike extends QzonePublicAction<PayloadType> {
override actionName = ActionName.SetQzonePublicCommentLike;
override actionSummary = '设置空友爱看评论或回复赞';
override actionDescription = '对完整 comment 点赞或取消点赞;传入完整 reply 时操作该回复,保留 comment/reply 的 like.id 等字段';
override payloadSchema = PayloadSchema;
override payloadExample = { feed: QzonePublicFeedExample, comment: QzonePublicCommentExample, enable: true };
async _handle (payload: PayloadType) {
return this.core.apis.QzonePublicApi.setCommentLike(payload.feed, payload.comment, parseQzonePublicEnable(payload.enable), payload.reply);
}
}
@@ -0,0 +1,18 @@
import { Static, Type } from '@sinclair/typebox';
import { ActionName } from '@/napcat-onebot/action/router';
import { parseQzonePublicEnable, QzonePublicAction, QzonePublicEnableSchema, QzonePublicFeedExample, QzonePublicFeedSchema } from './common';
const PayloadSchema = Type.Object({ feed: QzonePublicFeedSchema, enable: QzonePublicEnableSchema });
type PayloadType = Static<typeof PayloadSchema>;
export class SetQzonePublicFollow extends QzonePublicAction<PayloadType> {
override actionName = ActionName.SetQzonePublicFollow;
override actionSummary = '蹲或取消蹲空友爱看作者';
override actionDescription = '根据完整 feed 蹲或取消蹲动态作者;enable 默认 true,返回上游操作结果';
override payloadSchema = PayloadSchema;
override payloadExample = { feed: QzonePublicFeedExample, enable: true };
async _handle (payload: PayloadType) {
return this.core.apis.QzonePublicApi.setFollow(payload.feed, parseQzonePublicEnable(payload.enable));
}
}
@@ -0,0 +1,18 @@
import { Static, Type } from '@sinclair/typebox';
import { ActionName } from '@/napcat-onebot/action/router';
import { parseQzonePublicEnable, QzonePublicAction, QzonePublicEnableSchema, QzonePublicFeedExample, QzonePublicFeedSchema } from './common';
const PayloadSchema = Type.Object({ feed: QzonePublicFeedSchema, enable: QzonePublicEnableSchema });
type PayloadType = Static<typeof PayloadSchema>;
export class SetQzonePublicLike extends QzonePublicAction<PayloadType> {
override actionName = ActionName.SetQzonePublicLike;
override actionSummary = '设置空友爱看普通赞';
override actionDescription = '对完整 feed 设置或取消普通点赞(like);enable 默认 true,此操作与推分别处理';
override payloadSchema = PayloadSchema;
override payloadExample = { feed: QzonePublicFeedExample, enable: true };
async _handle (payload: PayloadType) {
return this.core.apis.QzonePublicApi.setLike(payload.feed, parseQzonePublicEnable(payload.enable));
}
}
@@ -0,0 +1,18 @@
import { Static, Type } from '@sinclair/typebox';
import { ActionName } from '@/napcat-onebot/action/router';
import { parseQzonePublicEnable, QzonePublicAction, QzonePublicEnableSchema, QzonePublicFeedExample, QzonePublicFeedSchema } from './common';
const PayloadSchema = Type.Object({ feed: QzonePublicFeedSchema, enable: QzonePublicEnableSchema });
type PayloadType = Static<typeof PayloadSchema>;
export class SetQzonePublicUpvote extends QzonePublicAction<PayloadType> {
override actionName = ActionName.SetQzonePublicUpvote;
override actionSummary = '推或取消推空友爱看动态';
override actionDescription = '对完整 feed 设置推或取消推(upvote);enable 默认 true,此操作与普通点赞分别处理';
override payloadSchema = PayloadSchema;
override payloadExample = { feed: QzonePublicFeedExample, enable: true };
async _handle (payload: PayloadType) {
return this.core.apis.QzonePublicApi.setUpvote(payload.feed, parseQzonePublicEnable(payload.enable));
}
}
@@ -0,0 +1,125 @@
import { Type } from '@sinclair/typebox';
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
import type { BaseCheckResult } from '@/napcat-onebot/action/router';
import type { QzonePublicObject } from '@/napcat-core/data/qzone-public';
export const QzonePublicObjectSchema = Type.Object({}, { additionalProperties: Type.Unknown() });
export const QzonePublicFeedSchema = Type.Object({}, {
additionalProperties: Type.Unknown(),
description: 'get_qzone_public_feeds 返回的完整 feed 对象,请保留全部字段',
});
export const QzonePublicCommentSchema = Type.Object({}, {
additionalProperties: Type.Unknown(),
description: '详情或评论列表返回的完整 comment 对象,请保留 like.id 等全部字段',
});
export const QzonePublicReplySchema = Type.Object({}, {
additionalProperties: Type.Unknown(),
description: '回复列表返回的完整 reply 对象,请保留全部字段',
});
export const QzonePublicResultSchema = Type.Object({}, {
additionalProperties: Type.Unknown(),
description: '上游返回的原始 JSON 对象,字段随内容类型和服务端版本变化',
});
export const QzonePublicEnableSchema = Type.Optional(Type.Union([
Type.Boolean(), Type.Number(), Type.String(),
], { default: true, description: '是否启用,默认 true;数字非 0 为 true,文本支持 true/false、1/0、yes/no、on/off' }));
export const QzonePublicContentSchema = Type.Union([
Type.String({ minLength: 1 }),
Type.Array(QzonePublicObjectSchema, { minItems: 1 }),
], { description: '非空文本或非空富文本对象数组,保留每个对象的全部字段' });
// 示例仅说明对象传递方式;实际调用应直接使用上游返回的完整对象。
export const QzonePublicFeedExample = {
cell_common: {
union_id: { uf_key: 'example-uf-key', ugc_id: 'example-ugc-id', feed_id: 'example-feed-id', ext_ids: [] },
cur_like_key: 'example-like-key',
domain_type: 3,
},
cell_user_info: { user: { uin: '10001' } },
cell_feed_transfer: {},
};
export const QzonePublicCommentExample = { id: 'example-comment', like: { id: 'example-comment-like' }, reply_attach_info: '' };
export const QzonePublicReplyExample = { id: 'example-reply', like: { id: 'example-reply-like' } };
export function parseQzonePublicEnable (value: boolean | number | string | undefined): boolean {
if (value === undefined) return true;
if (typeof value === 'boolean') return value;
if (typeof value === 'number') return value !== 0;
if (typeof value === 'string') {
const text = value.trim().toLowerCase();
if (['true', '1', 'yes', 'on'].includes(text)) return true;
if (['false', '0', 'no', 'off'].includes(text)) return false;
}
throw new Error('enable 必须为布尔值、数字或 true/false、1/0、yes/no、on/off 文本');
}
export function parseQzonePublicPageSize (value: number | string | undefined): number {
if (value === undefined) return 20;
if (typeof value !== 'number' && typeof value !== 'string') {
throw new Error('page_size 必须为数字或数值字符串,截断后应大于等于 1');
}
if (typeof value === 'string' && !value.trim()) {
throw new Error('page_size 必须为数字或数值字符串,截断后应大于等于 1');
}
const numericValue = Number(value);
const pageSize = Math.trunc(numericValue);
if (!Number.isFinite(numericValue) || pageSize < 1) {
throw new Error('page_size 必须为数字或数值字符串,截断后应大于等于 1');
}
return pageSize;
}
function isObject (value: unknown): value is QzonePublicObject {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}
export abstract class QzonePublicAction<PayloadType, ReturnType = QzonePublicObject> extends OneBotAction<PayloadType, ReturnType> {
override actionTags = ['QQ空间扩展'];
override returnSchema = QzonePublicResultSchema;
override returnExample = {};
protected override async check (payload: PayloadType): Promise<BaseCheckResult> {
// OneBot 的通用解析会转换原始类型。先校验这些透传参数,避免数字被当作文本或对象。
if (!isObject(payload)) return { valid: false, message: '请求参数必须为 JSON 对象' };
const parameters: QzonePublicObject = payload;
for (const key of ['feed', 'comment', 'reply']) {
if (parameters[key] !== undefined && !isObject(parameters[key])) {
return { valid: false, message: `${key} 必须为上游返回的完整 JSON 对象` };
}
}
for (const key of ['cursor', 'reason']) {
const value = parameters[key];
if (value !== undefined) {
if (!['string', 'number', 'boolean'].includes(typeof value)) {
return { valid: false, message: `${key} 必须为字符串、数字或布尔值` };
}
parameters[key] = String(value);
}
}
if ('content' in parameters) {
let content = parameters['content'];
if (typeof content === 'string') {
const text = content.trim();
if (text.startsWith('[') && text.endsWith(']')) {
try {
const decoded: unknown = JSON.parse(text);
if (Array.isArray(decoded)) content = decoded;
} catch {
// 无效 JSON 仍按原始文本处理。
}
}
}
if (typeof content === 'string' ? content.length === 0 : !Array.isArray(content) || content.length === 0 || !content.every(isObject)) {
return { valid: false, message: 'content 必须为非空文本或非空富文本对象数组' };
}
parameters['content'] = content;
}
try {
if ('enable' in parameters) parameters['enable'] = parseQzonePublicEnable(parameters['enable'] as boolean | number | string | undefined);
if ('page_size' in parameters) parameters['page_size'] = parseQzonePublicPageSize(parameters['page_size'] as number | string | undefined);
} catch (error) {
return { valid: false, message: (error as Error).message };
}
return super.check(payload as PayloadType);
}
}
@@ -0,0 +1,12 @@
/** Decode individual GET / form values before applying the public Qzone action contract. */
export function decodeQzonePublicHttpParameters (parameters: unknown): Record<string, unknown> {
if (parameters === null || typeof parameters !== 'object' || Array.isArray(parameters)) return {};
return Object.fromEntries(Object.entries(parameters).map(([key, value]) => {
if (typeof value !== 'string') return [key, value];
try {
return [key, JSON.parse(value) as unknown];
} catch {
return [key, value];
}
}));
}
@@ -0,0 +1,14 @@
export { GetQzonePublicCapabilities } from './GetQzonePublicCapabilities';
export { GetQzonePublicFeeds } from './GetQzonePublicFeeds';
export { GetQzonePublicFeedDetail } from './GetQzonePublicFeedDetail';
export { GetQzonePublicComments } from './GetQzonePublicComments';
export { GetQzonePublicReplies } from './GetQzonePublicReplies';
export { SetQzonePublicUpvote } from './SetQzonePublicUpvote';
export { SetQzonePublicLike } from './SetQzonePublicLike';
export { SetQzonePublicFollow } from './SetQzonePublicFollow';
export { SetQzonePublicCommentLike } from './SetQzonePublicCommentLike';
export { CommentQzonePublicFeed } from './CommentQzonePublicFeed';
export { ReplyQzonePublicFeed } from './ReplyQzonePublicFeed';
export { DeleteQzonePublicComment } from './DeleteQzonePublicComment';
export { DeleteQzonePublicReply } from './DeleteQzonePublicReply';
export { ForwardQzonePublicFeed } from './ForwardQzonePublicFeed';