SnowCat 雪猫底层全源码:NapCat 4.18.28 底座 + SnowLuma 能力移植 + V8 通道适配 9.9.37
26 包 monorepo(TS 全源码): - snowcat-core: NT 内核 API 封装 + listener(含 9.9.37 六回调适配)+ Packet 发包 - snowcat-onebot: OneBot v11 全动作 + MCP Streamable HTTP 适配器 + 稳定线 - snowcat-webui-frontend/backend: 雪猫控制台(React+HeroUI) - snowcat-shell: 单文件 bundle 出口(vite inlineDynamicImports) - snowcat-native: napi2native / MoeHoo / ffmpeg / pty 运行时 - 56 项 SnowLuma 独门动作移植(qzone 全家桶/群相册/装扮/闪照/收藏) - 工具件: 解包器/启动器/便携制作器/doctor GitHub 关联已全量指向 DandanLLab/SnowCatQQ
This commit is contained in:
commit
b9655a5ab1
1048 files changed
+130851
No files matched your search
@@ -0,0 +1,119 @@
|
||||
import { ActionName, BaseCheckResult } from './router';
|
||||
import { TypeCompiler, TypeCheck } from '@sinclair/typebox/compiler';
|
||||
import { Value } from '@sinclair/typebox/value';
|
||||
import { SnowCatCore } from 'snowcat-core';
|
||||
import { NapCatOneBot11Adapter, OB11Return } from '@/snowcat-onebot/index';
|
||||
import { NetworkAdapterConfig } from '../config/config';
|
||||
import { TSchema } from '@sinclair/typebox';
|
||||
import { StreamPacket, StreamPacketBasic, StreamStatus } from './stream/StreamBasic';
|
||||
export const ActionExamples = {
|
||||
Common: {
|
||||
errors: [
|
||||
{ code: 1400, description: '请求参数错误或业务逻辑执行失败' },
|
||||
{ code: 1401, description: '权限不足' },
|
||||
{ code: 1404, description: '资源不存在' },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export class OB11Response {
|
||||
private static createResponse<T> (data: T, status: string, retcode: number, message: string = '', echo: unknown = null, useStream: boolean = false): OB11Return<T> {
|
||||
return {
|
||||
status,
|
||||
retcode,
|
||||
data,
|
||||
message,
|
||||
wording: message,
|
||||
echo,
|
||||
stream: useStream ? 'stream-action' : 'normal-action',
|
||||
};
|
||||
}
|
||||
|
||||
static res<T> (data: T, status: string, retcode: number, message: string = '', echo: unknown = null, useStream: boolean = false): OB11Return<T> {
|
||||
return this.createResponse(data, status, retcode, message, echo, useStream);
|
||||
}
|
||||
|
||||
static ok<T> (data: T, echo: unknown = null, useStream: boolean = false): OB11Return<T> {
|
||||
return this.createResponse(data, 'ok', 0, '', echo, useStream);
|
||||
}
|
||||
|
||||
static error (err: string, retcode: number, echo: unknown = null, useStream: boolean = false): OB11Return<null | StreamPacketBasic> {
|
||||
return this.createResponse(useStream ? { type: StreamStatus.Error, data_type: 'error' } : null, 'failed', retcode, err, echo, useStream);
|
||||
}
|
||||
}
|
||||
export abstract class OneBotRequestToolkit {
|
||||
abstract send<T> (packet: StreamPacket<T>): Promise<void>;
|
||||
}
|
||||
export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
||||
actionName: typeof ActionName[keyof typeof ActionName] = ActionName.Unknown;
|
||||
core: SnowCatCore;
|
||||
private validate?: TypeCheck<TSchema> = undefined;
|
||||
payloadSchema?: TSchema = undefined;
|
||||
returnSchema?: TSchema = undefined;
|
||||
payloadExample?: unknown = undefined;
|
||||
returnExample?: unknown = undefined;
|
||||
actionSummary: string = '';
|
||||
actionDescription: string = '';
|
||||
actionTags: string[] = [];
|
||||
obContext: NapCatOneBot11Adapter;
|
||||
useStream: boolean = false;
|
||||
errorExamples: Array<{ code: number, description: string; }> = ActionExamples.Common.errors;
|
||||
|
||||
constructor (obContext: NapCatOneBot11Adapter, core: SnowCatCore) {
|
||||
this.obContext = obContext;
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
protected async check (payload: PayloadType): Promise<BaseCheckResult> {
|
||||
if (!this.payloadSchema) return { valid: true };
|
||||
try {
|
||||
this.validate = TypeCompiler.Compile(this.payloadSchema);
|
||||
let data = payload;
|
||||
data = Value.Parse(this.payloadSchema, data) as PayloadType;
|
||||
if (typeof payload === 'object' && payload !== null && data !== null) {
|
||||
Object.assign(payload, data);
|
||||
}
|
||||
} catch (e: any) {
|
||||
return { valid: false, message: `Schema compilation error: ${e.message}` };
|
||||
}
|
||||
if (this.validate && !this.validate.Check(payload)) {
|
||||
const errors = [...this.validate.Errors(payload)];
|
||||
const errorMessages = errors.map(e => `Key: ${e.path.split('/').slice(1).join('.')}, Message: ${e.message}`);
|
||||
return {
|
||||
valid: false,
|
||||
message: errorMessages.length > 0 ? errorMessages.join('\n') : '未知错误',
|
||||
};
|
||||
}
|
||||
return { valid: true };
|
||||
}
|
||||
|
||||
public async handle (payload: PayloadType, adaptername: string, config: NetworkAdapterConfig, req: OneBotRequestToolkit = { send: async () => { } }, echo?: string): Promise<OB11Return<ReturnDataType | StreamPacketBasic | null>> {
|
||||
const result = await this.check(payload);
|
||||
if (!result.valid) {
|
||||
return OB11Response.error(result.message, 400);
|
||||
}
|
||||
try {
|
||||
const resData = await this._handle(payload, adaptername, config, req);
|
||||
return OB11Response.ok(resData, echo, this.useStream);
|
||||
} catch (e: unknown) {
|
||||
this.core.context.logger.logError('发生错误', e);
|
||||
return OB11Response.error((e as Error).message.toString() || (e as Error)?.stack?.toString() || '未知错误,可能操作超时', 200, echo, this.useStream);
|
||||
}
|
||||
}
|
||||
|
||||
public async websocketHandle (payload: PayloadType, echo: unknown, adaptername: string, config: NetworkAdapterConfig, req: OneBotRequestToolkit = { send: async () => { } }): Promise<OB11Return<ReturnDataType | StreamPacketBasic | null>> {
|
||||
const result = await this.check(payload);
|
||||
if (!result.valid) {
|
||||
return OB11Response.error(result.message, 1400, echo, this.useStream);
|
||||
}
|
||||
try {
|
||||
const resData = await this._handle(payload, adaptername, config, req);
|
||||
return OB11Response.ok(resData, echo, this.useStream);
|
||||
} catch (e: unknown) {
|
||||
this.core.context.logger.logError('发生错误', e);
|
||||
return OB11Response.error(((e as Error).message.toString() || (e as Error).stack?.toString()) ?? 'Error', 1200, echo, this.useStream);
|
||||
}
|
||||
}
|
||||
|
||||
abstract _handle (payload: PayloadType, adaptername: string, config: NetworkAdapterConfig, req: OneBotRequestToolkit): Promise<ReturnDataType>;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { OneBotAction } from './OneBotAction';
|
||||
export const AutoRegisterRouter: Array<new (...args: any[]) => OneBotAction<unknown, unknown>> = [];
|
||||
|
||||
export function ActionHandler (target: new (...args: any[]) => OneBotAction<unknown, unknown>) {
|
||||
AutoRegisterRouter.push(target);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
export const ExtendsActionsExamples = {
|
||||
OCRImage: {
|
||||
payload: { image: 'image_id_123' },
|
||||
response: { texts: [{ text: '识别内容', coordinates: [] }] },
|
||||
},
|
||||
GetAiCharacters: {
|
||||
payload: { group_id: '123456' },
|
||||
response: [
|
||||
{
|
||||
type: 'string',
|
||||
characters: [
|
||||
{ character_id: 'id', character_name: 'name', preview_url: 'url' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
GetClientkey: {
|
||||
payload: {},
|
||||
response: { clientkey: 'abcdef123456' },
|
||||
},
|
||||
SetQQAvatar: {
|
||||
payload: { file: 'base64://...' },
|
||||
response: null,
|
||||
},
|
||||
SetGroupKickMembers: {
|
||||
payload: { group_id: '123456', user_id: ['123456789'], reject_add_request: false },
|
||||
response: null,
|
||||
},
|
||||
TranslateEnWordToZn: {
|
||||
payload: { words: ['hello'] },
|
||||
response: { words: ['你好'] },
|
||||
},
|
||||
GetRkey: {
|
||||
payload: {},
|
||||
response: { rkey: '...' },
|
||||
},
|
||||
SetLongNick: {
|
||||
payload: { longNick: '个性签名' },
|
||||
response: null,
|
||||
},
|
||||
SetSpecialTitle: {
|
||||
payload: { group_id: '123456', user_id: '123456789', special_title: '头衔' },
|
||||
response: null,
|
||||
},
|
||||
SendQzoneMsg: {
|
||||
payload: { content: '今天天气不错', images: [], ugc_right: 1, target_uins: [] },
|
||||
response: { tid: '1234567890abcdef1234567890' },
|
||||
},
|
||||
DeleteQzoneMsg: {
|
||||
payload: { tid: '1234567890abcdef1234567890' },
|
||||
response: null,
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
export const FileActionsExamples = {
|
||||
GetFile: {
|
||||
payload: { file: 'file_id_123' },
|
||||
response: { file: '/path/to/file', url: 'http://...', file_size: 1024, file_name: 'test.jpg' },
|
||||
},
|
||||
GetGroupFileUrl: {
|
||||
payload: { group_id: '123456', file_id: 'file_id_123', busid: 102 },
|
||||
response: { url: 'http://...' },
|
||||
},
|
||||
GetImage: {
|
||||
payload: { file: 'image_id_123' },
|
||||
response: { file: '/path/to/image', url: 'http://...' },
|
||||
},
|
||||
GetPrivateFileUrl: {
|
||||
payload: { user_id: '123456789', file_id: 'file_id_123' },
|
||||
response: { url: 'http://...' },
|
||||
},
|
||||
GetRecord: {
|
||||
payload: { file: 'record_id_123', out_format: 'mp3' },
|
||||
response: { file: '/path/to/record', url: 'http://...' },
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
export const GoCQHTTPActionsExamples = {
|
||||
GetStrangerInfo: {
|
||||
payload: { user_id: '123456789' },
|
||||
response: { user_id: 123456789, nickname: '昵称', sex: 'unknown' },
|
||||
},
|
||||
GetGroupHonorInfo: {
|
||||
payload: { group_id: '123456', type: 'all' },
|
||||
response: { group_id: 123456, current_talkative: {}, talkative_list: [] },
|
||||
},
|
||||
GetForwardMsg: {
|
||||
payload: { message_id: '123456' },
|
||||
response: { messages: [] },
|
||||
},
|
||||
SendForwardMsg: {
|
||||
payload: { group_id: '123456', messages: [] },
|
||||
response: { message_id: 123456 },
|
||||
},
|
||||
GetGroupAtAllRemain: {
|
||||
payload: { group_id: '123456' },
|
||||
response: { can_at_all: true, remain_at_all_count_for_group: 10, remain_at_all_count_for_self: 10 },
|
||||
},
|
||||
CreateGroupFileFolder: {
|
||||
payload: { group_id: '123456', name: '测试目录' },
|
||||
response: { result: {}, groupItem: {} },
|
||||
},
|
||||
DeleteGroupFile: {
|
||||
payload: { group_id: '123456', file_id: 'file_uuid_123' },
|
||||
response: {},
|
||||
},
|
||||
DeleteGroupFileFolder: {
|
||||
payload: { group_id: '123456', folder_id: 'folder_uuid_123' },
|
||||
response: {},
|
||||
},
|
||||
DownloadFile: {
|
||||
payload: { url: 'https://example.com/file.png', thread_count: 1, headers: 'User-Agent: NapCat' },
|
||||
response: { file: '/path/to/downloaded/file' },
|
||||
},
|
||||
GetFriendMsgHistory: {
|
||||
payload: { user_id: '123456789', message_seq: 0, count: 20 },
|
||||
response: { messages: [] },
|
||||
},
|
||||
GetGroupFilesByFolder: {
|
||||
payload: { group_id: '123456', folder_id: 'folder_id' },
|
||||
response: { files: [], folders: [] },
|
||||
},
|
||||
GetGroupFileSystemInfo: {
|
||||
payload: { group_id: '123456' },
|
||||
response: { file_count: 10, limit_count: 10000, used_space: 1024, total_space: 10737418240 },
|
||||
},
|
||||
GetGroupMsgHistory: {
|
||||
payload: { group_id: '123456', message_seq: 0, count: 20 },
|
||||
response: { messages: [] },
|
||||
},
|
||||
GetGroupRootFiles: {
|
||||
payload: { group_id: '123456' },
|
||||
response: { files: [], folders: [] },
|
||||
},
|
||||
GetOnlineClient: {
|
||||
payload: { no_cache: false },
|
||||
response: [],
|
||||
},
|
||||
GoCQHTTPCheckUrlSafely: {
|
||||
payload: { url: 'https://example.com' },
|
||||
response: { level: 1 },
|
||||
},
|
||||
GoCQHTTPDeleteFriend: {
|
||||
payload: { user_id: '123456789' },
|
||||
response: {},
|
||||
},
|
||||
GoCQHTTPGetModelShow: {
|
||||
payload: { model: 'iPhone 13' },
|
||||
response: { variants: [] },
|
||||
},
|
||||
GoCQHTTPSetModelShow: {
|
||||
payload: { model: 'iPhone 13', model_show: 'iPhone 13' },
|
||||
response: {},
|
||||
},
|
||||
QuickAction: {
|
||||
payload: { context: {}, operation: {} },
|
||||
response: {},
|
||||
},
|
||||
SendGroupNotice: {
|
||||
payload: { group_id: '123456', content: '公告内容', image: 'base64://...' },
|
||||
response: {},
|
||||
},
|
||||
SetGroupPortrait: {
|
||||
payload: { group_id: '123456', file: 'base64://...' },
|
||||
response: { result: 0, errMsg: '' },
|
||||
},
|
||||
SetQQProfile: {
|
||||
payload: { nickname: '新昵称', personal_note: '个性签名' },
|
||||
response: {},
|
||||
},
|
||||
UploadGroupFile: {
|
||||
payload: { group_id: '123456', file: '/path/to/file', name: 'test.txt' },
|
||||
response: { file_id: 'file_uuid_123' },
|
||||
},
|
||||
UploadPrivateFile: {
|
||||
payload: { user_id: '123456789', file: '/path/to/file', name: 'test.txt' },
|
||||
response: { file_id: 'file_uuid_123' },
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
export const GroupActionsExamples = {
|
||||
SetGroupMemberInvitePolicy: {
|
||||
payload: { group_id: '123456', policy: 'require_approval' },
|
||||
response: null,
|
||||
},
|
||||
SetGroupMemberPermissions: {
|
||||
payload: {
|
||||
group_id: '123456',
|
||||
allow_member_upload_album: true,
|
||||
allow_member_temporary_session: true,
|
||||
allow_member_create_group: true,
|
||||
},
|
||||
response: null,
|
||||
},
|
||||
SetGroupNewMemberHistoryVisibility: {
|
||||
payload: { group_id: '123456', visible: true },
|
||||
response: null,
|
||||
},
|
||||
DelEssenceMsg: {
|
||||
payload: { message_id: 123456 },
|
||||
response: null,
|
||||
},
|
||||
DelGroupNotice: {
|
||||
payload: { group_id: '123456', notice_id: 'notice_123' },
|
||||
response: null,
|
||||
},
|
||||
GetGroupDetailInfo: {
|
||||
payload: { group_id: '123456' },
|
||||
response: { group_id: 123456, group_name: '测试群', member_count: 100, max_member_count: 500 },
|
||||
},
|
||||
GetGroupEssence: {
|
||||
payload: { group_id: '123456' },
|
||||
response: [{ message_id: 123456, sender_id: 123456, sender_nick: '昵称', operator_id: 123456, operator_nick: '昵称', operator_time: 1710000000, content: '精华内容' }],
|
||||
},
|
||||
GetGroupInfo: {
|
||||
payload: { group_id: '123456' },
|
||||
response: { group_id: 123456, group_name: '测试群', member_count: 100, max_member_count: 500 },
|
||||
},
|
||||
GetGroupList: {
|
||||
payload: {},
|
||||
response: [{ group_id: 123456, group_name: '测试群', member_count: 100, max_member_count: 500 }],
|
||||
},
|
||||
GetGroupMemberInfo: {
|
||||
payload: { group_id: '123456', user_id: '123456789' },
|
||||
response: { group_id: 123456, user_id: 123456789, nickname: '昵称', card: '名片', role: 'member' },
|
||||
},
|
||||
GetGroupMemberList: {
|
||||
payload: { group_id: '123456' },
|
||||
response: [{ group_id: 123456, user_id: 123456789, nickname: '昵称', card: '名片', role: 'member' }],
|
||||
},
|
||||
GetGroupNotice: {
|
||||
payload: { group_id: '123456' },
|
||||
response: [{ notice_id: 'notice_123', sender_id: 123456, publish_time: 1710000000, message: { text: '公告内容', image: [] } }],
|
||||
},
|
||||
SendGroupMsg: {
|
||||
payload: { group_id: '123456', message: 'hello' },
|
||||
response: { message_id: 123456 },
|
||||
},
|
||||
SetEssenceMsg: {
|
||||
payload: { message_id: 123456 },
|
||||
response: null,
|
||||
},
|
||||
SetGroupAddRequest: {
|
||||
payload: { flag: 'flag_123', sub_type: 'add', approve: true },
|
||||
response: null,
|
||||
},
|
||||
SetGroupAdmin: {
|
||||
payload: { group_id: '123456', user_id: '123456789', enable: true },
|
||||
response: null,
|
||||
},
|
||||
SetGroupBan: {
|
||||
payload: { group_id: '123456', user_id: '123456789', duration: 1800 },
|
||||
response: null,
|
||||
},
|
||||
SetGroupCard: {
|
||||
payload: { group_id: '123456', user_id: '123456789', card: '新名片' },
|
||||
response: null,
|
||||
},
|
||||
SetGroupKick: {
|
||||
payload: { group_id: '123456', user_id: '123456789', reject_add_request: false },
|
||||
response: null,
|
||||
},
|
||||
SetGroupLeave: {
|
||||
payload: { group_id: '123456', is_dismiss: false },
|
||||
response: null,
|
||||
},
|
||||
SetGroupName: {
|
||||
payload: { group_id: '123456', group_name: '新群名' },
|
||||
response: null,
|
||||
},
|
||||
SetGroupWholeBan: {
|
||||
payload: { group_id: '123456', enable: true },
|
||||
response: null,
|
||||
},
|
||||
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
export const GuildActionsExamples = {
|
||||
GetGuildList: {
|
||||
payload: {},
|
||||
response: [{ guild_id: '123456', guild_name: '测试频道' }],
|
||||
},
|
||||
GetGuildProfile: {
|
||||
payload: { guild_id: '123456' },
|
||||
response: { guild_id: '123456', guild_name: '测试频道', guild_display_id: '123' },
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
export const NewActionsExamples = {
|
||||
GetDoubtFriendsAddRequest: {
|
||||
payload: { count: 10 },
|
||||
response: [{ user_id: 123456789, nickname: '昵称', age: 20, sex: 'male', reason: '申请理由', flag: 'flag_123' }],
|
||||
},
|
||||
SetDoubtFriendsAddRequest: {
|
||||
payload: { flag: 'flag_123', approve: true },
|
||||
response: {},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
export const PacketActionsExamples = {
|
||||
GetPacketStatus: {
|
||||
payload: {},
|
||||
response: { status: 'ok' },
|
||||
},
|
||||
SendPoke: {
|
||||
payload: { user_id: '123456789' },
|
||||
response: {},
|
||||
},
|
||||
SetGroupTodo: {
|
||||
payload: { group_id: '123456', message_id: '123456789' },
|
||||
response: {},
|
||||
},
|
||||
CompleteGroupTodo: {
|
||||
payload: { group_id: '123456', message_id: '123456789' },
|
||||
response: {},
|
||||
},
|
||||
CancelGroupTodo: {
|
||||
payload: { group_id: '123456', message_id: '123456789' },
|
||||
response: {},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
export const SystemActionsExamples = {
|
||||
CanSendImage: {
|
||||
payload: {},
|
||||
response: { yes: true },
|
||||
},
|
||||
CanSendRecord: {
|
||||
payload: {},
|
||||
response: { yes: true },
|
||||
},
|
||||
CleanCache: {
|
||||
payload: {},
|
||||
response: {},
|
||||
},
|
||||
GetCredentials: {
|
||||
payload: {},
|
||||
response: { cookies: '...', csrf_token: 123456789 },
|
||||
},
|
||||
GetCSRF: {
|
||||
payload: {},
|
||||
response: { token: 123456789 },
|
||||
},
|
||||
GetLoginInfo: {
|
||||
payload: {},
|
||||
response: { user_id: 123456789, nickname: '机器人' },
|
||||
},
|
||||
GetStatus: {
|
||||
payload: {},
|
||||
response: { online: true, good: true },
|
||||
},
|
||||
GetSystemMsg: {
|
||||
payload: {},
|
||||
response: { invited_requests: [], join_requests: [] },
|
||||
},
|
||||
GetVersionInfo: {
|
||||
payload: {},
|
||||
response: { app_name: 'SnowCatQQ', app_version: '1.0.0', protocol_version: 'v11' },
|
||||
},
|
||||
SetRestart: {
|
||||
payload: { delay: 0 },
|
||||
response: {},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,38 @@
|
||||
export const UserActionsExamples = {
|
||||
GetCookies: {
|
||||
payload: { domain: 'qun.qq.com' },
|
||||
response: { cookies: 'p_skey=xxx; p_uin=o0123456789;' },
|
||||
},
|
||||
GetFriendList: {
|
||||
payload: {},
|
||||
response: [{ user_id: 123456789, nickname: '昵称', remark: '备注' }],
|
||||
},
|
||||
GetRecentContact: {
|
||||
payload: { count: 10 },
|
||||
response: [
|
||||
{
|
||||
lastestMsg: 'hello',
|
||||
peerUin: '123456789',
|
||||
remark: 'remark',
|
||||
msgTime: '1710000000',
|
||||
chatType: 1,
|
||||
msgId: '12345',
|
||||
sendNickName: 'nick',
|
||||
sendMemberName: 'card',
|
||||
peerName: 'name',
|
||||
},
|
||||
],
|
||||
},
|
||||
SendLike: {
|
||||
payload: { user_id: '123456789', times: 10 },
|
||||
response: {},
|
||||
},
|
||||
SetFriendAddRequest: {
|
||||
payload: { flag: 'flag_123', approve: true, remark: '好友' },
|
||||
response: {},
|
||||
},
|
||||
SetFriendRemark: {
|
||||
payload: { user_id: '123456789', remark: '新备注' },
|
||||
response: {},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { OneBotAction } from '../OneBotAction';
|
||||
import { Type } from '@sinclair/typebox';
|
||||
|
||||
export class BotExit extends OneBotAction<void, void> {
|
||||
override actionName = ActionName.Exit;
|
||||
override payloadSchema = Type.Object({});
|
||||
override returnSchema = Type.Object({});
|
||||
override actionSummary = '退出登录';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {};
|
||||
override returnExample = null;
|
||||
|
||||
async _handle () {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { OneBotAction } from '../OneBotAction';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
bot_appid: Type.String({ description: '机器人AppID' }),
|
||||
button_id: Type.String({ default: '', description: '按钮ID' }),
|
||||
callback_data: Type.String({ default: '', description: '回调数据' }),
|
||||
msg_seq: Type.String({ default: '10086', description: '消息序列号' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '点击结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class ClickInlineKeyboardButton extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.ClickInlineKeyboardButton;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '点击内联键盘按钮';
|
||||
override actionTags = ['消息扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
bot_appid: '1234567890',
|
||||
button_id: 'btn_1',
|
||||
callback_data: '',
|
||||
msg_seq: '10086',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
};
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return await this.core.apis.MsgApi.clickInlineKeyboardButton({
|
||||
buttonId: payload.button_id,
|
||||
peerId: payload.group_id.toString(),
|
||||
botAppid: payload.bot_appid,
|
||||
msgSeq: payload.msg_seq,
|
||||
callback_data: payload.callback_data,
|
||||
dmFlag: 0,
|
||||
chatType: 2,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
rawData: Type.String({ description: '原始数据' }),
|
||||
brief: Type.String({ description: '简要描述' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '创建结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class CreateCollection extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.CreateCollection;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '创建收藏';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = {
|
||||
rawData: '收藏内容',
|
||||
brief: '收藏标题',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
result: 0,
|
||||
errMsg: '',
|
||||
};
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return await this.core.apis.CollectionApi.createCollection(
|
||||
this.core.selfInfo.uin,
|
||||
this.core.selfInfo.uid,
|
||||
this.core.selfInfo.nick,
|
||||
payload.brief, payload.rawData
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import { stat, readFile } from 'node:fs/promises';
|
||||
import { basename } from 'node:path';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
|
||||
const FetchCustomFaceDetailPayloadSchema = Type.Object({
|
||||
count: Type.Union([Type.Number(), Type.String()], { default: 48, description: '获取数量' }),
|
||||
});
|
||||
|
||||
type FetchCustomFaceDetailPayloadType = Static<typeof FetchCustomFaceDetailPayloadSchema>;
|
||||
|
||||
const FetchCustomFaceDetailReturnSchema = Type.Any({ description: '自定义表情详情列表' });
|
||||
|
||||
type FetchCustomFaceDetailReturnType = Static<typeof FetchCustomFaceDetailReturnSchema>;
|
||||
|
||||
export class FetchCustomFaceDetail extends OneBotAction<FetchCustomFaceDetailPayloadType, FetchCustomFaceDetailReturnType> {
|
||||
override actionName = ActionName.FetchCustomFaceDetail;
|
||||
override payloadSchema = FetchCustomFaceDetailPayloadSchema;
|
||||
override returnSchema = FetchCustomFaceDetailReturnSchema;
|
||||
override actionSummary = '获取自定义表情详情';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {
|
||||
count: 10,
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: FetchCustomFaceDetailPayloadType) {
|
||||
const ret = await this.core.apis.MsgApi.fetchFavEmojiList(+payload.count);
|
||||
return ret.emojiInfoList;
|
||||
}
|
||||
}
|
||||
|
||||
const AddCustomFacePayloadSchema = Type.Object({
|
||||
file: Type.String({ description: '本地表情文件路径' }),
|
||||
emoji_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: '表情ID,未提供时传空字符串' })),
|
||||
package_id: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: '表情包ID,未提供时传0' })),
|
||||
file_name: Type.Optional(Type.String({ description: '文件名,未提供时从file路径取basename' })),
|
||||
file_size: Type.Optional(Type.Union([Type.String(), Type.Number()], { description: '文件大小,未提供时读取本地文件' })),
|
||||
md5: Type.Optional(Type.String({ description: '文件MD5,未提供时读取本地文件计算' })),
|
||||
is_mark_face: Type.Optional(Type.Boolean({ description: '是否商城表情' })),
|
||||
is_origin: Type.Optional(Type.Boolean({ description: '是否原图' })),
|
||||
});
|
||||
|
||||
type AddCustomFacePayloadType = Static<typeof AddCustomFacePayloadSchema>;
|
||||
|
||||
const AddCustomFaceReturnSchema = Type.Any({ description: '添加结果' });
|
||||
|
||||
type AddCustomFaceReturnType = Static<typeof AddCustomFaceReturnSchema>;
|
||||
|
||||
export class AddCustomFace extends OneBotAction<AddCustomFacePayloadType, AddCustomFaceReturnType> {
|
||||
override actionName = ActionName.AddCustomFace;
|
||||
override payloadSchema = AddCustomFacePayloadSchema;
|
||||
override returnSchema = AddCustomFaceReturnSchema;
|
||||
override actionSummary = '添加自定义表情';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {
|
||||
file: 'C:\\Users\\Public\\face.png',
|
||||
is_origin: true,
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: AddCustomFacePayloadType) {
|
||||
const fileStat = await stat(payload.file);
|
||||
const md5 = payload.md5 ?? createHash('md5').update(await readFile(payload.file)).digest('hex');
|
||||
return await this.core.apis.MsgApi.addFavEmoji({
|
||||
emojiId: payload.emoji_id?.toString() ?? '',
|
||||
packageId: payload.package_id === undefined ? 0 : Number(payload.package_id),
|
||||
emojiPath: payload.file,
|
||||
fileSize: payload.file_size?.toString() ?? fileStat.size.toString(),
|
||||
fileName: payload.file_name ?? basename(payload.file),
|
||||
md5,
|
||||
isMarkFace: payload.is_mark_face ?? false,
|
||||
isOrigin: payload.is_origin ?? true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const DeleteCustomFacePayloadSchema = Type.Object({
|
||||
res_id: Type.Optional(Type.Union([
|
||||
Type.String({ description: 'fetch_custom_face_detail返回的resId' }),
|
||||
Type.Array(Type.String({ description: 'fetch_custom_face_detail返回的resId' })),
|
||||
])),
|
||||
id: Type.Optional(Type.Union([
|
||||
Type.String({ description: 'native deleteFavEmoji字符串ID,通常为resId' }),
|
||||
Type.Array(Type.String({ description: 'native deleteFavEmoji字符串ID,通常为resId' })),
|
||||
])),
|
||||
ids: Type.Optional(Type.Array(Type.String({ description: 'native deleteFavEmoji字符串ID列表,通常为resId列表' }))),
|
||||
md5: Type.Optional(Type.Union([
|
||||
Type.String({ description: '表情MD5,不能直接删除,请先通过fetch_custom_face_detail获取resId' }),
|
||||
Type.Array(Type.String({ description: '表情MD5,不能直接删除,请先通过fetch_custom_face_detail获取resId' })),
|
||||
])),
|
||||
});
|
||||
|
||||
type DeleteCustomFacePayloadType = Static<typeof DeleteCustomFacePayloadSchema>;
|
||||
|
||||
const DeleteCustomFaceReturnSchema = Type.Any({ description: '删除结果' });
|
||||
|
||||
type DeleteCustomFaceReturnType = Static<typeof DeleteCustomFaceReturnSchema>;
|
||||
|
||||
export class DeleteCustomFace extends OneBotAction<DeleteCustomFacePayloadType, DeleteCustomFaceReturnType> {
|
||||
override actionName = ActionName.DeleteCustomFace;
|
||||
override payloadSchema = DeleteCustomFacePayloadSchema;
|
||||
override returnSchema = DeleteCustomFaceReturnSchema;
|
||||
override actionSummary = '删除自定义表情';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {
|
||||
res_id: '2707600964_0_0_0_D8EAA70984B402EE10B0F33DB88F4173_0_0',
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: DeleteCustomFacePayloadType) {
|
||||
const ids = payload.ids ??
|
||||
(Array.isArray(payload.res_id) ? payload.res_id : payload.res_id ? [payload.res_id] : undefined) ??
|
||||
(Array.isArray(payload.id) ? payload.id : payload.id ? [payload.id] : undefined) ??
|
||||
[];
|
||||
if (ids.length === 0) throw new Error('res_id or ids is required');
|
||||
return await this.core.apis.MsgApi.deleteFavEmoji(ids);
|
||||
}
|
||||
}
|
||||
|
||||
const SetCustomFaceDescPayloadSchema = Type.Object({
|
||||
emoji_id: Type.Union([Type.Number(), Type.String()], { description: '表情ID' }),
|
||||
res_id: Type.String({ description: '资源ID' }),
|
||||
md5: Type.String({ description: '表情MD5' }),
|
||||
desc: Type.String({ description: '新的表情描述' }),
|
||||
});
|
||||
|
||||
type SetCustomFaceDescPayloadType = Static<typeof SetCustomFaceDescPayloadSchema>;
|
||||
|
||||
const SetCustomFaceDescReturnSchema = Type.Any({ description: '修改结果' });
|
||||
|
||||
type SetCustomFaceDescReturnType = Static<typeof SetCustomFaceDescReturnSchema>;
|
||||
|
||||
export class SetCustomFaceDesc extends OneBotAction<SetCustomFaceDescPayloadType, SetCustomFaceDescReturnType> {
|
||||
override actionName = ActionName.SetCustomFaceDesc;
|
||||
override payloadSchema = SetCustomFaceDescPayloadSchema;
|
||||
override returnSchema = SetCustomFaceDescReturnSchema;
|
||||
override actionSummary = '修改自定义表情描述';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {
|
||||
emoji_id: 1,
|
||||
res_id: 'resource-id',
|
||||
md5: 'd41d8cd98f00b204e9800998ecf8427e',
|
||||
desc: '新的描述',
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: SetCustomFaceDescPayloadType) {
|
||||
return await this.core.apis.MsgApi.modifyFavEmojiDesc([{
|
||||
emojiId: Number(payload.emoji_id),
|
||||
resId: payload.res_id,
|
||||
md5: payload.md5,
|
||||
desc: payload.desc,
|
||||
}]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
album_id: Type.String({ description: '相册ID' }),
|
||||
lloc: Type.String({ description: '媒体ID (lloc)' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '删除结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class DelGroupAlbumMedia extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.DelGroupAlbumMedia;
|
||||
override actionSummary = '删除群相册媒体';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
album_id: 'album_id_1',
|
||||
lloc: 'media_id_1',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
result: {},
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return await this.core.apis.WebApi.deleteAlbumMediaByNTQQ(
|
||||
payload.group_id,
|
||||
payload.album_id,
|
||||
payload.lloc
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
tid: Type.String({ description: '说说 tid (来自 get_qzone_msg_list / send_qzone_msg)' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
export class DeleteQzoneMsg extends OneBotAction<PayloadType, void> {
|
||||
override actionName = ActionName.DeleteQzoneMsg;
|
||||
override actionSummary = '删除QQ空间说说';
|
||||
override actionDescription = '删除QQ空间(Qzone)的一条说说, 按 tid 删除';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = ExtendsActionsExamples.DeleteQzoneMsg.payload;
|
||||
override returnExample = ExtendsActionsExamples.DeleteQzoneMsg.response;
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = Type.Null();
|
||||
|
||||
async _handle (payload: PayloadType): Promise<void> {
|
||||
await this.core.apis.WebApi.deleteQzoneMsg(payload.tid);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
export const DoGroupAlbumCommentPayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
album_id: Type.String({ description: '相册 ID' }),
|
||||
lloc: Type.String({ description: '图片 ID' }),
|
||||
content: Type.String({ description: '评论内容' }),
|
||||
});
|
||||
|
||||
export type DoGroupAlbumCommentPayload = Static<typeof DoGroupAlbumCommentPayloadSchema>;
|
||||
|
||||
export class DoGroupAlbumComment extends OneBotAction<DoGroupAlbumCommentPayload, any> {
|
||||
override actionName = ActionName.DoGroupAlbumComment;
|
||||
override actionSummary = '发表群相册评论';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
album_id: 'album_id_1',
|
||||
lloc: 'media_id_1',
|
||||
content: '很有意思',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
result: {},
|
||||
};
|
||||
|
||||
override payloadSchema = DoGroupAlbumCommentPayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '评论结果' });
|
||||
|
||||
async _handle (payload: DoGroupAlbumCommentPayload) {
|
||||
return await this.core.apis.WebApi.doAlbumMediaPlainCommentByNTQQ(
|
||||
payload.group_id,
|
||||
payload.album_id,
|
||||
payload.lloc,
|
||||
payload.content
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
count: Type.Union([Type.Number(), Type.String()], { default: 48, description: '获取数量' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Array(Type.String(), { description: '表情URL列表' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class FetchCustomFace extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.FetchCustomFace;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取自定义表情';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {
|
||||
count: 10,
|
||||
};
|
||||
|
||||
override returnExample = [
|
||||
'http://example.com/face1.png',
|
||||
];
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const ret = await this.core.apis.MsgApi.fetchFavEmojiList(+payload.count);
|
||||
return ret.emojiInfoList.map(e => e.url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { MessageUnique } from 'snowcat-common/src/message-unique';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
message_id: Type.Union([Type.Number(), Type.String()], { description: '消息ID' }),
|
||||
emojiId: Type.Union([Type.Number(), Type.String()], { description: '表情ID' }),
|
||||
emojiType: Type.Union([Type.Number(), Type.String()], { description: '表情类型' }),
|
||||
count: Type.Union([Type.Number(), Type.String()], { default: 20, description: '获取数量' }),
|
||||
cookie: Type.String({ default: '', description: '分页Cookie' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
emojiLikesList: Type.Array(Type.Object({
|
||||
tinyId: Type.String({ description: 'TinyID' }),
|
||||
nickName: Type.String({ description: '昵称' }),
|
||||
headUrl: Type.String({ description: '头像URL' }),
|
||||
}), { description: '表情回应列表' }),
|
||||
cookie: Type.String({ description: '分页Cookie' }),
|
||||
isLastPage: Type.Boolean({ description: '是否最后一页' }),
|
||||
isFirstPage: Type.Boolean({ description: '是否第一页' }),
|
||||
result: Type.Number({ description: '结果状态码' }),
|
||||
errMsg: Type.String({ description: '错 误信息' }),
|
||||
}, { description: '表情回应详情' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class FetchEmojiLike extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.FetchEmojiLike;
|
||||
override actionSummary = '获取表情点赞详情';
|
||||
override actionTags = ['消息扩展'];
|
||||
override payloadExample = {
|
||||
message_id: 12345,
|
||||
emojiId: '123',
|
||||
emojiType: 1,
|
||||
count: 10,
|
||||
cookie: '',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
emojiLikesList: [
|
||||
{
|
||||
tinyId: '123456',
|
||||
nickName: '测试用户',
|
||||
headUrl: 'http://example.com/avatar.png',
|
||||
},
|
||||
],
|
||||
cookie: '',
|
||||
isLastPage: true,
|
||||
isFirstPage: true,
|
||||
result: 0,
|
||||
errMsg: '',
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||
const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id);
|
||||
if (!msgIdPeer) throw new Error('消息不存在');
|
||||
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(msgIdPeer.Peer, [msgIdPeer.MsgId])).msgList[0];
|
||||
if (!msg) throw new Error('消息不存在');
|
||||
const res = await this.core.apis.MsgApi.getMsgEmojiLikesList(
|
||||
msgIdPeer.Peer, msg.msgSeq, payload.emojiId.toString(), payload.emojiType.toString(), payload.cookie, +payload.count
|
||||
);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
chat_type: Type.Union([Type.Number(), Type.String()], { default: 1, description: '聊天类型' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Array(
|
||||
Type.Object({
|
||||
type: Type.String({ description: '角色类型' }),
|
||||
characters: Type.Array(
|
||||
Type.Object({
|
||||
character_id: Type.String({ description: '角色ID' }),
|
||||
character_name: Type.String({ description: '角色名称' }),
|
||||
preview_url: Type.String({ description: '预览URL' }),
|
||||
}),
|
||||
{ description: '角色列表' }
|
||||
),
|
||||
}),
|
||||
{ description: 'AI角色列表' }
|
||||
);
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetAiCharacters extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GetAiCharacters;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取AI角色列表';
|
||||
override actionDescription = '获取群聊中的AI角色列表';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = ExtendsActionsExamples.GetAiCharacters.payload;
|
||||
override returnExample = ExtendsActionsExamples.GetAiCharacters.response;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const chatTypeNum = Number(payload.chat_type);
|
||||
const rawList = await this.core.apis.PacketApi.pkt.operation.FetchAiVoiceList(+payload.group_id, chatTypeNum);
|
||||
return rawList?.map((item) => ({
|
||||
type: item.category,
|
||||
characters: item.voices.map((voice) => ({
|
||||
character_id: voice.voiceId,
|
||||
character_name: voice.voiceDisplayName,
|
||||
preview_url: voice.voiceExampleUrl,
|
||||
})),
|
||||
})) ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { OneBotAction } from '../OneBotAction';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
clientkey: Type.Optional(Type.String({ description: '客户端Key' })),
|
||||
}, { description: '获取ClientKey结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetClientkey extends OneBotAction<void, ReturnType> {
|
||||
override actionName = ActionName.GetClientkey;
|
||||
override payloadSchema = Type.Object({});
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取ClientKey';
|
||||
override actionDescription = '获取当前登录帐号的ClientKey';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = ExtendsActionsExamples.GetClientkey.payload;
|
||||
override returnExample = ExtendsActionsExamples.GetClientkey.response;
|
||||
|
||||
async _handle () {
|
||||
return { clientkey: (await this.core.apis.UserApi.forceFetchClientKey()).clientKey };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
category: Type.String({ description: '分类ID' }),
|
||||
count: Type.String({ default: '50', description: '获取数量' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '收藏列表' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetCollectionList extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GetCollectionList;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取收藏列表';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {
|
||||
category: '0',
|
||||
count: '50',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
errCode: 0,
|
||||
errMsg: '',
|
||||
collectionSearchList: {
|
||||
collectionItemList: [
|
||||
{
|
||||
cid: '123456',
|
||||
type: 8,
|
||||
status: 1,
|
||||
author: {
|
||||
type: 2,
|
||||
numId: '123456',
|
||||
strId: '昵称',
|
||||
groupId: '123456',
|
||||
groupName: '群名',
|
||||
uid: '123456',
|
||||
},
|
||||
bid: 1,
|
||||
category: 1,
|
||||
createTime: '1769169157000',
|
||||
collectTime: '1769413477691',
|
||||
modifyTime: '1769413477691',
|
||||
sequence: '1769413476735',
|
||||
shareUrl: '',
|
||||
customGroupId: 0,
|
||||
securityBeat: false,
|
||||
summary: {
|
||||
textSummary: null,
|
||||
linkSummary: null,
|
||||
gallerySummary: null,
|
||||
audioSummary: null,
|
||||
videoSummary: null,
|
||||
fileSummary: null,
|
||||
locationSummary: null,
|
||||
richMediaSummary: {
|
||||
title: '',
|
||||
subTitle: '',
|
||||
brief: 'text',
|
||||
picList: [],
|
||||
contentType: 1,
|
||||
originalUri: '',
|
||||
publisher: '',
|
||||
richMediaVersion: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
hasMore: false,
|
||||
bottomTimeStamp: '1769413477691',
|
||||
},
|
||||
};
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return await this.core.apis.CollectionApi.getAllCollection(+payload.category, +payload.count);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { MessageUnique } from 'snowcat-common/src/message-unique';
|
||||
import { Peer, ChatType } from '@/snowcat-core';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.Optional(Type.String({ description: '群号,短ID可不传' })),
|
||||
message_id: Type.String({ description: '消息ID,可以传递长ID或短ID' }),
|
||||
emoji_id: Type.String({ description: '表情ID' }),
|
||||
emoji_type: Type.Optional(Type.String({ description: '表情类型' })),
|
||||
count: Type.Number({ default: 0, description: '数量,0代表全部' }),
|
||||
});
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
emoji_like_list: Type.Array(
|
||||
Type.Object({
|
||||
user_id: Type.String({ description: '点击者QQ号' }),
|
||||
nick_name: Type.String({ description: '昵称?' }),
|
||||
}),
|
||||
{ description: '表情回应列表' }
|
||||
),
|
||||
});
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetEmojiLikes extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GetEmojiLikes;
|
||||
override actionSummary = '获取消息表情点赞列表';
|
||||
override actionTags = ['消息扩展'];
|
||||
override payloadExample = {
|
||||
message_id: '12345',
|
||||
emoji_id: '123',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
emoji_like_list: [
|
||||
{
|
||||
user_id: '654321',
|
||||
nick_name: '测试用户',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
let peer: Peer;
|
||||
let msgId: string;
|
||||
|
||||
if (MessageUnique.isShortId(payload.message_id)) {
|
||||
const msgIdPeer = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id);
|
||||
if (!msgIdPeer) throw new Error('消息不存在');
|
||||
peer = msgIdPeer.Peer;
|
||||
msgId = msgIdPeer.MsgId;
|
||||
} else {
|
||||
if (!payload.group_id) throw new Error('长ID模式下必须提供群号');
|
||||
peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id };
|
||||
msgId = payload.message_id;
|
||||
}
|
||||
|
||||
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId])).msgList[0];
|
||||
if (!msg) throw new Error('消息不存在');
|
||||
|
||||
const emojiType = payload.emoji_type ?? (payload.emoji_id.length > 3 ? '2' : '1');
|
||||
const emojiLikeList: Array<{ user_id: string; nick_name: string; }> = [];
|
||||
let cookie = '';
|
||||
const needFetchCount = payload.count === 0 ? 200 : Math.ceil(payload.count / 15);
|
||||
for (let page = 0; page < needFetchCount; page++) {
|
||||
const res = await this.core.apis.MsgApi.getMsgEmojiLikesList(
|
||||
peer, msg.msgSeq, payload.emoji_id.toString(), emojiType, cookie, 15
|
||||
);
|
||||
|
||||
if (Array.isArray(res.emojiLikesList)) {
|
||||
for (const like of res.emojiLikesList) {
|
||||
emojiLikeList.push({ user_id: like.tinyId, nick_name: like.nickName });
|
||||
}
|
||||
}
|
||||
|
||||
if (res.isLastPage || !res.cookie) break;
|
||||
cookie = res.cookie;
|
||||
}
|
||||
// 切断多余部分
|
||||
if (payload.count > 0) {
|
||||
emojiLikeList.splice(payload.count);
|
||||
}
|
||||
return { emoji_like_list: emojiLikeList };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { OB11Construct } from '@/snowcat-onebot/helper/data';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
import { OB11UserSchema } from '../schemas';
|
||||
|
||||
const ReturnSchema = Type.Array(
|
||||
Type.Object({
|
||||
categoryId: Type.Number({ description: '分组ID' }),
|
||||
categoryName: Type.String({ description: '分组名称' }),
|
||||
categoryMbCount: Type.Number({ description: '分组内好友数量' }),
|
||||
buddyList: Type.Array(OB11UserSchema, { description: '好友列表' }),
|
||||
}),
|
||||
{ description: '带分组的好友列表' }
|
||||
);
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetFriendWithCategory extends OneBotAction<void, ReturnType> {
|
||||
override actionName = ActionName.GetFriendsWithCategory;
|
||||
override payloadSchema = Type.Object({});
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取带分组的好友列表';
|
||||
override actionTags = ['用户扩展'];
|
||||
override payloadExample = {};
|
||||
override returnExample = [
|
||||
{
|
||||
categoryId: 1,
|
||||
categoryName: '我的好友',
|
||||
categoryMbCount: 1,
|
||||
buddyList: [],
|
||||
},
|
||||
];
|
||||
|
||||
async _handle () {
|
||||
const categories = await this.core.apis.FriendApi.getBuddyV2ExWithCate();
|
||||
return categories.map(category => ({
|
||||
...category,
|
||||
buddyList: OB11Construct.friends(category.buddyList),
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { GroupNotifyMsgStatus } from 'snowcat-core';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const ReturnSchema = Type.Array(
|
||||
Type.Object({
|
||||
request_id: Type.Number({ description: '请求ID' }),
|
||||
invitor_uin: Type.Number({ description: '邀请者QQ' }),
|
||||
invitor_nick: Type.Optional(Type.String({ description: '邀请者昵称' })),
|
||||
group_id: Type.Number({ description: '群号' }),
|
||||
message: Type.Optional(Type.String({ description: '验证信息' })),
|
||||
group_name: Type.Optional(Type.String({ description: '群名称' })),
|
||||
checked: Type.Boolean({ description: '是否已处理' }),
|
||||
actor: Type.Number({ description: '处理者QQ' }),
|
||||
requester_nick: Type.Optional(Type.String({ description: '请求者昵称' })),
|
||||
}),
|
||||
{ description: '群通知列表' }
|
||||
);
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export default class GetGroupAddRequest extends OneBotAction<void, ReturnType> {
|
||||
override actionName = ActionName.GetGroupIgnoreAddRequest;
|
||||
override payloadSchema = Type.Object({});
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取群被忽略的加群请求';
|
||||
override actionTags = ['群组接口'];
|
||||
override payloadExample = {};
|
||||
override returnExample = [
|
||||
{
|
||||
request_id: 12345,
|
||||
invitor_uin: 123456789,
|
||||
invitor_nick: '邀请者',
|
||||
group_id: 123456789,
|
||||
message: '加群请求',
|
||||
group_name: '群名称',
|
||||
checked: false,
|
||||
actor: 0,
|
||||
requester_nick: '请求者',
|
||||
},
|
||||
];
|
||||
|
||||
async _handle (): Promise<ReturnType> {
|
||||
const NTQQUserApi = this.core.apis.UserApi;
|
||||
const NTQQGroupApi = this.core.apis.GroupApi;
|
||||
const ignoredNotifies = await NTQQGroupApi.getSingleScreenNotifies(true, 10);
|
||||
const retData: ReturnType = [];
|
||||
|
||||
const notifyPromises = ignoredNotifies
|
||||
.filter(notify => notify.type === 7)
|
||||
.map(async SSNotify => {
|
||||
const invitorUin = SSNotify.user1?.uid ? +await NTQQUserApi.getUinByUidV2(SSNotify.user1.uid) : 0;
|
||||
const actorUin = SSNotify.user2?.uid ? +await NTQQUserApi.getUinByUidV2(SSNotify.user2.uid) : 0;
|
||||
retData.push({
|
||||
request_id: +SSNotify.seq,
|
||||
invitor_uin: invitorUin,
|
||||
invitor_nick: SSNotify.user1?.nickName,
|
||||
group_id: +SSNotify.group?.groupCode,
|
||||
message: SSNotify?.postscript,
|
||||
group_name: SSNotify.group?.groupName,
|
||||
checked: SSNotify.status !== GroupNotifyMsgStatus.KUNHANDLE,
|
||||
actor: actorUin,
|
||||
requester_nick: SSNotify.user1?.nickName,
|
||||
});
|
||||
});
|
||||
|
||||
await Promise.all(notifyPromises);
|
||||
|
||||
return retData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
album_id: Type.String({ description: '相册ID' }),
|
||||
attach_info: Type.Optional(Type.String({ default: '', description: '附加信息(用于分页)' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '相册媒体列表' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetGroupAlbumMediaList extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GetGroupAlbumMediaList;
|
||||
override actionSummary = '获取群相册媒体列表';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
album_id: 'album_id_1',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
media_list: [
|
||||
{ media_id: 'media_id_1', url: 'http://example.com/1.jpg' },
|
||||
],
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return await this.core.apis.WebApi.getAlbumMediaListByNTQQ(
|
||||
payload.group_id,
|
||||
payload.album_id,
|
||||
payload.attach_info
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '群扩展信息' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetGroupInfoEx extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GetGroupInfoEx;
|
||||
override actionSummary = '获取群详细信息 (扩展)';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return (await this.core.apis.GroupApi.getGroupExtFE0Info([payload.group_id.toString()])).result.groupExtInfos.get(payload.group_id.toString());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()], { description: '群号' }),
|
||||
need_short_url: Type.Optional(Type.Union([Type.Boolean(), Type.String()], { default: true, description: '是否返回短链 (qm.qq.com/q/xxx),false 返回完整 universal-share 链接' })),
|
||||
src_id: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 73, description: '分享来源 ID' })),
|
||||
additional_param: Type.Optional(Type.String({ default: '', description: '附加参数' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
url: Type.String({ description: '群分享链接' }),
|
||||
}, { description: '群分享链接' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetGroupShareLink extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GetGroupShareLink;
|
||||
override actionSummary = '获取群分享链接';
|
||||
override actionDescription = '通过 NTQQ 原生 getJoinGroupLink 获取群的 H5 分享/加群链接 (qm.qq.com/q/xxx 或 qun.qq.com/universal-share/share?...)';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
need_short_url: true,
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
url: 'https://qm.qq.com/q/xxxxxxxxxx',
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const needShortUrl = payload.need_short_url === undefined ? true : payload.need_short_url.toString() === 'true';
|
||||
const srcId = payload.src_id === undefined ? 73 : Number(payload.src_id);
|
||||
const ret = await this.core.apis.GroupApi.getJoinGroupLink(
|
||||
payload.group_id.toString(),
|
||||
needShortUrl,
|
||||
srcId,
|
||||
payload.additional_param ?? ''
|
||||
);
|
||||
if (!ret?.url) {
|
||||
throw new Error(`获取群分享链接失败: ${ret?.errMsg || JSON.stringify(ret)}`);
|
||||
}
|
||||
return { url: ret.url };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.Union([Type.String(), Type.Number()], { description: '群号' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Array(
|
||||
Type.Object({
|
||||
user_id: Type.Number({ description: '打卡者QQ' }),
|
||||
nick: Type.String({ description: '打卡者昵称' }),
|
||||
time: Type.Number({ description: '打卡时间' }),
|
||||
rank: Type.Number({ description: '打卡排名' }),
|
||||
}),
|
||||
{ description: '群组今日打卡列表' }
|
||||
);
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetGroupSignedList extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GetGroupSignedList;
|
||||
override actionSummary = '获取群组今日打卡列表';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const data = await this.core.apis.WebApi.getDaySignedList(payload.group_id.toString());
|
||||
if (!data.response.page) throw new Error('无法获取该群组打卡列表');
|
||||
return data.response.page[0]?.infos?.map(info => ({
|
||||
user_id: +info.uid,
|
||||
nick: info.uidGroupNick,
|
||||
time: +info.signedTimeStamp,
|
||||
rank: (info.signInRank - 1) / 2 + 1,
|
||||
})) ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
|
||||
import { MiniAppInfo, MiniAppInfoHelper } from 'snowcat-core/packet/utils/helper/miniAppHelper';
|
||||
import { MiniAppReqCustomParams, MiniAppReqParams } from 'snowcat-core/packet/entities/miniApp';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Union([
|
||||
Type.Object({
|
||||
type: Type.Union([Type.Literal('bili'), Type.Literal('weibo')], { description: '模板类型' }),
|
||||
title: Type.String({ description: '标题' }),
|
||||
desc: Type.String({ description: '描述' }),
|
||||
picUrl: Type.String({ description: '图片URL' }),
|
||||
jumpUrl: Type.String({ description: '跳转URL' }),
|
||||
webUrl: Type.Optional(Type.String({ description: '网页URL' })),
|
||||
rawArkData: Type.Optional(Type.Union([Type.String()], { description: '是否返回原始Ark数据' })),
|
||||
}),
|
||||
Type.Object({
|
||||
title: Type.String({ description: '标题' }),
|
||||
desc: Type.String({ description: '描述' }),
|
||||
picUrl: Type.String({ description: '图片URL' }),
|
||||
jumpUrl: Type.String({ description: '跳转URL' }),
|
||||
iconUrl: Type.String({ description: '图标URL' }),
|
||||
webUrl: Type.Optional(Type.String({ description: '网页URL' })),
|
||||
appId: Type.String({ description: '小程序AppID' }),
|
||||
scene: Type.String({ description: '场景ID' }),
|
||||
templateType: Type.String({ description: '模板类型' }),
|
||||
businessType: Type.String({ description: '业务类型' }),
|
||||
verType: Type.String({ description: '版本类型' }),
|
||||
shareType: Type.String({ description: '分享类型' }),
|
||||
versionId: Type.String({ description: '版本ID' }),
|
||||
sdkId: Type.String({ description: 'SDK ID' }),
|
||||
withShareTicket: Type.String({ description: '是否携带分享票据' }),
|
||||
rawArkData: Type.Optional(Type.String({ description: '是否返回原始Ark数据' })),
|
||||
}),
|
||||
], { description: '小程序Ark参数' });
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
data: Type.Any({ description: 'Ark数据' }),
|
||||
}, { description: '获取小程序Ark结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetMiniAppArk extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GetMiniAppArk;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema; override actionSummary = '获取小程序 Ark';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {
|
||||
type: 'bili',
|
||||
title: '测试标题',
|
||||
desc: '测试描述',
|
||||
picUrl: 'http://example.com/pic.jpg',
|
||||
jumpUrl: 'http://example.com',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
data: {
|
||||
ark: 'ark_content',
|
||||
},
|
||||
};
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
let reqParam: MiniAppReqParams;
|
||||
const customParams: MiniAppReqCustomParams = {
|
||||
title: payload.title,
|
||||
desc: payload.desc,
|
||||
picUrl: payload.picUrl,
|
||||
jumpUrl: payload.jumpUrl,
|
||||
webUrl: payload.webUrl ?? '',
|
||||
};
|
||||
if ('type' in payload) {
|
||||
const template = MiniAppInfo.get(payload.type)?.template;
|
||||
if (!template) {
|
||||
throw new Error('未知的模板类型');
|
||||
}
|
||||
reqParam = MiniAppInfoHelper.generateReq(customParams, template);
|
||||
} else {
|
||||
const { appId, scene, iconUrl, templateType, businessType, verType, shareType, versionId, withShareTicket } = payload;
|
||||
reqParam = MiniAppInfoHelper.generateReq(
|
||||
customParams,
|
||||
{
|
||||
sdkId: payload.sdkId ?? MiniAppInfo.sdkId,
|
||||
appId,
|
||||
scene: +scene,
|
||||
iconUrl,
|
||||
templateType: +templateType,
|
||||
businessType: +businessType,
|
||||
verType: +verType,
|
||||
shareType: +shareType,
|
||||
versionId,
|
||||
withShareTicket: +withShareTicket,
|
||||
}
|
||||
);
|
||||
}
|
||||
const arkData = await this.core.apis.PacketApi.pkt.operation.GetMiniAppAdaptShareInfo(reqParam);
|
||||
return {
|
||||
data: payload.rawArkData === 'true' ? arkData : MiniAppInfoHelper.RawToSend(arkData),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
user_id: Type.Optional(Type.String({ description: 'QQ号' })),
|
||||
start: Type.Union([Type.Number(), Type.String()], { default: 0, description: '起始位置' }),
|
||||
count: Type.Union([Type.Number(), Type.String()], { default: 10, description: '获取数量' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
uid: Type.String({ description: '用户UID' }),
|
||||
time: Type.String({ description: '时间' }),
|
||||
favoriteInfo: Type.Object({
|
||||
userInfos: Type.Array(Type.Any(), { description: '点赞用户信息' }),
|
||||
total_count: Type.Number({ description: '总点赞数' }),
|
||||
last_time: Type.Number({ description: '最后点赞时间' }),
|
||||
today_count: Type.Number({ description: '今日点赞数' }),
|
||||
}),
|
||||
voteInfo: Type.Object({
|
||||
total_count: Type.Number({ description: '总点赞数' }),
|
||||
new_count: Type.Number({ description: '新增点赞数' }),
|
||||
new_nearby_count: Type.Number({ description: '新增附近点赞数' }),
|
||||
last_visit_time: Type.Number({ description: '最后访问时间' }),
|
||||
userInfos: Type.Array(Type.Any(), { description: '点赞用户信息' }),
|
||||
}),
|
||||
}, { description: '点赞详情' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetProfileLike extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GetProfileLike;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取资料点赞';
|
||||
override actionTags = ['用户扩展'];
|
||||
override payloadExample = {
|
||||
user_id: '123456789',
|
||||
start: 0,
|
||||
count: 10,
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
uid: 'u_123',
|
||||
time: '1734567890',
|
||||
favoriteInfo: {
|
||||
userInfos: [],
|
||||
total_count: 10,
|
||||
last_time: 1734567890,
|
||||
today_count: 5,
|
||||
},
|
||||
voteInfo: {
|
||||
total_count: 100,
|
||||
new_count: 2,
|
||||
new_nearby_count: 0,
|
||||
last_visit_time: 1734567890,
|
||||
userInfos: [],
|
||||
},
|
||||
};
|
||||
|
||||
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||
const isSelf = this.core.selfInfo.uin === payload.user_id || !payload.user_id;
|
||||
const userUid = isSelf || !payload.user_id ? this.core.selfInfo.uid : await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
const type = isSelf ? 2 : 1;
|
||||
const ret = await this.core.apis.UserApi.getProfileLike(userUid ?? this.core.selfInfo.uid, +payload.start, +payload.count, type);
|
||||
const data = ret.info.userLikeInfos[0];
|
||||
if (!data) {
|
||||
throw new Error('get info error');
|
||||
}
|
||||
for (const item of data.voteInfo.userInfos) {
|
||||
item.uin = +((await this.core.apis.UserApi.getUinByUidV2(item.uid)) ?? '');
|
||||
}
|
||||
for (const item of data.favoriteInfo.userInfos) {
|
||||
item.uin = +((await this.core.apis.UserApi.getUinByUidV2(item.uid)) ?? '');
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
attach_info: Type.Optional(Type.String({ default: '', description: '附加信息(用于分页,从上一次返回结果中获取)' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
album_list: Type.Array(Type.Any(), { description: '群相册列表' }),
|
||||
attach_info: Type.String({ description: '分页附加信息,传入下一次请求以获取更多数据' }),
|
||||
has_more: Type.Boolean({ description: '是否有更多数据' }),
|
||||
}, { description: '群相册列表响应' });
|
||||
|
||||
type GetQunAlbumListReturn = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetQunAlbumList extends OneBotAction<PayloadType, GetQunAlbumListReturn> {
|
||||
override actionName = ActionName.GetQunAlbumList;
|
||||
override actionSummary = '获取群相册列表';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
attach_info: '',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
album_list: [
|
||||
{
|
||||
album_id: 'album_1',
|
||||
album_name: '测试相册',
|
||||
cover_url: 'http://example.com/cover.jpg',
|
||||
create_time: 1734567890,
|
||||
},
|
||||
],
|
||||
attach_info: '',
|
||||
has_more: false,
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType): Promise<GetQunAlbumListReturn> {
|
||||
const resp = (await this.core.apis.WebApi.getAlbumListByNTQQ(payload.group_id, payload.attach_info)).response;
|
||||
return {
|
||||
album_list: resp.album_list,
|
||||
attach_info: resp.attach_info,
|
||||
has_more: resp.has_more,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const ReturnSchema = Type.Array(Type.Any(), { description: 'Rkey列表' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetRkey extends GetPacketStatusDepends<void, ReturnType> {
|
||||
override actionName = ActionName.GetRkey;
|
||||
override payloadSchema = Type.Object({});
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取 RKey';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {};
|
||||
override returnExample = [
|
||||
{
|
||||
key: 'rkey_value',
|
||||
expired: 1734567890,
|
||||
},
|
||||
];
|
||||
|
||||
async _handle () {
|
||||
return await this.core.apis.PacketApi.pkt.operation.FetchRkey();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const ReturnSchema = Type.Array(Type.Any(), { description: '机器人Uin范围列表' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetRobotUinRange extends OneBotAction<void, ReturnType> {
|
||||
override actionName = ActionName.GetRobotUinRange;
|
||||
override actionSummary = '获取机器人 UIN 范围';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {};
|
||||
override returnExample = [
|
||||
{ minUin: '12345678', maxUin: '87654321' },
|
||||
];
|
||||
|
||||
override payloadSchema = Type.Object({});
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle () {
|
||||
return await this.core.apis.UserApi.getRobotUinRange();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { PacketBuf } from 'snowcat-core/packet/transformer/base';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { ProtoBuf, ProtoBufBase, PBUint32, PBString } from 'napcat.protobuf';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
|
||||
const ReturnSchema = Type.Array(
|
||||
Type.Object({
|
||||
uin: Type.Number({ description: 'QQ号' }),
|
||||
uid: Type.String({ description: '用户UID' }),
|
||||
nick_name: Type.String({ description: '昵称' }),
|
||||
age: Type.Number({ description: '年龄' }),
|
||||
source: Type.String({ description: '来源' }),
|
||||
}),
|
||||
{ description: '单向好友列表' }
|
||||
);
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetUnidirectionalFriendList extends OneBotAction<void, ReturnType> {
|
||||
override actionName = ActionName.GetUnidirectionalFriendList;
|
||||
override payloadSchema = Type.Object({});
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取单向好友列表';
|
||||
override actionTags = ['用户扩展'];
|
||||
override payloadExample = {};
|
||||
override returnExample = [
|
||||
{
|
||||
uin: 123456789,
|
||||
uid: 'u_123',
|
||||
nick_name: '单向好友',
|
||||
age: 20,
|
||||
source: '来源',
|
||||
},
|
||||
];
|
||||
|
||||
async pack_data (data: string): Promise<Uint8Array> {
|
||||
return ProtoBuf(class extends ProtoBufBase {
|
||||
type = PBUint32(2, false, 0);
|
||||
data = PBString(3, false, data);
|
||||
}).encode();
|
||||
}
|
||||
|
||||
async _handle (): Promise<ReturnType> {
|
||||
const self_id = this.core.selfInfo.uin;
|
||||
const req_json = {
|
||||
uint64_uin: self_id,
|
||||
uint64_top: 0,
|
||||
uint32_req_num: 99,
|
||||
bytes_cookies: '',
|
||||
};
|
||||
const packed_data = await this.pack_data(JSON.stringify(req_json));
|
||||
const data = Buffer.from(packed_data);
|
||||
const rsq = { cmd: 'MQUpdateSvc_com_qq_ti.web.OidbSvc.0xe17_0', data: data as unknown as PacketBuf };
|
||||
const rsp_data = await this.core.apis.PacketApi.pkt.operation.sendPacket(rsq, true);
|
||||
const block_json = ProtoBuf(class extends ProtoBufBase { data = PBString(4); }).decode(rsp_data);
|
||||
interface BlockItem {
|
||||
uint64_uin: number;
|
||||
str_uid: string;
|
||||
bytes_nick: string;
|
||||
uint32_age: number;
|
||||
bytes_source: string;
|
||||
}
|
||||
const block_data: { rpt_block_list: BlockItem[]; } = JSON.parse(block_json.data);
|
||||
const block_list = block_data.rpt_block_list;
|
||||
|
||||
return block_list.map((block) => ({
|
||||
uin: block.uint64_uin,
|
||||
uid: block.str_uid,
|
||||
nick_name: Buffer.from(block.bytes_nick, 'base64').toString(),
|
||||
age: block.uint32_age,
|
||||
source: Buffer.from(block.bytes_source, 'base64').toString(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
user_id: Type.String({ description: 'QQ号' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
status: Type.Number({ description: '在线状态' }),
|
||||
ext_status: Type.Number({ description: '扩展状态' }),
|
||||
}, { description: '用户状态' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetUserStatus extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GetUserStatus;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取用户在线状态';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {
|
||||
user_id: '123456789',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
status: 10,
|
||||
ext_status: 0,
|
||||
};
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const res = await this.core.apis.PacketApi.pkt.operation.GetStrangerStatus(+payload.user_id);
|
||||
if (!res) {
|
||||
throw new Error('无法获取用户状态');
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { FileNapCatOneBotUUID } from 'snowcat-common/src/file-uuid';
|
||||
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
file_id: Type.String({ description: '文件ID' }),
|
||||
current_parent_directory: Type.String({ description: '当前父目录' }),
|
||||
target_parent_directory: Type.String({ description: '目标父目录' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
ok: Type.Boolean({ description: '是否成功' }),
|
||||
}, { description: '移动文件结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class MoveGroupFile extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.MoveGroupFile;
|
||||
override actionSummary = '移动群文件';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
file_id: '/file_id',
|
||||
current_parent_directory: '/current_folder_id',
|
||||
target_parent_directory: '/target_folder_id',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
ok: true,
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||
if (contextMsgFile?.fileUUID) {
|
||||
await this.core.apis.PacketApi.pkt.operation.MoveGroupFile(+payload.group_id, contextMsgFile.fileUUID, payload.current_parent_directory, payload.target_parent_directory);
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
}
|
||||
throw new Error('real fileUUID not found!');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { checkFileExist, uriToLocalFile } from 'snowcat-common/src/file';
|
||||
import fs from 'fs';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
image: Type.String({ description: '图片路径、URL或Base64' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: 'OCR结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
class OCRImageBase extends OneBotAction<PayloadType, ReturnType> {
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '图片 OCR 识别';
|
||||
override actionDescription = '识别图片中的文字内容(仅Windows端支持)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = ExtendsActionsExamples.OCRImage.payload;
|
||||
override returnExample = ExtendsActionsExamples.OCRImage.response;
|
||||
|
||||
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||
const { path, success, isLocal } = await uriToLocalFile(this.core.SnowCatTempPath, payload.image);
|
||||
if (!success) {
|
||||
throw new Error(`OCR ${payload.image}失败, image字段可能格式不正确`);
|
||||
}
|
||||
if (path) {
|
||||
try {
|
||||
await checkFileExist(path, 5000); // 避免崩溃
|
||||
const ret = await this.core.apis.SystemApi.ocrImage(path);
|
||||
if (!ret) {
|
||||
throw new Error(`OCR ${payload.image}失败`);
|
||||
}
|
||||
return ret.result;
|
||||
} finally {
|
||||
if (!isLocal) fs.unlink(path, () => { });
|
||||
}
|
||||
}
|
||||
throw new Error(`OCR ${payload.image}失败, 文件可能不存在`);
|
||||
}
|
||||
}
|
||||
|
||||
export class OCRImage extends OCRImageBase {
|
||||
override actionName = ActionName.OCRImage;
|
||||
override actionSummary = '图片 OCR 识别';
|
||||
}
|
||||
|
||||
export class IOCRImage extends OCRImageBase {
|
||||
override actionName = ActionName.IOCRImage;
|
||||
override actionSummary = '图片 OCR 识别 (内部)';
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { FileNapCatOneBotUUID } from 'snowcat-common/src/file-uuid';
|
||||
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
file_id: Type.String({ description: '文件ID' }),
|
||||
current_parent_directory: Type.String({ description: '当前父目录' }),
|
||||
new_name: Type.String({ description: '新文件名' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
ok: Type.Boolean({ description: '是否成功' }),
|
||||
}, { description: '重命名文件结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class RenameGroupFile extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.RenameGroupFile;
|
||||
override actionSummary = '重命名群文件';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
file_id: '/file_id',
|
||||
current_parent_directory: '/',
|
||||
new_name: 'new_name.jpg',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
ok: true,
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||
if (contextMsgFile?.fileUUID) {
|
||||
await this.core.apis.PacketApi.pkt.operation.RenameGroupFile(+payload.group_id, contextMsgFile.fileUUID, payload.current_parent_directory, payload.new_name);
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
}
|
||||
throw new Error('real fileUUID not found!');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { PacketBuf } from 'snowcat-core/packet/transformer/base';
|
||||
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
cmd: Type.String({ description: '命令字' }),
|
||||
data: Type.String({ description: '十六进制数据' }),
|
||||
rsp: Type.Union([Type.String(), Type.Boolean()], { default: true, description: '是否等待响应' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Union([Type.String({ description: '响应十六进制数据' }), Type.Undefined()], { description: '发包结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class SendPacket extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionName = ActionName.SendPacket;
|
||||
override actionSummary = '发送原始数据包';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {
|
||||
cmd: 'Example.Cmd',
|
||||
data: '123456',
|
||||
rsp: true,
|
||||
};
|
||||
|
||||
override returnExample = '123456';
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const rsp = typeof payload.rsp === 'boolean' ? payload.rsp : payload.rsp === 'true';
|
||||
const packetData = Buffer.from(payload.data, 'hex') as unknown as PacketBuf;
|
||||
const data = await this.core.apis.PacketApi.pkt.operation.sendPacket({ cmd: payload.cmd, data: packetData }, rsp);
|
||||
return typeof data === 'object' ? data.toString('hex') : undefined;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import { uriToLocalFile } from 'snowcat-common/src/file';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { existsSync, readFileSync } from 'node:fs';
|
||||
import { unlink } from 'node:fs/promises';
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
const ValidUgcRights = [1, 4, 16, 64, 128] as const;
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
content: Type.String({ description: '说说正文' }),
|
||||
images: Type.Optional(Type.Array(Type.String(), { description: '图片数组, 支持 file:// http(s):// base64:// , 自动上传' })),
|
||||
ugc_right: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 1, description: '查看权限: 1所有人可见 4好友可见 16部分好友可见 64仅自己可见 128部分好友不可见' })),
|
||||
target_uins: Type.Optional(Type.Array(Type.Union([Type.Number(), Type.String()]), { description: 'ugc_right为16/128时, 权限作用的QQ号数组' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
tid: Type.String({ description: '说说Tid' }),
|
||||
}, { description: '发说说结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class SendQzoneMsg extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SendQzoneMsg;
|
||||
override actionSummary = '发表QQ空间说说';
|
||||
override actionDescription = '在QQ空间(Qzone)发表说说, 支持纯文字或带(多)图, 可设置查看权限';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = ExtendsActionsExamples.SendQzoneMsg.payload;
|
||||
override returnExample = ExtendsActionsExamples.SendQzoneMsg.response;
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||
const ugcRight = payload.ugc_right !== undefined ? +payload.ugc_right : 1;
|
||||
if (!ValidUgcRights.includes(ugcRight as typeof ValidUgcRights[number])) {
|
||||
throw new Error(`ugc_right 参数不合法, 允许的值为: ${ValidUgcRights.join(', ')}`);
|
||||
}
|
||||
const targetUins = payload.target_uins?.map((uin) => +uin).filter((uin) => !Number.isNaN(uin));
|
||||
if ((ugcRight === 16 || ugcRight === 128) && (!targetUins || targetUins.length === 0)) {
|
||||
throw new Error('ugc_right 为 16 或 128 时, target_uins 不能为空');
|
||||
}
|
||||
|
||||
const richvals: string[] = [];
|
||||
const cleanupPaths: string[] = [];
|
||||
try {
|
||||
for (const image of payload.images ?? []) {
|
||||
const downloadResult = await uriToLocalFile(this.core.SnowCatTempPath, image);
|
||||
if (!downloadResult.success) {
|
||||
throw new Error(`图片${image}处理失败, ${downloadResult.errMsg}`);
|
||||
}
|
||||
if (!downloadResult.isLocal) {
|
||||
cleanupPaths.push(downloadResult.path);
|
||||
}
|
||||
const base64 = readFileSync(downloadResult.path).toString('base64');
|
||||
const richval = await this.core.apis.WebApi.uploadImageToQzone(base64);
|
||||
richvals.push(richval);
|
||||
}
|
||||
|
||||
const result = await this.core.apis.WebApi.publishQzoneMsg(payload.content, richvals, ugcRight, targetUins);
|
||||
return result;
|
||||
} finally {
|
||||
for (const p of cleanupPaths) {
|
||||
if (existsSync(p)) {
|
||||
await unlink(p).catch(() => { });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
face_id: Type.Union([Type.Number(), Type.String()], { description: '图标ID' }), // 参考 face_config.json 的 QSid
|
||||
face_type: Type.Union([Type.Number(), Type.String()], { default: '1', description: '图标类型' }),
|
||||
wording: Type.String({ default: ' ', description: '状态文字内容' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.String({ description: '错误信息(如果有)' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class SetDiyOnlineStatus extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SetDiyOnlineStatus;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema; override actionSummary = '设置自定义在线状态';
|
||||
override actionDescription = '设置自定义在线状态';
|
||||
override actionTags = ['用户扩展'];
|
||||
override payloadExample = {
|
||||
face_id: '123',
|
||||
face_type: '1',
|
||||
wording: '自定义状态',
|
||||
};
|
||||
|
||||
override returnExample = '';
|
||||
async _handle (payload: PayloadType) {
|
||||
const ret = await this.core.apis.UserApi.setDiySelfOnlineStatus(
|
||||
payload.face_id.toString(),
|
||||
payload.wording,
|
||||
payload.face_type.toString()
|
||||
);
|
||||
if (ret.result !== 0) {
|
||||
throw new Error('设置在线状态失败');
|
||||
}
|
||||
return ret.errMsg;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
add_type: Type.Number({ description: '加群方式' }),
|
||||
group_question: Type.Optional(Type.String({ description: '加群问题' })),
|
||||
group_answer: Type.Optional(Type.String({ description: '加群答案' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Null({ description: '返回结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export default class SetGroupAddOption extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SetGroupAddOption;
|
||||
override actionSummary = '设置群加群选项';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
add_type: 1,
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||
const ret = await this.core.apis.GroupApi.setGroupAddOption(payload.group_id, {
|
||||
addOption: payload.add_type,
|
||||
groupQuestion: payload.group_question,
|
||||
groupAnswer: payload.group_answer,
|
||||
});
|
||||
if (ret.result !== 0) {
|
||||
throw new Error(`设置群添加选项失败, ${ret.result}:${ret.errMsg}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
album_id: Type.String({ description: '相册ID' }),
|
||||
batch_id: Type.String({ description: 'batch_id' }),
|
||||
lloc: Type.Optional(Type.String({ description: 'lloc,若对整个上传操作则不填' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '操作结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class SetGroupAlbumMediaLike extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SetGroupAlbumMediaLike;
|
||||
override actionSummary = '点赞群相册媒体';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
album_id: 'album_id_123',
|
||||
batch_id: '112233',
|
||||
lloc: 'aabbcc12213123',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
result: {},
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return await this.core.apis.WebApi.doAlbumMediaLikeByNTQQ(
|
||||
payload.group_id,
|
||||
payload.album_id,
|
||||
payload.batch_id,
|
||||
payload.lloc,
|
||||
true // isLike = true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class CancelGroupAlbumMediaLike extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.CancelGroupAlbumMediaLike; // 注意:需要在 router/index.ts 的 ActionName 枚举中补充该定义
|
||||
override actionSummary = '取消点赞群相册媒体';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
album_id: 'album_id',
|
||||
batch_id: '112233',
|
||||
lloc: 'aabbcc',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
result: {},
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return await this.core.apis.WebApi.doAlbumMediaLikeByNTQQ(
|
||||
payload.group_id,
|
||||
payload.album_id,
|
||||
payload.batch_id,
|
||||
payload.lloc,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
user_id: Type.Array(Type.String(), { description: 'QQ号列表' }),
|
||||
reject_add_request: Type.Optional(Type.Union([Type.Boolean(), Type.String()], { description: '是否拒绝加群请求' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Null({ description: '返回结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export default class SetGroupKickMembers extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SetGroupKickMembers;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '批量踢出群成员';
|
||||
override actionDescription = '从指定群聊中批量踢出多个成员';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = ExtendsActionsExamples.SetGroupKickMembers.payload;
|
||||
override returnExample = ExtendsActionsExamples.SetGroupKickMembers.response;
|
||||
|
||||
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||
const rejectReq = payload.reject_add_request?.toString() === 'true';
|
||||
const uids: string[] = await Promise.all(payload.user_id.map(async uin => await this.core.apis.UserApi.getUidByUinV2(uin)));
|
||||
await this.core.apis.GroupApi.kickMember(payload.group_id.toString(), uids.filter(uid => !!uid), rejectReq);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
remark: Type.String({ description: '备注' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Null({ description: '返回结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export default class SetGroupRemark extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SetGroupRemark;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '设置群备注';
|
||||
override actionDescription = '设置群备注';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
remark: '测试群备注',
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||
const ret = await this.core.apis.GroupApi.setGroupRemark(payload.group_id, payload.remark);
|
||||
if (ret.result !== 0) {
|
||||
throw new Error(`设置群备注失败, ${ret.result}:${ret.errMsg}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
robot_member_switch: Type.Optional(Type.Number({ description: '机器人成员开关' })),
|
||||
robot_member_examine: Type.Optional(Type.Number({ description: '机器人成员审核' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Null({ description: '返回结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export default class SetGroupRobotAddOption extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SetGroupRobotAddOption;
|
||||
override actionSummary = '设置群机器人加群选项';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||
const ret = await this.core.apis.GroupApi.setGroupRobotAddOption(
|
||||
payload.group_id,
|
||||
payload.robot_member_switch,
|
||||
payload.robot_member_examine
|
||||
);
|
||||
if (ret.result !== 0) {
|
||||
throw new Error(`设置群机器人添加选项失败, ${ret.result}:${ret.errMsg}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
no_code_finger_open: Type.Optional(Type.Number({ description: '未知' })),
|
||||
no_finger_open: Type.Optional(Type.Number({ description: '未知' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Null({ description: '返回结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export default class SetGroupSearch extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SetGroupSearch;
|
||||
override actionSummary = '设置群搜索选项';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||
const ret = await this.core.apis.GroupApi.setGroupSearch(payload.group_id, {
|
||||
noCodeFingerOpenFlag: payload.no_code_finger_open,
|
||||
noFingerOpenFlag: payload.no_finger_open,
|
||||
});
|
||||
if (ret.result !== 0) {
|
||||
throw new Error(`设置群搜索失败, ${ret.result}:${ret.errMsg}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Void({ description: '打卡结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
class SetGroupSignBase extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '群打卡';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456789',
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return await this.core.apis.PacketApi.pkt.operation.GroupSign(+payload.group_id);
|
||||
}
|
||||
}
|
||||
|
||||
export class SetGroupSign extends SetGroupSignBase {
|
||||
override actionName = ActionName.SetGroupSign;
|
||||
}
|
||||
|
||||
export class SendGroupSign extends SetGroupSignBase {
|
||||
override actionName = ActionName.SendGroupSign;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { ChatType } from 'snowcat-core';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
user_id: Type.String({ description: 'QQ号' }),
|
||||
event_type: Type.Number({ description: '事件类型' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '设置结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class SetInputStatus extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SetInputStatus;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '设置输入状态';
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {
|
||||
user_id: '123456789',
|
||||
event_type: 1,
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!uid) throw new Error('uid is empty');
|
||||
const peer = {
|
||||
chatType: ChatType.KCHATTYPEC2C,
|
||||
peerUid: uid,
|
||||
};
|
||||
return await this.core.apis.MsgApi.sendShowInputStatusReq(peer, payload.event_type);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
longNick: Type.String({ description: '签名内容' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '设置结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class SetLongNick extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SetLongNick;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '设置个性签名';
|
||||
override actionDescription = '修改当前登录帐号的个性签名';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = ExtendsActionsExamples.SetLongNick.payload;
|
||||
override returnExample = ExtendsActionsExamples.SetLongNick.response;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return await this.core.apis.UserApi.setLongNick(payload.longNick);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
status: Type.Union([Type.Number(), Type.String()], { description: '在线状态' }),
|
||||
ext_status: Type.Union([Type.Number(), Type.String()], { description: '扩展状态' }),
|
||||
battery_status: Type.Union([Type.Number(), Type.String()], { description: '电量状态' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Null({ description: '设置结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class SetOnlineStatus extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SetOnlineStatus;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '设置在线状态';
|
||||
override actionDescription = statusText;
|
||||
override actionTags = ['系统扩展'];
|
||||
override payloadExample = {
|
||||
status: 11,
|
||||
ext_status: 0,
|
||||
battery_status: 100,
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const ret = await this.core.apis.UserApi.setSelfOnlineStatus(
|
||||
+payload.status,
|
||||
+payload.ext_status,
|
||||
+payload.battery_status
|
||||
);
|
||||
if (ret.result !== 0) {
|
||||
throw new Error('设置在线状态失败');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const statusText = `
|
||||
## 状态列表
|
||||
|
||||
### 在线
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 0, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### Q我吧
|
||||
\`\`\`json5;
|
||||
{ "status": 60, "ext_status": 0, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 离开
|
||||
\`\`\`json5;
|
||||
{ "status": 30, "ext_status": 0, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 忙碌
|
||||
\`\`\`json5;
|
||||
{ "status": 50, "ext_status": 0, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 请勿打扰
|
||||
\`\`\`json5;
|
||||
{ "status": 70, "ext_status": 0, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 隐身
|
||||
\`\`\`json5;
|
||||
{ "status": 40, "ext_status": 0, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 听歌中
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1028, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 春日限定
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 2037, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 一起元梦
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 2025, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 求星搭子
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 2026, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 被掏空
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 2014, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 今日天气
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1030, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 我crash了
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 2019, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 爱你
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 2006, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 恋爱中
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1051, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 好运锦鲤
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1071, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 水逆退散
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1201, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 嗨到飞起
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1056, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 元气满满
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1058, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 宝宝认证
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1070, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 一言难尽
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1063, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 难得糊涂
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 2001, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### emo中
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1401, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 我太难了
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1062, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 我想开了
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 2013, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 我没事
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1052, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 想静静
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1061, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 悠哉哉
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1059, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 去旅行
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 2015, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 信号弱
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1011, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 出去浪
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 2003, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 肝作业
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 2012, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 学习中
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1018, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 搬砖中
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 2023, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 摸鱼中
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1300, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 无聊中
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1060, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### timi中
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1027, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 睡觉中
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1016, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 熬夜中
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1032, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 追剧中
|
||||
\`\`\`json5;
|
||||
{ "status": 10, "ext_status": 1021, "battery_status": 0; }
|
||||
\`\`\`
|
||||
|
||||
### 我的电量
|
||||
\`\`\`json5;
|
||||
{
|
||||
"status": 10,
|
||||
"ext_status": 1000,
|
||||
"battery_status": 0;
|
||||
}
|
||||
\`\`\`
|
||||
`;
|
||||
@@ -0,0 +1,52 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import fs from 'node:fs/promises';
|
||||
import { checkFileExist, uriToLocalFile } from 'snowcat-common/src/file';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
file: Type.String({ description: '图片路径、URL或Base64' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Null({ description: '设置结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export default class SetAvatar extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SetQQAvatar;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '设置QQ头像';
|
||||
override actionDescription = '修改当前账号的QQ头像';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = ExtendsActionsExamples.SetQQAvatar.payload;
|
||||
override returnExample = ExtendsActionsExamples.SetQQAvatar.response;
|
||||
|
||||
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||
const { path, success, isLocal } = (await uriToLocalFile(this.core.SnowCatTempPath, payload.file));
|
||||
if (!success) {
|
||||
throw new Error(`头像${payload.file}设置失败,file字段可能格式不正确`);
|
||||
}
|
||||
if (path) {
|
||||
await checkFileExist(path, 5000);// 避免崩溃
|
||||
const ret = await this.core.apis.UserApi.setQQAvatar(path);
|
||||
if (!isLocal) fs.unlink(path).catch(() => { });
|
||||
if (!ret) {
|
||||
throw new Error(`头像${payload.file}设置失败,api无返回`);
|
||||
}
|
||||
// log(`头像设置返回:${JSON.stringify(ret)}`)
|
||||
if (Number(ret.result) === 1004022) {
|
||||
throw new Error(`头像${payload.file}设置失败,文件可能不是图片格式`);
|
||||
} else if (ret.result !== 0) {
|
||||
throw new Error(`头像${payload.file}设置失败,未知的错误,${ret.result}:${ret.errMsg}`);
|
||||
}
|
||||
} else {
|
||||
throw new Error(`头像${payload.file}设置失败,无法获取头像,文件可能不存在`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
user_id: Type.String({ description: 'QQ号' }),
|
||||
special_title: Type.String({ default: '', description: '专属头衔' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Void({ description: '设置结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class SetSpecialTitle extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.SetSpecialTitle;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '设置专属头衔';
|
||||
override actionDescription = '设置群聊中指定成员的专属头衔';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = ExtendsActionsExamples.SetSpecialTitle.payload;
|
||||
override returnExample = ExtendsActionsExamples.SetSpecialTitle.response;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!uid) throw new Error('User not found');
|
||||
await this.core.apis.PacketApi.pkt.operation.SetGroupSpecialTitle(+payload.group_id, uid, payload.special_title);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
user_id: Type.Optional(Type.String({ description: 'QQ号' })),
|
||||
group_id: Type.Optional(Type.String({ description: '群号' })),
|
||||
phone_number: Type.String({ default: '', description: '手机号' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '分享结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class SharePeerBase extends OneBotAction<PayloadType, ReturnType> {
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '分享用户 (Ark)';
|
||||
override actionDescription = '获取用户推荐的 Ark 内容';
|
||||
override actionTags = ['消息扩展'];
|
||||
override payloadExample = {
|
||||
user_id: '123456',
|
||||
phone_number: '',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
ark: '...',
|
||||
};
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
if (payload.group_id) {
|
||||
return await this.core.apis.GroupApi.getGroupRecommendContactArkJson(payload.group_id.toString());
|
||||
} else if (payload.user_id) {
|
||||
return await this.core.apis.UserApi.getBuddyRecommendContactArkJson(payload.user_id.toString(), payload.phone_number);
|
||||
}
|
||||
throw new Error('group_id or user_id is required');
|
||||
}
|
||||
}
|
||||
|
||||
const PayloadSchemaGroupEx = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
});
|
||||
export class SharePeer extends SharePeerBase {
|
||||
override actionName = ActionName.SharePeer;
|
||||
}
|
||||
type PayloadTypeGroupEx = Static<typeof PayloadSchemaGroupEx>;
|
||||
|
||||
const ReturnSchemaGroupEx = Type.String({ description: 'Ark Json内容' });
|
||||
|
||||
type ReturnTypeGroupEx = Static<typeof ReturnSchemaGroupEx>;
|
||||
|
||||
export class ShareGroupExBase extends OneBotAction<PayloadTypeGroupEx, ReturnTypeGroupEx> {
|
||||
override payloadSchema = PayloadSchemaGroupEx;
|
||||
override returnSchema = ReturnSchemaGroupEx;
|
||||
override actionSummary = '分享群 (Ark)';
|
||||
override actionDescription = '获取群分享的 Ark 内容';
|
||||
override actionTags = ['消息扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
};
|
||||
|
||||
override returnExample = '{"app": "com.tencent.structmsg", ...}';
|
||||
|
||||
async _handle (payload: PayloadTypeGroupEx) {
|
||||
return await this.core.apis.GroupApi.getArkJsonGroupShare(payload.group_id.toString());
|
||||
}
|
||||
}
|
||||
export class ShareGroupEx extends ShareGroupExBase {
|
||||
override actionName = ActionName.ShareGroupEx;
|
||||
}
|
||||
export class SendGroupArkShare extends ShareGroupExBase {
|
||||
override actionName = ActionName.SendGroupArkShare;
|
||||
}
|
||||
|
||||
export class SendArkShare extends SharePeerBase {
|
||||
override actionName = ActionName.SendArkShare;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { FileNapCatOneBotUUID } from 'snowcat-common/src/file-uuid';
|
||||
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
file_id: Type.String({ description: '文件ID' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
ok: Type.Boolean({ description: '是否成功' }),
|
||||
}, { description: '转发文件结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class TransGroupFile extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.TransGroupFile;
|
||||
override actionSummary = '传输群文件';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
file_id: '/file_id',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
ok: true,
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||
if (contextMsgFile?.fileUUID) {
|
||||
const result = await this.core.apis.GroupApi.transGroupFile(payload.group_id.toString(), contextMsgFile.fileUUID);
|
||||
if (result.transGroupFileResult.result.retCode === 0) {
|
||||
return {
|
||||
ok: true,
|
||||
};
|
||||
}
|
||||
throw new Error(result.transGroupFileResult.result.retMsg);
|
||||
}
|
||||
throw new Error('real fileUUID not found!');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
import { ExtendsActionsExamples } from '../example/ExtendsActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
words: Type.Array(Type.String(), { description: '待翻译单词列表' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
words: Type.Array(Type.String(), { description: '翻译结果列表' }),
|
||||
}, { description: '翻译结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class TranslateEnWordToZn extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.TranslateEnWordToZn;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '英文单词翻译';
|
||||
override actionDescription = '将英文单词列表翻译为中文';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = ExtendsActionsExamples.TranslateEnWordToZn.payload;
|
||||
override returnExample = ExtendsActionsExamples.TranslateEnWordToZn.response;
|
||||
|
||||
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||
const ret = await this.core.apis.SystemApi.translateEnWordToZn(payload.words);
|
||||
if (ret.result !== 0) {
|
||||
throw new Error('翻译失败');
|
||||
}
|
||||
return {
|
||||
words: ret.words,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { uriToLocalFile } from 'snowcat-common/src/file';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { unlink } from 'node:fs/promises';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
album_id: Type.String({ description: '相册ID' }),
|
||||
album_name: Type.String({ description: '相册名称' }),
|
||||
file: Type.String({ description: '图片路径、URL或Base64' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '上传结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class UploadImageToQunAlbum extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.UploadImageToQunAlbum;
|
||||
override actionSummary = '上传图片到群相册';
|
||||
override actionTags = ['群组扩展'];
|
||||
override payloadExample = {
|
||||
group_id: '123456',
|
||||
album_id: 'album_id_1',
|
||||
album_name: '相册1',
|
||||
file: '/path/to/image.jpg',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
result: null,
|
||||
};
|
||||
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const downloadResult = await uriToLocalFile(this.core.SnowCatTempPath, payload.file);
|
||||
try {
|
||||
return await this.core.apis.WebApi.uploadImageToQunAlbum(payload.group_id, payload.album_id, payload.album_name, downloadResult.path);
|
||||
} finally {
|
||||
if (!downloadResult.isLocal && downloadResult.path && existsSync(downloadResult.path)) {
|
||||
await unlink(downloadResult.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
host_uin: Type.String({ description: '说说主人的QQ号' }),
|
||||
tid: Type.String({ description: '说说tid' }),
|
||||
content: Type.String({ description: '评论正文' }),
|
||||
richval: Type.Optional(Type.String({ description: '图片评论时传图片直链(与发说说的richval串不同)' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
export class CommentQzoneMsg extends OneBotAction<PayloadType, unknown> {
|
||||
override actionName = ActionName.CommentQzoneMsg;
|
||||
override actionSummary = '评论说说';
|
||||
override actionDescription = '给一条说说发评论(emotion_cgi_re_feeds)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { host_uin: '123456789', tid: 'abc123', content: '说得好' };
|
||||
override returnExample = { comment_id: '999' };
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = Type.Object({ comment_id: Type.String() });
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return this.core.apis.WebApi.commentQzoneMsg(
|
||||
payload.host_uin,
|
||||
payload.tid,
|
||||
payload.content,
|
||||
payload.richval,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
page_num: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 1, description: '页码(无cursor时生效)' })),
|
||||
count: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 10, description: '每页条数(1-50)' })),
|
||||
cursor: Type.Optional(Type.String({ description: '上次返回的 next_cursor,续翻用' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
export class GetQzoneFeeds extends OneBotAction<PayloadType, unknown> {
|
||||
override actionName = ActionName.GetQzoneFeeds;
|
||||
override actionSummary = '获取空间好友动态流';
|
||||
override actionDescription = '读取QQ空间好友动态(HTML载荷数据级解析,不执行远程内容);cursor 续翻';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { page_num: 1, count: 10 };
|
||||
override returnExample = {
|
||||
feeds: [{ uin: 123456789, nickname: '张三', tid: 'abc', content: '动态正文', images: [], like_count: 5, comment_count: 2, appid: 311, key: 'k', time: 1760000000, html: '', content_complete: true }],
|
||||
has_more: true,
|
||||
next_cursor: 'eyJ...',
|
||||
};
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = Type.Object({
|
||||
feeds: Type.Array(Type.Any()),
|
||||
has_more: Type.Boolean(),
|
||||
next_cursor: Type.Union([Type.String(), Type.Null()]),
|
||||
});
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return this.core.apis.WebApi.getQzoneFeeds(
|
||||
payload.page_num !== undefined ? +payload.page_num : 1,
|
||||
payload.count !== undefined ? +payload.count : 10,
|
||||
payload.cursor,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
target_uin: Type.String({ description: '说说主人的QQ号' }),
|
||||
tid: Type.String({ description: '说说tid' }),
|
||||
pos: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 0, description: '评论起始偏移' })),
|
||||
num: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 20, description: '评论每页数量' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
export class GetQzoneMsgDetail extends OneBotAction<PayloadType, unknown> {
|
||||
override actionName = ActionName.GetQzoneMsgDetail;
|
||||
override actionSummary = '获取说说详情';
|
||||
override actionDescription = '读取一条说说正文与一页评论(含内联回复)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { target_uin: '123456789', tid: 'abc123' };
|
||||
override returnExample = {
|
||||
tid: 'abc123',
|
||||
content: '正文',
|
||||
comment_num: 2,
|
||||
comments: [],
|
||||
};
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = Type.Object({
|
||||
tid: Type.String(),
|
||||
content: Type.String(),
|
||||
comment_num: Type.Number(),
|
||||
comments: Type.Array(Type.Any()),
|
||||
});
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return this.core.apis.WebApi.getQzoneMsgDetail(
|
||||
payload.target_uin,
|
||||
payload.tid,
|
||||
payload.pos !== undefined ? +payload.pos : 0,
|
||||
payload.num !== undefined ? +payload.num : 20,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
// SnowCat · qzone Web 线动作组(SnowLuma 移植)
|
||||
// 全部走 core.apis.WebApi 的 cookie 管道(getQzoneAuth),无需本文件再做凭证处理。
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
target_uin: Type.Optional(Type.String({ description: '目标QQ号,省略=机器人自己' })),
|
||||
pos: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 0, description: '起始偏移' })),
|
||||
num: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 20, description: '每页数量' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
export class GetQzoneMsgList extends OneBotAction<PayloadType, unknown> {
|
||||
override actionName = ActionName.GetQzoneMsgList;
|
||||
override actionSummary = '获取说说列表';
|
||||
override actionDescription = '获取QQ空间说说列表(默认自己空间,可查有权限的他人空间);-10000限频自动切备路';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { target_uin: '123456789', pos: 0, num: 20 };
|
||||
override returnExample = {
|
||||
total: 100,
|
||||
msglist: [{
|
||||
tid: 'abc123',
|
||||
content: '今天天气不错',
|
||||
time: 1760000000,
|
||||
comment_num: 2,
|
||||
is_private: false,
|
||||
images: ['https://...'],
|
||||
}],
|
||||
};
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = Type.Object({
|
||||
total: Type.Number(),
|
||||
msglist: Type.Array(Type.Object({
|
||||
tid: Type.String(),
|
||||
content: Type.String(),
|
||||
time: Type.Number(),
|
||||
comment_num: Type.Number(),
|
||||
is_private: Type.Boolean(),
|
||||
images: Type.Array(Type.String()),
|
||||
})),
|
||||
});
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return this.core.apis.WebApi.getQzoneMsgList(
|
||||
payload.target_uin,
|
||||
payload.pos !== undefined ? +payload.pos : 0,
|
||||
payload.num !== undefined ? +payload.num : 20,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
target_uin: Type.Optional(Type.String({ description: '目标QQ号,省略=机器人自己' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
export class GetQzoneProfile extends OneBotAction<PayloadType, unknown> {
|
||||
override actionName = ActionName.GetQzoneProfile;
|
||||
override actionSummary = '获取空间名称和说明';
|
||||
override actionDescription = '获取QQ空间名称和说明(默认当前账号,可查有权限的他人空间)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { target_uin: '123456789' };
|
||||
override returnExample = { name: '我的空间', description: '记录日常' };
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = Type.Object({
|
||||
name: Type.String(),
|
||||
description: Type.String(),
|
||||
});
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return this.core.apis.WebApi.getQzoneProfile(payload.target_uin);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
host_uin: Type.String({ description: '说说主人的QQ号' }),
|
||||
tid: Type.String({ description: '说说tid' }),
|
||||
content: Type.String({ description: '回复正文' }),
|
||||
comment_id: Type.String({ description: '父评论id(顶层评论,回复楼中楼也传顶层)' }),
|
||||
comment_uin: Type.String({ description: '父评论作者QQ号' }),
|
||||
reply_to_uin: Type.Optional(Type.String({ description: '楼中楼点名作者的QQ号' })),
|
||||
reply_to_name: Type.Optional(Type.String({ description: '楼中楼点名作者的昵称' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
export class ReplyQzoneComment extends OneBotAction<PayloadType, unknown> {
|
||||
override actionName = ActionName.ReplyQzoneComment;
|
||||
override actionSummary = '回复说说评论';
|
||||
override actionDescription = '在说说的评论线程里回复(独立端点;点名作者自动转@提及)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { host_uin: '123456789', tid: 'abc123', content: '同意', comment_id: '999', comment_uin: '987654321' };
|
||||
override returnExample = { comment_id: '1001' };
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = Type.Object({ comment_id: Type.String() });
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const replyTo = payload.reply_to_uin && payload.reply_to_name
|
||||
? { uin: payload.reply_to_uin, name: payload.reply_to_name }
|
||||
: undefined;
|
||||
return this.core.apis.WebApi.replyQzoneComment(
|
||||
payload.host_uin,
|
||||
payload.tid,
|
||||
payload.content,
|
||||
{
|
||||
commentId: payload.comment_id,
|
||||
commentUin: payload.comment_uin,
|
||||
...(replyTo ? { replyTo } : {}),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
// SnowCat 富返回版发说说:与官方风格 send_qzone_msg 并存,多返回 time。
|
||||
|
||||
const ValidRights = [1, 4, 16, 64, 128] as const;
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
content: Type.String({ description: '说说正文' }),
|
||||
richvals: Type.Optional(Type.Array(Type.String(), { description: 'richval 数组(来自 uploadImageToQzone 或 get_qzone_msg_list 的图片机制),多图 \t 自动拼接' })),
|
||||
ugc_right: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 1, description: '查看权限: 1所有人 4好友 16部分好友 64仅自己 128部分好友不可见' })),
|
||||
target_uins: Type.Optional(Type.Array(Type.String(), { description: 'ugc_right为16/128时权限作用的QQ号数组' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
export class SendQzoneMsgFull extends OneBotAction<PayloadType, unknown> {
|
||||
override actionName = ActionName.SendQzoneMsgFull;
|
||||
override actionSummary = '发表说说(富返回)';
|
||||
override actionDescription = '发表QQ空间说说,返回 tid + 发布时间(send_qzone_msg 的富参版)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { content: '今天天气不错' };
|
||||
override returnExample = { tid: 'abc123', time: 1760000000 };
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = Type.Object({
|
||||
tid: Type.String(),
|
||||
time: Type.Number(),
|
||||
});
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const ugcRight = payload.ugc_right !== undefined ? +payload.ugc_right : 1;
|
||||
if (!ValidRights.includes(ugcRight as typeof ValidRights[number])) {
|
||||
throw new Error(`ugc_right 参数不合法, 允许的值为: ${ValidRights.join(', ')}`);
|
||||
}
|
||||
if ((ugcRight === 16 || ugcRight === 128) && (!payload.target_uins || payload.target_uins.length === 0)) {
|
||||
throw new Error('ugc_right 为 16 或 128 时, target_uins 不能为空');
|
||||
}
|
||||
return this.core.apis.WebApi.publishQzoneMsgFull(
|
||||
payload.content,
|
||||
payload.richvals ?? [],
|
||||
ugcRight,
|
||||
payload.target_uins,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
target_uin: Type.String({ description: '目标QQ号' }),
|
||||
ban: Type.Boolean({ default: true, description: 'true=拉黑 false=解拉黑' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
export class SetQzoneBlack extends OneBotAction<PayloadType, null> {
|
||||
override actionName = ActionName.SetQzoneBlack;
|
||||
override actionSummary = '空间拉黑/解拉黑';
|
||||
override actionDescription = '把一个用户加入/移出机器人QQ空间的黑名单';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { target_uin: '123456789', ban: true };
|
||||
override returnExample = null;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = Type.Null();
|
||||
|
||||
async _handle (payload: PayloadType): Promise<null> {
|
||||
await this.core.apis.WebApi.setQzoneBlack(payload.target_uin, payload.ban);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
target_uin: Type.String({ description: '说说主人的QQ号' }),
|
||||
tid: Type.String({ description: '说说tid' }),
|
||||
like: Type.Boolean({ default: true, description: 'true=点赞 false=取消赞' }),
|
||||
abstime: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 0, description: '目标帖发帖时间(unix秒),传真实值更稳' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
export class SetQzoneLike extends OneBotAction<PayloadType, null> {
|
||||
override actionName = ActionName.SetQzoneLike;
|
||||
override actionSummary = '说说点赞/取消赞';
|
||||
override actionDescription = '对一条说说点赞或取消赞(appid 311 mood)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { target_uin: '123456789', tid: 'abc123', like: true };
|
||||
override returnExample = null;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = Type.Null();
|
||||
|
||||
async _handle (payload: PayloadType): Promise<null> {
|
||||
await this.core.apis.WebApi.setQzoneLike(
|
||||
payload.target_uin,
|
||||
payload.tid,
|
||||
payload.like,
|
||||
payload.abstime !== undefined ? +payload.abstime : 0,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const ValidRights = [1, 4, 16, 64, 128] as const;
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
tid: Type.String({ description: '说说tid(自己的说说)' }),
|
||||
ugc_right: Type.Union([Type.Number(), Type.String()], { description: '查看权限: 1所有人 4好友 16部分好友 64仅自己 128部分好友不可见' }),
|
||||
target_uins: Type.Optional(Type.Array(Type.String(), { description: 'ugc_right为16/128时权限作用的QQ号数组' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
export class SetQzoneMsgRight extends OneBotAction<PayloadType, unknown> {
|
||||
override actionName = ActionName.SetQzoneMsgRight;
|
||||
override actionSummary = '修改说说查看权限';
|
||||
override actionDescription = '改一条已有说说的查看权限(内部走 msgdetail→update 两步全量重提)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { tid: 'abc123', ugc_right: 4 };
|
||||
override returnExample = { ugc_right: 4 };
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = Type.Object({ ugc_right: Type.Number() });
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const right = +payload.ugc_right;
|
||||
if (!ValidRights.includes(right as typeof ValidRights[number])) {
|
||||
throw new Error(`ugc_right 参数不合法, 允许的值为: ${ValidRights.join(', ')}`);
|
||||
}
|
||||
if ((right === 16 || right === 128) && (!payload.target_uins || payload.target_uins.length === 0)) {
|
||||
throw new Error('ugc_right 为 16 或 128 时, target_uins 不能为空');
|
||||
}
|
||||
return this.core.apis.WebApi.setQzoneMsgRight(payload.tid, right, payload.target_uins);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
name: Type.Optional(Type.String({ description: '空间名称,省略=不动' })),
|
||||
description: Type.Optional(Type.String({ description: '空间说明,省略=不动;空串=清空' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
export class SetQzoneProfile extends OneBotAction<PayloadType, null> {
|
||||
override actionName = ActionName.SetQzoneProfile;
|
||||
override actionSummary = '修改空间名称和说明';
|
||||
override actionDescription = '修改当前账号的空间名称/说明,只动传了的字段,空串=清空';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { name: '我的空间', description: '记录日常' };
|
||||
override returnExample = null;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = Type.Null();
|
||||
|
||||
async _handle (payload: PayloadType): Promise<null> {
|
||||
await this.core.apis.WebApi.setQzoneProfile({
|
||||
...(payload.name !== undefined ? { name: payload.name } : {}),
|
||||
...(payload.description !== undefined ? { description: payload.description } : {}),
|
||||
});
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* SnowCat · 第三批动作组(SnowLuma 移植):群相册补/群管补/自定义表情/杂项
|
||||
*
|
||||
* 底层取舍(对账定谳 2026-11-11):
|
||||
* - 群相册 get_group_album_list:走 NapCat 现役 Web 线 getAlbumList(qun_list_album_v2,已实装)
|
||||
* - 群相册 Web 线媒体列表:getAlbumMediaListByNTQQ(NT AlbumService,已实装)
|
||||
* - get_msg_emoji_likes:NT getMsgEmojiLikesList(已实装,补 OB11 动作壳)
|
||||
* - get_friend_dress:NT getUserDetailInfo(已实装,补动作壳)
|
||||
* - fetch_sys_faces:NapCat 无目录表,返回空集占位(SnowLuma 侧是协议自编目录,待后续独立移植)
|
||||
* - get_group_todo_list/get_group_tags/set_group_reaction 等 OIDB 线:NapCat PacketApi 只透传裸包
|
||||
* (send_packet 动作已在),SDK 级 OIDB 编解码层为后续独立批次,此批不重复造。
|
||||
*/
|
||||
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
// ─────────────── 群相册补 ───────────────
|
||||
|
||||
const AlbumListPayload = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()], { description: '群号' }),
|
||||
});
|
||||
export class GetGroupAlbumList extends OneBotAction<Static<typeof AlbumListPayload>, unknown> {
|
||||
override actionName = ActionName.GetGroupAlbumList;
|
||||
override actionSummary = '获取群相册列表';
|
||||
override actionDescription = '获取群相册列表(qzone Web 线 qun_list_album_v2)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { group_id: 123456789 };
|
||||
override returnExample = [{ id: '1', title: '相册一' }];
|
||||
override payloadSchema = AlbumListPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof AlbumListPayload>) {
|
||||
return this.core.apis.WebApi.getAlbumList(String(payload.group_id));
|
||||
}
|
||||
}
|
||||
|
||||
const AlbumWebMediaPayload = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()], { description: '群号' }),
|
||||
album_id: Type.String({ description: '相册id(来自 get_group_album_list)' }),
|
||||
});
|
||||
export class GetGroupAlbumMediaListWeb extends OneBotAction<Static<typeof AlbumWebMediaPayload>, unknown> {
|
||||
override actionName = ActionName.GetGroupAlbumMediaListOnly;
|
||||
override actionSummary = '获取群相册媒体列表(Web线)';
|
||||
override actionDescription = '经 NT AlbumService 读取群相册媒体列表(与 get_group_album_media_list 分页线并存)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { group_id: 123456789, album_id: '1' };
|
||||
override returnExample = [];
|
||||
override payloadSchema = AlbumWebMediaPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof AlbumWebMediaPayload>) {
|
||||
return this.core.apis.WebApi.getAlbumMediaListByNTQQ(String(payload.group_id), payload.album_id);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────── 群管补 ───────────────
|
||||
|
||||
const MsgEmojiLikesPayload = Type.Object({
|
||||
group_id: Type.Union([Type.Number(), Type.String()], { description: '群号' }),
|
||||
message_seq: Type.Union([Type.Number(), Type.String()], { description: '消息 seq' }),
|
||||
emoji_id: Type.String({ description: '表情id' }),
|
||||
emoji_type: Type.Optional(Type.String({ description: '表情类型(1-2,省略按 id 长度推断)' })),
|
||||
cookie: Type.Optional(Type.String({ description: '翻页 cookie,来自上一页返回' })),
|
||||
count: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 20, description: '每页数量' })),
|
||||
});
|
||||
export class GetMsgEmojiLikes extends OneBotAction<Static<typeof MsgEmojiLikesPayload>, unknown> {
|
||||
override actionName = ActionName.GetMsgEmojiLikes;
|
||||
override actionSummary = '查询消息表情回应列表';
|
||||
override actionDescription = '查询一条群消息的表情回应(谁点了什么表情),可翻页';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { group_id: 123456789, message_seq: 12345, emoji_id: '128077' };
|
||||
override returnExample = { cookie: '', likes: [] };
|
||||
override payloadSchema = MsgEmojiLikesPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof MsgEmojiLikesPayload>) {
|
||||
const peer = {
|
||||
chatType: 1,
|
||||
peerUin: String(payload.group_id),
|
||||
peerUid: '',
|
||||
guildId: '',
|
||||
};
|
||||
return this.core.apis.MsgApi.getMsgEmojiLikesList(
|
||||
peer,
|
||||
String(payload.message_seq),
|
||||
payload.emoji_id,
|
||||
payload.emoji_type ?? (payload.emoji_id.length > 3 ? '2' : '1'),
|
||||
payload.cookie ?? '',
|
||||
payload.count !== undefined ? +payload.count : 20,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────── 自定义表情/装扮 ───────────────
|
||||
|
||||
const FriendDressPayload = Type.Object({
|
||||
user_id: Type.Optional(Type.String({ description: '目标QQ号,省略=自己' })),
|
||||
});
|
||||
export class GetFriendDress extends OneBotAction<Static<typeof FriendDressPayload>, unknown> {
|
||||
override actionName = ActionName.GetFriendDress;
|
||||
override actionSummary = '获取用户装扮';
|
||||
override actionDescription = '获取好友/自己的装扮信息(挂件/气泡等,getUserDetailInfo)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { user_id: '123456789' };
|
||||
override returnExample = { dress: {} };
|
||||
override payloadSchema = FriendDressPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof FriendDressPayload>) {
|
||||
const uin = payload.user_id ?? this.core.selfInfo.uin;
|
||||
const uid = uin === this.core.selfInfo.uin
|
||||
? this.core.selfInfo.uid
|
||||
: await this.core.apis.UserApi.getUidByUinV2(uin);
|
||||
if (!uid) throw new Error('用户不存在');
|
||||
const info = await this.core.apis.UserApi.getUserDetailInfo(uid);
|
||||
return { dress: info as unknown as Record<string, unknown> };
|
||||
}
|
||||
}
|
||||
|
||||
const FetchSysFacesPayload = Type.Object({
|
||||
count: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 50, description: '取回数量' })),
|
||||
});
|
||||
export class FetchSysFaces extends OneBotAction<Static<typeof FetchSysFacesPayload>, unknown> {
|
||||
override actionName = ActionName.FetchSysFaces;
|
||||
override actionSummary = '获取系统表情目录';
|
||||
override actionDescription = '获取 QQ 系统表情目录(占位:协议自编目录待后续批次移植,当前返回空集)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { count: 50 };
|
||||
override returnExample = { items: [], note: 'sys face catalog 移植批次待排' };
|
||||
override payloadSchema = FetchSysFacesPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof FetchSysFacesPayload>) {
|
||||
void payload;
|
||||
return { items: [], note: 'sys face catalog 移植批次待排' };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* SnowCat · QQ空间公共空间(空友推荐)13 动作
|
||||
*
|
||||
* 底层:snowcat-core/apis/qzone-public-web.ts(SnowLuma qzone-public.ts 移植)
|
||||
* 动作约定:feed/comment/reply 等复杂对象由调用方从 get_qzone_public_feeds /
|
||||
* get_qzone_public_feed_detail 的返回中原样回传(透明通道,不做二次建模)。
|
||||
*/
|
||||
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const FeedObject = Type.Any({ description: '完整动态对象(来自 get_qzone_public_feeds/feed_detail,原样回传)' });
|
||||
const CommentObject = Type.Any({ description: '评论对象(原样回传)' });
|
||||
const ReplyObject = Type.Optional(Type.Any({ description: '回复对象(原样回传)' }));
|
||||
const UserObject = Type.Any({ description: '用户身份对象(详情/列表响应里的 user 形态)' });
|
||||
|
||||
/** 公共动作共用的 WebApi 快捷取凭证。 */
|
||||
function publicApi (action: OneBotAction<unknown, unknown>) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
return (action as any).core.apis.WebApi;
|
||||
}
|
||||
|
||||
// 1. get_qzone_public_capabilities —— 查询账号空友入口白名单
|
||||
const CapPayload = Type.Object({});
|
||||
export class GetQzonePublicCapabilities extends OneBotAction<Static<typeof CapPayload>, unknown> {
|
||||
override actionName = ActionName.GetQzonePublicCapabilities;
|
||||
override actionSummary = '查询空友入口能力';
|
||||
override actionDescription = '查询当前账号在 QQ 空间公共空间(空友)各入口的白名单能力';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = {};
|
||||
override returnExample = { entrance1: true, entrance15: false };
|
||||
override payloadSchema = CapPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle () {
|
||||
const api = publicApi(this);
|
||||
const { uin, g_tk, cookie } = await api.getQzoneAuth();
|
||||
return api.getQzonePublicCapabilities(cookie, g_tk, uin);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. get_qzone_public_feeds —— 空友推荐动态流
|
||||
const FeedsPayload = Type.Object({
|
||||
cursor: Type.Optional(Type.String({ description: '上次返回的 next_cursor,续翻用' })),
|
||||
});
|
||||
export class GetQzonePublicFeeds extends OneBotAction<Static<typeof FeedsPayload>, unknown> {
|
||||
override actionName = ActionName.GetQzonePublicFeeds;
|
||||
override actionSummary = '获取空友推荐动态';
|
||||
override actionDescription = '读取QQ空间公共空间(空友)推荐动态流,cursor 续翻';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = {};
|
||||
override returnExample = { feeds: [], has_more: false, next_cursor: null };
|
||||
override payloadSchema = FeedsPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof FeedsPayload>) {
|
||||
const { uin, g_tk, cookie } = await publicApi(this).getQzoneAuth();
|
||||
return publicApi(this).getQzonePublicFeeds(cookie, g_tk, uin, payload.cursor);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. get_qzone_public_feed_detail —— 空友单条动态详情
|
||||
const DetailPayload = Type.Object({ feed: FeedObject });
|
||||
export class GetQzonePublicFeedDetail extends OneBotAction<Static<typeof DetailPayload>, unknown> {
|
||||
override actionName = ActionName.GetQzonePublicFeedDetail;
|
||||
override actionSummary = '获取空友动态详情';
|
||||
override actionDescription = '获取一条空友推荐动态的完整详情(含评论首屏)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { feed: {} };
|
||||
override returnExample = {};
|
||||
override payloadSchema = DetailPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof DetailPayload>) {
|
||||
const { g_tk, cookie } = await publicApi(this).getQzoneAuth();
|
||||
return publicApi(this).getQzonePublicFeedDetail(cookie, g_tk, payload.feed);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. get_qzone_public_comments —— 空友评论分页
|
||||
const CommentsPayload = Type.Object({
|
||||
feed: FeedObject,
|
||||
cursor: Type.String({ description: '详情/上页返回的评论 attach_info' }),
|
||||
});
|
||||
export class GetQzonePublicComments extends OneBotAction<Static<typeof CommentsPayload>, unknown> {
|
||||
override actionName = ActionName.GetQzonePublicComments;
|
||||
override actionSummary = '获取空友评论分页';
|
||||
override actionDescription = '分页读取一条空友动态的评论';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { feed: {}, cursor: '' };
|
||||
override returnExample = {};
|
||||
override payloadSchema = CommentsPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof CommentsPayload>) {
|
||||
const { g_tk, cookie } = await publicApi(this).getQzoneAuth();
|
||||
return publicApi(this).getQzonePublicComments(cookie, g_tk, payload.feed, payload.cursor);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. get_qzone_public_replies —— 空友楼中楼分页
|
||||
const RepliesPayload = Type.Object({
|
||||
feed: FeedObject,
|
||||
comment: CommentObject,
|
||||
cursor: Type.Optional(Type.String({ description: '回复 attach_info,省略用 comment 自带' })),
|
||||
page_size: Type.Optional(Type.Union([Type.Number(), Type.String()], { default: 20, description: '每页条数' })),
|
||||
});
|
||||
export class GetQzonePublicReplies extends OneBotAction<Static<typeof RepliesPayload>, unknown> {
|
||||
override actionName = ActionName.GetQzonePublicReplies;
|
||||
override actionSummary = '获取空友楼中楼分页';
|
||||
override actionDescription = '分页读取一条空友评论的楼中楼回复';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { feed: {}, comment: {}, cursor: '', page_size: 20 };
|
||||
override returnExample = {};
|
||||
override payloadSchema = RepliesPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof RepliesPayload>) {
|
||||
const { g_tk, cookie } = await publicApi(this).getQzoneAuth();
|
||||
return publicApi(this).getQzonePublicReplies(
|
||||
cookie, g_tk, payload.feed, payload.comment,
|
||||
payload.cursor ?? '', payload.page_size !== undefined ? +payload.page_size : 20,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. set_qzone_public_upvote —— 空友动态点赞/取消(UFO Upvote)
|
||||
const UpvotePayload = Type.Object({ feed: FeedObject, enable: Type.Boolean({ default: true }) });
|
||||
export class SetQzonePublicUpvote extends OneBotAction<Static<typeof UpvotePayload>, unknown> {
|
||||
override actionName = ActionName.SetQzonePublicUpvote;
|
||||
override actionSummary = '空友动态点赞/取消';
|
||||
override actionDescription = '对一条空友推荐动态点赞或取消点赞(UFO Upvote/Downvote)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { feed: {}, enable: true };
|
||||
override returnExample = {};
|
||||
override payloadSchema = UpvotePayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof UpvotePayload>) {
|
||||
const { g_tk, cookie } = await publicApi(this).getQzoneAuth();
|
||||
return publicApi(this).setQzonePublicUpvote(cookie, g_tk, payload.feed, payload.enable);
|
||||
}
|
||||
}
|
||||
|
||||
// 7. set_qzone_public_like —— 空友动态点赞(DoLike 系)
|
||||
const LikePayload = Type.Object({ feed: FeedObject, enable: Type.Boolean({ default: true }) });
|
||||
export class SetQzonePublicLike extends OneBotAction<Static<typeof LikePayload>, unknown> {
|
||||
override actionName = ActionName.SetQzonePublicLike;
|
||||
override actionSummary = '空友动态点赞(DoLike)';
|
||||
override actionDescription = '对一条空友动态点赞/取消赞(DoLike/UnLike,key 取 cur_like_key)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { feed: {}, enable: true };
|
||||
override returnExample = {};
|
||||
override payloadSchema = LikePayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof LikePayload>) {
|
||||
const { g_tk, cookie } = await publicApi(this).getQzoneAuth();
|
||||
return publicApi(this).setQzonePublicLike(cookie, g_tk, payload.feed, payload.enable);
|
||||
}
|
||||
}
|
||||
|
||||
// 8. set_qzone_public_comment_like —— 空友评论/回复点赞
|
||||
const CommentLikePayload = Type.Object({
|
||||
feed: FeedObject,
|
||||
comment: CommentObject,
|
||||
reply: ReplyObject,
|
||||
enable: Type.Boolean({ default: true }),
|
||||
});
|
||||
export class SetQzonePublicCommentLike extends OneBotAction<Static<typeof CommentLikePayload>, unknown> {
|
||||
override actionName = ActionName.SetQzonePublicCommentLike;
|
||||
override actionSummary = '空友评论/回复点赞';
|
||||
override actionDescription = '给空友评论点赞(like_type=2)或给楼中楼回复点赞(like_type=3)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { feed: {}, comment: {}, enable: true };
|
||||
override returnExample = {};
|
||||
override payloadSchema = CommentLikePayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof CommentLikePayload>) {
|
||||
const { g_tk, cookie } = await publicApi(this).getQzoneAuth();
|
||||
return publicApi(this).setQzonePublicCommentLike(
|
||||
cookie, g_tk, payload.feed, payload.comment, payload.enable,
|
||||
payload.reply as Record<string, unknown> | undefined,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 9. comment_qzone_public_feed —— 空友发评论
|
||||
const CommentPayload = Type.Object({
|
||||
feed: FeedObject,
|
||||
user: UserObject,
|
||||
content: Type.Union([Type.String(), Type.Array(Type.Any())], { description: '评论内容:纯文本或富内容数组' }),
|
||||
});
|
||||
export class CommentQzonePublicFeed extends OneBotAction<Static<typeof CommentPayload>, unknown> {
|
||||
override actionName = ActionName.CommentQzonePublicFeed;
|
||||
override actionSummary = '空友发评论';
|
||||
override actionDescription = '给一条空友推荐动态发评论';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { feed: {}, user: {}, content: '说得好' };
|
||||
override returnExample = {};
|
||||
override payloadSchema = CommentPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof CommentPayload>) {
|
||||
const { g_tk, cookie } = await publicApi(this).getQzoneAuth();
|
||||
return publicApi(this).commentQzonePublicFeed(
|
||||
cookie, g_tk, payload.feed,
|
||||
payload.user as Record<string, unknown>,
|
||||
payload.content as string | Record<string, unknown>[],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 10. reply_qzone_public_feed —— 空友回复评论/楼中楼
|
||||
const ReplyPayload = Type.Object({
|
||||
feed: FeedObject,
|
||||
user: UserObject,
|
||||
comment: CommentObject,
|
||||
content: Type.Union([Type.String(), Type.Array(Type.Any())], { description: '回复内容' }),
|
||||
reply: ReplyObject,
|
||||
});
|
||||
export class ReplyQzonePublicFeed extends OneBotAction<Static<typeof ReplyPayload>, unknown> {
|
||||
override actionName = ActionName.ReplyQzonePublicFeed;
|
||||
override actionSummary = '空友回复评论';
|
||||
override actionDescription = '在空友动态的评论线程里回复(可点名楼中楼目标)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { feed: {}, user: {}, comment: {}, content: '同意' };
|
||||
override returnExample = {};
|
||||
override payloadSchema = ReplyPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof ReplyPayload>) {
|
||||
const { g_tk, cookie } = await publicApi(this).getQzoneAuth();
|
||||
return publicApi(this).replyQzonePublicFeed(
|
||||
cookie, g_tk, payload.feed,
|
||||
payload.user as Record<string, unknown>,
|
||||
payload.comment as Record<string, unknown>,
|
||||
payload.content as string | Record<string, unknown>[],
|
||||
payload.reply as Record<string, unknown> | undefined,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 11. delete_qzone_public_comment —— 删空友评论
|
||||
const DelCommentPayload = Type.Object({ feed: FeedObject, comment: CommentObject });
|
||||
export class DeleteQzonePublicComment extends OneBotAction<Static<typeof DelCommentPayload>, unknown> {
|
||||
override actionName = ActionName.DeleteQzonePublicComment;
|
||||
override actionSummary = '删空友评论';
|
||||
override actionDescription = '删除一条空友动态的评论(自己的评论)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { feed: {}, comment: {} };
|
||||
override returnExample = {};
|
||||
override payloadSchema = DelCommentPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof DelCommentPayload>) {
|
||||
const { g_tk, cookie } = await publicApi(this).getQzoneAuth();
|
||||
return publicApi(this).deleteQzonePublicComment(
|
||||
cookie, g_tk, payload.feed, payload.comment as Record<string, unknown>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 12. delete_qzone_public_reply —— 删空友楼中楼
|
||||
const DelReplyPayload = Type.Object({ feed: FeedObject, comment: CommentObject, reply: Type.Any({ description: '回复对象(原样回传)' }) });
|
||||
export class DeleteQzonePublicReply extends OneBotAction<Static<typeof DelReplyPayload>, unknown> {
|
||||
override actionName = ActionName.DeleteQzonePublicReply;
|
||||
override actionSummary = '删空友楼中楼';
|
||||
override actionDescription = '删除一条空友评论的楼中楼回复(自己的回复)';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { feed: {}, comment: {}, reply: {} };
|
||||
override returnExample = {};
|
||||
override payloadSchema = DelReplyPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof DelReplyPayload>) {
|
||||
const { g_tk, cookie } = await publicApi(this).getQzoneAuth();
|
||||
return publicApi(this).deleteQzonePublicReply(
|
||||
cookie, g_tk, payload.feed,
|
||||
payload.comment as Record<string, unknown>,
|
||||
payload.reply as Record<string, unknown>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 13. set_qzone_public_follow —— 空友关注/取关
|
||||
const FollowPayload = Type.Object({ feed: FeedObject, enable: Type.Boolean({ default: true }) });
|
||||
export class SetQzonePublicFollow extends OneBotAction<Static<typeof FollowPayload>, unknown> {
|
||||
override actionName = ActionName.SetQzonePublicFollow;
|
||||
override actionSummary = '空友关注/取关';
|
||||
override actionDescription = '关注/取关一条空友动态的作者';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { feed: {}, enable: true };
|
||||
override returnExample = {};
|
||||
override payloadSchema = FollowPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof FollowPayload>) {
|
||||
const { g_tk, cookie } = await publicApi(this).getQzoneAuth();
|
||||
return publicApi(this).setQzonePublicFollow(cookie, g_tk, payload.feed, payload.enable);
|
||||
}
|
||||
}
|
||||
|
||||
// 14. forward_qzone_public_feed —— 转发空友动态
|
||||
const ForwardPayload = Type.Object({ feed: FeedObject, reason: Type.String({ description: '转发理由/附言' }) });
|
||||
export class ForwardQzonePublicFeed extends OneBotAction<Static<typeof ForwardPayload>, unknown> {
|
||||
override actionName = ActionName.ForwardQzonePublicFeed;
|
||||
override actionSummary = '转发空友动态';
|
||||
override actionDescription = '转发一条空友推荐动态到自己的空间';
|
||||
override actionTags = ['扩展接口'];
|
||||
override payloadExample = { feed: {}, reason: '转发' };
|
||||
override returnExample = {};
|
||||
override payloadSchema = ForwardPayload;
|
||||
override returnSchema = Type.Any();
|
||||
|
||||
async _handle (payload: Static<typeof ForwardPayload>) {
|
||||
const { g_tk, cookie } = await publicApi(this).getQzoneAuth();
|
||||
return publicApi(this).forwardQzonePublicFeed(cookie, g_tk, payload.feed, payload.reason);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import fs from 'fs/promises';
|
||||
import { FileNapCatOneBotUUID } from 'snowcat-common/src/file-uuid';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { OB11MessageImage, OB11MessageVideo } from '@/snowcat-onebot/types';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { calculateTimeout } from '@/snowcat-onebot/config/config';
|
||||
|
||||
import { FileActionsExamples } from '../example/FileActionsExamples';
|
||||
|
||||
export const GetFilePayloadSchema = Type.Object({
|
||||
file: Type.Optional(Type.String({ description: '文件路径、URL或Base64' })),
|
||||
file_id: Type.Optional(Type.String({ description: '文件ID' })),
|
||||
});
|
||||
|
||||
export type GetFilePayload = Static<typeof GetFilePayloadSchema>;
|
||||
|
||||
export const GetFileReturnSchema = Type.Object({
|
||||
file: Type.Optional(Type.String({ description: '本地路径' })),
|
||||
url: Type.Optional(Type.String({ description: '下载URL' })),
|
||||
file_size: Type.Optional(Type.String({ description: '文件大小' })),
|
||||
file_name: Type.Optional(Type.String({ description: '文件名' })),
|
||||
base64: Type.Optional(Type.String({ description: 'Base64编码' })),
|
||||
}, { description: '文件信息' });
|
||||
|
||||
export type GetFileResponse = Static<typeof GetFileReturnSchema>;
|
||||
|
||||
export class GetFileBase extends OneBotAction<GetFilePayload, GetFileResponse> {
|
||||
override payloadSchema = GetFilePayloadSchema;
|
||||
override returnSchema = GetFileReturnSchema;
|
||||
override actionTags = ['文件接口'];
|
||||
|
||||
async _handle (payload: GetFilePayload): Promise<GetFileResponse> {
|
||||
payload.file ||= payload.file_id || '';
|
||||
// 接收消息标记模式
|
||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file);
|
||||
if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) {
|
||||
const { peer, msgId, elementId } = contextMsgFile;
|
||||
const downloadPath = await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', '');
|
||||
const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList
|
||||
.find(msg => msg.msgId === msgId);
|
||||
const mixElement = rawMessage?.elements.find(e => e.elementId === elementId);
|
||||
const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement;
|
||||
if (!mixElementInner) throw new Error('element not found');
|
||||
const fileSize = mixElementInner.fileSize?.toString() ?? '';
|
||||
const fileName = mixElementInner.fileName ?? '';
|
||||
let url = '';
|
||||
if (mixElement?.picElement && rawMessage) {
|
||||
const tempData =
|
||||
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement, { parseMultMsg: false, disableGetUrl: false, quick_reply: true }) as OB11MessageImage | undefined;
|
||||
url = tempData?.data.url ?? '';
|
||||
}
|
||||
if (mixElement?.videoElement && rawMessage) {
|
||||
const tempData =
|
||||
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement, { parseMultMsg: false, disableGetUrl: false, quick_reply: true }) as OB11MessageVideo | undefined;
|
||||
url = tempData?.data.url ?? '';
|
||||
}
|
||||
const res: GetFileResponse = {
|
||||
file: downloadPath,
|
||||
url: url !== '' ? url : downloadPath,
|
||||
file_size: fileSize,
|
||||
file_name: fileName,
|
||||
};
|
||||
|
||||
if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) {
|
||||
try {
|
||||
res.base64 = await fs.readFile(downloadPath, 'base64');
|
||||
} catch (e) {
|
||||
throw new Error('文件下载失败. ' + e);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 群文件模式
|
||||
const contextModelIdFile = FileNapCatOneBotUUID.decodeModelId(payload.file);
|
||||
if (contextModelIdFile && contextModelIdFile.modelId) {
|
||||
const { peer, modelId } = contextModelIdFile;
|
||||
const downloadPath = await this.core.apis.FileApi.downloadFileForModelId(peer, modelId, '');
|
||||
const res: GetFileResponse = {
|
||||
file: downloadPath,
|
||||
url: downloadPath,
|
||||
file_size: '',
|
||||
file_name: '',
|
||||
};
|
||||
|
||||
if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) {
|
||||
try {
|
||||
res.base64 = await fs.readFile(downloadPath, 'base64');
|
||||
} catch (e) {
|
||||
throw new Error('文件下载失败. ' + e);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 搜索名字模式
|
||||
const searchResult = (await this.core.apis.FileApi.searchForFile([payload.file]));
|
||||
if (searchResult) {
|
||||
const fileSize = parseInt(searchResult.fileSize);
|
||||
const timeoutConfig = this.obContext.configLoader.configData.timeout;
|
||||
const estimatedTime = calculateTimeout(timeoutConfig, fileSize, timeoutConfig.downloadSpeedKBps);
|
||||
const downloadPath = await this.core.apis.FileApi.downloadFileById(searchResult.id, fileSize, estimatedTime);
|
||||
const res: GetFileResponse = {
|
||||
file: downloadPath,
|
||||
url: downloadPath,
|
||||
file_size: searchResult.fileSize.toString(),
|
||||
file_name: searchResult.fileName,
|
||||
};
|
||||
if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) {
|
||||
try {
|
||||
res.base64 = await fs.readFile(downloadPath, 'base64');
|
||||
} catch (e) {
|
||||
throw new Error('文件下载失败. ' + e);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
throw new Error('file not found');
|
||||
}
|
||||
}
|
||||
|
||||
export default class GetFile extends GetFileBase {
|
||||
override actionName = ActionName.GetFile;
|
||||
override actionSummary = '获取文件';
|
||||
override actionDescription = '获取指定文件的详细信息及下载路径';
|
||||
override actionTags = ['文件接口'];
|
||||
override payloadExample = FileActionsExamples.GetFile.payload;
|
||||
override returnExample = FileActionsExamples.GetFile.response;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { FileNapCatOneBotUUID } from 'snowcat-common/src/file-uuid';
|
||||
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
import { FileActionsExamples } from '../example/FileActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
file_id: Type.String({ description: '文件ID' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
url: Type.Optional(Type.String({ description: '文件下载链接' })),
|
||||
}, { description: '群文件URL信息' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetGroupFileUrl extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GOCQHTTP_GetGroupFileUrl;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取群文件URL';
|
||||
override actionDescription = '获取指定群文件的下载链接';
|
||||
override actionTags = ['文件接口'];
|
||||
override payloadExample = FileActionsExamples.GetGroupFileUrl.payload;
|
||||
override returnExample = FileActionsExamples.GetGroupFileUrl.response;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||
if (contextMsgFile?.fileUUID) {
|
||||
return {
|
||||
url: await this.core.apis.PacketApi.pkt.operation.GetGroupFileUrl(+payload.group_id, contextMsgFile.fileUUID),
|
||||
};
|
||||
}
|
||||
throw new Error('real fileUUID not found!');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { GetFileBase } from './GetFile';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
|
||||
import { FileActionsExamples } from '../example/FileActionsExamples';
|
||||
|
||||
export default class GetImage extends GetFileBase {
|
||||
override actionName = ActionName.GetImage;
|
||||
override actionSummary = '获取图片';
|
||||
override actionDescription = '获取指定图片的信息及路径';
|
||||
override actionTags = ['文件接口'];
|
||||
override payloadExample = FileActionsExamples.GetImage.payload;
|
||||
override returnExample = FileActionsExamples.GetImage.response;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { FileNapCatOneBotUUID } from 'snowcat-common/src/file-uuid';
|
||||
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
import { FileActionsExamples } from '../example/FileActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
file_id: Type.String({ description: '文件ID' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
url: Type.Optional(Type.String({ description: '文件下载链接' })),
|
||||
}, { description: '私聊文件URL信息' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetPrivateFileUrl extends GetPacketStatusDepends<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.NapCat_GetPrivateFileUrl;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取私聊文件URL';
|
||||
override actionDescription = '获取指定私聊文件的下载链接';
|
||||
override actionTags = ['文件接口'];
|
||||
override payloadExample = FileActionsExamples.GetPrivateFileUrl.payload;
|
||||
override returnExample = FileActionsExamples.GetPrivateFileUrl.response;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id);
|
||||
|
||||
if (contextMsgFile?.fileUUID && contextMsgFile.msgId) {
|
||||
const msg = await this.core.apis.MsgApi.getMsgsByMsgId(contextMsgFile.peer, [contextMsgFile.msgId]);
|
||||
const self_id = this.core.selfInfo.uid;
|
||||
const file_hash = msg.msgList[0]?.elements.map(ele => ele.fileElement?.file10MMd5)[0];
|
||||
if (file_hash) {
|
||||
return {
|
||||
url: await this.core.apis.PacketApi.pkt.operation.GetPrivateFileUrl(self_id, contextMsgFile.fileUUID, file_hash),
|
||||
};
|
||||
}
|
||||
}
|
||||
throw new Error('real fileUUID not found!');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { promises as fs } from 'fs';
|
||||
import { FFmpegService } from '@/snowcat-core/helper/ffmpeg/ffmpeg';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
import { FileActionsExamples } from '../example/FileActionsExamples';
|
||||
|
||||
const out_format_list = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac'];
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
file: Type.Optional(Type.String({ description: '文件路径、URL或Base64' })),
|
||||
file_id: Type.Optional(Type.String({ description: '文件ID' })),
|
||||
out_format: Type.String({ description: '输出格式' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
export default class GetRecord extends GetFileBase {
|
||||
override actionName = ActionName.GetRecord;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override actionSummary = '获取语音';
|
||||
override actionDescription = '获取指定语音文件的信息,并支持格式转换';
|
||||
override actionTags = ['文件接口'];
|
||||
override payloadExample = FileActionsExamples.GetRecord.payload;
|
||||
override returnExample = FileActionsExamples.GetRecord.response;
|
||||
|
||||
override async _handle (payload: PayloadType): Promise<GetFileResponse> {
|
||||
const res = await super._handle(payload as GetFilePayload);
|
||||
if (payload.out_format && typeof payload.out_format === 'string') {
|
||||
const inputFile = res.file;
|
||||
if (!inputFile) throw new Error('file not found');
|
||||
if (!out_format_list.includes(payload.out_format)) {
|
||||
throw new Error('转换失败 out_format 字段可能格式不正确');
|
||||
}
|
||||
const outputFile = `${inputFile}.${payload.out_format}`;
|
||||
try {
|
||||
await fs.access(inputFile);
|
||||
try {
|
||||
await fs.access(outputFile);
|
||||
} catch {
|
||||
await FFmpegService.convertAudioFmt(inputFile, outputFile, payload.out_format);
|
||||
}
|
||||
const base64Data = await fs.readFile(outputFile, { encoding: 'base64' });
|
||||
res.file = outputFile;
|
||||
res.url = outputFile;
|
||||
res.base64 = base64Data;
|
||||
} catch (error) {
|
||||
console.error('Error processing file:', error);
|
||||
throw error; // 重新抛出错误以便调用者可以处理
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import path from 'node:path';
|
||||
|
||||
const richMediaList = [
|
||||
'.mp4', '.mov', '.avi', '.wmv', '.mpeg', '.mpg', '.flv', '.mkv',
|
||||
'.png', '.gif', '.jpg', '.jpeg', '.webp', '.bmp',
|
||||
];
|
||||
|
||||
export const CreateFlashTaskPayloadSchema = Type.Object({
|
||||
files: Type.Union([
|
||||
Type.Array(Type.String()),
|
||||
Type.String(),
|
||||
], { description: '文件列表或单个文件路径' }),
|
||||
name: Type.Optional(Type.String({ description: '任务名称' })),
|
||||
thumb_path: Type.Optional(Type.String({ description: '缩略图路径' })),
|
||||
});
|
||||
export type CreateFlashTaskPayload = Static<typeof CreateFlashTaskPayloadSchema>;
|
||||
|
||||
export class CreateFlashTask extends OneBotAction<CreateFlashTaskPayload, any> {
|
||||
override actionName = ActionName.CreateFlashTask;
|
||||
override payloadSchema = CreateFlashTaskPayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '任务创建结果' });
|
||||
override actionSummary = '创建闪传任务';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
files: 'C:\\test.jpg',
|
||||
name: 'test_task',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
task_id: 'task_123',
|
||||
};
|
||||
|
||||
async _handle (payload: CreateFlashTaskPayload) {
|
||||
const fileList = Array.isArray(payload.files) ? payload.files : [payload.files];
|
||||
let thumbPath: string = '';
|
||||
|
||||
if (fileList.length === 1) {
|
||||
// 我是真没hook到那种合并的缩略图是哪个方法产生的,暂时不实现(怀疑是js直接canvas渲染的!!) // 确认了猜想
|
||||
const filePath = fileList[0];
|
||||
if (filePath === undefined) {
|
||||
return {};
|
||||
}
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
|
||||
if (richMediaList.includes(ext)) {
|
||||
try {
|
||||
const res = await this.core.apis.FlashApi.createFileThumbnail(filePath);
|
||||
if (res && typeof res === 'object' && 'result' in res && res.result === 0) {
|
||||
thumbPath = res.targetPath as string;
|
||||
}
|
||||
} catch (_e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toPlatformPath (inputPath: string) {
|
||||
const unifiedPath = inputPath.replace(/[\\/]/g, path.sep);
|
||||
return path.normalize(unifiedPath);
|
||||
}
|
||||
|
||||
let normalPath: string;
|
||||
if (payload.thumb_path !== undefined) {
|
||||
normalPath = path.normalize(payload.thumb_path);
|
||||
} else {
|
||||
normalPath = toPlatformPath(thumbPath);
|
||||
}
|
||||
return await this.core.apis.FlashApi.createFlashTransferUploadTask(fileList, normalPath, payload.name || '');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
export const DownloadFilesetPayloadSchema = Type.Object({
|
||||
fileset_id: Type.String({ description: '文件集 ID' }),
|
||||
});
|
||||
|
||||
export type DownloadFilesetPayload = Static<typeof DownloadFilesetPayloadSchema>;
|
||||
|
||||
export class DownloadFileset extends OneBotAction<DownloadFilesetPayload, any> {
|
||||
override actionName = ActionName.DownloadFileset;
|
||||
override payloadSchema = DownloadFilesetPayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '下载结果' });
|
||||
override actionSummary = '下载文件集';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
fileset_id: 'set_123',
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: DownloadFilesetPayload) {
|
||||
// 默认路径 / fileset_id /为下载路径
|
||||
return await this.core.apis.FlashApi.downloadFileSetBySetId(payload.fileset_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
export const GetFilesetIdPayloadSchema = Type.Object({
|
||||
share_code: Type.String({ description: '分享码或分享链接' }),
|
||||
});
|
||||
|
||||
export type GetFilesetIdPayload = Static<typeof GetFilesetIdPayloadSchema>;
|
||||
|
||||
export class GetFilesetId extends OneBotAction<GetFilesetIdPayload, { fileset_id: string }> {
|
||||
override actionName = ActionName.GetFilesetId;
|
||||
override payloadSchema = GetFilesetIdPayloadSchema;
|
||||
override returnSchema = Type.Object({
|
||||
fileset_id: Type.String({ description: '文件集 ID' }),
|
||||
});
|
||||
|
||||
override actionSummary = '获取文件集 ID';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
share_code: '123456',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
fileset_id: 'set_123',
|
||||
};
|
||||
|
||||
async _handle (payload: GetFilesetIdPayload) {
|
||||
// 适配share_link 防止被传 Link无法解析
|
||||
const code = payload.share_code.includes('=') ? payload.share_code.split('=').slice(1).join('=') : payload.share_code;
|
||||
const result = await this.core.apis.FlashApi.fromShareLinkFindSetId(code);
|
||||
return { fileset_id: result.fileSetId };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
export const GetFilesetInfoPayloadSchema = Type.Object({
|
||||
fileset_id: Type.String({ description: '文件集 ID' }),
|
||||
});
|
||||
|
||||
export type GetFilesetInfoPayload = Static<typeof GetFilesetInfoPayloadSchema>;
|
||||
|
||||
export class GetFilesetInfo extends OneBotAction<GetFilesetInfoPayload, any> {
|
||||
override actionName = ActionName.GetFilesetInfo;
|
||||
override payloadSchema = GetFilesetInfoPayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '文件集信息' });
|
||||
override actionSummary = '获取文件集信息';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
fileset_id: 'set_123',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
fileset_id: 'set_123',
|
||||
file_list: [],
|
||||
};
|
||||
|
||||
async _handle (payload: GetFilesetInfoPayload) {
|
||||
return await this.core.apis.FlashApi.getFileSetIndoBySetId(payload.fileset_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
export const GetFlashFileListPayloadSchema = Type.Object({
|
||||
fileset_id: Type.String({ description: '文件集 ID' }),
|
||||
});
|
||||
|
||||
export type GetFlashFileListPayload = Static<typeof GetFlashFileListPayloadSchema>;
|
||||
|
||||
export class GetFlashFileList extends OneBotAction<GetFlashFileListPayload, any> {
|
||||
override actionName = ActionName.GetFlashFileList;
|
||||
override payloadSchema = GetFlashFileListPayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '文件列表' });
|
||||
override actionSummary = '获取闪传文件列表';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
fileset_id: 'set_123',
|
||||
};
|
||||
|
||||
override returnExample = [
|
||||
{
|
||||
file_name: 'test.jpg',
|
||||
size: 1024,
|
||||
},
|
||||
];
|
||||
|
||||
async _handle (payload: GetFlashFileListPayload) {
|
||||
return await this.core.apis.FlashApi.getFileListBySetId(payload.fileset_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
export const GetFlashFileUrlPayloadSchema = Type.Object({
|
||||
fileset_id: Type.String({ description: '文件集 ID' }),
|
||||
file_name: Type.Optional(Type.String({ description: '文件名' })),
|
||||
file_index: Type.Optional(Type.Number({ description: '文件索引' })),
|
||||
});
|
||||
|
||||
export type GetFlashFileUrlPayload = Static<typeof GetFlashFileUrlPayloadSchema>;
|
||||
|
||||
export class GetFlashFileUrl extends OneBotAction<GetFlashFileUrlPayload, any> {
|
||||
override actionName = ActionName.GetFlashFileUrl;
|
||||
override payloadSchema = GetFlashFileUrlPayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '文件下载链接' });
|
||||
override actionSummary = '获取闪传文件链接';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
fileset_id: 'set_123',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
url: 'http://example.com/flash.jpg',
|
||||
};
|
||||
|
||||
async _handle (payload: GetFlashFileUrlPayload) {
|
||||
// 文件的索引依旧从0开始
|
||||
return await this.core.apis.FlashApi.getFileTransUrl(payload.fileset_id, {
|
||||
fileName: payload.file_name,
|
||||
fileIndex: payload.file_index,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
export const GetShareLinkPayloadSchema = Type.Object({
|
||||
fileset_id: Type.String({ description: '文件集 ID' }),
|
||||
});
|
||||
|
||||
export type GetShareLinkPayload = Static<typeof GetShareLinkPayloadSchema>;
|
||||
|
||||
export class GetShareLink extends OneBotAction<GetShareLinkPayload, any> {
|
||||
override actionName = ActionName.GetShareLink;
|
||||
override payloadSchema = GetShareLinkPayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '分享链接' });
|
||||
override actionSummary = '获取文件分享链接';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
fileset_id: 'set_123',
|
||||
};
|
||||
|
||||
override returnExample = 'http://example.com/share';
|
||||
|
||||
async _handle (payload: GetShareLinkPayload) {
|
||||
return await this.core.apis.FlashApi.getShareLinkBySetId(payload.fileset_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType, Peer } from 'snowcat-core/types';
|
||||
|
||||
export const SendFlashMsgPayloadSchema = Type.Object({
|
||||
fileset_id: Type.String({ description: '文件集 ID' }),
|
||||
user_id: Type.Optional(Type.String({ description: '用户 QQ' })),
|
||||
group_id: Type.Optional(Type.String({ description: '群号' })),
|
||||
});
|
||||
|
||||
export type SendFlashMsgPayload = Static<typeof SendFlashMsgPayloadSchema>;
|
||||
|
||||
export class SendFlashMsg extends OneBotAction<SendFlashMsgPayload, any> {
|
||||
override actionName = ActionName.SendFlashMsg;
|
||||
override payloadSchema = SendFlashMsgPayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '发送结果' });
|
||||
override actionSummary = '发送闪传消息';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
fileset_id: 'set_123',
|
||||
user_id: '123456789',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
message_id: 123456,
|
||||
};
|
||||
|
||||
async _handle (payload: SendFlashMsgPayload) {
|
||||
let peer: Peer;
|
||||
|
||||
if (payload.group_id) {
|
||||
peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() };
|
||||
} else if (payload.user_id) {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!uid) throw new Error('User not found');
|
||||
|
||||
// 可能需要更严格的判断
|
||||
const isBuddy = await this.core.apis.FriendApi.isBuddy(uid);
|
||||
peer = {
|
||||
chatType: isBuddy ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP,
|
||||
peerUid: uid,
|
||||
};
|
||||
} else {
|
||||
throw new Error('user_id or group_id is required');
|
||||
}
|
||||
|
||||
return await this.core.apis.FlashApi.sendFlashMessage(payload.fileset_id, peer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType } from 'snowcat-core/types';
|
||||
|
||||
export const CancelOnlineFilePayloadSchema = Type.Object({
|
||||
user_id: Type.String({ description: '用户 QQ' }),
|
||||
msg_id: Type.String({ description: '消息 ID' }),
|
||||
});
|
||||
|
||||
export type CancelOnlineFilePayload = Static<typeof CancelOnlineFilePayloadSchema>;
|
||||
|
||||
export class CancelOnlineFile extends OneBotAction<CancelOnlineFilePayload, any> {
|
||||
override actionName = ActionName.CancelOnlineFile;
|
||||
override payloadSchema = CancelOnlineFilePayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '取消结果' });
|
||||
override actionSummary = '取消在线文件';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
user_id: '123456789',
|
||||
msg_id: '123',
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: CancelOnlineFilePayload) {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!uid) throw new Error('User not found');
|
||||
|
||||
// 仅私聊
|
||||
const peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: uid };
|
||||
|
||||
return await this.core.apis.OnlineApi.cancelMyOnlineFileMsg(peer, payload.msg_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType } from 'snowcat-core/types';
|
||||
|
||||
export const GetOnlineFileMessagesPayloadSchema = Type.Object({
|
||||
user_id: Type.String({ description: '用户 QQ' }),
|
||||
});
|
||||
|
||||
export type GetOnlineFileMessagesPayload = Static<typeof GetOnlineFileMessagesPayloadSchema>;
|
||||
|
||||
export class GetOnlineFileMessages extends OneBotAction<GetOnlineFileMessagesPayload, any> {
|
||||
override actionName = ActionName.GetOnlineFileMessages;
|
||||
override payloadSchema = GetOnlineFileMessagesPayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '在线文件消息列表' });
|
||||
override actionSummary = '获取在线文件消息';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
user_id: '123456789',
|
||||
};
|
||||
|
||||
override returnExample = [];
|
||||
|
||||
async _handle (payload: GetOnlineFileMessagesPayload) {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!uid) throw new Error('User not found');
|
||||
|
||||
// 仅私聊
|
||||
const peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: uid };
|
||||
|
||||
return await this.core.apis.OnlineApi.getOnlineFileMsg(peer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType } from 'snowcat-core/types';
|
||||
|
||||
export const ReceiveOnlineFilePayloadSchema = Type.Object({
|
||||
user_id: Type.String({ description: '用户 QQ' }),
|
||||
msg_id: Type.String({ description: '消息 ID' }),
|
||||
element_id: Type.String({ description: '元素 ID' }),
|
||||
});
|
||||
|
||||
export type ReceiveOnlineFilePayload = Static<typeof ReceiveOnlineFilePayloadSchema>;
|
||||
|
||||
export class ReceiveOnlineFile extends OneBotAction<ReceiveOnlineFilePayload, any> {
|
||||
override actionName = ActionName.ReceiveOnlineFile;
|
||||
override payloadSchema = ReceiveOnlineFilePayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '接收结果' });
|
||||
override actionSummary = '接收在线文件';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
user_id: '123456789',
|
||||
msg_id: '123',
|
||||
save_path: 'C:\\save',
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: ReceiveOnlineFilePayload) {
|
||||
// 默认下载路径
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!uid) throw new Error('User not found');
|
||||
|
||||
const peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: uid };
|
||||
|
||||
return await this.core.apis.OnlineApi.receiveOnlineFileOrFolder(peer, payload.msg_id, payload.element_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType } from 'snowcat-core/types';
|
||||
|
||||
export const RefuseOnlineFilePayloadSchema = Type.Object({
|
||||
user_id: Type.String({ description: '用户 QQ' }),
|
||||
msg_id: Type.String({ description: '消息 ID' }),
|
||||
element_id: Type.String({ description: '元素 ID' }),
|
||||
});
|
||||
|
||||
export type RefuseOnlineFilePayload = Static<typeof RefuseOnlineFilePayloadSchema>;
|
||||
|
||||
export class RefuseOnlineFile extends OneBotAction<RefuseOnlineFilePayload, any> {
|
||||
override actionName = ActionName.RefuseOnlineFile;
|
||||
override payloadSchema = RefuseOnlineFilePayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '拒绝结果' });
|
||||
override actionSummary = '拒绝在线文件';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
user_id: '123456789',
|
||||
msg_id: '123',
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: RefuseOnlineFilePayload) {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!uid) throw new Error('User not found');
|
||||
|
||||
const peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: uid };
|
||||
|
||||
return await this.core.apis.OnlineApi.refuseOnlineFileMsg(peer, payload.msg_id, payload.element_id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType } from 'snowcat-core/types';
|
||||
|
||||
export const SendOnlineFilePayloadSchema = Type.Object({
|
||||
user_id: Type.String({ description: '用户 QQ' }),
|
||||
file_path: Type.String({ description: '本地文件路径' }),
|
||||
file_name: Type.Optional(Type.String({ description: '文件名 (可选)' })),
|
||||
});
|
||||
|
||||
export type SendOnlineFilePayload = Static<typeof SendOnlineFilePayloadSchema>;
|
||||
|
||||
export class SendOnlineFile extends OneBotAction<SendOnlineFilePayload, any> {
|
||||
override actionName = ActionName.SendOnlineFile;
|
||||
override payloadSchema = SendOnlineFilePayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '发送结果' });
|
||||
override actionSummary = '发送在线文件';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
user_id: '123456789',
|
||||
file_path: 'C:\\path\\to\\file.txt',
|
||||
file_name: 'test.txt',
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: SendOnlineFilePayload) {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!uid) throw new Error('User not found');
|
||||
|
||||
// 仅私聊
|
||||
const peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: uid };
|
||||
const fileName = payload.file_name || '';
|
||||
|
||||
return await this.core.apis.OnlineApi.sendOnlineFile(peer, payload.file_path, fileName);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType } from 'snowcat-core/types';
|
||||
|
||||
export const SendOnlineFolderPayloadSchema = Type.Object({
|
||||
user_id: Type.String({ description: '用户 QQ' }),
|
||||
folder_path: Type.String({ description: '本地文件夹路径' }),
|
||||
folder_name: Type.Optional(Type.String({ description: '文件夹名称 (可选)' })),
|
||||
});
|
||||
|
||||
export type SendOnlineFolderPayload = Static<typeof SendOnlineFolderPayloadSchema>;
|
||||
|
||||
export class SendOnlineFolder extends OneBotAction<SendOnlineFolderPayload, any> {
|
||||
override actionName = ActionName.SendOnlineFolder;
|
||||
override payloadSchema = SendOnlineFolderPayloadSchema;
|
||||
override returnSchema = Type.Any({ description: '发送结果' });
|
||||
override actionSummary = '发送在线文件夹';
|
||||
override actionTags = ['文件扩展'];
|
||||
override payloadExample = {
|
||||
user_id: '123456789',
|
||||
folder_path: 'C:\\path\\to\\folder',
|
||||
};
|
||||
|
||||
override returnExample = null;
|
||||
|
||||
async _handle (payload: SendOnlineFolderPayload) {
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!uid) throw new Error('User not found');
|
||||
|
||||
const peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: uid };
|
||||
|
||||
return await this.core.apis.OnlineApi.sendOnlineFolder(peer, payload.folder_path, payload.folder_name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
// 兼容gocq 与name二选一
|
||||
folder_name: Type.Optional(Type.String({ description: '文件夹名称' })),
|
||||
// 兼容gocq 与folder_name二选一
|
||||
name: Type.Optional(Type.String({ description: '文件夹名称' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
result: Type.Any({ description: '操作结果' }),
|
||||
groupItem: Type.Any({ description: '群项信息' }),
|
||||
}, { description: '创建文件夹结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class CreateGroupFileFolder extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GoCQHTTP_CreateGroupFileFolder;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '创建群文件目录';
|
||||
override actionDescription = '在群文件系统中创建新的文件夹';
|
||||
override actionTags = ['Go-CQHTTP'];
|
||||
override payloadExample = {
|
||||
group_id: '123456789',
|
||||
folder_name: '新建文件夹',
|
||||
};
|
||||
|
||||
override returnExample = {
|
||||
result: {},
|
||||
groupItem: {},
|
||||
};
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const folderName = payload.folder_name || payload.name;
|
||||
return (await this.core.apis.GroupApi.creatGroupFileFolder(payload.group_id.toString(), folderName!)).resultWithGroupItem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { FileNapCatOneBotUUID } from 'snowcat-common/src/file-uuid';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { GoCQHTTPActionsExamples } from '../example/GoCQHTTPActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
file_id: Type.String({ description: '文件ID' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '删除结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class DeleteGroupFile extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GOCQHTTP_DeleteGroupFile;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '删除群文件';
|
||||
override actionDescription = '在群文件系统中删除指定的文件';
|
||||
override actionTags = ['Go-CQHTTP'];
|
||||
override payloadExample = GoCQHTTPActionsExamples.DeleteGroupFile.payload;
|
||||
override returnExample = GoCQHTTPActionsExamples.DeleteGroupFile.response;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const data = FileNapCatOneBotUUID.decodeModelId(payload.file_id);
|
||||
if (!data || !data.fileId) throw new Error('Invalid file_id');
|
||||
return await this.core.apis.GroupApi.delGroupFile(payload.group_id.toString(), [data.fileId]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { GoCQHTTPActionsExamples } from '../example/GoCQHTTPActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
folder_id: Type.Optional(Type.String({ description: '文件夹ID' })),
|
||||
folder: Type.Optional(Type.String({ description: '文件夹ID' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Any({ description: '删除结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class DeleteGroupFileFolder extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GoCQHTTP_DeleteGroupFileFolder;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '删除群文件目录';
|
||||
override actionDescription = '在群文件系统中删除指定的文件夹';
|
||||
override actionTags = ['Go-CQHTTP'];
|
||||
override payloadExample = GoCQHTTPActionsExamples.DeleteGroupFileFolder.payload;
|
||||
override returnExample = GoCQHTTPActionsExamples.DeleteGroupFileFolder.response;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
return (await this.core.apis.GroupApi.delGroupFileFolder(
|
||||
payload.group_id.toString(), payload.folder ?? payload.folder_id ?? '')).groupFileCommonResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import fs from 'fs';
|
||||
import { join as joinPath } from 'node:path';
|
||||
import { calculateFileMD5, uriToLocalFile } from 'snowcat-common/src/file';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { GoCQHTTPActionsExamples } from '../example/GoCQHTTPActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
url: Type.Optional(Type.String({ description: '下载链接' })),
|
||||
base64: Type.Optional(Type.String({ description: 'base64数据' })),
|
||||
name: Type.Optional(Type.String({ description: '文件名' })),
|
||||
headers: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())], { description: '请求头' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
file: Type.String({ description: '文件路径' }),
|
||||
}, { description: '下载结果' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export default class GoCQHTTPDownloadFile extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GoCQHTTP_DownloadFile;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '下载文件';
|
||||
override actionDescription = '下载网络文件到本地临时目录';
|
||||
override actionTags = ['Go-CQHTTP'];
|
||||
override payloadExample = GoCQHTTPActionsExamples.DownloadFile.payload;
|
||||
override returnExample = GoCQHTTPActionsExamples.DownloadFile.response;
|
||||
|
||||
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||
const isRandomName = !payload.name;
|
||||
const name = payload.name || randomUUID();
|
||||
let result: Awaited<globalThis.ReturnType<typeof uriToLocalFile>>;
|
||||
|
||||
if (payload.base64) {
|
||||
result = await uriToLocalFile(this.core.SnowCatTempPath, `base64://${payload.base64}`, name);
|
||||
} else if (payload.url) {
|
||||
const headers = this.getHeaders(payload.headers);
|
||||
result = await uriToLocalFile(this.core.SnowCatTempPath, payload.url, name, headers);
|
||||
} else {
|
||||
throw new Error('不存在任何文件, 无法下载');
|
||||
}
|
||||
if (!result.success) {
|
||||
throw new Error(result.errMsg);
|
||||
}
|
||||
const filePath = result.path;
|
||||
if (fs.existsSync(filePath)) {
|
||||
if (isRandomName) {
|
||||
// 默认实现要名称未填写时文件名为文件 md5
|
||||
const md5 = await calculateFileMD5(filePath);
|
||||
const newPath = joinPath(this.core.SnowCatTempPath, md5);
|
||||
fs.renameSync(filePath, newPath);
|
||||
return { file: newPath };
|
||||
}
|
||||
return { file: filePath };
|
||||
} else {
|
||||
throw new Error('文件写入失败, 检查权限');
|
||||
}
|
||||
}
|
||||
|
||||
getHeaders (headersIn?: string | string[]): Record<string, string> {
|
||||
const headers: Record<string, string> = {};
|
||||
if (typeof headersIn === 'string') {
|
||||
headersIn = headersIn.split('[\\r\\n]');
|
||||
}
|
||||
if (Array.isArray(headersIn)) {
|
||||
for (const headerItem of headersIn) {
|
||||
const spilt = headerItem.indexOf('=');
|
||||
if (spilt < 0) {
|
||||
headers[headerItem] = '';
|
||||
} else {
|
||||
const key = headerItem.substring(0, spilt);
|
||||
headers[key] = headerItem.substring(spilt + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!headers['Content-Type']) {
|
||||
headers['Content-Type'] = 'application/octet-stream';
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { OB11Message, OB11MessageData, OB11MessageDataType, OB11MessageForward, OB11MessageNodePlain as OB11MessageNode } from '@/snowcat-onebot/index';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { MessageUnique } from 'snowcat-common/src/message-unique';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType, ElementType, MsgSourceType, NTMsgType, RawMessage } from 'snowcat-core';
|
||||
import { isNumeric } from 'snowcat-common/src/helper';
|
||||
import { GoCQHTTPActionsExamples } from '../example/GoCQHTTPActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
message_id: Type.Optional(Type.String({ description: '消息ID' })),
|
||||
id: Type.Optional(Type.String({ description: '消息ID' })),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
messages: Type.Optional(Type.Array(Type.Unknown(), { description: '消息列表' })),
|
||||
}, { description: '合并转发消息' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
function isForward (msg: OB11MessageData | string): msg is OB11MessageForward {
|
||||
return typeof msg !== 'string' && msg.type === OB11MessageDataType.forward;
|
||||
}
|
||||
|
||||
export class GoCQHTTPGetForwardMsgAction extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GoCQHTTP_GetForwardMsg;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取合并转发消息';
|
||||
override actionDescription = '获取合并转发消息的具体内容';
|
||||
override actionTags = ['Go-CQHTTP'];
|
||||
override payloadExample = GoCQHTTPActionsExamples.GetForwardMsg.payload;
|
||||
override returnExample = GoCQHTTPActionsExamples.GetForwardMsg.response;
|
||||
|
||||
private createTemplateNode (message: OB11Message): OB11MessageNode {
|
||||
return {
|
||||
type: OB11MessageDataType.node,
|
||||
data: {
|
||||
user_id: message.user_id,
|
||||
nickname: message.sender.nickname,
|
||||
message: [],
|
||||
content: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async parseForward (messages: OB11Message[]): Promise<OB11MessageNode[]> {
|
||||
const retMsg: OB11MessageNode[] = [];
|
||||
|
||||
for (const message of messages) {
|
||||
const templateNode = this.createTemplateNode(message);
|
||||
|
||||
for (const msgdata of message.message) {
|
||||
if (isForward(msgdata)) {
|
||||
const newNode = this.createTemplateNode(message);
|
||||
newNode.data.message = await this.parseForward(msgdata.data.content ?? []);
|
||||
|
||||
templateNode.data.message.push(newNode);
|
||||
} else {
|
||||
if (typeof msgdata === 'string') {
|
||||
const textMsg: OB11MessageData = { type: OB11MessageDataType.text, data: { text: msgdata } };
|
||||
templateNode.data.message.push(textMsg);
|
||||
} else {
|
||||
templateNode.data.message.push(msgdata);
|
||||
}
|
||||
}
|
||||
}
|
||||
retMsg.push(templateNode);
|
||||
}
|
||||
|
||||
return retMsg;
|
||||
}
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
// 1. 检查消息ID是否存在
|
||||
const msgId = payload.message_id || payload.id;
|
||||
if (!msgId) {
|
||||
throw new Error('message_id is required');
|
||||
}
|
||||
|
||||
// 2. 定义辅助函数 - 创建伪转发消息对象
|
||||
const createFakeForwardMsg = (resId: string): RawMessage => {
|
||||
const fakeMsg: RawMessage = {
|
||||
chatType: ChatType.KCHATTYPEGROUP,
|
||||
elements: [{
|
||||
elementType: ElementType.MULTIFORWARD,
|
||||
elementId: '',
|
||||
multiForwardMsgElement: {
|
||||
resId,
|
||||
fileName: '',
|
||||
xmlContent: '',
|
||||
},
|
||||
}],
|
||||
guildId: '',
|
||||
isOnlineMsg: false,
|
||||
msgId: '', // TODO: no necessary
|
||||
msgRandom: '0',
|
||||
msgSeq: '',
|
||||
msgTime: '',
|
||||
msgType: NTMsgType.KMSGTYPEMIX,
|
||||
parentMsgIdList: [],
|
||||
parentMsgPeer: {
|
||||
chatType: ChatType.KCHATTYPEGROUP,
|
||||
peerUid: '',
|
||||
},
|
||||
peerName: '',
|
||||
peerUid: '284840486',
|
||||
peerUin: '284840486',
|
||||
recallTime: '0',
|
||||
records: [],
|
||||
sendNickName: '',
|
||||
sendRemarkName: '',
|
||||
senderUid: '',
|
||||
senderUin: '1094950020',
|
||||
sourceType: MsgSourceType.K_DOWN_SOURCETYPE_UNKNOWN,
|
||||
subMsgType: 1,
|
||||
};
|
||||
return fakeMsg;
|
||||
};
|
||||
|
||||
// 3. 定义协议回退逻辑函数
|
||||
const protocolFallbackLogic = async (resId: string) => {
|
||||
const ob = (await this.obContext.apis.MsgApi.parseMessageV2(createFakeForwardMsg(resId), true))?.arrayMsg;
|
||||
const firstMsg = ob?.message?.[0];
|
||||
if (firstMsg && isForward(firstMsg)) {
|
||||
return {
|
||||
messages: firstMsg.data.content,
|
||||
};
|
||||
}
|
||||
throw new Error('protocolFallbackLogic: 找不到相关的聊天记录');
|
||||
};
|
||||
// 4. 尝试通过正常渠道获取消息
|
||||
// 如果是数字ID,优先使用getMsgsByMsgId获取
|
||||
if (!isNumeric(msgId)) {
|
||||
const ret = await protocolFallbackLogic(msgId);
|
||||
if (ret.messages) {
|
||||
return ret;
|
||||
}
|
||||
throw new Error('ResId无效: 找不到相关的聊天记录');
|
||||
}
|
||||
const rootMsgId = MessageUnique.getShortIdByMsgId(msgId.toString());
|
||||
const rootMsg = MessageUnique.getMsgIdAndPeerByShortId(rootMsgId ?? +msgId);
|
||||
|
||||
if (rootMsg) {
|
||||
// 5. 获取消息内容
|
||||
const data = await this.core.apis.MsgApi.getMsgHistory(rootMsg.Peer, rootMsg.MsgId, 1);// getMsgsIncludeSelf
|
||||
|
||||
if (data && data.result === 0 && data.msgList.length > 0) {
|
||||
const singleMsg = data.msgList[0];
|
||||
if (!singleMsg) {
|
||||
throw new Error('消息不存在或已过期');
|
||||
}
|
||||
// 6. 解析消息内容
|
||||
const resMsg = (await this.obContext.apis.MsgApi.parseMessage(singleMsg, 'array', true));
|
||||
|
||||
const firstMsg = resMsg?.message?.[0];
|
||||
if (firstMsg && isForward(firstMsg)) {
|
||||
const forwardContent = firstMsg.data.content;
|
||||
if (forwardContent) {
|
||||
return { messages: forwardContent };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 说明消息已过期或者为内层消息 NapCat 一次返回不处理内层消息
|
||||
throw new Error('消息已过期或者为内层消息,无法获取转发消息');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { MessageFormat } from '@/snowcat-onebot/config/config';
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { OB11Message } from '@/snowcat-onebot/index';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { ChatType } from 'snowcat-core/types';
|
||||
import { MessageUnique } from 'snowcat-common/src/message-unique';
|
||||
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { NetworkAdapterConfig } from '@/snowcat-onebot/config/config';
|
||||
import { GoCQHTTPActionsExamples } from '../example/GoCQHTTPActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
user_id: Type.String({ description: '用户QQ' }),
|
||||
message_seq: Type.Optional(Type.String({ description: '起始消息序号' })),
|
||||
count: Type.Number({ default: 20, description: '获取消息数量' }),
|
||||
reverse_order: Type.Boolean({ default: false, description: '是否反向排序' }),
|
||||
disable_get_url: Type.Boolean({ default: false, description: '是否禁用获取URL' }),
|
||||
parse_mult_msg: Type.Boolean({ default: true, description: '是否解析合并消息' }),
|
||||
quick_reply: Type.Boolean({ default: false, description: '是否快速回复' }),
|
||||
reverseOrder: Type.Boolean({ default: false, description: '是否反向排序(旧版本兼容)' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
messages: Type.Array(Type.Any(), { description: '消息列表' }),
|
||||
}, { description: '好友历史消息' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export default class GetFriendMsgHistory extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GetFriendMsgHistory;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取好友历史消息';
|
||||
override actionDescription = '获取指定好友的历史聊天记录';
|
||||
override actionTags = ['Go-CQHTTP'];
|
||||
override payloadExample = GoCQHTTPActionsExamples.GetFriendMsgHistory.payload;
|
||||
override returnExample = GoCQHTTPActionsExamples.GetFriendMsgHistory.response;
|
||||
|
||||
async _handle (payload: PayloadType, _adapter: string, config: NetworkAdapterConfig): Promise<ReturnType> {
|
||||
// 处理参数
|
||||
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
|
||||
if (!uid) throw new Error(`记录${payload.user_id}不存在`);
|
||||
const friend = await this.core.apis.FriendApi.isBuddy(uid);
|
||||
const peer = { chatType: friend ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP, peerUid: uid };
|
||||
const hasMessageSeq = !payload.message_seq ? !!payload.message_seq : !(payload.message_seq?.toString() === '' || payload.message_seq?.toString() === '0');
|
||||
const startMsgId = hasMessageSeq ? (MessageUnique.getMsgIdAndPeerByShortId(+payload.message_seq!)?.MsgId ?? payload.message_seq!.toString()) : '0';
|
||||
const msgList = hasMessageSeq
|
||||
? (await this.core.apis.MsgApi.getMsgHistory(peer, startMsgId, +payload.count, payload.reverse_order || payload.reverseOrder)).msgList
|
||||
: (await this.core.apis.MsgApi.getAioFirstViewLatestMsgs(peer, +payload.count)).msgList;
|
||||
if (msgList.length === 0) throw new Error(`消息${payload.message_seq}不存在`);
|
||||
// 转换序号
|
||||
await Promise.all(msgList.map(async msg => {
|
||||
msg.id = MessageUnique.createUniqueMsgId({ guildId: '', chatType: msg.chatType, peerUid: msg.peerUid }, msg.msgId);
|
||||
}));
|
||||
// 烘焙消息
|
||||
const ob11MsgList = (await Promise.all(
|
||||
msgList.map(msg => this.obContext.apis.MsgApi.parseMessage(msg, config['messagePostFormat'] as MessageFormat | undefined ?? 'array', payload.parse_mult_msg, payload.disable_get_url)))
|
||||
).filter((msg): msg is OB11Message => msg !== undefined);
|
||||
return { messages: ob11MsgList };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { GoCQHTTPActionsExamples } from '../example/GoCQHTTPActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
can_at_all: Type.Boolean({ description: '是否可以艾特全体' }),
|
||||
remain_at_all_count_for_group: Type.Number({ description: '群艾特全体剩余次数' }),
|
||||
remain_at_all_count_for_uin: Type.Number({ description: '个人艾特全体剩余次数' }),
|
||||
}, { description: '群艾特全体剩余次数' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GoCQHTTPGetGroupAtAllRemain extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GoCQHTTP_GetGroupAtAllRemain;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取群艾特全体剩余次数';
|
||||
override actionDescription = '获取指定群聊中艾特全体成员的剩余次数';
|
||||
override actionTags = ['Go-CQHTTP'];
|
||||
override payloadExample = GoCQHTTPActionsExamples.GetGroupAtAllRemain.payload;
|
||||
override returnExample = GoCQHTTPActionsExamples.GetGroupAtAllRemain.response;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const ret = await this.core.apis.GroupApi.getGroupRemainAtTimes(payload.group_id.toString());
|
||||
const data = {
|
||||
can_at_all: ret.atInfo.canAtAll,
|
||||
remain_at_all_count_for_group: ret.atInfo.RemainAtAllCountForGroup,
|
||||
remain_at_all_count_for_uin: ret.atInfo.RemainAtAllCountForUin,
|
||||
};
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { GoCQHTTPActionsExamples } from '../example/GoCQHTTPActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
file_count: Type.Number({ description: '文件总数' }),
|
||||
limit_count: Type.Number({ description: '文件上限' }),
|
||||
used_space: Type.Number({ description: '已使用空间' }),
|
||||
total_space: Type.Number({ description: '总空间' }),
|
||||
}, { description: '群文件系统信息' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetGroupFileSystemInfo extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GoCQHTTP_GetGroupFileSystemInfo;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取群文件系统信息';
|
||||
override actionDescription = '获取群聊文件系统的空间及状态信息';
|
||||
override actionTags = ['Go-CQHTTP'];
|
||||
override payloadExample = GoCQHTTPActionsExamples.GetGroupFileSystemInfo.payload;
|
||||
override returnExample = GoCQHTTPActionsExamples.GetGroupFileSystemInfo.response;
|
||||
|
||||
async _handle (payload: PayloadType) {
|
||||
const groupCode = payload.group_id.toString();
|
||||
const [countResult, space] = await Promise.all([
|
||||
this.core.apis.GroupApi.getGroupFileCount([groupCode]),
|
||||
this.core.apis.GroupApi.getGroupFileSpace(groupCode),
|
||||
]);
|
||||
const groupFileCount = countResult.groupFileCounts[0];
|
||||
if (groupFileCount === undefined) {
|
||||
throw new Error('Group not found');
|
||||
}
|
||||
return {
|
||||
file_count: groupFileCount,
|
||||
limit_count: 10000,
|
||||
// Real values from the kernel's groupSpaceResult; fall back to the old
|
||||
// placeholders if the field is unexpectedly absent.
|
||||
used_space: space?.usedSpace ?? 0,
|
||||
total_space: space?.totalSpace ?? 10 * 1024 * 1024 * 1024,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/snowcat-onebot/action/router';
|
||||
import { OB11Construct } from '@/snowcat-onebot/helper/data';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { GoCQHTTPActionsExamples } from '../example/GoCQHTTPActionsExamples';
|
||||
|
||||
const PayloadSchema = Type.Object({
|
||||
group_id: Type.String({ description: '群号' }),
|
||||
folder_id: Type.Optional(Type.String({ description: '文件夹ID' })),
|
||||
folder: Type.Optional(Type.String({ description: '文件夹ID' })),
|
||||
file_count: Type.Union([Type.Number(), Type.String()], { default: 50, description: '文件数量' }),
|
||||
});
|
||||
|
||||
type PayloadType = Static<typeof PayloadSchema>;
|
||||
|
||||
const ReturnSchema = Type.Object({
|
||||
files: Type.Array(Type.Unknown(), { description: '文件列表' }),
|
||||
folders: Type.Array(Type.Unknown(), { description: '文件夹列表' }),
|
||||
}, { description: '群文件夹文件列表' });
|
||||
|
||||
type ReturnType = Static<typeof ReturnSchema>;
|
||||
|
||||
export class GetGroupFilesByFolder extends OneBotAction<PayloadType, ReturnType> {
|
||||
override actionName = ActionName.GoCQHTTP_GetGroupFilesByFolder;
|
||||
override payloadSchema = PayloadSchema;
|
||||
override returnSchema = ReturnSchema;
|
||||
override actionSummary = '获取群文件夹文件列表';
|
||||
override actionDescription = '获取指定群文件夹下的文件及子文件夹列表';
|
||||
override actionTags = ['Go-CQHTTP'];
|
||||
override payloadExample = GoCQHTTPActionsExamples.GetGroupFilesByFolder.payload;
|
||||
override returnExample = GoCQHTTPActionsExamples.GetGroupFilesByFolder.response;
|
||||
|
||||
async _handle (payload: PayloadType): Promise<ReturnType> {
|
||||
const retRaw = await this.core.apis.MsgApi.getGroupFileList(payload.group_id.toString(), {
|
||||
sortType: 1,
|
||||
fileCount: +payload.file_count,
|
||||
startIndex: 0,
|
||||
sortOrder: 2,
|
||||
showOnlinedocFolder: 0,
|
||||
folderId: payload.folder ?? payload.folder_id ?? '',
|
||||
});
|
||||
const ret = Array.isArray(retRaw) ? retRaw : [];
|
||||
return {
|
||||
files: ret.filter(item => item.fileInfo)
|
||||
.map(item => OB11Construct.file(item.peerId, item.fileInfo!)),
|
||||
folders: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
Loaded 100 of 1048 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user