-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Muse, Radio & Music): Migrate to tcp services & keep count of us…
…ed instances by hand
- Loading branch information
1 parent
002bfde
commit eecd2a4
Showing
39 changed files
with
773 additions
and
639 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
apps/muse/src/modules/discord/music/controllers/music.controller.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { LavalinkMusicEvent } from '@music'; | ||
import { Controller, Logger } from '@nestjs/common'; | ||
import { EventPattern, Payload } from '@nestjs/microservices'; | ||
import { PlayerState } from 'kazagumo'; | ||
import { MusicInstancesService } from '../services/instances.service'; | ||
|
||
@Controller() | ||
export class MusicController { | ||
private readonly _logger = new Logger(MusicController.name); | ||
|
||
constructor(private _instances: MusicInstancesService) {} | ||
|
||
@EventPattern('MUSIC_INSTANCE_BOOTED') | ||
booted( | ||
@Payload() | ||
{ instance }: { instance: number }, | ||
) { | ||
this._logger.log(`Music instance booted up ${instance}`); | ||
this._instances.clearInstance(instance); | ||
} | ||
|
||
@EventPattern('MUSIC_STATE_CHANGED') | ||
stateChanged( | ||
@Payload() | ||
{ | ||
instance, | ||
guildId, | ||
state, | ||
}: { | ||
instance: number; | ||
guildId: string; | ||
state: PlayerState; | ||
}, | ||
) { | ||
this._logger.log(`Music instance state changed ${instance} - ${state}`); | ||
this._instances.updateState(instance, guildId, state); | ||
} | ||
|
||
@EventPattern('MUSIC_CONNECTED') | ||
connected( | ||
@Payload() | ||
{ | ||
instance, | ||
guildId, | ||
voiceChannelId, | ||
}: { instance: number } & LavalinkMusicEvent, | ||
) { | ||
this._logger.log(`Received music connected message for ${instance}`); | ||
this._instances.connect(instance, guildId, voiceChannelId); | ||
} | ||
|
||
@EventPattern('MUSIC_DISCONNECTED') | ||
disconnected( | ||
@Payload() | ||
{ instance, guildId }: { instance: number } & LavalinkMusicEvent, | ||
) { | ||
this._logger.log(`Received music disconnected message for ${instance}`); | ||
this._instances.disconnect(instance, guildId); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './instances.service'; | ||
export * from './music.service'; | ||
export * from './settings.service'; |
199 changes: 199 additions & 0 deletions
199
apps/muse/src/modules/discord/music/services/instances.service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
import { | ||
Inject, | ||
Injectable, | ||
Logger, | ||
OnModuleInit, | ||
Optional, | ||
} from '@nestjs/common'; | ||
import { ClientProxy } from '@nestjs/microservices'; | ||
import { Cron } from '@nestjs/schedule'; | ||
import { PrismaService } from '@prisma'; | ||
import { PlayerState } from 'kazagumo'; | ||
import { firstValueFrom, take, timeout } from 'rxjs'; | ||
|
||
@Injectable() | ||
export class MusicInstancesService implements OnModuleInit { | ||
private readonly _logger = new Logger(MusicInstancesService.name); | ||
|
||
private _instances: ClientProxy[] = []; | ||
|
||
constructor( | ||
private _prisma: PrismaService, | ||
@Optional() @Inject('MUSIC_SERVICE_1') private _music: ClientProxy, | ||
@Optional() @Inject('MUSIC_SERVICE_2') private _music2: ClientProxy, | ||
@Optional() @Inject('MUSIC_SERVICE_3') private _music3: ClientProxy, | ||
) { | ||
this._instances.push(this._music); | ||
this._instances.push(this._music2); | ||
this._instances.push(this._music3); | ||
} | ||
|
||
onModuleInit() { | ||
setTimeout(() => this._checkInstances(), 2000); | ||
} | ||
|
||
async getAvailableOrExisting(guildId: string, voiceChannelId: string) { | ||
const exists = await this.getByVoiceId(guildId, voiceChannelId); | ||
if (exists) { | ||
return exists; | ||
} | ||
|
||
const all = await this._prisma.musicServiceMap.findMany({ | ||
where: { | ||
guildId, | ||
}, | ||
}); | ||
|
||
if (all.length === this._instances.length) { | ||
return null; | ||
} | ||
|
||
const available: number[] = this._instances | ||
.filter((i) => !!i) | ||
.map((_, i) => i + 1); | ||
for (const entry of all) { | ||
const index = available.indexOf(entry.instance); | ||
available.splice(index, 1); | ||
} | ||
|
||
if (!available.length) { | ||
return null; | ||
} | ||
|
||
return available[0]; | ||
} | ||
|
||
async getByVoiceId(guildId: string, voiceChannelId: string) { | ||
const current = await this._prisma.musicServiceMap.findFirst({ | ||
where: { | ||
guildId, | ||
voiceChannelId, | ||
}, | ||
}); | ||
|
||
if (!current) { | ||
return null; | ||
} | ||
|
||
return current.instance; | ||
} | ||
|
||
get(instance: number) { | ||
if (instance < 1) { | ||
instance = 1; | ||
} | ||
|
||
return this._instances[instance - 1]; | ||
} | ||
|
||
async connect(instance: number, guildId: string, voiceChannelId: string) { | ||
await this.disconnect(instance, guildId); | ||
|
||
const result = await this.sendCommand<{ state: number }>( | ||
instance, | ||
'MUSIC_STATUS', | ||
{ | ||
guildId, | ||
}, | ||
); | ||
|
||
await this._prisma.musicServiceMap.create({ | ||
data: { | ||
guildId, | ||
voiceChannelId, | ||
state: result.state, | ||
instance, | ||
}, | ||
}); | ||
} | ||
|
||
async disconnect(instance: number, guildId: string) { | ||
const hasOne = await this._prisma.musicServiceMap.findFirst({ | ||
where: { | ||
guildId, | ||
instance, | ||
}, | ||
}); | ||
|
||
if (hasOne) { | ||
await this._prisma.musicServiceMap.delete({ | ||
where: { | ||
id: hasOne.id, | ||
}, | ||
}); | ||
} | ||
} | ||
|
||
async clearInstance(instance: number) { | ||
await this._prisma.musicServiceMap.deleteMany({ | ||
where: { | ||
instance, | ||
}, | ||
}); | ||
} | ||
|
||
async sendCommand<T = any>(instance: number, command: string, data: any) { | ||
const _instance = this.get(instance); | ||
|
||
if (!_instance) { | ||
return null; | ||
} | ||
|
||
const result = await (firstValueFrom( | ||
_instance.send(command, data).pipe(take(1), timeout(5000)), | ||
) as Promise<T & { result: string }>); | ||
|
||
return result; | ||
} | ||
|
||
async updateState(instance: number, guildId: string, state: PlayerState) { | ||
const entry = await this._prisma.musicServiceMap.findFirst({ | ||
where: { | ||
instance, | ||
guildId, | ||
}, | ||
}); | ||
|
||
if (!entry) { | ||
return null; | ||
} | ||
|
||
return this._prisma.musicServiceMap.update({ | ||
where: { | ||
id: entry.id, | ||
}, | ||
data: { | ||
state, | ||
}, | ||
}); | ||
} | ||
|
||
@Cron('0 */10 * * * *') | ||
protected async _checkInstances() { | ||
this._logger.log('Checking alive instances'); | ||
const unfinished = await this._prisma.musicServiceMap.findMany(); | ||
|
||
for (const entry of unfinished) { | ||
const result = await this.sendCommand<{ state: number }>( | ||
entry.instance, | ||
'MUSIC_STATUS', | ||
{ | ||
guildId: entry.guildId, | ||
}, | ||
).catch(() => null); | ||
|
||
if ( | ||
!result || | ||
[ | ||
PlayerState.DISCONNECTED, | ||
PlayerState.DISCONNECTING, | ||
PlayerState.DESTROYING, | ||
PlayerState.DESTROYED, | ||
-1, | ||
].indexOf(result.state) >= 0 | ||
) { | ||
this.disconnect(entry.instance, entry.guildId); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.