mirror of
https://github.com/crystelf/crystelf-core.git
synced 2025-12-05 10:31:56 +00:00
feat:随机表情包
This commit is contained in:
parent
264cd37dd5
commit
c7108833ed
4
.gitignore
vendored
4
.gitignore
vendored
@ -2,6 +2,10 @@
|
|||||||
/dist
|
/dist
|
||||||
/node_modules
|
/node_modules
|
||||||
/build
|
/build
|
||||||
|
/private
|
||||||
|
/public
|
||||||
|
/logs
|
||||||
|
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { SystemWebModule } from './modules/system/systemWeb.module';
|
|||||||
import { BotModule } from './modules/bot/bot.module';
|
import { BotModule } from './modules/bot/bot.module';
|
||||||
import { CdnModule } from './modules/cdn/cdn.module';
|
import { CdnModule } from './modules/cdn/cdn.module';
|
||||||
import { WordsModule } from './modules/words/words.module';
|
import { WordsModule } from './modules/words/words.module';
|
||||||
|
import { MemeModule } from './modules/meme/meme.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -28,6 +29,7 @@ import { WordsModule } from './modules/words/words.module';
|
|||||||
BotModule,
|
BotModule,
|
||||||
CdnModule,
|
CdnModule,
|
||||||
WordsModule,
|
WordsModule,
|
||||||
|
MemeModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@ -28,6 +28,8 @@ export class PathService {
|
|||||||
package: path.join(this.baseDir, 'package.json'),
|
package: path.join(this.baseDir, 'package.json'),
|
||||||
modules: path.join(this.baseDir, 'src/modules'),
|
modules: path.join(this.baseDir, 'src/modules'),
|
||||||
words: path.join(this.baseDir, 'private/words/src'),
|
words: path.join(this.baseDir, 'private/words/src'),
|
||||||
|
private: path.join(this.baseDir, 'private'),
|
||||||
|
meme: path.join(this.baseDir, 'private/meme'),
|
||||||
};
|
};
|
||||||
|
|
||||||
return type ? mappings[type] : this.baseDir;
|
return type ? mappings[type] : this.baseDir;
|
||||||
@ -45,6 +47,7 @@ export class PathService {
|
|||||||
this.get('temp'),
|
this.get('temp'),
|
||||||
this.get('public'),
|
this.get('public'),
|
||||||
this.get('words'),
|
this.get('words'),
|
||||||
|
this.get('meme'),
|
||||||
];
|
];
|
||||||
|
|
||||||
pathsToInit.forEach((dirPath) => {
|
pathsToInit.forEach((dirPath) => {
|
||||||
@ -105,5 +108,7 @@ export type PathType =
|
|||||||
| 'temp'
|
| 'temp'
|
||||||
| 'userData'
|
| 'userData'
|
||||||
| 'package'
|
| 'package'
|
||||||
|
| 'meme'
|
||||||
| 'modules'
|
| 'modules'
|
||||||
|
| 'private'
|
||||||
| 'words';
|
| 'words';
|
||||||
|
|||||||
69
src/modules/meme/meme.controller.ts
Normal file
69
src/modules/meme/meme.controller.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Res,
|
||||||
|
HttpException,
|
||||||
|
HttpStatus,
|
||||||
|
Logger,
|
||||||
|
Inject,
|
||||||
|
} 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';
|
||||||
|
|
||||||
|
class MemeRequestDto {
|
||||||
|
@ApiProperty({ description: '角色名称', example: 'zhenxun', required: false })
|
||||||
|
character?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '状态', example: 'happy', required: false })
|
||||||
|
status?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Controller('meme')
|
||||||
|
@ApiTags('Meme')
|
||||||
|
export class MemeController {
|
||||||
|
private readonly logger = new Logger(MemeController.name);
|
||||||
|
|
||||||
|
constructor(@Inject(MemeService) private readonly memeService: MemeService) {}
|
||||||
|
|
||||||
|
@Post('getRandom')
|
||||||
|
@ApiOperation({ summary: '获取随机表情包' })
|
||||||
|
@ApiBody({ type: MemeRequestDto })
|
||||||
|
async getRandomMeme(@Body() dto: MemeRequestDto, @Res() res: Response) {
|
||||||
|
try {
|
||||||
|
const memePath = await this.memeService.getRandomMemePath(
|
||||||
|
dto.character,
|
||||||
|
dto.status,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!memePath) {
|
||||||
|
throw new HttpException(
|
||||||
|
'没有找到符合条件的表情包',
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const stream = fs.createReadStream(memePath);
|
||||||
|
stream.on('error', () => {
|
||||||
|
throw new HttpException(
|
||||||
|
'读取表情包失败',
|
||||||
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
stream.pipe(res);
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(`获取表情包失败: ${e.message}`);
|
||||||
|
throw new HttpException('服务器错误', HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/modules/meme/meme.module.ts
Normal file
12
src/modules/meme/meme.module.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { MemeService } from './meme.service';
|
||||||
|
import { MemeController } from './meme.controller';
|
||||||
|
import { PathService } from '../../core/path/path.service';
|
||||||
|
import { PathModule } from '../../core/path/path.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PathModule],
|
||||||
|
providers: [MemeService],
|
||||||
|
controllers: [MemeController],
|
||||||
|
})
|
||||||
|
export class MemeModule {}
|
||||||
83
src/modules/meme/meme.service.ts
Normal file
83
src/modules/meme/meme.service.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as fs from 'fs/promises';
|
||||||
|
import { PathService } from '../../core/path/path.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MemeService {
|
||||||
|
private readonly logger = new Logger(MemeService.name);
|
||||||
|
|
||||||
|
constructor(@Inject(PathService) private readonly pathService: PathService) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取表情路径
|
||||||
|
* @param character 角色
|
||||||
|
* @param status 状态
|
||||||
|
*/
|
||||||
|
async getRandomMemePath(
|
||||||
|
character?: string,
|
||||||
|
status?: string,
|
||||||
|
): Promise<string | null> {
|
||||||
|
const baseDir = path.join(this.pathService.get('private'), 'meme');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!character) {
|
||||||
|
return this.getRandomFileRecursive(baseDir);
|
||||||
|
}
|
||||||
|
const characterDir = path.join(baseDir, character);
|
||||||
|
if (!status) {
|
||||||
|
return this.getRandomFileRecursive(characterDir);
|
||||||
|
}
|
||||||
|
const statusDir = path.join(characterDir, status);
|
||||||
|
return this.getRandomFileFromDir(statusDir);
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.error(`获取表情包失败: ${e.message}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从目录中随机获取一张图片
|
||||||
|
*/
|
||||||
|
private async getRandomFileFromDir(dir: string): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const files = await fs.readdir(dir);
|
||||||
|
const images = files.filter((f) => /\.(jpg|jpeg|png|gif|webp)$/i.test(f));
|
||||||
|
if (images.length === 0) return null;
|
||||||
|
const randomFile = images[Math.floor(Math.random() * images.length)];
|
||||||
|
return path.join(dir, randomFile);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 随机选择一张图片
|
||||||
|
* @param dir 绝对路径
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
private async getRandomFileRecursive(dir: string): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
||||||
|
const files: string[] = [];
|
||||||
|
const dirs: string[] = [];
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
dirs.push(path.join(dir, entry.name));
|
||||||
|
} else if (/\.(jpg|jpeg|png|gif|webp)$/i.test(entry.name)) {
|
||||||
|
files.push(path.join(dir, entry.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let allFiles = [...files];
|
||||||
|
for (const subDir of dirs) {
|
||||||
|
const subFile = await this.getRandomFileRecursive(subDir);
|
||||||
|
if (subFile) allFiles.push(subFile);
|
||||||
|
}
|
||||||
|
if (allFiles.length === 0) return null;
|
||||||
|
return allFiles[Math.floor(Math.random() * allFiles.length)];
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user