diff --git a/src/app.module.ts b/src/app.module.ts index d7c7cd2..4a9d1b9 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -9,7 +9,6 @@ import { PersistenceModule } from './core/persistence/persistence.module'; import { RedisModule } from './core/redis/redis.module'; import { WsModule } from './core/ws/ws.module'; import { SystemWebModule } from './modules/system/systemWeb.module'; -import { BotModule } from './modules/bot/bot.module'; import { CdnModule } from './modules/cdn/cdn.module'; import { WordsModule } from './modules/words/words.module'; import { MemeModule } from './modules/meme/meme.module'; @@ -27,7 +26,6 @@ import { OpenListModule } from './core/openlist/openlist.module'; RedisModule, WsModule, SystemWebModule, - BotModule, CdnModule, WordsModule, MemeModule, diff --git a/src/modules/bot/bot.controller.ts b/src/modules/bot/bot.controller.ts deleted file mode 100644 index 23c68d5..0000000 --- a/src/modules/bot/bot.controller.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common'; -import { ApiOperation, ApiTags, ApiBody, ApiHeader } from '@nestjs/swagger'; -import { BotService } from './bot.service'; -import { WsClientManager } from 'src/core/ws/ws-client.manager'; -import { TokenAuthGuard } from 'src/core/tools/token-auth.guard'; -import { - BroadcastDto, - GroupInfoDto, - SendMessageDto, - TokenDto, -} from './bot.dto'; - -@ApiTags('Bot相关操作') -@Controller('bot') -export class BotController { - constructor( - @Inject(BotService) - private readonly botService: BotService, - @Inject(WsClientManager) - private readonly wsClientManager: WsClientManager, - ) {} - - @Post('getBotId') - @UseGuards(TokenAuthGuard) - @ApiOperation({ summary: '获取当前连接到核心的全部 botId 数组' }) - @ApiHeader({ name: 'x-token', description: '身份验证token', required: true }) - @ApiBody({ - description: '获取botId请求', - schema: { - type: 'object', - properties: {}, - }, - }) - public async postBotsId(@Body() dto: TokenDto) { - return this.botService.getBotId(); - } - - @Post('getGroupInfo') - @UseGuards(TokenAuthGuard) - @ApiOperation({ summary: '获取群聊信息' }) - @ApiHeader({ name: 'x-token', description: '身份验证token', required: true }) - @ApiBody({ - description: '获取群聊信息参数', - schema: { - type: 'object', - properties: { - groupId: { type: 'number', description: '群号', example: 114514 }, - }, - required: ['groupId'], - }, - }) - public async postGroupInfo(@Body() dto: GroupInfoDto) { - return this.botService.getGroupInfo({ groupId: dto.groupId }); - } - - @Post('reportBots') - @UseGuards(TokenAuthGuard) - @ApiOperation({ summary: '广播:要求同步群聊信息和 bot 连接情况' }) - @ApiHeader({ name: 'x-token', description: '身份验证token', required: true }) - @ApiBody({ - description: '广播同步请求', - schema: { - type: 'object', - properties: {}, - }, - }) - public async reportBots(@Body() dto: TokenDto) { - const sendMessage = { - type: 'reportBots', - data: {}, - }; - await this.wsClientManager.broadcast(sendMessage); - return { message: '正在请求同步 bot 数据..' }; - } - - @Post('sendMessage') - @UseGuards(TokenAuthGuard) - @ApiOperation({ summary: '发送消息到群聊', description: '自动选择bot发送' }) - @ApiHeader({ name: 'x-token', description: '身份验证token', required: true }) - @ApiBody({ - description: '发送消息参数', - schema: { - type: 'object', - properties: { - groupId: { type: 'number', description: '群号', example: 114514 }, - message: { type: 'string', description: '要发送的消息', example: 'Ciallo~(∠・ω< )⌒★' }, - }, - required: ['groupId', 'message'], - }, - }) - public async sendMessage(@Body() dto: SendMessageDto) { - const flag = await this.botService.sendMessage(dto.groupId, dto.message); - if (!flag) { - return { message: '消息发送失败' }; - } - return { message: '消息发送成功' }; - } - - @Post('broadcast') - @UseGuards(TokenAuthGuard) - @ApiOperation({ summary: '广播消息到全部群聊', description: '随机延迟' }) - @ApiHeader({ name: 'x-token', description: '身份验证token', required: true }) - @ApiBody({ - description: '广播消息参数', - schema: { - type: 'object', - properties: { - message: { type: 'string', description: '要广播的消息', example: '全体目光向我看齐!我宣布个事儿..' }, - }, - required: ['message'], - }, - }) - public async smartBroadcast(@Body() dto: BroadcastDto) { - await this.botService.broadcastToAllGroups(dto.message); - return { message: '广播任务已开始,正在后台执行..' }; - } -} diff --git a/src/modules/bot/bot.dto.ts b/src/modules/bot/bot.dto.ts deleted file mode 100644 index 7794b6e..0000000 --- a/src/modules/bot/bot.dto.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class TokenDto { -} - -export class GroupInfoDto { - @ApiProperty({ description: '群号', example: 114514 }) - groupId: number; -} - -export class SendMessageDto extends GroupInfoDto { - @ApiProperty({ description: '要发送的消息', example: 'Ciallo~(∠・ω< )⌒★' }) - message: string; -} - -export class BroadcastDto { - @ApiProperty({ - description: '要广播的消息', - example: '全体目光向我看齐!我宣布个事儿..', - }) - message: string; -} diff --git a/src/modules/bot/bot.module.ts b/src/modules/bot/bot.module.ts deleted file mode 100644 index aef49d8..0000000 --- a/src/modules/bot/bot.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Module } from '@nestjs/common'; -import { RedisModule } from '../../core/redis/redis.module'; -import { WsModule } from '../../core/ws/ws.module'; -import { ToolsModule } from '../../core/tools/tools.module'; -import { PathModule } from '../../core/path/path.module'; -import { BotController } from './bot.controller'; -import { BotService } from './bot.service'; - -@Module({ - imports: [RedisModule, WsModule, ToolsModule, PathModule], - controllers: [BotController], - providers: [BotService], -}) -export class BotModule {} diff --git a/src/modules/bot/bot.service.ts b/src/modules/bot/bot.service.ts deleted file mode 100644 index 193d41d..0000000 --- a/src/modules/bot/bot.service.ts +++ /dev/null @@ -1,255 +0,0 @@ -import { Inject, Injectable, Logger } from '@nestjs/common'; -import * as fs from 'fs/promises'; -import * as path from 'path'; -import { RedisService } from 'src/core/redis/redis.service'; -import { WsClientManager } from 'src/core/ws/ws-client.manager'; -import { ToolsService } from '../../core/tools/tools.service'; -import { PathService } from '../../core/path/path.service'; - -@Injectable() -export class BotService { - private readonly logger = new Logger(BotService.name); - - constructor( - @Inject(RedisService) - private readonly redisService: RedisService, - @Inject(WsClientManager) - private readonly wsClientManager: WsClientManager, - @Inject(ToolsService) - private readonly tools: ToolsService, - @Inject(PathService) - private readonly paths: PathService, - ) {} - - /** - * 获取botId数组 - */ - public async getBotId(): Promise<{ uin: number; nickName: string }[]> { - this.logger.debug('正在请求获取在线的bot..'); - const userPath = this.paths.get('userData'); - const botsPath = path.join(userPath, '/crystelfBots'); - const dirData = await fs.readdir(botsPath); - const uins: { uin: number; nickName: string }[] = []; - - for (const fileName of dirData) { - if (!fileName.endsWith('.json')) continue; - - try { - const raw = await this.redisService.fetch('crystelfBots', fileName); - if (!raw || !Array.isArray(raw)) continue; - - for (const bot of raw) { - const uin = Number(bot.uin); - const nickName = bot.nickName || ''; - if (!isNaN(uin)) { - uins.push({ uin, nickName }); - } - } - } catch (err) { - this.logger.error(`读取或解析 ${fileName} 出错: ${err}`); - } - } - return uins; - } - - /** - * 获取群聊信息 - * @param data - */ - public async getGroupInfo(data: { - botId?: number; - groupId: number; - clientId?: string; - }): Promise { - this.logger.debug(`正在尝试获取${data.groupId}的信息..)`); - const sendBot: number | undefined = - data.botId ?? (await this.getGroupBot(data.groupId)); - if (!sendBot) { - this.logger.warn(`不存在能向群聊${data.groupId}发送消息的Bot!`); - return undefined; - } - - const sendData = { - type: 'getGroupInfo', - data: { - botId: sendBot, - groupId: data.groupId, - clientID: data.clientId ?? (await this.getBotClient(sendBot)), - }, - }; - - if (sendData.data.clientID) { - const returnData = await this.wsClientManager.sendAndWait( - sendData.data.clientID, - sendData, - ); - return returnData ?? undefined; - } - return undefined; - } - - /** - * 发送消息到群聊 - * @param groupId 群号 - * @param message 消息 - */ - public async sendMessage(groupId: number, message: string): Promise { - this.logger.log(`发送${message}到${groupId}..`); - const sendBot = await this.getGroupBot(groupId); - if (!sendBot) { - this.logger.warn(`不存在能向群聊${groupId}发送消息的Bot!`); - return false; - } - const client = await this.getBotClient(sendBot); - if (!client) { - this.logger.warn(`不存在${sendBot}对应的client!`); - return false; - } - const sendData = { - type: 'sendMessage', - data: { botId: sendBot, groupId, clientId: client, message }, - }; - await this.wsClientManager.send(client, sendData); - return true; - } - - /** - * 广播消息 - * @param message 要广播的消息 - */ - public async broadcastToAllGroups(message: string): Promise { - const userPath = this.paths.get('userData'); - const botsPath = path.join(userPath, '/crystelfBots'); - const dirData = await fs.readdir(botsPath); - const groupMap: Map = - new Map(); - this.logger.log(`广播消息:${message}`); - for (const fileName of dirData) { - if (!fileName.endsWith('.json')) continue; - - const clientId = path.basename(fileName, '.json'); - const botList = await this.redisService.fetch('crystelfBots', fileName); - if (!Array.isArray(botList)) continue; - - for (const bot of botList) { - const botId = Number(bot.uin); - const groups = bot.groups; - - if (!botId || !Array.isArray(groups)) continue; - - for (const group of groups) { - if (group.group_id === '未知') continue; - const groupId = Number(group.group_id); - if (isNaN(groupId)) continue; - - if (!groupMap.has(groupId)) { - groupMap.set(groupId, []); - } - groupMap.get(groupId)!.push({ botId, clientId }); - } - } - } - - for (const [groupId, botEntries] of groupMap.entries()) { - this.logger.debug( - `[群 ${groupId}] 候选Bot列表: ${JSON.stringify(botEntries)}`, - ); - - const clientGroups = new Map(); - botEntries.forEach(({ botId, clientId }) => { - if (!clientGroups.has(clientId)) clientGroups.set(clientId, []); - clientGroups.get(clientId)!.push(botId); - }); - - const selectedClientId = this.tools.getRandomItem([ - ...clientGroups.keys(), - ]); - const botCandidates = clientGroups.get(selectedClientId)!; - const selectedBotId = this.tools.getRandomItem(botCandidates); - const delay = this.tools.getRandomDelay(10_000, 150_000); - - setTimeout(() => { - const sendData = { - type: 'sendMessage', - data: { - botId: selectedBotId, - groupId, - clientId: selectedClientId, - message, - }, - }; - this.logger.log( - `[广播] 向群 ${groupId} 使用Bot ${selectedBotId}(客户端 ${selectedClientId})发送消息${message},延迟 ${ - delay / 1000 - } 秒`, - ); - this.wsClientManager.send(selectedClientId, sendData).catch((e) => { - this.logger.error(`发送到群${groupId}失败:`, e); - }); - }, delay); - } - } - - /** - * 获取botId对应的client - * @param botId - * @private - */ - private async getBotClient(botId: number): Promise { - const userPath = this.paths.get('userData'); - const botsPath = path.join(userPath, '/crystelfBots'); - const dirData = await fs.readdir(botsPath); - - for (const clientId of dirData) { - if (!clientId.endsWith('.json')) continue; - - try { - const raw = await this.redisService.fetch('crystelfBots', clientId); - if (!Array.isArray(raw)) continue; - - for (const bot of raw) { - const uin = Number(bot.uin); - if (!isNaN(uin) && uin === botId) { - return path.basename(clientId, '.json'); - } - } - } catch (err) { - this.logger.error(`读取${clientId}出错..`); - } - } - return undefined; - } - - /** - * 获取groupId对应的botId - * @param groupId - * @private - */ - private async getGroupBot(groupId: number): Promise { - const userPath = this.paths.get('userData'); - const botsPath = path.join(userPath, '/crystelfBots'); - const dirData = await fs.readdir(botsPath); - - for (const clientId of dirData) { - if (!clientId.endsWith('.json')) continue; - - try { - const raw = await this.redisService.fetch('crystelfBots', clientId); - if (!Array.isArray(raw)) continue; - - for (const bot of raw) { - const uin = Number(bot.uin); - const groups = bot.groups; - if (!uin || !Array.isArray(groups)) continue; - - if (groups.find((g) => Number(g.group_id) === groupId)) { - return uin; - } - } - } catch (err) { - this.logger.error(`读取${clientId}出错..`); - } - } - return undefined; - } -}