diff --git a/src/modules/muzika.ts b/src/modules/muzika.ts index 3126eba..6ad3c1c 100644 --- a/src/modules/muzika.ts +++ b/src/modules/muzika.ts @@ -1,38 +1,37 @@ // Tady bude muzika, vole -import { AudioPlayerStatus, getVoiceConnection } from "@discordjs/voice"; -import { Client, VoiceBasedChannel } from "discord.js"; +import { AudioPlayerStatus, VoiceConnectionStatus } from "@discordjs/voice"; +import { Client } from "discord.js"; import { search, soundcloud, stream, validate, video_basic_info } from "play-dl"; import { emouty } from "../utils/emotes"; -import { Modul, SRecord } from "../utils/types"; +import { Modul } from "../utils/types"; import { adminLog, log } from "../utils/utils"; -import { novejPlay, Priority, stopPlayer } from "../utils/voice"; +import { getConn, novejJoin, novejPlay, Priority, stopPlayer } from "../utils/voice"; -const kjus: SRecord<{ name: string; url: string; }[]> = {}; +const guildy = new Map(); -async function playNext(channel: VoiceBasedChannel, seek?: number) { +async function playNext(guildId: string, seek?: number) { try { - const kju = kjus[channel.guildId]!; + const guilda = guildy.get(guildId)!; + const kju = guilda.kju; const item = kju[0]; const src = await stream(item.url, { seek }); - novejPlay(channel, { src: src.stream, volume: 1, type: src.type }, Priority.Music) + novejPlay(guildId, { src: src.stream, volume: 1, type: src.type }, Priority.Music) .then(state => { if (state.status != AudioPlayerStatus.Idle) return; - kju.shift(); + if (!guilda.loop) kju.shift(); if (kju.length) - return playNext(channel); - - delete kjus[channel.guildId]; + return playNext(guildId); }) .catch(e => { log("chyba v muziki", e); }); } catch (e) { if (e instanceof Error && e.message.startsWith("Seeking beyond limit")) - return stopPlayer(channel.guildId, Priority.Music); + return stopPlayer(guildId, Priority.Music); log(e); const client: Client = module.exports.client; @@ -45,9 +44,10 @@ const exp: Modul = { zahraj: { DMUnsafe: true, run: async (mes, txt) => { - const kanel = mes.member?.voice.channel; + const kanel = mes.member?.voice.channelId; if (!kanel) return "nejsi ve vojsu ty kkt"; + const guildId = mes.guildId!; const ajtem = { name: "", url: "" }; const druh = await validate(txt); if (druh && druh != "search") { @@ -79,12 +79,19 @@ const exp: Modul = { msg.then(m => void m.edit(`zahraju \`${ajtem.name}\``)); } - const kju = (kjus[mes.guildId!] ??= []); + if (!guildy.has(guildId)) { + guildy.set(guildId, { kju: [], loop: false }); + } + const kju = guildy.get(guildId)!.kju; kju.push(ajtem); if (kju.length != 1) return; - playNext(mes.member.voice.channel); + await novejJoin(mes.guild!, kanel); + getConn(guildId)?.once(VoiceConnectionStatus.Destroyed, () => { + kju.length = 0; + }); + playNext(guildId); } }, @@ -92,8 +99,8 @@ const exp: Modul = { als: ["q", "kju"], DMUnsafe: true, run: mes => { - const kju = kjus[mes.guildId!]; - if (!kju) return "nehraje nic"; + const kju = guildy.get(mes.guildId!)?.kju; + if (!kju?.length) return "nehraje nic"; return `tuto je kurentni repertoar:\n${kju.reduce((acc, cur, i) => { const jmeno = `${cur.name} (<${cur.url}>)`; @@ -105,8 +112,8 @@ const exp: Modul = { skip: { DMUnsafe: true, run: mes => { - const kju = kjus[mes.guildId!]; - if (!kju) return "nic nehraje"; + const kju = guildy.get(mes.guildId!)?.kju; + if (!kju?.length) return "nehraje nic"; stopPlayer(mes.guildId!, Priority.Music); mes.react(emouty.d3k); @@ -120,8 +127,8 @@ const exp: Modul = { const numero = Number(arg); if (isNaN(numero)) return "cokundo?"; - const kju = kjus[mes.guildId!]; - if (!kju) return "nic nehraje"; + const kju = guildy.get(mes.guildId!)?.kju; + if (!kju?.length) return "nehraje nic"; if (kju.length <= numero) return "tolik toho nehrae"; @@ -136,14 +143,27 @@ const exp: Modul = { const numero = Number(arg); if (isNaN(numero)) return "huh?"; - const kju = kjus[mes.guildId!]; - if (!kju) return "nehraje nic"; + const kju = guildy.get(mes.guildId!)?.kju; + if (!kju?.length) return "nehraje nic"; - const channel = mes.client.channels.cache.get(getVoiceConnection(mes.guildId!)?.joinConfig.channelId ?? "") as VoiceBasedChannel; - if (!channel) return "neco nefunguje"; - playNext(channel, numero); + playNext(mes.guildId!, numero); mes.react(emouty.d3k); } + }, + + loop: { + DMUnsafe: true, + run: mes => { + if (!guildy.has(mes.guildId!)) { + guildy.set(mes.guildId!, { kju: [], loop: false }); + } + + const guilda = guildy.get(mes.guildId!)!; + + guilda.loop = !guilda.loop; + + return `lup je ${guilda.loop ? "zapnutej" : "ukoncen"}`; + } } } }; diff --git a/src/modules/vojs.ts b/src/modules/vojs.ts index 1c2f660..a35913f 100644 --- a/src/modules/vojs.ts +++ b/src/modules/vojs.ts @@ -26,14 +26,18 @@ const vytahnout = (member: GuildMember, patro: number) => { setTimeout(() => vytahnout(member, patro), 1e3); }; -async function playAndFukOff(mes: Message, jinak: string, zvuk: string) { +async function playAndFukOff(mes: Message, jinak: string, zvuk: string | Hratelny[]) { const channel = mes.member?.voice.channel; if (!channel) return jinak; - await novejPlay(channel, zvuk, Priority.Etc, true) + const prev = await novejJoin(mes.guild!, channel.id); + + await novejPlay(mes.guildId!, zvuk, Priority.Etc) .catch(e => { log("Pičo chyba při fukofování", e); }); + + handlePrevVoice(prev, mes.guild!); } const exp: Modul = { @@ -51,9 +55,9 @@ const exp: Modul = { const nahlas = !arg.startsWith("potichu"); if (nahlas) mes.channel.send("<@&591306633196339261> vojs"); - novejJoin(channel) + novejJoin(mes.guild!, channel.id) .then(prev => { - if (prev != true && nahlas) novejPlay(channel, "zvuky/nazdar.ogg", Priority.Etc); + if (prev != true && nahlas) novejPlay(mes.guildId!, "zvuky/nazdar.ogg", Priority.Etc); }); } }, @@ -110,7 +114,7 @@ const exp: Modul = { const je = h > 1 && h < 5 ? "jsou" : "je"; const channel = mes.member?.voice.channel; - if (!channel) return `${je} ${h} ${hod} ${m} ${min}`; + if (!channel) return; const zvuky: Hratelny[] = [{ src: `${pre}${je}.mp3`, volume: 2.5 }]; @@ -121,12 +125,7 @@ const exp: Modul = { zvuky.push({ src: `${pre}${min}.mp3`, volume: 2.5 }); } - const prev = await novejJoin(channel); - await novejPlay(channel, zvuky, Priority.Etc) - .catch(e => { - log("chyba tady, si to najdi", e); - }); - handlePrevVoice(prev, mes.guildId!); + playAndFukOff(mes, `${je} ${h} ${hod} ${m} ${min}`, zvuky); } }, @@ -158,11 +157,8 @@ const exp: Modul = { const conn = getVoiceConnection(aft.guild.id); if (!aft.channel || !conn || aft.member?.user.id == aft.client.user?.id) return; - const channel = aft.client.channels.cache.get(conn.joinConfig.channelId ?? "") as VoiceBasedChannel; - if (!channel) return log(new Error("kanela jsem nenasel")); - if (neodposlouchavani && aft.selfMute && !aft.deaf) { - novejPlay(channel, { src: "./zvuky/neodposlouchavej.ogg", volume: 0.38 }, Priority.Etc) + novejPlay(aft.channel.guildId, { src: "./zvuky/neodposlouchavej.ogg", volume: 0.38 }, Priority.Etc) .then(() => { if (!aft.selfMute) return; aft.setDeaf(true, "otposlouchávala ta gadza"); diff --git a/src/utils/types.ts b/src/utils/types.ts index ae5ca7b..c1d125c 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -74,12 +74,6 @@ export interface ZmenovejObjekt { status?: string; } -export interface MuzikaFace { - name: string | Readable; - volume: number; - type?: StreamType; -} - export type KomandNaExport = { name: string; custom?: true; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 75c1c71..4b42507 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -91,7 +91,7 @@ export function log(...content: unknown[]) { } export async function sendDM(user: User, txt: string) { - user.send(txt).catch(() => console.log(`dementovi ${user} nejde poslat DM`)); + user.send(txt).catch(() => log(new Error(`dementovi ${user} nejde poslat DM`))); } export const rand = (max: number) => Math.floor(Math.random() * max); diff --git a/src/utils/voice.ts b/src/utils/voice.ts index a48e452..9ca2b07 100644 --- a/src/utils/voice.ts +++ b/src/utils/voice.ts @@ -1,5 +1,5 @@ import { AudioPlayer, AudioPlayerState, AudioPlayerStatus, AudioResource, createAudioPlayer, createAudioResource, entersState, joinVoiceChannel, StreamType, VoiceConnection, VoiceConnectionStatus } from "@discordjs/voice"; -import { VoiceBasedChannel } from "discord.js"; +import { Guild } from "discord.js"; import { log } from "./utils"; import { Readable } from "node:stream"; @@ -10,7 +10,6 @@ export type Hratelny = string | Readable | { }; type Vojska = { - channel: VoiceBasedChannel; conn: VoiceConnection; players: [AudioPlayer, AudioPlayer, AudioPlayer]; queues: [AudioResource[], AudioResource[], AudioResource[]]; @@ -31,28 +30,46 @@ const getId = () => `Id:${lastId++}`; * @returns Předchozí stav bota, před tímto příkazem * `true` znamená, že byl bot do tohoto kanelu již připojen * `false` znamená, že nebyl připojen nikam - * `VoiceBasedChannel` znamená, že v tutý gildě byl předtím připojenej do daného kanelu + * `channelId` znamená, že v tutý gildě byl předtím připojenej do kanelu s tímto ID */ -export const novejJoin = (channel: VoiceBasedChannel) => new Promise(async (resolve, reject) => { - const guildId = channel.guildId; - let prev: boolean | VoiceBasedChannel = false; +export const novejJoin = (guild: Guild, channelId: string) => new Promise(async (resolve, reject) => { + const guildId = guild.id; + let prev: false | string = false; const gildina = vojsy.get(guildId); if (gildina) { - if (gildina.channel.id == channel.id) { - if (gildina.conn.state.status != VoiceConnectionStatus.Ready) { + const con = gildina.conn; + if (con.joinConfig.channelId == channelId) { + if (con.state.status != VoiceConnectionStatus.Ready) { log(`pri pripojovani do vojsu tady uz existovalo stejny pripojeni, ale status byl ${gildina.conn.state.status}, tak jsem to zahodil`); novejLeave(guildId); } else return resolve(true); } - else prev = gildina.channel; + else { + prev = con.joinConfig.channelId!; + if (!prev) { + log(new Error("fakt tam nic neni, jak je to mozny?")); + prev = false; + } + + // Přesunout se + const state = guild.voiceStates.cache.get(guild.client.user!.id); + if (state) { + await state.setChannel(channelId); + resolve(prev); + return; + } + // Z nějakýho neznámího důvodu se nepodařilo přepojit, prostě vytvoříme noví připojení + log(new Error("jo tak tuto nechapu")); + novejLeave(guildId); + }; } const conn = joinVoiceChannel({ - channelId: channel.id, + channelId: channelId, guildId, - adapterCreator: channel.guild.voiceAdapterCreator + adapterCreator: guild.voiceAdapterCreator }); await entersState(conn, VoiceConnectionStatus.Ready, 1e4) @@ -76,7 +93,6 @@ export const novejJoin = (channel: VoiceBasedChannel) => new Promise new Promise(async (resolve, reject) => { - let prev: Awaited>; - if (!(vojsy.get(channel.guildId)?.channel.id == channel.id)) { - prev = await novejJoin(channel); - } +export const novejPlay = (guildId: string, co: Hratelny | Hratelny[], priority: Priority) => new Promise(async (resolve, reject) => { - const vojska = vojsy.get(channel.guildId); - if (!vojska) return reject("silenost"); + const vojska = vojsy.get(guildId); + if (!vojska) return reject("nejsi pripojenej do vojsu debile"); const resources: { id: string; res: AudioResource; }[] = []; if (!Array.isArray(co)) { @@ -175,7 +187,7 @@ export const novejPlay = (channel: VoiceBasedChannel, co: Hratelny | Hratelny[], }); } - // Tuto se možná může někdy hodit, třeba už za 5 minut, třeba nikdy, kdovi + // Tuto se možná může někdy hodit, třeba nikdy, kdovi // if (neprepisovat) { // vojska.queues[priority].push(resource); // } else { @@ -187,23 +199,22 @@ export const novejPlay = (channel: VoiceBasedChannel, co: Hratelny | Hratelny[], const posledniId = resources.at(-1)?.id; function nasafunkca(pred: AudioPlayerState, po: AudioPlayerState) { + log({ id: posledniId, pred: pred.status, po: po.status }); if (!(("resource" in pred && pred.resource.metadata == posledniId) && (("resource" in po && po.resource.metadata != posledniId) || !("resource" in po)))) return; - log("odebiram sa", { id: posledniId }); vojska?.players[priority].removeListener("stateChange", nasafunkca); - if (handlePrev) handlePrevVoice(prev, channel.guildId); resolve(po); - }; + } vojska.players[priority].on("stateChange", nasafunkca); handelieren(vojska, priority); }); -export const handlePrevVoice = (prev: boolean | VoiceBasedChannel, guildId: string) => { +export const handlePrevVoice = (prev: boolean | string, guild: Guild) => { if (prev === true) return; // Byl jsem v tomdle vojsu - if (prev === false) return novejLeave(guildId); // Nebyl jsem ve vojsu vůbec - novejJoin(prev); // Byl jsem jinde + if (prev === false) return novejLeave(guild.id); // Nebyl jsem ve vojsu vůbec + novejJoin(guild, prev); // Byl jsem jinde }; export function stopPlayer(guildId: string, priorita: Priority) { @@ -214,23 +225,25 @@ export function stopPlayer(guildId: string, priorita: Priority) { return true; } +export const getConn = (guildId: string) => vojsy.get(guildId)?.conn; + // Ohlašování času const timeouty = new Map(); const nula = (a: number) => a < 10 ? `0${a}` : a; -const vypocitatCas = (channel: VoiceBasedChannel) => { +const vypocitatCas = (guildId: string) => { const d = new Date(); d.setMinutes((d.getMinutes() >= 30 ? 60 : 30)); d.setSeconds(0); - timeouty.set(channel.guildId, setTimeout(() => rekniCas(channel, `${nula(d.getHours())}${nula(d.getMinutes())}`), Number(d) - Number(new Date()))); + timeouty.set(guildId, setTimeout(() => rekniCas(guildId, `${nula(d.getHours())}${nula(d.getMinutes())}`), Number(d) - Number(new Date()))); }; -const rekniCas = (channel: VoiceBasedChannel, cas: string) => { - novejPlay(channel, [{ src: "zvuky/intro.mp3", volume: 0.8 }, { src: `zvuky/${cas}.mp3`, volume: 2 }, { src: "zvuky/grg.mp3", volume: 0.5 }], Priority.Time) +const rekniCas = (guildId: string, cas: string) => { + novejPlay(guildId, [{ src: "zvuky/intro.mp3", volume: 0.8 }, { src: `zvuky/${cas}.mp3`, volume: 2 }, { src: "zvuky/grg.mp3", volume: 0.5 }], Priority.Time) .then(() => { - vypocitatCas(channel); + vypocitatCas(guildId); }) .catch(err => { log("cas error:", err); }); };