Compare commits

...

4 Commits

Author SHA1 Message Date
tkrmagid-desktop
04044650d0 redis onoff만들어서 쓸때만 .env에 on하기 2026-04-26 20:23:17 +09:00
tkrmagid-desktop
3864b5d082 의도치않은 종료시 오류코드 작성하게 변경 2026-04-26 20:22:59 +09:00
tkrmagid-desktop
7c4c32d849 혼자음성 나가는 오류수정
확실히 이거때문인지는 몰라도 문제가 있어서 수정함
2026-04-26 20:22:40 +09:00
tkrmagid-desktop
cbd56126f7 주석처리
자꾸 오류나서 주석처리했음
2026-04-26 20:20:03 +09:00
8 changed files with 81 additions and 20 deletions

View File

@@ -7,6 +7,7 @@ import { GuildType } from "../../db/db";
import { DB } from "../utils/Database";
import { shuffle } from "../utils/Shuffle";
import { checkTextChannelAndMsg } from "../utils/music/Channel";
import { Logger } from "../utils/Logger";
const DelayAfterErrMs = 1000 * 5;
const idleEndTime = 1000 * 60 * 10;
@@ -31,7 +32,7 @@ export class GuildPlayer {
) {
this.player.setGlobalVolume(50);
this.player.on("start", (_data: TrackStartEvent) => {
Redis.publishState("player_update", {
Redis?.publishState("player_update", {
guildId: this.guild.id,
});
});
@@ -50,7 +51,40 @@ export class GuildPlayer {
});
this.player.on("closed", () => {
if (this.isDead) return;
this.delete();
Logger.info(`[GuildPlayer] 음성 연결이 끊어졌습니다. 재접속을 대기합니다...`);
setTimeout(() => {
if (this.isDead) return;
// 5초가 지났는데도 연결이 복구되지 않았을 때만 방을 나갑니다.
// 디스코드 방에 내 봇(me)이 없으면 봇을 삭제(delete)한다!
if (!this.guild.members.me?.voice?.channelId) {
Logger.warn(`[GuildPlayer] 음성채널에 봇이 없습니다. player를 초기화합니다.`);
return this.delete();
}
/**
* declare enum State {
* CONNECTING = 0,
* CONNECTED = 1,
* DISCONNECTING = 2,
* DISCONNECTED = 3
* }
*/
// (1 = CONNECTED, Shoukaku 버전에 따라 연결 상태 체크가 다를 수 있으니 안전하게 확인)
if (this.player && this.player.node.state !== 1) {
Logger.warn(`[GuildPlayer] 연결 복구 실패. 봇을 퇴장시킵니다.`);
return this.delete();
}
}, 5000);
});
this.player.on("exception", async (data) => {
Logger.error(`[Lavalink] 재생 중 에러 발생: ${data.exception?.message}`);
await this.errMsg("유튜브 차단 또는 재생 오류로 인해 이 곡을 건너뜁니다.");
});
this.player.on("stuck", async (data) => {
Logger.error(`[Lavalink] 곡 로딩 멈춤(Stuck) 발생: ${data.thresholdMs}ms 초과`);
await this.errMsg("음원 로딩이 멈췄습니다. 다음 곡으로 넘어갑니다.");
});
}
@@ -155,7 +189,7 @@ export class GuildPlayer {
if (!this.isPlaying) return;
if (!this.nowTrack) return;
this.player.seekTo(num);
Redis.publishState("player_update", { guildId: this.guild.id });
Redis?.publishState("player_update", { guildId: this.guild.id });
}
private async autoPlay() {
@@ -210,8 +244,8 @@ export class GuildPlayer {
}
public async setMsg(update: { player: boolean; queue: boolean; } = { player: true, queue: true }) {
if (update.player) Redis.publishState("player_update", { guildId: this.guild.id });
if (update.queue) Redis.publishState("queue_update", { guildId: this.guild.id });
if (update.player) Redis?.publishState("player_update", { guildId: this.guild.id });
if (update.queue) Redis?.publishState("queue_update", { guildId: this.guild.id });
const { channel, msg, check } = await checkTextChannelAndMsg(this.guild, this.textChannel, this.msg);
if (!check) return;
this.textChannel = channel;

View File

@@ -23,7 +23,12 @@ type SubAction =
"queue_set" |
"queue_remove";
export class RedisClient {
export const RedisClient = () => {
if (Config.redis.state) return new RedisClientClass();
return null;
}
class RedisClientClass {
public pub: Redis = new Redis({ host: Config.redis.host, port: Config.redis.port });
public sub: Redis = new Redis({ host: Config.redis.host, port: Config.redis.port });

View File

@@ -14,9 +14,11 @@ export const clientReady = async () => {
reloadMsg();
const guildIds = client.guilds.cache.map(guild => guild.id);
Redis.pub.set("bot-guilds", JSON.stringify(guildIds));
Logger.info(`[Redis Pub] bot-guilds 설정 완료: [${guildIds.join(",")}]`);
if (Redis) {
const guildIds = client.guilds.cache.map(guild => guild.id);
Redis.pub.set("bot-guilds", JSON.stringify(guildIds));
Logger.info(`[Redis Pub] bot-guilds 설정 완료: [${guildIds.join(",")}]`);
}
if (!Config.dev) return;
try {

View File

@@ -2,7 +2,9 @@ import { client, Redis } from "../index";
import { Logger } from "../utils/Logger";
export const guildCreate = async () => {
const guildIds = client.guilds.cache.map(guild => guild.id);
Redis.pub.set("bot-guilds", JSON.stringify(guildIds));
Logger.info(`[Redis Pub] bot-guilds 수정 완료: [${guildIds.join(",")}]`);
if (Redis) {
const guildIds = client.guilds.cache.map(guild => guild.id);
Redis.pub.set("bot-guilds", JSON.stringify(guildIds));
Logger.info(`[Redis Pub] bot-guilds 수정 완료: [${guildIds.join(",")}]`);
}
}

View File

@@ -2,7 +2,9 @@ import { client, Redis } from "../index";
import { Logger } from "../utils/Logger";
export const guildDelete = async () => {
const guildIds = client.guilds.cache.map(guild => guild.id);
Redis.pub.set("bot-guilds", JSON.stringify(guildIds));
Logger.info(`[Redis Pub] bot-guilds 수정 완료: [${guildIds.join(",")}]`);
if (Redis) {
const guildIds = client.guilds.cache.map(guild => guild.id);
Redis.pub.set("bot-guilds", JSON.stringify(guildIds));
Logger.info(`[Redis Pub] bot-guilds 수정 완료: [${guildIds.join(",")}]`);
}
}

View File

@@ -1,5 +1,5 @@
import { VoiceState } from "discord.js";
import { client } from "../index";
// import { VoiceState } from "discord.js";
// import { client } from "../index";
export const voiceStateUpdate = async (oldState: VoiceState, newState: VoiceState): Promise<void> => {
}
// export const voiceStateUpdate = async (oldState: VoiceState, newState: VoiceState): Promise<void> => {
// }

View File

@@ -3,11 +3,25 @@ import { LavalinkManager } from "./classes/LavalinkManager";
import { Handler } from "./classes/Handler";
import { onEvents } from "./events";
import { RedisClient } from "./classes/RedisClient";
import { Logger } from "./utils/Logger";
// 봇이 예기치 않게 죽기 직전에 에러를 잡아내서 로그로 남기는 코드
process.on('unhandledRejection', (reason, _promise) => {
Logger.error('🚨 [치명적 오류] 처리되지 않은 Promise 거부 발생:'+String(reason));
});
process.on('uncaughtException', (err) => {
Logger.error('🚨 [치명적 오류] 잡지 못한 예외 발생:'+String(err));
});
process.on('SIGTERM', () => {
Logger.log('💀 시스템에 의해 강제 종료(SIGTERM) 신호를 받았습니다!');
});
export const client = new BotClient();
export const lavalinkManager = new LavalinkManager(client);
export const handler = new Handler();
export const Redis = new RedisClient();
export const Redis = RedisClient();
for (const eventName of Object.keys(onEvents) as (keyof typeof onEvents)[]) {
client.onEvent(eventName, onEvents[eventName]);

View File

@@ -70,6 +70,7 @@ export const Config = {
},
_redis: {
state: process.env.REDIS?.trim()?.toLocaleLowerCase() === "true",
host: process.env.REDIS_HOST?.trim(),
port: process.env.REDIS_PORT?.trim(),
},
@@ -79,6 +80,7 @@ export const Config = {
const port = Number(this._redis.port!);
if (isNaN(port)) throw new TypeError("REDIS_PORT must be a number");
return {
state: this._redis.state,
host: this._redis.host,
port: port,
};