Refactor Satori actions with schema validation and router

Refactored all Satori action classes to use TypeBox schemas for payload validation and unified action naming via a new router. Added schema-based parameter checking to the SatoriAction base class. Introduced new actions for guild and member approval, and login retrieval. Centralized action name constants and types in a new router module. Enhanced event and message APIs with more structured event types and parsing logic. Added helper utilities for XML parsing. Updated exports and registration logic to support the new structure.
This commit is contained in:
手瓜一十雪 2026-01-14 17:52:38 +08:00
parent 32c0c93f3b
commit b0d88d3705
31 changed files with 1575 additions and 644 deletions

View File

@ -1,17 +1,84 @@
import { NapCatCore } from 'napcat-core';
import { NapCatSatoriAdapter } from '../index';
import Ajv, { ErrorObject, ValidateFunction } from 'ajv';
import { TSchema } from '@sinclair/typebox';
export interface SatoriCheckResult {
valid: boolean;
message?: string;
}
export interface SatoriResponse<T = unknown> {
data?: T;
error?: {
code: number;
message: string;
};
}
export class SatoriResponseHelper {
static success<T> (data: T): SatoriResponse<T> {
return { data };
}
static error (code: number, message: string): SatoriResponse<null> {
return { error: { code, message } };
}
}
export abstract class SatoriAction<PayloadType, ReturnType> {
abstract actionName: string;
protected satoriAdapter: NapCatSatoriAdapter;
protected core: NapCatCore;
payloadSchema?: TSchema = undefined;
private validate?: ValidateFunction<unknown> = undefined;
constructor (satoriAdapter: NapCatSatoriAdapter, core: NapCatCore) {
this.satoriAdapter = satoriAdapter;
this.core = core;
}
abstract handle (payload: PayloadType): Promise<ReturnType>;
/**
*
*/
protected async check (payload: PayloadType): Promise<SatoriCheckResult> {
if (this.payloadSchema) {
this.validate = new Ajv({
allowUnionTypes: true,
useDefaults: true,
coerceTypes: true,
}).compile(this.payloadSchema);
}
if (this.validate && !this.validate(payload)) {
const errors = this.validate.errors as ErrorObject[];
const errorMessages = errors.map(
(e) => `Key: ${e.instancePath.split('/').slice(1).join('.')}, Message: ${e.message}`
);
return {
valid: false,
message: errorMessages.join('\n') ?? '未知错误',
};
}
return { valid: true };
}
/**
*
*/
async handle (payload: PayloadType): Promise<ReturnType> {
const checkResult = await this.check(payload);
if (!checkResult.valid) {
throw new Error(checkResult.message || '参数验证失败');
}
return this._handle(payload);
}
/**
*
*/
protected abstract _handle (payload: PayloadType): Promise<ReturnType>;
protected get logger () {
return this.core.context.logger;

View File

@ -1,14 +1,19 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { SatoriChannel, SatoriChannelType } from '../../types';
interface ChannelGetPayload {
channel_id: string;
}
const SchemaData = Type.Object({
channel_id: Type.String(),
});
export class ChannelGetAction extends SatoriAction<ChannelGetPayload, SatoriChannel> {
actionName = 'channel.get';
type Payload = Static<typeof SchemaData>;
async handle (payload: ChannelGetPayload): Promise<SatoriChannel> {
export class ChannelGetAction extends SatoriAction<Payload, SatoriChannel> {
actionName = SatoriActionName.ChannelGet;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<SatoriChannel> {
const { channel_id } = payload;
const parts = channel_id.split(':');
@ -31,7 +36,7 @@ export class ChannelGetAction extends SatoriAction<ChannelGetPayload, SatoriChan
} else if (type === 'group') {
// 先从群列表缓存中查找
const groups = await this.core.apis.GroupApi.getGroups();
let group = groups.find(e => e.groupCode === id);
const group = groups.find((e) => e.groupCode === id);
if (!group) {
// 如果缓存中没有,尝试获取详细信息

View File

@ -1,21 +1,26 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { SatoriChannel, SatoriChannelType, SatoriPageResult } from '../../types';
interface ChannelListPayload {
guild_id: string;
next?: string;
}
const SchemaData = Type.Object({
guild_id: Type.String(),
next: Type.Optional(Type.String()),
});
export class ChannelListAction extends SatoriAction<ChannelListPayload, SatoriPageResult<SatoriChannel>> {
actionName = 'channel.list';
type Payload = Static<typeof SchemaData>;
async handle (payload: ChannelListPayload): Promise<SatoriPageResult<SatoriChannel>> {
export class ChannelListAction extends SatoriAction<Payload, SatoriPageResult<SatoriChannel>> {
actionName = SatoriActionName.ChannelList;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<SatoriPageResult<SatoriChannel>> {
const { guild_id } = payload;
// 在 QQ 中,群组只有一个文本频道
// 先从群列表缓存中查找
const groups = await this.core.apis.GroupApi.getGroups();
let group = groups.find(e => e.groupCode === guild_id);
const group = groups.find((e) => e.groupCode === guild_id);
let groupName: string;
if (!group) {

View File

@ -0,0 +1,39 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { GroupNotifyMsgType, NTGroupRequestOperateTypes } from 'napcat-core';
const SchemaData = Type.Object({
message_id: Type.String(), // 邀请请求的 seq
approve: Type.Boolean(),
comment: Type.Optional(Type.String()),
});
type Payload = Static<typeof SchemaData>;
export class GuildApproveAction extends SatoriAction<Payload, void> {
actionName = SatoriActionName.GuildApprove;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<void> {
const { message_id, approve, comment } = payload;
// message_id 是邀请请求的 seq
const notifies = await this.core.apis.GroupApi.getSingleScreenNotifies(true, 100);
const notify = notifies.find(
(e) =>
e.seq == message_id && // 使用 loose equality 以防类型不匹配
(e.type === GroupNotifyMsgType.INVITED_BY_MEMBER || e.type === GroupNotifyMsgType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS)
);
if (!notify) {
throw new Error(`未找到加群邀请: ${message_id}`);
}
const operateType = approve
? NTGroupRequestOperateTypes.KAGREE
: NTGroupRequestOperateTypes.KREFUSE;
await this.core.apis.GroupApi.handleGroupRequest(false, notify, operateType, comment);
}
}

View File

@ -1,19 +1,24 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { SatoriGuild } from '../../types';
interface GuildGetPayload {
guild_id: string;
}
const SchemaData = Type.Object({
guild_id: Type.String(),
});
export class GuildGetAction extends SatoriAction<GuildGetPayload, SatoriGuild> {
actionName = 'guild.get';
type Payload = Static<typeof SchemaData>;
async handle (payload: GuildGetPayload): Promise<SatoriGuild> {
export class GuildGetAction extends SatoriAction<Payload, SatoriGuild> {
actionName = SatoriActionName.GuildGet;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<SatoriGuild> {
const { guild_id } = payload;
// 先从群列表缓存中查找
const groups = await this.core.apis.GroupApi.getGroups();
let group = groups.find(e => e.groupCode === guild_id);
const group = groups.find((e) => e.groupCode === guild_id);
if (!group) {
// 如果缓存中没有,尝试获取详细信息

View File

@ -1,14 +1,19 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { SatoriGuild, SatoriPageResult } from '../../types';
interface GuildListPayload {
next?: string;
}
const SchemaData = Type.Object({
next: Type.Optional(Type.String()),
});
export class GuildListAction extends SatoriAction<GuildListPayload, SatoriPageResult<SatoriGuild>> {
actionName = 'guild.list';
type Payload = Static<typeof SchemaData>;
async handle (_payload: GuildListPayload): Promise<SatoriPageResult<SatoriGuild>> {
export class GuildListAction extends SatoriAction<Payload, SatoriPageResult<SatoriGuild>> {
actionName = SatoriActionName.GuildList;
override payloadSchema = SchemaData;
protected async _handle (_payload: Payload): Promise<SatoriPageResult<SatoriGuild>> {
const groups = await this.core.apis.GroupApi.getGroups(true);
const guilds: SatoriGuild[] = groups.map((group) => ({

View File

@ -0,0 +1,39 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { GroupNotifyMsgType, NTGroupRequestOperateTypes } from 'napcat-core';
const SchemaData = Type.Object({
message_id: Type.String(), // 入群请求的 seq
approve: Type.Boolean(),
comment: Type.Optional(Type.String()),
});
type Payload = Static<typeof SchemaData>;
export class GuildMemberApproveAction extends SatoriAction<Payload, void> {
actionName = SatoriActionName.GuildMemberApprove;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<void> {
const { message_id, approve, comment } = payload;
// message_id 是入群请求的 seq
const notifies = await this.core.apis.GroupApi.getSingleScreenNotifies(true, 100);
const notify = notifies.find(
(e) =>
e.seq === message_id &&
e.type === GroupNotifyMsgType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS
);
if (!notify) {
throw new Error(`未找到入群请求: ${message_id}`);
}
const operateType = approve
? NTGroupRequestOperateTypes.KAGREE
: NTGroupRequestOperateTypes.KREFUSE;
await this.core.apis.GroupApi.handleGroupRequest(false, notify, operateType, comment);
}
}

View File

@ -1,15 +1,20 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { SatoriGuildMember } from '../../types';
interface GuildMemberGetPayload {
guild_id: string;
user_id: string;
}
const SchemaData = Type.Object({
guild_id: Type.String(),
user_id: Type.String(),
});
export class GuildMemberGetAction extends SatoriAction<GuildMemberGetPayload, SatoriGuildMember> {
actionName = 'guild.member.get';
type Payload = Static<typeof SchemaData>;
async handle (payload: GuildMemberGetPayload): Promise<SatoriGuildMember> {
export class GuildMemberGetAction extends SatoriAction<Payload, SatoriGuildMember> {
actionName = SatoriActionName.GuildMemberGet;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<SatoriGuildMember> {
const { guild_id, user_id } = payload;
const memberInfo = await this.core.apis.GroupApi.getGroupMember(guild_id, user_id);

View File

@ -1,15 +1,20 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
interface GuildMemberKickPayload {
guild_id: string;
user_id: string;
permanent?: boolean;
}
const SchemaData = Type.Object({
guild_id: Type.String(),
user_id: Type.String(),
permanent: Type.Optional(Type.Boolean({ default: false })),
});
export class GuildMemberKickAction extends SatoriAction<GuildMemberKickPayload, void> {
actionName = 'guild.member.kick';
type Payload = Static<typeof SchemaData>;
async handle (payload: GuildMemberKickPayload): Promise<void> {
export class GuildMemberKickAction extends SatoriAction<Payload, void> {
actionName = SatoriActionName.GuildMemberKick;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<void> {
const { guild_id, user_id, permanent } = payload;
await this.core.apis.GroupApi.kickMember(

View File

@ -1,16 +1,21 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { SatoriGuildMember, SatoriPageResult } from '../../types';
import { GroupMember } from 'napcat-core';
interface GuildMemberListPayload {
guild_id: string;
next?: string;
}
const SchemaData = Type.Object({
guild_id: Type.String(),
next: Type.Optional(Type.String()),
});
export class GuildMemberListAction extends SatoriAction<GuildMemberListPayload, SatoriPageResult<SatoriGuildMember>> {
actionName = 'guild.member.list';
type Payload = Static<typeof SchemaData>;
async handle (payload: GuildMemberListPayload): Promise<SatoriPageResult<SatoriGuildMember>> {
export class GuildMemberListAction extends SatoriAction<Payload, SatoriPageResult<SatoriGuildMember>> {
actionName = SatoriActionName.GuildMemberList;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<SatoriPageResult<SatoriGuildMember>> {
const { guild_id } = payload;
// 使用 getGroupMemberAll 获取所有群成员

View File

@ -1,15 +1,20 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
interface GuildMemberMutePayload {
guild_id: string;
user_id: string;
duration?: number; // 禁言时长毫秒0 表示解除禁言
}
const SchemaData = Type.Object({
guild_id: Type.String(),
user_id: Type.String(),
duration: Type.Optional(Type.Number({ default: 0 })), // 禁言时长毫秒0 表示解除禁言
});
export class GuildMemberMuteAction extends SatoriAction<GuildMemberMutePayload, void> {
actionName = 'guild.member.mute';
type Payload = Static<typeof SchemaData>;
async handle (payload: GuildMemberMutePayload): Promise<void> {
export class GuildMemberMuteAction extends SatoriAction<Payload, void> {
actionName = SatoriActionName.GuildMemberMute;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<void> {
const { guild_id, user_id, duration } = payload;
// 将毫秒转换为秒

View File

@ -10,13 +10,16 @@ import { ChannelGetAction } from './channel/ChannelGet';
import { ChannelListAction } from './channel/ChannelList';
import { GuildGetAction } from './guild/GuildGet';
import { GuildListAction } from './guild/GuildList';
import { GuildApproveAction } from './guild/GuildApprove';
import { GuildMemberGetAction } from './guild/GuildMemberGet';
import { GuildMemberListAction } from './guild/GuildMemberList';
import { GuildMemberKickAction } from './guild/GuildMemberKick';
import { GuildMemberMuteAction } from './guild/GuildMemberMute';
import { GuildMemberApproveAction } from './guild/GuildMemberApprove';
import { UserGetAction } from './user/UserGet';
import { FriendListAction } from './user/FriendList';
import { FriendApproveAction } from './user/FriendApprove';
import { LoginGetAction } from './login/LoginGet';
import { UploadCreateAction } from './upload/UploadCreate';
export type SatoriActionMap = Map<string, SatoriAction<unknown, unknown>>;
@ -38,14 +41,18 @@ export function createSatoriActionMap (
// 群组相关
new GuildGetAction(satoriAdapter, core),
new GuildListAction(satoriAdapter, core),
new GuildApproveAction(satoriAdapter, core),
new GuildMemberGetAction(satoriAdapter, core),
new GuildMemberListAction(satoriAdapter, core),
new GuildMemberKickAction(satoriAdapter, core),
new GuildMemberMuteAction(satoriAdapter, core),
new GuildMemberApproveAction(satoriAdapter, core),
// 用户相关
new UserGetAction(satoriAdapter, core),
new FriendListAction(satoriAdapter, core),
new FriendApproveAction(satoriAdapter, core),
// 登录相关
new LoginGetAction(satoriAdapter, core),
// 上传相关
new UploadCreateAction(satoriAdapter, core),
];
@ -57,4 +64,5 @@ export function createSatoriActionMap (
return actionMap;
}
export { SatoriAction } from './SatoriAction';
export { SatoriAction, SatoriCheckResult, SatoriResponse, SatoriResponseHelper } from './SatoriAction';
export { SatoriActionName, SatoriActionNameType } from './router';

View File

@ -0,0 +1,26 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { SatoriLogin, SatoriLoginStatus } from '../../types';
const SchemaData = Type.Object({});
type Payload = Static<typeof SchemaData>;
export class LoginGetAction extends SatoriAction<Payload, SatoriLogin> {
actionName = SatoriActionName.LoginGet;
override payloadSchema = SchemaData;
protected async _handle (_payload: Payload): Promise<SatoriLogin> {
return {
user: {
id: this.selfInfo.uin,
name: this.selfInfo.nick,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${this.selfInfo.uin}&s=640`,
},
self_id: this.selfInfo.uin,
platform: this.platform,
status: SatoriLoginStatus.ONLINE,
};
}
}

View File

@ -1,16 +1,21 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { SatoriMessage, SatoriChannelType } from '../../types';
import { ChatType, SendMessageElement } from 'napcat-core';
interface MessageCreatePayload {
channel_id: string;
content: string;
}
const SchemaData = Type.Object({
channel_id: Type.String(),
content: Type.String(),
});
export class MessageCreateAction extends SatoriAction<MessageCreatePayload, SatoriMessage[]> {
actionName = 'message.create';
type Payload = Static<typeof SchemaData>;
async handle (payload: MessageCreatePayload): Promise<SatoriMessage[]> {
export class MessageCreateAction extends SatoriAction<Payload, SatoriMessage[]> {
actionName = SatoriActionName.MessageCreate;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<SatoriMessage[]> {
const { channel_id, content } = payload;
// 解析 channel_id格式: private:{user_id} 或 group:{group_id}

View File

@ -1,15 +1,20 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { ChatType } from 'napcat-core';
interface MessageDeletePayload {
channel_id: string;
message_id: string;
}
const SchemaData = Type.Object({
channel_id: Type.String(),
message_id: Type.String(),
});
export class MessageDeleteAction extends SatoriAction<MessageDeletePayload, void> {
actionName = 'message.delete';
type Payload = Static<typeof SchemaData>;
async handle (payload: MessageDeletePayload): Promise<void> {
export class MessageDeleteAction extends SatoriAction<Payload, void> {
actionName = SatoriActionName.MessageDelete;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<void> {
const { channel_id, message_id } = payload;
const parts = channel_id.split(':');

View File

@ -1,16 +1,21 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { SatoriMessage, SatoriChannelType } from '../../types';
import { ChatType } from 'napcat-core';
interface MessageGetPayload {
channel_id: string;
message_id: string;
}
const SchemaData = Type.Object({
channel_id: Type.String(),
message_id: Type.String(),
});
export class MessageGetAction extends SatoriAction<MessageGetPayload, SatoriMessage> {
actionName = 'message.get';
type Payload = Static<typeof SchemaData>;
async handle (payload: MessageGetPayload): Promise<SatoriMessage> {
export class MessageGetAction extends SatoriAction<Payload, SatoriMessage> {
actionName = SatoriActionName.MessageGet;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<SatoriMessage> {
const { channel_id, message_id } = payload;
const parts = channel_id.split(':');

View File

@ -0,0 +1,57 @@
/**
* Satori Action
*/
export const SatoriActionName = {
// 消息相关
MessageCreate: 'message.create',
MessageGet: 'message.get',
MessageDelete: 'message.delete',
MessageUpdate: 'message.update',
MessageList: 'message.list',
// 频道相关
ChannelGet: 'channel.get',
ChannelList: 'channel.list',
ChannelCreate: 'channel.create',
ChannelUpdate: 'channel.update',
ChannelDelete: 'channel.delete',
ChannelMute: 'channel.mute',
// 群组/公会相关
GuildGet: 'guild.get',
GuildList: 'guild.list',
GuildApprove: 'guild.approve',
// 群成员相关
GuildMemberGet: 'guild.member.get',
GuildMemberList: 'guild.member.list',
GuildMemberKick: 'guild.member.kick',
GuildMemberMute: 'guild.member.mute',
GuildMemberApprove: 'guild.member.approve',
GuildMemberRole: 'guild.member.role',
// 角色相关
GuildRoleList: 'guild.role.list',
GuildRoleCreate: 'guild.role.create',
GuildRoleUpdate: 'guild.role.update',
GuildRoleDelete: 'guild.role.delete',
// 用户相关
UserGet: 'user.get',
UserChannelCreate: 'user.channel.create',
// 好友相关
FriendList: 'friend.list',
FriendApprove: 'friend.approve',
// 登录相关
LoginGet: 'login.get',
// 上传相关
UploadCreate: 'upload.create',
// 内部互操作Satori 可选)
InternalAction: 'internal.action',
} as const;
export type SatoriActionNameType = typeof SatoriActionName[keyof typeof SatoriActionName];

View File

@ -1,17 +1,20 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
interface UploadCreatePayload {
[key: string]: unknown;
}
const SchemaData = Type.Record(Type.String(), Type.Unknown());
type Payload = Static<typeof SchemaData>;
interface UploadResult {
[key: string]: string;
}
export class UploadCreateAction extends SatoriAction<UploadCreatePayload, UploadResult> {
actionName = 'upload.create';
export class UploadCreateAction extends SatoriAction<Payload, UploadResult> {
actionName = SatoriActionName.UploadCreate;
override payloadSchema = SchemaData;
async handle (payload: UploadCreatePayload): Promise<UploadResult> {
protected async _handle (payload: Payload): Promise<UploadResult> {
const result: UploadResult = {};
// 处理上传的文件

View File

@ -1,21 +1,26 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
interface FriendApprovePayload {
message_id: string;
approve: boolean;
comment?: string;
}
const SchemaData = Type.Object({
message_id: Type.String(),
approve: Type.Boolean(),
comment: Type.Optional(Type.String()),
});
export class FriendApproveAction extends SatoriAction<FriendApprovePayload, void> {
actionName = 'friend.approve';
type Payload = Static<typeof SchemaData>;
async handle (payload: FriendApprovePayload): Promise<void> {
export class FriendApproveAction extends SatoriAction<Payload, void> {
actionName = SatoriActionName.FriendApprove;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<void> {
const { message_id, approve } = payload;
// message_id 格式: reqTime (好友请求的时间戳)
// 需要从好友请求列表中找到对应的请求
const buddyReqData = await this.core.apis.FriendApi.getBuddyReq();
const notify = buddyReqData.buddyReqs.find(e => e.reqTime === message_id);
const notify = buddyReqData.buddyReqs.find((e) => e.reqTime === message_id);
if (!notify) {
throw new Error(`未找到好友请求: ${message_id}`);

View File

@ -1,14 +1,19 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { SatoriUser, SatoriPageResult } from '../../types';
interface FriendListPayload {
next?: string;
}
const SchemaData = Type.Object({
next: Type.Optional(Type.String()),
});
export class FriendListAction extends SatoriAction<FriendListPayload, SatoriPageResult<SatoriUser>> {
actionName = 'friend.list';
type Payload = Static<typeof SchemaData>;
async handle (_payload: FriendListPayload): Promise<SatoriPageResult<SatoriUser>> {
export class FriendListAction extends SatoriAction<Payload, SatoriPageResult<SatoriUser>> {
actionName = SatoriActionName.FriendList;
override payloadSchema = SchemaData;
protected async _handle (_payload: Payload): Promise<SatoriPageResult<SatoriUser>> {
const friends = await this.core.apis.FriendApi.getBuddy();
const friendList: SatoriUser[] = friends.map((friend) => ({

View File

@ -1,14 +1,19 @@
import { Static, Type } from '@sinclair/typebox';
import { SatoriAction } from '../SatoriAction';
import { SatoriActionName } from '../router';
import { SatoriUser } from '../../types';
interface UserGetPayload {
user_id: string;
}
const SchemaData = Type.Object({
user_id: Type.String(),
});
export class UserGetAction extends SatoriAction<UserGetPayload, SatoriUser> {
actionName = 'user.get';
type Payload = Static<typeof SchemaData>;
async handle (payload: UserGetPayload): Promise<SatoriUser> {
export class UserGetAction extends SatoriAction<Payload, SatoriUser> {
actionName = SatoriActionName.UserGet;
override payloadSchema = SchemaData;
protected async _handle (payload: Payload): Promise<SatoriUser> {
const { user_id } = payload;
const uid = await this.core.apis.UserApi.getUidByUinV2(user_id);

View File

@ -1,4 +1,4 @@
import { NapCatCore, RawMessage, ChatType } from 'napcat-core';
import { NapCatCore, RawMessage, ChatType, GroupNotify, FriendRequest } from 'napcat-core';
import { NapCatSatoriAdapter } from '../index';
import {
SatoriEvent,
@ -6,6 +6,51 @@ import {
SatoriLoginStatus,
} from '../types';
/**
* Satori
*/
export const SatoriEventType = {
// 消息事件
MESSAGE_CREATED: 'message-created',
MESSAGE_UPDATED: 'message-updated',
MESSAGE_DELETED: 'message-deleted',
// 频道事件
CHANNEL_CREATED: 'channel-created',
CHANNEL_UPDATED: 'channel-updated',
CHANNEL_DELETED: 'channel-deleted',
// 群组/公会事件
GUILD_ADDED: 'guild-added',
GUILD_UPDATED: 'guild-updated',
GUILD_REMOVED: 'guild-removed',
GUILD_REQUEST: 'guild-request',
// 群成员事件
GUILD_MEMBER_ADDED: 'guild-member-added',
GUILD_MEMBER_UPDATED: 'guild-member-updated',
GUILD_MEMBER_REMOVED: 'guild-member-removed',
GUILD_MEMBER_REQUEST: 'guild-member-request',
// 角色事件
GUILD_ROLE_CREATED: 'guild-role-created',
GUILD_ROLE_UPDATED: 'guild-role-updated',
GUILD_ROLE_DELETED: 'guild-role-deleted',
// 好友事件
FRIEND_REQUEST: 'friend-request',
// 登录事件
LOGIN_ADDED: 'login-added',
LOGIN_REMOVED: 'login-removed',
LOGIN_UPDATED: 'login-updated',
// 内部事件
INTERNAL: 'internal',
} as const;
export type SatoriEventTypeName = typeof SatoriEventType[keyof typeof SatoriEventType];
export class SatoriEventApi {
private satoriAdapter: NapCatSatoriAdapter;
private core: NapCatCore;
@ -28,6 +73,19 @@ export class SatoriEventApi {
return this.core.selfInfo.uin;
}
/**
*
*/
private createBaseEvent (type: SatoriEventTypeName): SatoriEvent {
return {
id: this.getNextEventId(),
type,
platform: this.platform,
self_id: this.selfId,
timestamp: Date.now(),
};
}
/**
* NapCat Satori
*/
@ -36,25 +94,20 @@ export class SatoriEventApi {
const content = await this.satoriAdapter.apis.MsgApi.parseElements(message.elements);
const isPrivate = message.chatType === ChatType.KCHATTYPEC2C;
const event: SatoriEvent = {
id: this.getNextEventId(),
type: 'message-created',
platform: this.platform,
self_id: this.selfId,
timestamp: parseInt(message.msgTime) * 1000,
channel: {
id: isPrivate ? `private:${message.senderUin}` : `group:${message.peerUin}`,
type: isPrivate ? SatoriChannelType.DIRECT : SatoriChannelType.TEXT,
},
user: {
id: message.senderUin,
name: message.sendNickName,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${message.senderUin}&s=640`,
},
message: {
id: message.msgId,
content,
},
const event = this.createBaseEvent(SatoriEventType.MESSAGE_CREATED);
event.timestamp = parseInt(message.msgTime) * 1000;
event.channel = {
id: isPrivate ? `private:${message.senderUin}` : `group:${message.peerUin}`,
type: isPrivate ? SatoriChannelType.DIRECT : SatoriChannelType.TEXT,
};
event.user = {
id: message.senderUin,
name: message.sendNickName,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${message.senderUin}&s=640`,
};
event.message = {
id: message.msgId,
content,
};
if (!isPrivate) {
@ -75,65 +128,95 @@ export class SatoriEventApi {
}
}
/**
*
*/
async createMessageUpdatedEvent (message: RawMessage): Promise<SatoriEvent | null> {
try {
const content = await this.satoriAdapter.apis.MsgApi.parseElements(message.elements);
const isPrivate = message.chatType === ChatType.KCHATTYPEC2C;
const event = this.createBaseEvent(SatoriEventType.MESSAGE_UPDATED);
event.channel = {
id: isPrivate ? `private:${message.senderUin}` : `group:${message.peerUin}`,
type: isPrivate ? SatoriChannelType.DIRECT : SatoriChannelType.TEXT,
};
event.user = {
id: message.senderUin,
name: message.sendNickName,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${message.senderUin}&s=640`,
};
event.message = {
id: message.msgId,
content,
};
return event;
} catch (error) {
this.core.context.logger.logError('[Satori] 创建消息更新事件失败:', error);
return null;
}
}
/**
*
*/
createFriendRequestEvent (
userId: string,
userName: string,
comment: string,
flag: string
): SatoriEvent {
return {
id: this.getNextEventId(),
type: 'friend-request',
platform: this.platform,
self_id: this.selfId,
timestamp: Date.now(),
user: {
id: userId,
name: userName,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${userId}&s=640`,
},
message: {
id: flag,
content: comment,
},
createFriendRequestEvent (request: FriendRequest): SatoriEvent {
const event = this.createBaseEvent(SatoriEventType.FRIEND_REQUEST);
event.user = {
id: request.friendUid,
name: request.friendNick,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${request.friendUid}&s=640`,
};
event.message = {
id: request.reqTime,
content: request.extWords,
};
return event;
}
/**
*
*/
createGuildMemberRequestEvent (
guildId: string,
guildName: string,
userId: string,
userName: string,
comment: string,
flag: string
): SatoriEvent {
return {
id: this.getNextEventId(),
type: 'guild-member-request',
platform: this.platform,
self_id: this.selfId,
timestamp: Date.now(),
guild: {
id: guildId,
name: guildName,
avatar: `https://p.qlogo.cn/gh/${guildId}/${guildId}/640`,
},
user: {
id: userId,
name: userName,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${userId}&s=640`,
},
message: {
id: flag,
content: comment,
},
createGuildMemberRequestEvent (notify: GroupNotify): SatoriEvent {
const event = this.createBaseEvent(SatoriEventType.GUILD_MEMBER_REQUEST);
event.guild = {
id: notify.group.groupCode,
name: notify.group.groupName,
avatar: `https://p.qlogo.cn/gh/${notify.group.groupCode}/${notify.group.groupCode}/640`,
};
event.user = {
id: notify.user1.uid,
name: notify.user1.nickName,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${notify.user1.uid}&s=640`,
};
event.message = {
id: notify.seq,
content: notify.postscript,
};
return event;
}
/**
*
*/
createGuildRequestEvent (notify: GroupNotify): SatoriEvent {
const event = this.createBaseEvent(SatoriEventType.GUILD_REQUEST);
event.guild = {
id: notify.group.groupCode,
name: notify.group.groupName,
avatar: `https://p.qlogo.cn/gh/${notify.group.groupCode}/${notify.group.groupCode}/640`,
};
event.user = {
id: notify.user2.uid,
name: notify.user2.nickName,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${notify.user2.uid}&s=640`,
};
event.message = {
id: notify.seq,
content: notify.postscript,
};
return event;
}
/**
@ -146,22 +229,16 @@ export class SatoriEventApi {
userName: string,
operatorId?: string
): SatoriEvent {
const event: SatoriEvent = {
id: this.getNextEventId(),
type: 'guild-member-added',
platform: this.platform,
self_id: this.selfId,
timestamp: Date.now(),
guild: {
id: guildId,
name: guildName,
avatar: `https://p.qlogo.cn/gh/${guildId}/${guildId}/640`,
},
user: {
id: userId,
name: userName,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${userId}&s=640`,
},
const event = this.createBaseEvent(SatoriEventType.GUILD_MEMBER_ADDED);
event.guild = {
id: guildId,
name: guildName,
avatar: `https://p.qlogo.cn/gh/${guildId}/${guildId}/640`,
};
event.user = {
id: userId,
name: userName,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${userId}&s=640`,
};
if (operatorId) {
@ -184,22 +261,16 @@ export class SatoriEventApi {
userName: string,
operatorId?: string
): SatoriEvent {
const event: SatoriEvent = {
id: this.getNextEventId(),
type: 'guild-member-removed',
platform: this.platform,
self_id: this.selfId,
timestamp: Date.now(),
guild: {
id: guildId,
name: guildName,
avatar: `https://p.qlogo.cn/gh/${guildId}/${guildId}/640`,
},
user: {
id: userId,
name: userName,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${userId}&s=640`,
},
const event = this.createBaseEvent(SatoriEventType.GUILD_MEMBER_REMOVED);
event.guild = {
id: guildId,
name: guildName,
avatar: `https://p.qlogo.cn/gh/${guildId}/${guildId}/640`,
};
event.user = {
id: userId,
name: userName,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${userId}&s=640`,
};
if (operatorId) {
@ -212,6 +283,38 @@ export class SatoriEventApi {
return event;
}
/**
*
*/
createGuildAddedEvent (
guildId: string,
guildName: string
): SatoriEvent {
const event = this.createBaseEvent(SatoriEventType.GUILD_ADDED);
event.guild = {
id: guildId,
name: guildName,
avatar: `https://p.qlogo.cn/gh/${guildId}/${guildId}/640`,
};
return event;
}
/**
* 退
*/
createGuildRemovedEvent (
guildId: string,
guildName: string
): SatoriEvent {
const event = this.createBaseEvent(SatoriEventType.GUILD_REMOVED);
event.guild = {
id: guildId,
name: guildName,
avatar: `https://p.qlogo.cn/gh/${guildId}/${guildId}/640`,
};
return event;
}
/**
*
*/
@ -222,24 +325,18 @@ export class SatoriEventApi {
operatorId?: string
): SatoriEvent {
const isPrivate = channelId.startsWith('private:');
const event: SatoriEvent = {
id: this.getNextEventId(),
type: 'message-deleted',
platform: this.platform,
self_id: this.selfId,
timestamp: Date.now(),
channel: {
id: channelId,
type: isPrivate ? SatoriChannelType.DIRECT : SatoriChannelType.TEXT,
},
user: {
id: userId,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${userId}&s=640`,
},
message: {
id: messageId,
content: '',
},
const event = this.createBaseEvent(SatoriEventType.MESSAGE_DELETED);
event.channel = {
id: channelId,
type: isPrivate ? SatoriChannelType.DIRECT : SatoriChannelType.TEXT,
};
event.user = {
id: userId,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${userId}&s=640`,
};
event.message = {
id: messageId,
content: '',
};
if (operatorId) {
@ -252,26 +349,69 @@ export class SatoriEventApi {
return event;
}
/**
*
*/
createLoginAddedEvent (): SatoriEvent {
const event = this.createBaseEvent(SatoriEventType.LOGIN_ADDED);
event.login = {
user: {
id: this.selfId,
name: this.core.selfInfo.nick,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${this.selfId}&s=640`,
},
self_id: this.selfId,
platform: this.platform,
status: SatoriLoginStatus.ONLINE,
};
return event;
}
/**
*
*/
createLoginRemovedEvent (): SatoriEvent {
const event = this.createBaseEvent(SatoriEventType.LOGIN_REMOVED);
event.login = {
user: {
id: this.selfId,
name: this.core.selfInfo.nick,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${this.selfId}&s=640`,
},
self_id: this.selfId,
platform: this.platform,
status: SatoriLoginStatus.OFFLINE,
};
return event;
}
/**
*
*/
createLoginUpdatedEvent (status: SatoriLoginStatus): SatoriEvent {
return {
id: this.getNextEventId(),
type: 'login-updated',
platform: this.platform,
self_id: this.selfId,
timestamp: Date.now(),
login: {
user: {
id: this.selfId,
name: this.core.selfInfo.nick,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${this.selfId}&s=640`,
},
self_id: this.selfId,
platform: this.platform,
status,
const event = this.createBaseEvent(SatoriEventType.LOGIN_UPDATED);
event.login = {
user: {
id: this.selfId,
name: this.core.selfInfo.nick,
avatar: `https://q1.qlogo.cn/g?b=qq&nk=${this.selfId}&s=640`,
},
self_id: this.selfId,
platform: this.platform,
status,
};
return event;
}
/**
*
*/
createInternalEvent (typeName: string, data: Record<string, unknown>): SatoriEvent {
const event = this.createBaseEvent(SatoriEventType.INTERNAL);
event._type = typeName;
event._data = data;
return event;
}
}
export { SatoriEventType as EventType };

View File

@ -1,75 +1,32 @@
import { NapCatCore, MessageElement, ElementType, NTMsgAtType } from 'napcat-core';
import { NapCatSatoriAdapter } from '../index';
import SatoriElement from '@satorijs/element';
/**
* Satori API
* 使 @satorijs/element
*/
export class SatoriMsgApi {
private core: NapCatCore;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
private _adapter: NapCatSatoriAdapter;
constructor (_satoriAdapter: NapCatSatoriAdapter, core: NapCatCore) {
constructor (satoriAdapter: NapCatSatoriAdapter, core: NapCatCore) {
this._adapter = satoriAdapter;
this.core = core;
}
/**
* Satori NapCat
* 使 @satorijs/element
*/
async parseContent (content: string): Promise<MessageElement[]> {
const elements: MessageElement[] = [];
const parsed = SatoriElement.parse(content);
// 简单的 XML 解析
const tagRegex = /<(\w+)([^>]*)(?:\/>|>([\s\S]*?)<\/\1>)/g;
let lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = tagRegex.exec(content)) !== null) {
// 处理标签前的文本
if (match.index > lastIndex) {
const text = content.slice(lastIndex, match.index);
if (text.trim()) {
elements.push(this.createTextElement(text));
}
}
const [, tagName, attrs = '', innerContent] = match;
const parsedAttrs = this.parseAttributes(attrs);
switch (tagName) {
case 'at':
elements.push(await this.createAtElement(parsedAttrs));
break;
case 'img':
case 'image':
elements.push(await this.createImageElement(parsedAttrs));
break;
case 'audio':
elements.push(await this.createAudioElement(parsedAttrs));
break;
case 'video':
elements.push(await this.createVideoElement(parsedAttrs));
break;
case 'file':
elements.push(await this.createFileElement(parsedAttrs));
break;
case 'face':
elements.push(this.createFaceElement(parsedAttrs));
break;
case 'quote':
elements.push(await this.createQuoteElement(parsedAttrs));
break;
default:
// 未知标签,作为文本处理
if (innerContent) {
elements.push(this.createTextElement(innerContent));
}
}
lastIndex = match.index + match[0].length;
}
// 处理剩余文本
if (lastIndex < content.length) {
const text = content.slice(lastIndex);
if (text.trim()) {
elements.push(this.createTextElement(text));
}
for (const elem of parsed) {
const parsedElements = await this.parseSatoriElement(elem);
elements.push(...parsedElements);
}
// 如果没有解析到任何元素,将整个内容作为文本
@ -81,73 +38,231 @@ export class SatoriMsgApi {
}
/**
* NapCat Satori
* satorijs
*/
async parseElements (elements: MessageElement[]): Promise<string> {
const parts: string[] = [];
private async parseSatoriElement (elem: SatoriElement): Promise<MessageElement[]> {
const elements: MessageElement[] = [];
for (const element of elements) {
switch (element.elementType) {
case ElementType.TEXT:
if (element.textElement) {
parts.push(this.escapeXml(element.textElement.content));
}
break;
case ElementType.PIC:
if (element.picElement) {
const src = element.picElement.sourcePath || '';
parts.push(`<img src="${this.escapeXml(src)}"/>`);
}
break;
case ElementType.PTT:
if (element.pttElement) {
const src = element.pttElement.filePath || '';
parts.push(`<audio src="${this.escapeXml(src)}"/>`);
}
break;
case ElementType.VIDEO:
if (element.videoElement) {
const src = element.videoElement.filePath || '';
parts.push(`<video src="${this.escapeXml(src)}"/>`);
}
break;
case ElementType.FILE:
if (element.fileElement) {
const src = element.fileElement.filePath || '';
parts.push(`<file src="${this.escapeXml(src)}"/>`);
}
break;
case ElementType.FACE:
if (element.faceElement) {
parts.push(`<face id="${element.faceElement.faceIndex}"/>`);
}
break;
case ElementType.REPLY:
if (element.replyElement) {
parts.push(`<quote id="${element.replyElement.sourceMsgIdInRecords}"/>`);
}
break;
default:
// 其他类型暂不处理
break;
switch (elem.type) {
case 'text':
if (elem.attrs['content']) {
elements.push(this.createTextElement(elem.attrs['content']));
}
break;
case 'at': {
const attrs = elem.attrs;
elements.push(await this.createAtElement({
id: attrs['id'] || '',
type: attrs['type'] || '',
name: attrs['name'] || '',
}));
break;
}
case 'img':
case 'image': {
const attrs = elem.attrs;
elements.push(await this.createImageElement({
src: attrs['src'] || '',
width: attrs['width'] || '',
height: attrs['height'] || '',
}));
break;
}
case 'audio': {
const attrs = elem.attrs;
elements.push(await this.createAudioElement({
src: attrs['src'] || '',
duration: attrs['duration'] || '',
}));
break;
}
case 'video': {
const attrs = elem.attrs;
elements.push(await this.createVideoElement({
src: attrs['src'] || '',
}));
break;
}
case 'file': {
const attrs = elem.attrs;
elements.push(await this.createFileElement({
src: attrs['src'] || '',
title: attrs['title'] || '',
}));
break;
}
case 'face': {
const attrs = elem.attrs;
elements.push(this.createFaceElement({
id: attrs['id'] || '0',
}));
break;
}
case 'quote': {
const attrs = elem.attrs;
elements.push(await this.createQuoteElement({
id: attrs['id'] || '',
}));
break;
}
case 'a': {
const href = elem.attrs['href'];
if (href) {
const linkText = elem.children.map((c) => c.toString()).join('');
elements.push(this.createTextElement(`${linkText} (${href})`));
}
break;
}
case 'button': {
const text = elem.attrs['text'];
if (text) {
elements.push(this.createTextElement(`[${text}]`));
}
break;
}
case 'br':
elements.push(this.createTextElement('\n'));
break;
case 'p':
for (const child of elem.children) {
elements.push(...await this.parseSatoriElement(child));
}
elements.push(this.createTextElement('\n'));
break;
default:
// 递归处理子元素
if (elem.children) {
for (const child of elem.children) {
elements.push(...await this.parseSatoriElement(child));
}
}
}
return parts.join('');
return elements;
}
private parseAttributes (attrString: string): Record<string, string> {
const attrs: Record<string, string> = {};
const attrRegex = /(\w+)=["']([^"']*)["']/g;
let match: RegExpExecArray | null;
while ((match = attrRegex.exec(attrString)) !== null) {
const key = match[1];
const value = match[2];
if (key !== undefined && value !== undefined) {
attrs[key] = value;
/**
* NapCat Satori XML
*/
async parseElements (elements: MessageElement[]): Promise<string> {
const satoriElements: SatoriElement[] = [];
for (const element of elements) {
const node = await this.elementToSatoriElement(element);
if (node) {
satoriElements.push(node);
}
}
return attrs;
return satoriElements.map((e) => e.toString()).join('');
}
/**
* SatoriElement
*/
private async elementToSatoriElement (element: MessageElement): Promise<SatoriElement | null> {
switch (element.elementType) {
case ElementType.TEXT:
if (element.textElement) {
if (element.textElement.atType === NTMsgAtType.ATTYPEALL) {
return SatoriElement('at', { type: 'all' });
} else if (element.textElement.atType === NTMsgAtType.ATTYPEONE && element.textElement.atUid) {
const uin = await this.core.apis.UserApi.getUinByUidV2(element.textElement.atUid);
return SatoriElement('at', { id: uin, name: element.textElement.content?.replace('@', '') });
}
return SatoriElement.text(element.textElement.content);
}
break;
case ElementType.PIC:
if (element.picElement) {
const src = await this.getMediaUrl(element.picElement.sourcePath || '', 'image');
return SatoriElement('img', {
src,
width: element.picElement.picWidth,
height: element.picElement.picHeight,
});
}
break;
case ElementType.PTT:
if (element.pttElement) {
const src = await this.getMediaUrl(element.pttElement.filePath || '', 'audio');
return SatoriElement('audio', {
src,
duration: element.pttElement.duration,
});
}
break;
case ElementType.VIDEO:
if (element.videoElement) {
const src = await this.getMediaUrl(element.videoElement.filePath || '', 'video');
return SatoriElement('video', { src });
}
break;
case ElementType.FILE:
if (element.fileElement) {
const src = element.fileElement.filePath || '';
return SatoriElement('file', {
src,
title: element.fileElement.fileName,
});
}
break;
case ElementType.FACE:
if (element.faceElement) {
return SatoriElement('face', { id: element.faceElement.faceIndex });
}
break;
case ElementType.REPLY:
if (element.replyElement) {
const msgId = element.replyElement.sourceMsgIdInRecords || element.replyElement.replayMsgId || '';
return SatoriElement('quote', { id: msgId });
}
break;
case ElementType.MFACE:
if (element.marketFaceElement) {
return SatoriElement('face', { id: element.marketFaceElement.emojiId || '0' });
}
break;
default:
break;
}
return null;
}
/**
* URL
*/
private async getMediaUrl (path: string, _type: 'image' | 'audio' | 'video'): Promise<string> {
if (path.startsWith('http://') || path.startsWith('https://') || path.startsWith('data:')) {
return path;
}
if (path.startsWith('/') || /^[a-zA-Z]:/.test(path)) {
return `file://${path.replace(/\\/g, '/')}`;
}
return path;
}
private createTextElement (content: string): MessageElement {
@ -155,7 +270,7 @@ export class SatoriMsgApi {
elementType: ElementType.TEXT,
elementId: '',
textElement: {
content: this.unescapeXml(content),
content,
atType: NTMsgAtType.ATTYPEUNKNOWN,
atUid: '',
atTinyId: '',
@ -164,9 +279,8 @@ export class SatoriMsgApi {
};
}
private async createAtElement (attrs: Record<string, string>): Promise<MessageElement> {
const id = attrs['id'] || '';
const type = attrs['type'];
private async createAtElement (attrs: { id: string; type?: string; name?: string; }): Promise<MessageElement> {
const { id, type } = attrs;
if (type === 'all') {
return {
@ -198,34 +312,33 @@ export class SatoriMsgApi {
};
}
private async createImageElement (attrs: Record<string, string>): Promise<MessageElement> {
const src = attrs['src'] || '';
// 这里需要根据 src 类型处理URL、base64、本地路径等
private async createImageElement (attrs: { src: string; width?: string; height?: string; }): Promise<MessageElement> {
const src = attrs.src;
return {
elementType: ElementType.PIC,
elementId: '',
picElement: {
sourcePath: src,
picWidth: parseInt(attrs['width'] || '0', 10),
picHeight: parseInt(attrs['height'] || '0', 10),
picWidth: parseInt(attrs.width || '0', 10),
picHeight: parseInt(attrs.height || '0', 10),
},
} as MessageElement;
}
private async createAudioElement (attrs: Record<string, string>): Promise<MessageElement> {
const src = attrs['src'] || '';
private async createAudioElement (attrs: { src: string; duration?: string; }): Promise<MessageElement> {
const src = attrs.src;
return {
elementType: ElementType.PTT,
elementId: '',
pttElement: {
filePath: src,
duration: parseInt(attrs['duration'] || '0', 10),
duration: parseInt(attrs.duration || '0', 10),
},
} as MessageElement;
}
private async createVideoElement (attrs: Record<string, string>): Promise<MessageElement> {
const src = attrs['src'] || '';
private async createVideoElement (attrs: { src: string; }): Promise<MessageElement> {
const src = attrs.src;
return {
elementType: ElementType.VIDEO,
elementId: '',
@ -238,32 +351,32 @@ export class SatoriMsgApi {
} as MessageElement;
}
private async createFileElement (attrs: Record<string, string>): Promise<MessageElement> {
const src = attrs['src'] || '';
private async createFileElement (attrs: { src: string; title?: string; }): Promise<MessageElement> {
const src = attrs.src;
return {
elementType: ElementType.FILE,
elementId: '',
fileElement: {
filePath: src,
fileName: attrs['title'] || '',
fileName: attrs.title || '',
fileSize: '',
},
} as MessageElement;
}
private createFaceElement (attrs: Record<string, string>): MessageElement {
private createFaceElement (attrs: { id: string; }): MessageElement {
return {
elementType: ElementType.FACE,
elementId: '',
faceElement: {
faceIndex: parseInt(attrs['id'] || '0', 10),
faceIndex: parseInt(attrs.id || '0', 10),
faceType: 1,
},
} as MessageElement;
}
private async createQuoteElement (attrs: Record<string, string>): Promise<MessageElement> {
const id = attrs['id'] || '';
private async createQuoteElement (attrs: { id: string; }): Promise<MessageElement> {
const id = attrs.id;
return {
elementType: ElementType.REPLY,
elementId: '',
@ -276,22 +389,4 @@ export class SatoriMsgApi {
},
} as MessageElement;
}
private escapeXml (str: string): string {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
}
private unescapeXml (str: string): string {
return str
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'");
}
}

View File

@ -0,0 +1 @@
export * from './xml';

View File

@ -0,0 +1,320 @@
/**
* Satori XML
*/
export interface SatoriXmlNode {
type: string;
attrs: Record<string, string>;
children: (SatoriXmlNode | string)[];
}
/**
* Satori XML
* Satori XML
* 使
*/
export class SatoriXmlUtils {
/**
* Satori XML
*/
static parse (xmlString: string): SatoriXmlNode[] {
const nodes: SatoriXmlNode[] = [];
const tagRegex = /<(\w+)([^>]*)(?:\/>|>([\s\S]*?)<\/\1>)/g;
let lastIndex = 0;
let match: RegExpExecArray | null;
while ((match = tagRegex.exec(xmlString)) !== null) {
// 处理标签前的文本
if (match.index > lastIndex) {
const text = xmlString.slice(lastIndex, match.index);
if (text.trim()) {
nodes.push({
type: 'text',
attrs: { content: this.unescapeXml(text) },
children: [],
});
}
}
const [, tagName, rawAttrs = '', innerContent] = match;
if (!tagName) continue;
const attrs = this.parseAttributes(rawAttrs);
const children: (SatoriXmlNode | string)[] = [];
// 如果有内部内容,递归解析
if (innerContent) {
const innerNodes = this.parse(innerContent);
// 如果解析出来只有一个空文本,直接用内容
if (innerNodes.length === 1 && innerNodes[0]?.type === 'text') {
children.push(innerNodes[0]);
} else if (innerNodes.length > 0) {
children.push(...innerNodes);
} else if (innerContent.trim()) {
children.push({
type: 'text',
attrs: { content: this.unescapeXml(innerContent) },
children: [],
});
}
}
nodes.push({
type: tagName.toLowerCase(),
attrs,
children,
});
lastIndex = match.index + match[0].length;
}
// 处理剩余文本
if (lastIndex < xmlString.length) {
const text = xmlString.slice(lastIndex);
if (text.trim()) {
nodes.push({
type: 'text',
attrs: { content: this.unescapeXml(text) },
children: [],
});
}
}
return nodes;
}
/**
*
*/
private static parseAttributes (attrString: string): Record<string, string> {
const attrs: Record<string, string> = {};
const attrRegex = /(\w+)=["']([^"']*)["']/g;
let match: RegExpExecArray | null;
while ((match = attrRegex.exec(attrString)) !== null) {
const key = match[1];
const value = match[2];
if (key !== undefined && value !== undefined) {
attrs[key] = this.unescapeXml(value);
}
}
return attrs;
}
/**
* XML
*/
static serialize (nodes: SatoriXmlNode[]): string {
return nodes.map((node) => this.serializeNode(node)).join('');
}
/**
*
*/
private static serializeNode (node: SatoriXmlNode): string {
if (node.type === 'text') {
return this.escapeXml(node.attrs['content'] || '');
}
const attrs = Object.entries(node.attrs)
.map(([key, value]) => `${key}="${this.escapeXml(value)}"`)
.join(' ');
const hasChildren = node.children.length > 0;
if (!hasChildren) {
return attrs ? `<${node.type} ${attrs}/>` : `<${node.type}/>`;
}
const openTag = attrs ? `<${node.type} ${attrs}>` : `<${node.type}>`;
const childrenStr = node.children
.map((child) => (typeof child === 'string' ? this.escapeXml(child) : this.serializeNode(child)))
.join('');
return `${openTag}${childrenStr}</${node.type}>`;
}
/**
*
*/
static createText (content: string): SatoriXmlNode {
return { type: 'text', attrs: { content }, children: [] };
}
/**
* at
*/
static createAt (id?: string, name?: string, type?: string): SatoriXmlNode {
const attrs: Record<string, string> = {};
if (id) attrs['id'] = id;
if (name) attrs['name'] = name;
if (type) attrs['type'] = type;
return { type: 'at', attrs, children: [] };
}
/**
*
*/
static createImg (src: string, attrs?: { width?: number; height?: number; title?: string; }): SatoriXmlNode {
const nodeAttrs: Record<string, string> = { src };
if (attrs?.width) nodeAttrs['width'] = String(attrs.width);
if (attrs?.height) nodeAttrs['height'] = String(attrs.height);
if (attrs?.title) nodeAttrs['title'] = attrs.title;
return { type: 'img', attrs: nodeAttrs, children: [] };
}
/**
*
*/
static createAudio (src: string, attrs?: { duration?: number; title?: string; }): SatoriXmlNode {
const nodeAttrs: Record<string, string> = { src };
if (attrs?.duration) nodeAttrs['duration'] = String(attrs.duration);
if (attrs?.title) nodeAttrs['title'] = attrs.title;
return { type: 'audio', attrs: nodeAttrs, children: [] };
}
/**
*
*/
static createVideo (src: string, attrs?: { width?: number; height?: number; duration?: number; title?: string; }): SatoriXmlNode {
const nodeAttrs: Record<string, string> = { src };
if (attrs?.width) nodeAttrs['width'] = String(attrs.width);
if (attrs?.height) nodeAttrs['height'] = String(attrs.height);
if (attrs?.duration) nodeAttrs['duration'] = String(attrs.duration);
if (attrs?.title) nodeAttrs['title'] = attrs.title;
return { type: 'video', attrs: nodeAttrs, children: [] };
}
/**
*
*/
static createFile (src: string, attrs?: { title?: string; }): SatoriXmlNode {
const nodeAttrs: Record<string, string> = { src };
if (attrs?.title) nodeAttrs['title'] = attrs.title;
return { type: 'file', attrs: nodeAttrs, children: [] };
}
/**
*
*/
static createFace (id: string | number): SatoriXmlNode {
return { type: 'face', attrs: { id: String(id) }, children: [] };
}
/**
*
*/
static createQuote (id: string): SatoriXmlNode {
return { type: 'quote', attrs: { id }, children: [] };
}
/**
*
*/
static createMessage (attrs?: { id?: string; forward?: boolean; }, children?: SatoriXmlNode[]): SatoriXmlNode {
const nodeAttrs: Record<string, string> = {};
if (attrs?.id) nodeAttrs['id'] = attrs.id;
if (attrs?.forward !== undefined) nodeAttrs['forward'] = String(attrs.forward);
return { type: 'message', attrs: nodeAttrs, children: children || [] };
}
/**
*
*/
static createAuthor (attrs: { id?: string; name?: string; avatar?: string; }): SatoriXmlNode {
const nodeAttrs: Record<string, string> = {};
if (attrs.id) nodeAttrs['id'] = attrs.id;
if (attrs.name) nodeAttrs['name'] = attrs.name;
if (attrs.avatar) nodeAttrs['avatar'] = attrs.avatar;
return { type: 'author', attrs: nodeAttrs, children: [] };
}
/**
*
*/
static createBr (): SatoriXmlNode {
return { type: 'br', attrs: {}, children: [] };
}
/**
*
*/
static createButton (attrs: { id?: string; type?: string; href?: string; text?: string; }): SatoriXmlNode {
const nodeAttrs: Record<string, string> = {};
if (attrs.id) nodeAttrs['id'] = attrs.id;
if (attrs.type) nodeAttrs['type'] = attrs.type;
if (attrs.href) nodeAttrs['href'] = attrs.href;
if (attrs.text) nodeAttrs['text'] = attrs.text;
return { type: 'button', attrs: nodeAttrs, children: [] };
}
/**
*
*/
static createStyled (type: 'b' | 'i' | 'u' | 's' | 'code' | 'sup' | 'sub' | 'spl', children: SatoriXmlNode[]): SatoriXmlNode {
return { type, attrs: {}, children };
}
/**
* XML
*/
static escapeXml (str: string): string {
return str
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;');
}
/**
* XML
*/
static unescapeXml (str: string): string {
return str
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'");
}
/**
*
*/
static walk (nodes: SatoriXmlNode[], callback: (node: SatoriXmlNode) => void): void {
for (const node of nodes) {
callback(node);
if (node.children) {
const childNodes = node.children.filter((c): c is SatoriXmlNode => typeof c !== 'string');
this.walk(childNodes, callback);
}
}
}
/**
*
*/
static find (nodes: SatoriXmlNode[], type: string): SatoriXmlNode[] {
const result: SatoriXmlNode[] = [];
this.walk(nodes, (node) => {
if (node.type === type) {
result.push(node);
}
});
return result;
}
/**
*
*/
static extractText (nodes: SatoriXmlNode[]): string {
const texts: string[] = [];
this.walk(nodes, (node) => {
if (node.type === 'text' && node.attrs['content']) {
texts.push(node.attrs['content']);
}
});
return texts.join('');
}
}
export { SatoriXmlUtils as XmlUtils };

View File

@ -249,13 +249,7 @@ export class NapCatSatoriAdapter {
}
try {
const requesterUin = await this.core.apis.UserApi.getUinByUidV2(req.friendUid);
const event = this.apis.EventApi.createFriendRequestEvent(
requesterUin,
req.friendNick || requesterUin,
req.extWords,
req.friendUid
);
const event = this.apis.EventApi.createFriendRequestEvent(req);
await this.networkManager.emitEvent(event);
} catch (error) {
this.context.logger.logError('[Satori] 处理好友请求失败', error);
@ -284,15 +278,7 @@ export class NapCatSatoriAdapter {
[GroupNotifyMsgType.REQUEST_JOIN_NEED_ADMINI_STRATOR_PASS].includes(notify.type) &&
notify.status === GroupNotifyMsgStatus.KUNHANDLE
) {
const requestUin = await this.core.apis.UserApi.getUinByUidV2(notify.user1.uid);
const event = this.apis.EventApi.createGuildMemberRequestEvent(
notify.group.groupCode,
notify.group.groupName,
requestUin,
notify.user1.nickName || requestUin,
notify.postscript,
notify.seq
);
const event = this.apis.EventApi.createGuildMemberRequestEvent(notify);
await this.networkManager.emitEvent(event);
}
} catch (error) {
@ -322,3 +308,5 @@ export class NapCatSatoriAdapter {
export * from './types';
export * from './config';
export * from './action';
export * from './helper';

View File

@ -2,14 +2,14 @@ import express, { Express, Request, Response, NextFunction } from 'express';
import { createServer, Server } from 'http';
import { NapCatCore } from 'napcat-core';
import { NapCatSatoriAdapter } from '../index';
import { SatoriActionMap } from '../action';
import { SatoriActionMap, SatoriResponseHelper } from '../action';
import { SatoriHttpServerConfig } from '../config/config';
import {
ISatoriNetworkAdapter,
SatoriEmitEventContent,
SatoriNetworkReloadType,
} from './adapter';
import { SatoriApiResponse, SatoriLoginStatus } from '../types';
import { SatoriLoginStatus } from '../types';
export class SatoriHttpServerAdapter extends ISatoriNetworkAdapter<SatoriHttpServerConfig> {
private app: Express | null = null;
@ -30,7 +30,7 @@ export class SatoriHttpServerAdapter extends ISatoriNetworkAdapter<SatoriHttpSer
try {
this.app = express();
this.app.use(express.json());
this.app.use(express.json({ limit: '50mb' }));
// Token 验证中间件
this.app.use(this.config.path || '/v1', (req: Request, res: Response, next: NextFunction): void => {
@ -38,7 +38,7 @@ export class SatoriHttpServerAdapter extends ISatoriNetworkAdapter<SatoriHttpSer
const authHeader = req.headers.authorization;
const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : authHeader;
if (token !== this.config.token) {
res.status(401).json({ error: { code: 401, message: 'Unauthorized' } });
res.status(401).json(SatoriResponseHelper.error(401, 'Unauthorized'));
return;
}
}
@ -110,7 +110,24 @@ export class SatoriHttpServerAdapter extends ISatoriNetworkAdapter<SatoriHttpSer
const basePath = this.config.path || '/v1';
const router = express.Router();
// 获取登录信息
// 通用 action 处理器
const handleAction = async (actionName: string, req: Request, res: Response): Promise<void> => {
const action = this.actions.get(actionName);
if (!action) {
res.status(404).json(SatoriResponseHelper.error(404, `未知的 action: ${actionName}`));
return;
}
try {
const result = await action.handle(req.body || {});
res.json(SatoriResponseHelper.success(result));
} catch (error) {
this.logger.logError(`[Satori] Action ${actionName} 执行失败:`, error);
res.status(500).json(SatoriResponseHelper.error(500, `${error}`));
}
};
// 登录信息(特殊处理,可以使用缓存)
router.post('/login.get', async (_req: Request, res: Response) => {
try {
const result = {
@ -123,234 +140,37 @@ export class SatoriHttpServerAdapter extends ISatoriNetworkAdapter<SatoriHttpSer
platform: this.satoriContext.configLoader.configData.platform,
status: SatoriLoginStatus.ONLINE,
};
this.sendSuccess(res, result);
res.json(SatoriResponseHelper.success(result));
} catch (error) {
this.sendError(res, 500, `获取登录信息失败: ${error}`);
res.status(500).json(SatoriResponseHelper.error(500, `获取登录信息失败: ${error}`));
}
});
// 发送消息
router.post('/message.create', async (req: Request, res: Response): Promise<void> => {
try {
const { channel_id, content } = req.body;
if (!channel_id || !content) {
this.sendError(res, 400, '缺少必要参数');
return;
}
const result = await this.actions.get('message.create')?.handle({ channel_id, content });
this.sendSuccess(res, result);
} catch (error) {
this.sendError(res, 500, `发送消息失败: ${error}`);
}
});
// 动态注册所有 action 路由
for (const [actionName] of this.actions) {
const routePath = `/${actionName.replace(/\./g, '/')}`;
router.post(routePath, (req, res) => handleAction(actionName, req, res));
// 获取消息
router.post('/message.get', async (req: Request, res: Response): Promise<void> => {
try {
const { channel_id, message_id } = req.body;
if (!channel_id || !message_id) {
this.sendError(res, 400, '缺少必要参数');
return;
}
const result = await this.actions.get('message.get')?.handle({ channel_id, message_id });
this.sendSuccess(res, result);
} catch (error) {
this.sendError(res, 500, `获取消息失败: ${error}`);
}
});
// 同时支持点号格式的路由
router.post(`/${actionName}`, (req, res) => handleAction(actionName, req, res));
}
// 删除消息
router.post('/message.delete', async (req: Request, res: Response): Promise<void> => {
try {
const { channel_id, message_id } = req.body;
if (!channel_id || !message_id) {
this.sendError(res, 400, '缺少必要参数');
return;
}
await this.actions.get('message.delete')?.handle({ channel_id, message_id });
this.sendSuccess(res, {});
} catch (error) {
this.sendError(res, 500, `删除消息失败: ${error}`);
}
});
// 获取频道信息
router.post('/channel.get', async (req: Request, res: Response): Promise<void> => {
try {
const { channel_id } = req.body;
if (!channel_id) {
this.sendError(res, 400, '缺少必要参数');
return;
}
const result = await this.actions.get('channel.get')?.handle({ channel_id });
this.sendSuccess(res, result);
} catch (error) {
this.sendError(res, 500, `获取频道信息失败: ${error}`);
}
});
// 获取频道列表
router.post('/channel.list', async (req: Request, res: Response): Promise<void> => {
try {
const { guild_id, next } = req.body;
if (!guild_id) {
this.sendError(res, 400, '缺少必要参数');
return;
}
const result = await this.actions.get('channel.list')?.handle({ guild_id, next });
this.sendSuccess(res, result);
} catch (error) {
this.sendError(res, 500, `获取频道列表失败: ${error}`);
}
});
// 获取群组信息
router.post('/guild.get', async (req: Request, res: Response): Promise<void> => {
try {
const { guild_id } = req.body;
if (!guild_id) {
this.sendError(res, 400, '缺少必要参数');
return;
}
const result = await this.actions.get('guild.get')?.handle({ guild_id });
this.sendSuccess(res, result);
} catch (error) {
this.sendError(res, 500, `获取群组信息失败: ${error}`);
}
});
// 获取群组列表
router.post('/guild.list', async (req: Request, res: Response) => {
try {
const { next } = req.body;
const result = await this.actions.get('guild.list')?.handle({ next });
this.sendSuccess(res, result);
} catch (error) {
this.sendError(res, 500, `获取群组列表失败: ${error}`);
}
});
// 获取群成员信息
router.post('/guild.member.get', async (req: Request, res: Response): Promise<void> => {
try {
const { guild_id, user_id } = req.body;
if (!guild_id || !user_id) {
this.sendError(res, 400, '缺少必要参数');
return;
}
const result = await this.actions.get('guild.member.get')?.handle({ guild_id, user_id });
this.sendSuccess(res, result);
} catch (error) {
this.sendError(res, 500, `获取群成员信息失败: ${error}`);
}
});
// 获取群成员列表
router.post('/guild.member.list', async (req: Request, res: Response): Promise<void> => {
try {
const { guild_id, next } = req.body;
if (!guild_id) {
this.sendError(res, 400, '缺少必要参数');
return;
}
const result = await this.actions.get('guild.member.list')?.handle({ guild_id, next });
this.sendSuccess(res, result);
} catch (error) {
this.sendError(res, 500, `获取群成员列表失败: ${error}`);
}
});
// 获取用户信息
router.post('/user.get', async (req: Request, res: Response): Promise<void> => {
try {
const { user_id } = req.body;
if (!user_id) {
this.sendError(res, 400, '缺少必要参数');
return;
}
const result = await this.actions.get('user.get')?.handle({ user_id });
this.sendSuccess(res, result);
} catch (error) {
this.sendError(res, 500, `获取用户信息失败: ${error}`);
}
});
// 获取好友列表
router.post('/friend.list', async (req: Request, res: Response) => {
try {
const { next } = req.body;
const result = await this.actions.get('friend.list')?.handle({ next });
this.sendSuccess(res, result);
} catch (error) {
this.sendError(res, 500, `获取好友列表失败: ${error}`);
}
});
// 处理好友请求
router.post('/friend.approve', async (req: Request, res: Response): Promise<void> => {
try {
const { message_id, approve, comment } = req.body;
if (message_id === undefined || approve === undefined) {
this.sendError(res, 400, '缺少必要参数');
return;
}
await this.actions.get('friend.approve')?.handle({ message_id, approve, comment });
this.sendSuccess(res, {});
} catch (error) {
this.sendError(res, 500, `处理好友请求失败: ${error}`);
}
});
// 踢出群成员
router.post('/guild.member.kick', async (req: Request, res: Response): Promise<void> => {
try {
const { guild_id, user_id, permanent } = req.body;
if (!guild_id || !user_id) {
this.sendError(res, 400, '缺少必要参数');
return;
}
await this.actions.get('guild.member.kick')?.handle({ guild_id, user_id, permanent });
this.sendSuccess(res, {});
} catch (error) {
this.sendError(res, 500, `踢出群成员失败: ${error}`);
}
});
// 禁言群成员
router.post('/guild.member.mute', async (req: Request, res: Response): Promise<void> => {
try {
const { guild_id, user_id, duration } = req.body;
if (!guild_id || !user_id) {
this.sendError(res, 400, '缺少必要参数');
return;
}
await this.actions.get('guild.member.mute')?.handle({ guild_id, user_id, duration });
this.sendSuccess(res, {});
} catch (error) {
this.sendError(res, 500, `禁言群成员失败: ${error}`);
}
});
// 上传文件
router.post('/upload.create', async (req: Request, res: Response) => {
try {
const result = await this.actions.get('upload.create')?.handle(req.body);
this.sendSuccess(res, result);
} catch (error) {
this.sendError(res, 500, `上传文件失败: ${error}`);
// 通用 action 入口
router.post('/:action(*)', async (req: Request, res: Response) => {
const actionParam = req.params['action'];
if (!actionParam) {
res.status(400).json(SatoriResponseHelper.error(400, '缺少 action 参数'));
return;
}
const actionName = actionParam.replace(/\//g, '.');
await handleAction(actionName, req, res);
});
this.app.use(basePath, router);
}
private sendSuccess<T> (res: Response, data: T): void {
const response: SatoriApiResponse<T> = { data };
res.json(response);
}
private sendError (res: Response, code: number, message: string): void {
const response: SatoriApiResponse = { error: { code, message } };
res.status(code >= 400 && code < 600 ? code : 500).json(response);
// Debug 日志
if (this.config.debug) {
this.logger.logDebug(`[Satori] 已注册 ${this.actions.size} 个 action 路由`);
}
}
}

View File

@ -25,7 +25,10 @@
"ws": "^8.18.0",
"@sinclair/typebox": "^0.34.33",
"ajv": "^8.17.1",
"json5": "^2.2.3"
"json5": "^2.2.3",
"@satorijs/core": "^4.3.1",
"@satorijs/element": "^3.1.3",
"@satorijs/protocol": "^1.4.1"
},
"devDependencies": {
"@types/express": "^5.0.0",

View File

@ -1,13 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"declaration": true,
"declarationMap": true,
"noEmit": true
},
"include": [
"./**/*.ts"
"*.ts",
"**/*.ts"
],
"exclude": [
"node_modules",

View File

@ -270,6 +270,15 @@ importers:
packages/napcat-satori:
dependencies:
'@satorijs/core':
specifier: ^4.3.1
version: 4.5.2(cordis@3.18.1)
'@satorijs/element':
specifier: ^3.1.3
version: 3.1.8
'@satorijs/protocol':
specifier: ^1.4.1
version: 1.6.1
'@sinclair/typebox':
specifier: ^0.34.33
version: 0.34.41
@ -869,6 +878,34 @@ packages:
resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==}
engines: {node: '>=0.1.90'}
'@cordisjs/core@3.18.1':
resolution: {integrity: sha512-yRuATOamFxeD1ztE2L3o1SaHuT2zw5DTXijQYxt9azVNeILbvtomfGG6sLZegrynJO9XZbNvXbOlQ7LBJDOlAg==}
'@cordisjs/loader@0.13.1':
resolution: {integrity: sha512-YGw22gT1F/jXT1UeWMjIavt7WHMfEmKve83v4PTg63SW4eC2s89ivsN4RQWi5YWe0LQNNg8LItL66FeEcgQS5w==}
peerDependencies:
'@cordisjs/core': ^3.18.1
'@cordisjs/logger@0.3.3':
resolution: {integrity: sha512-UcyZRXAL86YLAEEta+fxJM1CcDanJvoIMD9ywCyP2AZJuib68UynENGTFo4mYkeg+8WMXrlA/aNNwENMiLUlXA==}
peerDependencies:
'@cordisjs/core': ^3.17.1
'@cordisjs/plugin-http@0.6.3':
resolution: {integrity: sha512-kmw4G1t39ZZzFGpBKUNuTv2aefEUtxa1e4qu4+bTrM5Z8uYHTRzPpyJfkj9zuZwopP/Vz271OUr2UfDN5lrJKQ==}
peerDependencies:
cordis: ^3.18.1
'@cordisjs/schema@0.1.1':
resolution: {integrity: sha512-AYTSyEXEASO6uVHqXBHIId+8OGFs0idtFoj3q7qee9J7OGcMjVEUsrsdiP9SnmzTR4ea17esShledUC50fco1g==}
peerDependencies:
'@cordisjs/core': ^3.17.0
'@cordisjs/timer@0.3.2':
resolution: {integrity: sha512-frdIvc+1AqKVHMWnNacqqZ2Cm52FfoYKPpBQ+kZgr/cFs7HXDKqetL35CLy/EZAF22l/6VTvpQOYUwS6tvVJZA==}
peerDependencies:
'@cordisjs/core': ^3.12.0
'@dabh/diagnostics@2.0.8':
resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==}
@ -2639,6 +2676,17 @@ packages:
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
'@satorijs/core@4.5.2':
resolution: {integrity: sha512-lW5gqt6z1h56GGkyXKEFn212JstDcVN2rUxb+FBvT9i6OKLdB2055QgynrkbLqzDN+Zq8hbW+9T6lpS/fpyMSw==}
peerDependencies:
cordis: ^3.18.1
'@satorijs/element@3.1.8':
resolution: {integrity: sha512-Sm+YCO0GMKDtAsQ+JKZ6aJeJZYVeMehqmzk4EJr57MnvZt5kP9iqtN8vYOZc/idE69Y5nA/jxKNCSrgrm1yYrA==}
'@satorijs/protocol@1.6.1':
resolution: {integrity: sha512-A/G/S5PWXGXg9B4aMAmvt8+wP2BnV6uNEzkd1MH1ZkqWfgJ2TX9L4ghR311mbde57MJOoSbX51lnO4J9XjeNKQ==}
'@simplewebauthn/server@13.2.2':
resolution: {integrity: sha512-HcWLW28yTMGXpwE9VLx9J+N2KEUaELadLrkPEEI9tpI5la70xNEVEsu/C+m3u7uoq4FulLqZQhgBCzR9IZhFpA==}
engines: {node: '>=20.0.0'}
@ -3205,6 +3253,10 @@ packages:
abbrev@1.1.1:
resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
@ -3475,6 +3527,9 @@ packages:
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
bufferstreams@3.0.0:
resolution: {integrity: sha512-Qg0ggJUWJq90vtg4lDsGN9CDWvzBMQxhiEkSOD/sJfYt6BLect3eV1/S6K7SCSKJ34n60rf6U5eUPmQENVE4UA==}
engines: {node: '>=8.12.0'}
@ -3487,6 +3542,10 @@ packages:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
cacache@16.1.3:
resolution: {integrity: sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==}
engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@ -3712,6 +3771,11 @@ packages:
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
engines: {node: '>=18'}
cordis@3.18.1:
resolution: {integrity: sha512-9IbthFbFBVJ15WBDNPqHdl59TyEAMNWxF1Dxsdi14625ePQkMX35WvcqgGno0BcXlpXDbUaBZgQ+jfBYT+uuSQ==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
@ -3719,6 +3783,9 @@ packages:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
cosmokit@1.8.1:
resolution: {integrity: sha512-PDBv4l90xZKrUsZ0vtoycgZpO/j4iFsqJXrAxsyBDsnQRI7ZMJXIjgDJsKNjd5L8jnVnnlrDCdhkFbTncgCVjQ==}
crelt@1.0.6:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
@ -3860,6 +3927,10 @@ packages:
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
engines: {node: '>=0.10.0'}
dotenv@16.6.1:
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
engines: {node: '>=12'}
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@ -4173,9 +4244,17 @@ packages:
event-source-polyfill@1.0.31:
resolution: {integrity: sha512-4IJSItgS/41IxN5UVAVuAyczwZF7ZIEsM1XAoUzIHA6A+xzusEZUutdXz2Nr+MQPLxfTiCvqE79/C8HT8fKFvA==}
event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
eventemitter3@5.0.1:
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
events@3.3.0:
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
engines: {node: '>=0.8.x'}
expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
@ -4261,6 +4340,10 @@ packages:
resolution: {integrity: sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==}
engines: {node: '>= 12'}
file-type@16.5.4:
resolution: {integrity: sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==}
engines: {node: '>=10'}
file-type@21.1.0:
resolution: {integrity: sha512-boU4EHmP3JXkwDo4uhyBhTt5pPstxB6eEXKJBu2yu2l7aAMMm7QQYQEzssJmKReZYrFdFOJS8koVo6bXIBGDqA==}
engines: {node: '>=20'}
@ -4928,6 +5011,10 @@ packages:
resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
engines: {node: '>=0.10.0'}
kleur@4.1.5:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'}
kuler@2.0.0:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
@ -5543,6 +5630,10 @@ packages:
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
peek-readable@4.1.0:
resolution: {integrity: sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==}
engines: {node: '>=8'}
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
@ -5641,6 +5732,10 @@ packages:
process-nextick-args@2.0.1:
resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
process@0.11.10:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
promise-inflight@1.0.1:
resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
peerDependencies:
@ -5862,6 +5957,14 @@ packages:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
readable-stream@4.7.0:
resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
readable-web-to-node-stream@3.0.4:
resolution: {integrity: sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==}
engines: {node: '>=8'}
readdirp@3.6.0:
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
engines: {node: '>=8.10.0'}
@ -5893,6 +5996,9 @@ packages:
resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==}
engines: {node: '>=8'}
reggol@1.7.1:
resolution: {integrity: sha512-0Vr1vRYDFnsA14BipkHRn3+4FxlZxQJ4VQG2pFE2obSrR68hTHVbbQzS3/ZAHNb6l/+ThaJrc5BPgrYDM3yNyg==}
remark-gfm@4.0.1:
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
@ -6011,6 +6117,9 @@ packages:
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
schemastery@3.17.2:
resolution: {integrity: sha512-hKOOlPLRM4/4jbWzc3Hv9wKOy0aYv5G09Bj9+eZ6mLSCrPTfPs5q/6EMiQAKwfOOYqq4Eq86+cMLj6I2q6jhgA==}
screenfull@5.2.0:
resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==}
engines: {node: '>=0.10.0'}
@ -6266,6 +6375,10 @@ packages:
resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==}
engines: {node: '>=18'}
strtok3@6.3.0:
resolution: {integrity: sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==}
engines: {node: '>=10'}
style-mod@4.1.3:
resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==}
@ -6284,6 +6397,10 @@ packages:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
supports-color@8.1.1:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
engines: {node: '>=10'}
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
@ -6396,6 +6513,10 @@ packages:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
token-types@4.2.1:
resolution: {integrity: sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==}
engines: {node: '>=10'}
token-types@6.1.1:
resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==}
engines: {node: '>=14.16'}
@ -7056,6 +7177,45 @@ snapshots:
'@colors/colors@1.6.0': {}
'@cordisjs/core@3.18.1':
dependencies:
cosmokit: 1.8.1
'@cordisjs/loader@0.13.1(@cordisjs/core@3.18.1)':
dependencies:
'@cordisjs/core': 3.18.1
cosmokit: 1.8.1
dotenv: 16.6.1
js-yaml: 4.1.1
'@cordisjs/logger@0.3.3(@cordisjs/core@3.18.1)':
dependencies:
'@cordisjs/core': 3.18.1
cosmokit: 1.8.1
reggol: 1.7.1
'@cordisjs/plugin-http@0.6.3(cordis@3.18.1)':
dependencies:
cordis: 3.18.1
cosmokit: 1.8.1
file-type: 16.5.4
mime-db: 1.54.0
ws: 8.18.3
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@cordisjs/schema@0.1.1(@cordisjs/core@3.18.1)':
dependencies:
'@cordisjs/core': 3.18.1
cosmokit: 1.8.1
schemastery: 3.17.2
'@cordisjs/timer@0.3.2(@cordisjs/core@3.18.1)':
dependencies:
'@cordisjs/core': 3.18.1
cosmokit: 1.8.1
'@dabh/diagnostics@2.0.8':
dependencies:
'@so-ric/colorspace': 1.1.6
@ -9433,6 +9593,27 @@ snapshots:
'@rtsao/scc@1.1.0': {}
'@satorijs/core@4.5.2(cordis@3.18.1)':
dependencies:
'@cordisjs/plugin-http': 0.6.3(cordis@3.18.1)
'@satorijs/element': 3.1.8
'@satorijs/protocol': 1.6.1
cordis: 3.18.1
cosmokit: 1.8.1
path-to-regexp: 8.3.0
transitivePeerDependencies:
- bufferutil
- utf-8-validate
'@satorijs/element@3.1.8':
dependencies:
cosmokit: 1.8.1
'@satorijs/protocol@1.6.1':
dependencies:
'@satorijs/element': 3.1.8
cosmokit: 1.8.1
'@simplewebauthn/server@13.2.2':
dependencies:
'@hexagon/base64': 1.1.28
@ -10014,6 +10195,10 @@ snapshots:
abbrev@1.1.1: {}
abort-controller@3.0.0:
dependencies:
event-target-shim: 5.0.1
accepts@1.3.8:
dependencies:
mime-types: 2.1.35
@ -10357,6 +10542,11 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
buffer@6.0.3:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
bufferstreams@3.0.0:
dependencies:
readable-stream: 3.6.2
@ -10367,6 +10557,8 @@ snapshots:
bytes@3.1.2: {}
cac@6.7.14: {}
cacache@16.1.3:
dependencies:
'@npmcli/fs': 2.1.2
@ -10603,6 +10795,18 @@ snapshots:
cookie@1.0.2: {}
cordis@3.18.1:
dependencies:
'@cordisjs/core': 3.18.1
'@cordisjs/loader': 0.13.1(@cordisjs/core@3.18.1)
'@cordisjs/logger': 0.3.3(@cordisjs/core@3.18.1)
'@cordisjs/schema': 0.1.1(@cordisjs/core@3.18.1)
'@cordisjs/timer': 0.3.2(@cordisjs/core@3.18.1)
cac: 6.7.14
cosmokit: 1.8.1
kleur: 4.1.5
reggol: 1.7.1
core-util-is@1.0.3: {}
cors@2.8.5:
@ -10610,6 +10814,8 @@ snapshots:
object-assign: 4.1.1
vary: 1.1.2
cosmokit@1.8.1: {}
crelt@1.0.6: {}
cross-spawn@7.0.6:
@ -10721,6 +10927,8 @@ snapshots:
dependencies:
esutils: 2.0.3
dotenv@16.6.1: {}
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@ -11188,8 +11396,12 @@ snapshots:
event-source-polyfill@1.0.31: {}
event-target-shim@5.0.1: {}
eventemitter3@5.0.1: {}
events@3.3.0: {}
expand-template@2.0.3: {}
expect-type@1.2.2: {}
@ -11320,6 +11532,12 @@ snapshots:
dependencies:
tslib: 2.8.1
file-type@16.5.4:
dependencies:
readable-web-to-node-stream: 3.0.4
strtok3: 6.3.0
token-types: 4.2.1
file-type@21.1.0:
dependencies:
'@tokenizer/inflate': 0.3.1
@ -12033,6 +12251,8 @@ snapshots:
kind-of@6.0.3: {}
kleur@4.1.5: {}
kuler@2.0.0: {}
language-subtag-registry@0.3.23: {}
@ -12873,6 +13093,8 @@ snapshots:
pathe@2.0.3: {}
peek-readable@4.1.0: {}
pend@1.2.0: {}
peowly@1.3.2: {}
@ -12951,6 +13173,8 @@ snapshots:
process-nextick-args@2.0.1: {}
process@0.11.10: {}
promise-inflight@1.0.1: {}
promise-retry@2.0.1:
@ -13200,6 +13424,18 @@ snapshots:
string_decoder: 1.3.0
util-deprecate: 1.0.2
readable-stream@4.7.0:
dependencies:
abort-controller: 3.0.0
buffer: 6.0.3
events: 3.3.0
process: 0.11.10
string_decoder: 1.3.0
readable-web-to-node-stream@3.0.4:
dependencies:
readable-stream: 4.7.0
readdirp@3.6.0:
dependencies:
picomatch: 2.3.1
@ -13239,6 +13475,12 @@ snapshots:
regexpp@3.2.0: {}
reggol@1.7.1:
dependencies:
cosmokit: 1.8.1
object-inspect: 1.13.4
supports-color: 8.1.1
remark-gfm@4.0.1:
dependencies:
'@types/mdast': 4.0.4
@ -13397,6 +13639,10 @@ snapshots:
scheduler@0.27.0: {}
schemastery@3.17.2:
dependencies:
cosmokit: 1.8.1
screenfull@5.2.0: {}
scroll-into-view-if-needed@3.0.10:
@ -13742,6 +13988,11 @@ snapshots:
dependencies:
'@tokenizer/token': 0.3.0
strtok3@6.3.0:
dependencies:
'@tokenizer/token': 0.3.0
peek-readable: 4.1.0
style-mod@4.1.3: {}
style-to-js@1.1.19:
@ -13766,6 +14017,10 @@ snapshots:
dependencies:
has-flag: 4.0.0
supports-color@8.1.1:
dependencies:
has-flag: 4.0.0
supports-preserve-symlinks-flag@1.0.0: {}
synckit@0.9.3:
@ -13913,6 +14168,11 @@ snapshots:
toidentifier@1.0.1: {}
token-types@4.2.1:
dependencies:
'@tokenizer/token': 0.3.0
ieee754: 1.2.1
token-types@6.1.1:
dependencies:
'@borewit/text-codec': 0.1.1

View File

@ -26,7 +26,7 @@
"forceConsistentCasingInFileNames": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noUnusedLocals": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"useDefineForClassFields": true,