Compare commits

..

No commits in common. "c1c3f9a5fdb19afce679f79d5798f892c6361d44" and "296bd924cde8d1ed79a5142582c4cb061c9807e8" have entirely different histories.

19 changed files with 54 additions and 143 deletions

View File

@ -13,4 +13,4 @@
## 贡献
- fork到自己的储存库
- 在自己的储存库内推送更新
- 提交pr,等待合并
- 提交pr等待合并

View File

@ -30,7 +30,6 @@
"rxjs": "^7.8.1",
"simple-git": "^3.28.0",
"ssh2": "^1.16.0",
"stream-throttle": "^0.1.3",
"uuid": "^11.1.0",
"ws": "^8.18.3"
},
@ -45,7 +44,6 @@
"@types/express": "^5.0.0",
"@types/jest": "^29.5.14",
"@types/node": "^22.16.4",
"@types/stream-throttle": "^0.1.4",
"@types/supertest": "^6.0.2",
"@types/ws": "^8.18.1",
"eslint": "^9.18.0",

39
pnpm-lock.yaml generated
View File

@ -53,9 +53,6 @@ importers:
ssh2:
specifier: ^1.16.0
version: 1.16.0
stream-throttle:
specifier: ^0.1.3
version: 0.1.3
uuid:
specifier: ^11.1.0
version: 11.1.0
@ -93,9 +90,6 @@ importers:
'@types/node':
specifier: ^22.16.4
version: 22.16.5
'@types/stream-throttle':
specifier: ^0.1.4
version: 0.1.4
'@types/supertest':
specifier: ^6.0.2
version: 6.0.3
@ -705,42 +699,49 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@napi-rs/nice-linux-arm64-musl@1.0.4':
resolution: {integrity: sha512-4b1KYG+sriufhFrpUS9uNOEYYJqSfcbnwGx6uGX7JjrH8tELG90cOpCawz5THNIwlS3DhLgnCOcn0+4p6z26QA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@napi-rs/nice-linux-ppc64-gnu@1.0.4':
resolution: {integrity: sha512-iaf3vMRgr23oe1PUaKpxaH3DS0IMN0+N9iEiWVwYPm/U15vZFYdqVegGfN2PzrZLUl5lc8ZxbmEKDfuqslhAMA==}
engines: {node: '>= 10'}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@napi-rs/nice-linux-riscv64-gnu@1.0.4':
resolution: {integrity: sha512-UXoREY6Yw6rHrGuTwQgBxpfjK34t6mTjibE9/cXbefL9AuUCJ9gEgwNKZiONuR5QGswChqo9cnthjdKkYyAdDg==}
engines: {node: '>= 10'}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@napi-rs/nice-linux-s390x-gnu@1.0.4':
resolution: {integrity: sha512-eFbgYCRPmsqbYPAlLYU5hYTNbogmIDUvknilehHsFhCH1+0/kN87lP+XaLT0Yeq4V/rpwChSd9vlz4muzFArtw==}
engines: {node: '>= 10'}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@napi-rs/nice-linux-x64-gnu@1.0.4':
resolution: {integrity: sha512-4T3E6uTCwWT6IPnwuPcWVz3oHxvEp/qbrCxZhsgzwTUBEwu78EGNXGdHfKJQt3soth89MLqZJw+Zzvnhrsg1mQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@napi-rs/nice-linux-x64-musl@1.0.4':
resolution: {integrity: sha512-NtbBkAeyBPLvCBkWtwkKXkNSn677eaT0cX3tygq+2qVv71TmHgX4gkX6o9BXjlPzdgPGwrUudavCYPT9tzkEqQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@napi-rs/nice-win32-arm64-msvc@1.0.4':
resolution: {integrity: sha512-vubOe3i+YtSJGEk/++73y+TIxbuVHi+W8ZzrRm2eETCjCRwNlgbfToQZ85dSA+4iBB/NJRGNp+O4hfdbbttZWA==}
@ -985,24 +986,28 @@ packages:
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@swc/core-linux-arm64-musl@1.13.1':
resolution: {integrity: sha512-JaqFdBCarIBKiMu5bbAp+kWPMNGg97ej+7KzbKOzWP5pRptqKi86kCDZT3WmjPe8hNG6dvBwbm7Y8JNry5LebQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@swc/core-linux-x64-gnu@1.13.1':
resolution: {integrity: sha512-t4cLkku10YECDaakWUH0452WJHIZtrLPRwezt6BdoMntVMwNjvXRX7C8bGuYcKC3YxRW7enZKFpozLhQIQ37oA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@swc/core-linux-x64-musl@1.13.1':
resolution: {integrity: sha512-fSMwZOaG+3ukUucbEbzz9GhzGhUhXoCPqHe9qW0/Vc2IZRp538xalygKyZynYweH5d9EHux1aj3+IO8/xBaoiA==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@swc/core-win32-arm64-msvc@1.13.1':
resolution: {integrity: sha512-tweCXK/79vAwj1NhAsYgICy8T1z2QEairmN2BFEBYFBFNMEB1iI1YlXwBkBtuihRvgZrTh1ORusKa4jLYzLCZA==}
@ -1147,9 +1152,6 @@ packages:
'@types/stack-utils@2.0.3':
resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
'@types/stream-throttle@0.1.4':
resolution: {integrity: sha512-VxXIHGjVuK8tYsVm60rIQMmF/0xguCeen5OmK5S4Y6K64A+z+y4/GI6anRnVzaUZaJB9Ah9IfbDcO0o1gZCc/w==}
'@types/superagent@8.1.9':
resolution: {integrity: sha512-pTVjI73witn+9ILmoJdajHGW2jkSaOzhiFYF1Rd3EQ94kymLqB9PjD9ISg7WaALC7+dCHT0FGe9T2LktLq/3GQ==}
@ -2617,9 +2619,6 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
limiter@1.1.5:
resolution: {integrity: sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==}
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
@ -3263,11 +3262,6 @@ packages:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
stream-throttle@0.1.3:
resolution: {integrity: sha512-889+B9vN9dq7/vLbGyuHeZ6/ctf5sNuGWsDy89uNxkFTAgzy0eK7+w5fL3KLNRTkLle7EgZGvHUphZW0Q26MnQ==}
engines: {node: '>= 0.10.0'}
hasBin: true
streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
engines: {node: '>=10.0.0'}
@ -4834,10 +4828,6 @@ snapshots:
'@types/stack-utils@2.0.3': {}
'@types/stream-throttle@0.1.4':
dependencies:
'@types/node': 22.16.5
'@types/superagent@8.1.9':
dependencies:
'@types/cookiejar': 2.1.5
@ -6621,8 +6611,6 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
limiter@1.1.5: {}
lines-and-columns@1.2.4: {}
load-esm@1.0.2: {}
@ -7221,11 +7209,6 @@ snapshots:
statuses@2.0.2: {}
stream-throttle@0.1.3:
dependencies:
commander: 2.20.3
limiter: 1.1.5
streamsearch@1.1.0: {}
streamx@2.22.1:

View File

@ -25,7 +25,7 @@ export class AppConfigService implements OnModuleInit {
if (defaultValue !== undefined) {
return defaultValue;
}
this.logger.error(`环境变量 ${key} 未定义!`);
this.logger.error(`环境变量 ${key} 未定义`);
}
return value;
}

View File

@ -33,17 +33,17 @@ export class AutoUpdateService {
label = '子仓库',
): Promise<boolean> {
try {
this.logger.log(`[${label}] 检查仓库更新中..`);
this.logger.log(`[${label}] 检查仓库更新中...`);
const repoGit = simpleGit(folderPath);
const status = await repoGit.status();
if (status.ahead > 0) {
this.logger.warn(`[${label}] 检测到本地仓库有未提交的更改,跳过更新..`);
this.logger.warn(`[${label}] 检测到本地仓库有未提交的更改,跳过更新`);
return false;
}
this.logger.log(`[${label}] 正在获取远程仓库信息..`);
this.logger.log(`[${label}] 正在获取远程仓库信息...`);
await repoGit.fetch();
const localBranch = status.current;
@ -52,22 +52,22 @@ export class AutoUpdateService {
]);
if (diffSummary.files.length > 0) {
this.logger.log(`[${label}] 检测到远程仓库有更新!`);
this.logger.log(`[${label}] 检测到远程仓库有更新`);
if (localBranch) {
this.logger.log(`[${label}] 正在拉取远程代码..`);
this.logger.log(`[${label}] 正在拉取远程代码...`);
await repoGit.pull('origin', localBranch);
} else {
this.logger.error(`[${label}] 当前分支名称未知,无法执行拉取操作..`);
this.logger.error(`[${label}] 当前分支名称未知,无法执行拉取操作。`);
return false;
}
this.logger.log(`[${label}] 代码更新成功,开始更新依赖..`);
this.logger.log(`[${label}] 代码更新成功,开始更新依赖...`);
await this.updateDependencies(folderPath, label);
this.logger.log(`[${label}] 自动更新流程完成!`);
this.logger.log(`[${label}] 自动更新流程完成`);
return true;
} else {
this.logger.log(`[${label}] 远程仓库没有新变化..`);
this.logger.log(`[${label}] 远程仓库没有新变化`);
return false;
}
} catch (error) {
@ -94,16 +94,16 @@ export class AutoUpdateService {
try {
pkgJson = JSON.parse(readFileSync(pkgPath, 'utf-8'));
} catch {
this.logger.warn(`[${label}] 未找到 package.json,跳过依赖构建`);
this.logger.warn(`[${label}] 未找到 package.json跳过依赖构建`);
return;
}
if (pkgJson.scripts?.build) {
this.logger.log(`[${label}] 检测到 build 脚本,执行 pnpm build..`);
this.logger.log(`[${label}] 检测到 build 脚本,执行 pnpm build...`);
await execAsync('pnpm build', { cwd: folderPath });
this.logger.log(`[${label}] 构建完成!`);
this.logger.log(`[${label}] 构建完成`);
} else {
this.logger.log(`[${label}] 未检测到 build 脚本,跳过构建..`);
this.logger.log(`[${label}] 未检测到 build 脚本,跳过构建`);
}
} catch (error) {
this.logger.error(`[${label}] 更新依赖或构建失败:`, error);

View File

@ -178,29 +178,8 @@ export class RedisService implements OnModuleInit {
await this.Persistence.writeDataLocal(key, data, fileName);
}
/**
* IP
* @param ip IP
* @param bytes
* @param window
*/
public async incrementIpTraffic(
ip: string,
bytes: number,
window = 1,
): Promise<number> {
const key = `traffic:${ip}`;
const total = await this.client.incrby(key, bytes);
await this.client.expire(key, window);
return total;
}
/**
* IP
*/
public async getIpTraffic(ip: string): Promise<number> {
const key = `traffic:${ip}`;
const value = await this.client.get(key);
return value ? parseInt(value, 10) : 0;
public async test(): Promise<void> {
const user = await this.fetch<IUser>('Jerry', 'IUser');
this.logger.debug('User:', user);
}
}

View File

@ -42,7 +42,7 @@ export class SystemService {
const prev = Number(fs.readFileSync(this.restartFile, 'utf-8'));
const duration = ((Date.now() - prev) / 1000 - 5).toFixed(2);
fs.unlinkSync(this.restartFile);
this.logger.debug(`检测到重启,耗时: ${duration}`);
this.logger.debug(`检测到重启耗时: ${duration}`);
return Number(duration);
}
return null;
@ -65,7 +65,7 @@ export class SystemService {
this.logger.debug('检查系统代码更新..');
const updated = await this.autoUpdateService.checkForUpdates();
if (updated) {
this.logger.warn('系统代码已更新,正在重启..');
this.logger.warn('系统代码已更新正在重启..');
process.exit(1);
}
}

View File

@ -65,8 +65,8 @@ export class ToolsService {
public checkToken(token: string): boolean {
const expected = this.config.get<string>('TOKEN');
if (!expected) {
this.logger.error('环境变量 TOKEN 未配置,无法进行验证!');
throw new UnauthorizedException('系统配置错误,缺少 TOKEN');
this.logger.error('环境变量 TOKEN 未配置,无法进行验证!');
throw new UnauthorizedException('系统配置错误缺少 TOKEN');
}
return token === expected;
}

View File

@ -6,7 +6,7 @@ export class WsTools {
static async send(socket: WebSocket, data: unknown): Promise<boolean> {
if (socket.readyState !== 1) {
this.logger.warn('尝试向非 OPEN 状态的 socket 发送消息,已丢弃');
this.logger.warn('尝试向非 OPEN 状态的 socket 发送消息已丢弃');
return false;
}

View File

@ -16,10 +16,10 @@ async function bootstrap() {
if (!fs.existsSync(envPath)) {
if (fs.existsSync(envExamplePath)) {
fs.copyFileSync(envExamplePath, envPath);
Logger.warn(`.env 文件已自动生成,请修改配置后重启核心..`, '', 'ENV');
Logger.warn(`.env 文件已自动生成请修改配置后重启核心..`, '', 'ENV');
process.exit(1);
} else {
Logger.error('配置模块初始化出错,请重新拉取应用!', '', 'ENV');
Logger.error('配置模块初始化出错请重新拉取应用!', '', 'ENV');
process.exit(1);
}
}
@ -39,7 +39,7 @@ async function bootstrap() {
const systemService = app.get(SystemService);
const restartDuration = systemService.checkRestartTime();
if (restartDuration) {
Logger.warn(`重启完成!耗时 ${restartDuration}`, '', 'System');
Logger.warn(`重启完成耗时 ${restartDuration}`, '', 'System');
}
const config = new DocumentBuilder()
.setTitle('晶灵核心')

View File

@ -65,6 +65,6 @@ export class BotController {
@ApiBody({ type: BroadcastDto })
public async smartBroadcast(@Body() dto: BroadcastDto) {
await this.botService.broadcastToAllGroups(dto.message);
return { message: '广播任务已开始,正在后台执行..' };
return { message: '广播任务已开始正在后台执行..' };
}
}

View File

@ -18,7 +18,7 @@ export class SendMessageDto extends GroupInfoDto {
export class BroadcastDto extends TokenDto {
@ApiProperty({
description: '要广播的消息',
example: '全体目光向我看齐!我宣布个事儿..',
example: '全体目光向我看齐我宣布个事儿..',
})
message: string;
}

View File

@ -179,7 +179,7 @@ export class BotService {
},
};
this.logger.log(
`[广播] 向群 ${groupId} 使用Bot ${selectedBotId}(客户端 ${selectedClientId})发送消息${message},延迟 ${
`[广播] 向群 ${groupId} 使用Bot ${selectedBotId}(客户端 ${selectedClientId})发送消息${message}延迟 ${
delay / 1000
} `,
);

View File

@ -26,7 +26,7 @@ export class CdnController {
const filePath = await this.fileService.getFile(relativePath);
if (!filePath) {
this.logger.warn(`${relativePath}:文件不存在..`);
throw new HttpException('文件不存在啦!', HttpStatus.NOT_FOUND);
throw new HttpException('文件不存在啦', HttpStatus.NOT_FOUND);
}
res.sendFile(filePath, (err) => {

View File

@ -7,15 +7,11 @@ import {
HttpStatus,
Logger,
Inject,
Ip,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBody, ApiProperty } from '@nestjs/swagger';
import { MemeService } from './meme.service';
import { Response } from 'express';
import * as fs from 'fs';
import { Throttle } from 'stream-throttle';
import { ToolsService } from '../../core/tools/tools.service';
import { RedisService } from '../../core/redis/redis.service';
class MemeRequestDto {
@ApiProperty({ description: '角色名称', example: 'zhenxun', required: false })
@ -23,13 +19,6 @@ class MemeRequestDto {
@ApiProperty({ description: '状态', example: 'happy', required: false })
status?: string;
@ApiProperty({
description: '可选访问令牌',
example: 'token',
required: false,
})
token?: string;
}
@Controller('meme')
@ -37,14 +26,7 @@ class MemeRequestDto {
export class MemeController {
private readonly logger = new Logger(MemeController.name);
constructor(
@Inject(MemeService)
private readonly memeService: MemeService,
@Inject(ToolsService)
private readonly toolsService: ToolsService,
@Inject(RedisService)
private readonly redisService: RedisService,
) {}
constructor(@Inject(MemeService) private readonly memeService: MemeService) {}
@Post('getRandom')
@ApiOperation({ summary: '获取随机表情包' })
@ -52,13 +34,8 @@ export class MemeController {
public async getRandomMeme(
@Body() dto: MemeRequestDto,
@Res() res: Response,
@Ip() ip: string,
) {
try {
const realToken = dto.token;
const hasValidToken =
realToken && this.toolsService.checkToken(realToken);
const memePath = await this.memeService.getRandomMemePath(
dto.character,
dto.status,
@ -71,15 +48,7 @@ export class MemeController {
);
}
const ext = memePath.split('.').pop()?.toLowerCase();
let contentType = 'image/jpeg';
if (ext === 'png') contentType = 'image/png';
if (ext === 'gif') contentType = 'image/gif';
if (ext === 'webp') contentType = 'image/webp';
res.setHeader('Content-Type', contentType);
const stream = fs.createReadStream(memePath);
stream.on('error', () => {
throw new HttpException(
'读取表情包失败',
@ -87,30 +56,16 @@ export class MemeController {
);
});
if (hasValidToken) {
this.logger.log(`有token入不限速: ${memePath}`);
stream.pipe(res);
} else {
stream.on('data', async (chunk) => {
const bytes = chunk.length;
const total = await this.redisService.incrementIpTraffic(
ip,
bytes,
1,
);
if (total > 100 * 1024) {
this.logger.warn(`IP ${ip} 超过速率限制,断开连接..`);
stream.destroy();
res.end();
}
});
const ext = memePath.split('.').pop()?.toLowerCase();
let contentType = 'image/jpeg';
if (ext === 'png') contentType = 'image/png';
if (ext === 'gif') contentType = 'image/gif';
if (ext === 'webp') contentType = 'image/webp';
const throttle = new Throttle({ rate: 100 * 1024 });
this.logger.log(`白嫖的入限速!(${ip}) => ${memePath}`);
stream.pipe(throttle).pipe(res);
}
res.setHeader('Content-Type', contentType);
stream.pipe(res);
} catch (e) {
this.logger.error(`获取表情包失败:${e.message}`);
this.logger.error(`获取表情包失败: ${e.message}`);
throw new HttpException('服务器错误', HttpStatus.INTERNAL_SERVER_ERROR);
}
}

View File

@ -3,11 +3,9 @@ import { MemeService } from './meme.service';
import { MemeController } from './meme.controller';
import { PathModule } from '../../core/path/path.module';
import { AutoUpdateModule } from '../../core/auto-update/auto-update.module';
import { ToolsModule } from '../../core/tools/tools.module';
import { RedisModule } from '../../core/redis/redis.module';
@Module({
imports: [PathModule, AutoUpdateModule, ToolsModule, RedisModule],
imports: [PathModule, AutoUpdateModule],
providers: [MemeService],
controllers: [MemeController],
})

View File

@ -21,7 +21,6 @@ export class MemeService {
private startAutoUpdate() {
setInterval(async () => {
const memePath = this.pathService.get('meme');
//const memePath = path.join(this.pathService.get('meme'),'..'); TODO 需确认检查src更新是否影响正常运行
this.logger.log('定时检查表情仓库更新..');
const updated = await this.autoUpdateService.checkRepoForUpdates(
memePath,

View File

@ -31,7 +31,6 @@ export class WordsService {
private startAutoUpdate() {
setInterval(async () => {
//const wordsPath = path.join(this.paths.get('words'),'..'); TODO 需确认检查src更新是否影响正常运行
const wordsPath = this.paths.get('words');
this.logger.log('定时检查文案仓库更新..');
const updated = await this.autoUpdateService.checkRepoForUpdates(
@ -39,7 +38,7 @@ export class WordsService {
'words 仓库',
);
if (updated) {
this.logger.log('文案仓库已更新,清理缓存..');
this.logger.log('文案仓库已更新清理缓存..');
this.wordCache = {};
}
}, this.updateMs);

View File

@ -1,6 +1,6 @@
while true; do
echo "启动核心.."
echo "启动服务..."
pnpm start
echo "核心退出,5秒后重启.."
echo "服务退出5秒后重启..."
sleep 5
done