xiongzhu 2 年之前
父節點
當前提交
650c7f55ee

+ 5 - 5
.env

@@ -39,11 +39,11 @@ TYPEORM_ENTITIES_DIR="src/entity"
 TYPEORM_MIGRATIONS_DIR="src/migrations"
 TYPEORM_SUBSCRIBERS_DIR="src/subscriber"
 
-ALIYUN_ACCESS_KEY_ID=LTAI5tMMG3xUpcrHJghSzr5b
-ALIYUN_ACCESS_KEY_SECRET=mLd5cMMPjxAd4eGgsNlNXQgzalYDjN
-ALIYUN_OSS_ENDPOINT=oss-ap-southeast-5.aliyuncs.com
-ALIYUN_OSS_BUCKET=nbooks
-ALIYUN_OSS_REGION=oss-ap-southeast-5
+ALIYUN_ACCESS_KEY_ID=PXzJyah5rZfWHIIH
+ALIYUN_ACCESS_KEY_SECRET=e1MS6j0wypXJrw8CM0hObZu8qKbfah
+ALIYUN_OSS_ENDPOINT=oss-cn-hangzhou.aliyuncs.com
+ALIYUN_OSS_BUCKET=nebuai
+ALIYUN_OSS_REGION=oss-cn-hangzhou
 ALIYUN_OSS_CDN=https://cdn.raex.vip
 ALIYUN_SMS_SIGN=走马信息
 ALIYUN_SMS_TEMPLATE_CODE=SMS_175485688

+ 5 - 5
.env.production

@@ -39,11 +39,11 @@ TYPEORM_ENTITIES_DIR="src/entity"
 TYPEORM_MIGRATIONS_DIR="src/migrations"
 TYPEORM_SUBSCRIBERS_DIR="src/subscriber"
 
-ALIYUN_ACCESS_KEY_ID=LTAI5tMMG3xUpcrHJghSzr5b
-ALIYUN_ACCESS_KEY_SECRET=mLd5cMMPjxAd4eGgsNlNXQgzalYDjN
-ALIYUN_OSS_ENDPOINT=oss-ap-southeast-5.aliyuncs.com
-ALIYUN_OSS_BUCKET=nbooks
-ALIYUN_OSS_REGION=oss-ap-southeast-5
+ALIYUN_ACCESS_KEY_ID=PXzJyah5rZfWHIIH
+ALIYUN_ACCESS_KEY_SECRET=e1MS6j0wypXJrw8CM0hObZu8qKbfah
+ALIYUN_OSS_ENDPOINT=oss-cn-hangzhou.aliyuncs.com
+ALIYUN_OSS_BUCKET=nebuai
+ALIYUN_OSS_REGION=oss-cn-hangzhou
 ALIYUN_OSS_CDN=https://cdn.raex.vip
 ALIYUN_SMS_SIGN=走马信息
 ALIYUN_SMS_TEMPLATE_CODE=SMS_175485688

+ 6 - 1
src/danmu/danmu.module.ts

@@ -7,9 +7,14 @@ import { Game } from '../game/entities/game.entity'
 import { AccessToken } from './entities/access-token.entity'
 import { DanmuUser } from './entities/danmu-user.entity'
 import { RoomModule } from 'src/room/room.module'
+import { FileModule } from 'src/file/file.module'
 
 @Module({
-    imports: [TypeOrmModule.forFeature([Danmu, Game, AccessToken, DanmuUser]), forwardRef(() => RoomModule)],
+    imports: [
+        TypeOrmModule.forFeature([Danmu, Game, AccessToken, DanmuUser]),
+        forwardRef(() => RoomModule),
+        FileModule
+    ],
     controllers: [DanmuController],
     providers: [DanmuService],
     exports: [DanmuService]

+ 43 - 10
src/danmu/danmu.service.ts

@@ -1,6 +1,6 @@
 import { Inject, Injectable, Logger, OnModuleInit, forwardRef } from '@nestjs/common'
 import { InjectRepository } from '@nestjs/typeorm'
-import { In, MoreThan, Repository } from 'typeorm'
+import { In, Like, MoreThan, Repository } from 'typeorm'
 import { Danmu } from './entities/danmu.enitity'
 import axios from 'axios'
 import { Client } from 'tmi.js'
@@ -9,6 +9,9 @@ import { StreamPlatform } from '../common/enums/stream-platform.enum'
 import { DanmuUser } from './entities/danmu-user.entity'
 import { RoomService } from '../room/room.service'
 import { Room } from '../room/entities/room.entity'
+import { mkdtempSync, rmSync } from 'fs'
+import { FileService } from 'src/file/file.service'
+import path = require('path')
 
 @Injectable()
 export class DanmuService implements OnModuleInit {
@@ -23,7 +26,8 @@ export class DanmuService implements OnModuleInit {
         @InjectRepository(DanmuUser)
         private readonly danmuUserRepository: Repository<DanmuUser>,
         @Inject(forwardRef(() => RoomService))
-        private readonly roomService: RoomService
+        private readonly roomService: RoomService,
+        private readonly fileService: FileService
     ) {}
 
     async onModuleInit() {
@@ -119,6 +123,7 @@ export class DanmuService implements OnModuleInit {
                         danmuUserId: danmuUser.id,
                         channel,
                         platformRoomId: context['room-id'],
+                        platformUserId: platformUserId,
                         content: msg,
                         roomId: room.id,
                         gameId: room.currentGameId
@@ -166,18 +171,46 @@ export class DanmuService implements OnModuleInit {
         })
     }
 
-    async pickAsCharactor(gameId: number, startTime: Date) {
-        const res = await this.danmuRepository
-            .createQueryBuilder('danmu')
-            .select()
-            .where('danmu.gameId = :gameId', { gameId })
-            .andWhere('danmu.createdAt > :startTime', { startTime })
-            .andWhere('danmu.content like :charactor', { charactor: '扮演%' })
-            .getRawMany()
+    async pickCharactor(gameId: number, startTime: Date) {
+        const res = await this.danmuRepository.find({
+            where: {
+                gameId,
+                createdAt: MoreThan(startTime),
+                content: Like('我是%')
+            }
+        })
         if (res.length === 0) {
             return null
         }
         const index = Math.floor(Math.random() * res.length)
         return res[index]
     }
+
+    async findDanmuUser(danmuUserId: number) {
+        const danmuUser = await this.danmuUserRepository.findOneBy({
+            id: danmuUserId
+        })
+        const {
+            data: {
+                data: [user]
+            }
+        } = await axios.get('https://api.twitch.tv/helix/users', {
+            headers: {
+                Authorization: `Bearer ${await this.getTwitchAppAccessToken()}`,
+                'Client-ID': process.env.TWITCH_CLIENT_ID
+            },
+            params: {
+                id: danmuUser.platformUserId
+            }
+        })
+        const { data } = await axios.get(user.profile_image_url, { responseType: 'arraybuffer' })
+        danmuUser.avatar = (
+            await this.fileService.uploadBuffer(
+                data,
+                `avatar/${danmuUser.platform}/${danmuUser.platformUserId}.${path.extname(user.profile_image_url)}`
+            )
+        ).url
+        danmuUser.name = user.display_name
+        return await this.danmuUserRepository.save(danmuUser)
+    }
 }

+ 5 - 2
src/danmu/entities/danmu.enitity.ts

@@ -20,8 +20,11 @@ export class Danmu {
     @Column()
     danmuUserId: number
 
-    @Column({ nullable: true })
-    platformRoomId?: string
+    @Column()
+    platformRoomId: string
+
+    @Column()
+    platformUserId: string
 
     @Column({ nullable: true })
     channel?: string

+ 2 - 1
src/file/file.module.ts

@@ -6,6 +6,7 @@ import { AliyunModule } from '../aliyun/aliyun.module'
 @Module({
     imports: [AliyunModule],
     providers: [FileService],
-    controllers: [FileController]
+    controllers: [FileController],
+    exports: [FileService]
 })
 export class FileModule {}

+ 5 - 0
src/file/file.service.ts

@@ -29,4 +29,9 @@ export class FileService {
         }
         return filename.substring(index)
     }
+
+    public async uploadBuffer(buffer: Buffer, path) {
+        const result = await this.aliyunService.uploadFile(path, buffer)
+        return result
+    }
 }

+ 28 - 15
src/game/game.service.ts

@@ -27,7 +27,7 @@ import { StructuredOutputParser } from 'langchain/output_parsers'
 import { z } from 'zod'
 import { UpdateGameDto } from './dto/update-game.dto'
 import { encoding_for_model } from 'tiktoken'
-import { startOfDay, getHours, format, addDays, differenceInSeconds } from 'date-fns'
+import { startOfDay, getHours, format, addDays, differenceInSeconds, addMinutes } from 'date-fns'
 import { zhCN } from 'date-fns/locale'
 import { GameStatus } from './enums/game-status.enum'
 import dedent from 'dedent'
@@ -410,15 +410,19 @@ export class GameService implements OnModuleInit {
         const formatedDatatime = this.formatDatetime(date, time)
 
         if (newCharactor) {
-            newCharactor = new Charactor({
-                ...newCharactor,
-                hp: 100,
-                joinAt: date
-            })
-            this.send(`${id}`, {
-                type: 'newCharactor',
-                data: options
-            })
+            if (charactors.find((i) => i.danmuUserId === newCharactor.danmuUserId)) {
+                newCharactor = null
+            } else {
+                newCharactor = new Charactor({
+                    ...newCharactor,
+                    hp: 100,
+                    joinAt: date
+                })
+                this.send(`${id}`, {
+                    type: 'newCharactor',
+                    data: options
+                })
+            }
         }
         const input = await prompt.format({
             background: game.background,
@@ -611,7 +615,7 @@ export class GameService implements OnModuleInit {
             while (!stop) {
                 const [state, c] = await Promise.all([
                     this.continue(id, null, createOptions, newCharactor),
-                    this.createCharactorFromDanmu(id, new Date())
+                    this.createCharactorFromDanmu(id, addMinutes(new Date(), -5))
                 ])
                 if (c) {
                     newCharactor = { ...c }
@@ -698,10 +702,19 @@ export class GameService implements OnModuleInit {
     }
 
     async createCharactorFromDanmu(id: number, date: Date) {
-        const danmu = await this.danmuService.pickAsCharactor(id, date)
+        this.logger.log(`从弹幕中创建角色`)
+        const danmu = await this.danmuService.pickCharactor(id, date)
         if (danmu) {
             const addCharactor = danmu.content.replace(/^我是/, '')
-            return await this.createNewCharactor(id, addCharactor)
+            const danmuUser = await this.danmuService.findDanmuUser(danmu.danmuUserId)
+            const charactor = await this.createNewCharactor(id, addCharactor)
+            this.logger.log(`已创建角色: ${JSON.stringify(charactor)}`)
+            charactor.name = danmuUser.name
+            charactor.avatar = danmuUser.avatar
+            charactor.danmuUserId = danmu.danmuUserId
+            return charactor
+        } else {
+            this.logger.log(`没有找到弹幕`)
         }
         return null
     }
@@ -769,7 +782,7 @@ export class GameService implements OnModuleInit {
                         age: z.string().describe('年龄'),
                         occupation: z.string().describe('职业'),
                         personality: z.string().describe('性格'),
-                        background: z.string().describe('背景')
+                        background: z.string().describe('背景, 20字以内')
                     })
                 )
                 const formatInstructions = parser.getFormatInstructions()
@@ -787,7 +800,7 @@ export class GameService implements OnModuleInit {
                     new HumanMessage(input)
                 ])
                 const output = await parser.parse(response.content)
-                return output
+                return output as Charactor
             } catch (error) {
                 this.logger.error(error)
             }

+ 2 - 0
src/game/models/charactor.model.ts

@@ -31,6 +31,8 @@ export class Charactor {
 
     deadAt?: Date
 
+    danmuUserId?: number
+
     constructor(charactor: Partial<Charactor>) {
         Object.assign(this, charactor)
     }