지금까지 내용 커밋
This commit is contained in:
278
bot/src/classes/GuildPlayer.ts
Normal file
278
bot/src/classes/GuildPlayer.ts
Normal file
@@ -0,0 +1,278 @@
|
||||
import { Guild, Message, TextChannel } from "discord.js";
|
||||
import { LoadType, Player, Track, TrackEndEvent } from "shoukaku";
|
||||
import { client, lavalinkManager } from "../index";
|
||||
import { timeFormat } from "../utils/music/Utils";
|
||||
import { default_content, default_embed, default_image, getButtons } from "../utils/music/Config";
|
||||
import { GuildType } from "../../db/db";
|
||||
import { DB } from "../utils/Database";
|
||||
import { shuffle } from "../utils/Shuffle";
|
||||
import { checkTextChannelAndMsg } from "../utils/music/Channel";
|
||||
|
||||
const DelayAfterErrMs = 1000 * 5;
|
||||
const idleEndTime = 1000 * 60 * 10;
|
||||
|
||||
type QueueTrack = Track & { userId: string; };
|
||||
|
||||
export class GuildPlayer {
|
||||
private isDead = false;
|
||||
private canRecommend = true;
|
||||
private _GDB: GuildType | undefined;
|
||||
private lastPlayedTrack: QueueTrack | undefined;
|
||||
public queue: QueueTrack[] = [];
|
||||
private errorTimer: NodeJS.Timeout | undefined;
|
||||
private endTimer: NodeJS.Timeout | undefined;
|
||||
|
||||
constructor(
|
||||
public guild: Guild,
|
||||
public player: Player,
|
||||
public voiceChannelId: string,
|
||||
public textChannel: TextChannel,
|
||||
public msg: Message,
|
||||
) {
|
||||
this.player.setGlobalVolume(50);
|
||||
this.player.on("end", async (data: TrackEndEvent) => {
|
||||
if (this.isDead) return;
|
||||
if (data.reason === "replaced") return;
|
||||
// 방금 끝난 곡을 대기열에서 지우면서 lastPlayedTrack에 저장
|
||||
this.lastPlayedTrack = this.queue.shift();
|
||||
if (this.queue.length > 0) {
|
||||
await this.playNext();
|
||||
} else if (this.isRecommend && this.canRecommend) {
|
||||
await this.autoPlay();
|
||||
} else {
|
||||
this.end();
|
||||
}
|
||||
});
|
||||
this.player.on("closed", () => {
|
||||
if (this.isDead) return;
|
||||
this.delete();
|
||||
});
|
||||
}
|
||||
|
||||
private get GDB() {
|
||||
if (!this._GDB) this._GDB = DB.guild.get(this.guild.id);
|
||||
return this._GDB;
|
||||
}
|
||||
public get isPlaying(): boolean {
|
||||
return this.player.track !== null;
|
||||
}
|
||||
public get nowTrack(): QueueTrack | null {
|
||||
return this.queue[0] ?? null;
|
||||
}
|
||||
public get isPaused(): boolean {
|
||||
return this.player.paused;
|
||||
}
|
||||
public get isRecommend(): boolean {
|
||||
return this.GDB?.options.recommend ?? false;
|
||||
}
|
||||
|
||||
public async addTrack(track: Track, userId: string) {
|
||||
this.queue.push({ ...track, userId });
|
||||
if (this.queue.length === 1) {
|
||||
// 큐가 비어있으면 바로 재생
|
||||
await this.playNext();
|
||||
} else {
|
||||
// 재생목록 추가되었을때
|
||||
this.setMsg();
|
||||
}
|
||||
}
|
||||
|
||||
public async addTracks(tracks: Track[], userId: string) {
|
||||
const first = this.queue.length === 0;
|
||||
for (const track of tracks) this.queue.push({ ...track, userId });
|
||||
if (first) {
|
||||
// 큐가 비어있으면 바로 재생
|
||||
await this.playNext();
|
||||
} else {
|
||||
// 재생목록 추가되었을때
|
||||
this.setMsg();
|
||||
}
|
||||
}
|
||||
|
||||
public async playNext() {
|
||||
const track = this.queue[0];
|
||||
if (!track) return this.end();
|
||||
await this.player.playTrack({ track: { encoded: track.encoded } });
|
||||
// 재생관련
|
||||
this.setMsg();
|
||||
}
|
||||
|
||||
public async setPause() {
|
||||
if (this.isPaused) {
|
||||
await this.player.setPaused(false);
|
||||
this.setMsg();
|
||||
} else if (this.isPlaying) {
|
||||
await this.player.setPaused(true);
|
||||
this.setMsg();
|
||||
}
|
||||
}
|
||||
|
||||
public stop() {
|
||||
this.queue = [];
|
||||
this.canRecommend = false;
|
||||
this.player.stopTrack();
|
||||
}
|
||||
|
||||
public skip() {
|
||||
// 곡 스킵했을때
|
||||
this.player.stopTrack();
|
||||
}
|
||||
|
||||
public setShuffle() {
|
||||
let nowQueue = this.queue.slice(1);
|
||||
nowQueue = shuffle(nowQueue);
|
||||
this.queue = [this.queue[0], ...nowQueue];
|
||||
this.setMsg();
|
||||
}
|
||||
|
||||
public setRecommend() {
|
||||
if (!this.GDB) return;
|
||||
this.GDB.options.recommend = !this.GDB.options.recommend;
|
||||
DB.guild.update(this.GDB);
|
||||
this.setMsg();
|
||||
}
|
||||
|
||||
public seek(num: number) {
|
||||
if (!this.isPlaying) return;
|
||||
if (!this.nowTrack) return;
|
||||
this.player.seekTo(num);
|
||||
}
|
||||
|
||||
private async autoPlay() {
|
||||
if (!this.lastPlayedTrack) return;
|
||||
const source = this.lastPlayedTrack.info.sourceName;
|
||||
const trackId = this.lastPlayedTrack.info.identifier;
|
||||
let result;
|
||||
if (source === "spotify") {
|
||||
result = await this.player.node.rest.resolve(`sprec:seed_tracks=${trackId}&limit=11`);
|
||||
} else {
|
||||
result = await this.player.node.rest.resolve(`https://music.youtube.com/watch?v=${trackId}&list=RD${trackId}`);
|
||||
}
|
||||
let tracks: Track[] = [];
|
||||
if (result?.loadType === LoadType.PLAYLIST) {
|
||||
tracks = result.data.tracks;
|
||||
} else if (result?.loadType === LoadType.SEARCH || Array.isArray(result?.data)) {
|
||||
tracks = Array.isArray(result.data) ? result.data : [];
|
||||
} else if (result?.loadType === LoadType.TRACK) {
|
||||
tracks = [ result.data ];
|
||||
}
|
||||
if (tracks.length === 0) {
|
||||
this.end();
|
||||
return;
|
||||
}
|
||||
if (tracks[0].info.identifier === trackId) tracks = tracks.slice(1);
|
||||
this.addTracks(tracks, "자동재생");
|
||||
}
|
||||
|
||||
public end() {
|
||||
if (this.errorTimer !== undefined) {
|
||||
clearTimeout(this.errorTimer);
|
||||
this.errorTimer = undefined;
|
||||
}
|
||||
if (this.endTimer !== undefined) clearTimeout(this.endTimer);
|
||||
this.endTimer = setTimeout(() => {
|
||||
this.endTimer = undefined;
|
||||
this.delete(true);
|
||||
}, idleEndTime);
|
||||
this.lastPlayedTrack = undefined;
|
||||
this.queue = [];
|
||||
this.canRecommend = true;
|
||||
this.setMsg();
|
||||
}
|
||||
|
||||
public delete(afterEnd?: boolean) {
|
||||
if (this.isDead) return;
|
||||
if (!afterEnd) this.end();
|
||||
this.isDead = true;
|
||||
this.player.destroy().catch(() => {});
|
||||
lavalinkManager.delPlayer(this.guild.id);
|
||||
lavalinkManager.shoukaku.leaveVoiceChannel(this.guild.id);
|
||||
}
|
||||
|
||||
private async setMsg() {
|
||||
const { channel, msg, check } = await checkTextChannelAndMsg(this.guild, this.textChannel, this.msg);
|
||||
if (!check) return;
|
||||
this.textChannel = channel;
|
||||
this.msg = msg;
|
||||
if (!this.msg) return;
|
||||
|
||||
// content
|
||||
let content = default_content;
|
||||
let realQueue = this.queue.slice(1);
|
||||
if (realQueue.length > 0) {
|
||||
content = "__**대기열 목록:**__\n";
|
||||
let list: string[] = [];
|
||||
let nowLength = content.length + 50; // 50자 여유두기
|
||||
for (let i=0; i<realQueue.length; i++) {
|
||||
let track = realQueue[i];
|
||||
let text = `${
|
||||
(i+1).toString().padStart(realQueue.length.toString().length, "0")
|
||||
}\\. ${
|
||||
track.info.author.replace(" - Topic","")
|
||||
} - ${
|
||||
track.info.title
|
||||
} [${
|
||||
timeFormat(track.info.length)
|
||||
}] ~ ${
|
||||
track.userId === "자동재생" ? "자동재생" : `<@${track.userId}>`
|
||||
}`;
|
||||
if (nowLength + text.length > 2000) {
|
||||
content += `+ ${realQueue.length-list.length}곡\n`;
|
||||
break;
|
||||
}
|
||||
nowLength += text.length;
|
||||
list.push(text);
|
||||
}
|
||||
content += list.reverse().join("\n");
|
||||
}
|
||||
|
||||
// embed
|
||||
const embed = (this.isPlaying || this.isPaused) && !!this.nowTrack
|
||||
? client.mkembed({
|
||||
title: `**[${timeFormat(this.nowTrack.info.length)}] ${this.nowTrack.info.author.replace(" - Topic","")} - ${this.nowTrack.info.title}**`,
|
||||
description: `노래 요청자: ${this.nowTrack.userId === "자동재생" ? "자동재생" : `<@${this.nowTrack.userId}>`}`,
|
||||
image: this.nowTrack.info.artworkUrl,
|
||||
url: this.nowTrack.info.author.includes(" - Topic") ? this.nowTrack.info.uri?.replace("www.youtube","music.youtube") : this.nowTrack.info.uri,
|
||||
footer: { text: `대기열: ${realQueue.length}개 | 볼륨: ${this.player.volume}${
|
||||
this.isRecommend ? " | 자동재생: 활성화" : ""
|
||||
}${
|
||||
this.isPaused ? " | 노래가 일시중지 되었습니다." : ""
|
||||
}` }
|
||||
})
|
||||
: default_embed(this.guild.id);
|
||||
|
||||
this.msg.edit({
|
||||
content: content,
|
||||
embeds: [ embed ],
|
||||
files: embed.data.image?.url === default_embed(this.guild.id).data.image?.url ? [ default_image ] : [],
|
||||
components: [ getButtons(
|
||||
this.isPlaying || this.isPaused,
|
||||
this.isPaused,
|
||||
realQueue.length > 1,
|
||||
) ]
|
||||
});
|
||||
}
|
||||
|
||||
public async errMsg(reason: string) {
|
||||
if (!this.msg) return null;
|
||||
await this.msg.edit({
|
||||
content: this.msg.content,
|
||||
embeds: [ client.mkembed({
|
||||
title: "오류발생!!",
|
||||
description: reason,
|
||||
color: "DarkRed",
|
||||
}) ],
|
||||
files: [],
|
||||
components: [ getButtons(
|
||||
false,
|
||||
false,
|
||||
this.queue.length > 1,
|
||||
) ]
|
||||
});
|
||||
this.errorTimer = setTimeout(() => {
|
||||
this.errorTimer = undefined;
|
||||
this.playNext();
|
||||
}, DelayAfterErrMs);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user