Konečně přepis vojsování doprele e e e

This commit is contained in:
Histmy 2023-07-26 18:49:19 +02:00
parent 30f68e212d
commit ae353a0fec
6 changed files with 700 additions and 878 deletions

992
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "denim_3001", "name": "denim_3001",
"version": "3001.51.1", "version": "3001.52.0",
"description": "Toto je velmi kvalitní bot.", "description": "Toto je velmi kvalitní bot.",
"repository": { "repository": {
"url": "https://github.com/Histmy/Denim-Bot/" "url": "https://github.com/Histmy/Denim-Bot/"
@ -14,21 +14,19 @@
"author": "Histmy + det-fys", "author": "Histmy + det-fys",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@discordjs/voice": "^0.15.0", "@discordjs/voice": "^0.16.0",
"discord.js": "^14.8.0", "discord.js": "^14.11.0",
"js-levenshtein": "^1.1.6", "js-levenshtein": "^1.1.6",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"opusscript": "^0.0.8", "opusscript": "^0.1.0",
"play-dl": "^1.9.6", "play-dl": "^1.9.6",
"tiny-typed-emitter": "^2.1.0", "tiny-typed-emitter": "^2.1.0",
"tweetnacl": "^1.0.3" "tweetnacl": "^1.0.3"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.0", "@types/jest": "^29.5.3",
"@types/js-levenshtein": "^1.1.1", "@types/js-levenshtein": "^1.1.1",
"@types/node-fetch": "^2.6.2", "@types/node-fetch": "^2.6.4",
"jest": "^29.5.0", "jest": "^29.6.1"
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.2"
} }
} }

View File

@ -1,23 +1,40 @@
// Tady bude muzika, vole // Tady bude muzika, vole
import { AudioPlayerStatus, VoiceConnection } from "@discordjs/voice"; import { AudioPlayerStatus, getVoiceConnection } from "@discordjs/voice";
import { Client } from "discord.js"; import { Client, VoiceBasedChannel } from "discord.js";
import { search, soundcloud, stream, validate, video_basic_info } from "play-dl"; import { search, soundcloud, stream, validate, video_basic_info } from "play-dl";
import { emouty } from "../utils/emotes"; import { emouty } from "../utils/emotes";
import { Modul, SRecord } from "../utils/types"; import { Modul, SRecord } from "../utils/types";
import { adminLog, configureTimeAnouncment, getCurrentPlayer, joinVoice, log, play } from "../utils/utils"; import { adminLog, log } from "../utils/utils";
import { novejPlay, Priority, stopPlayer } from "../utils/voice";
const kjus: SRecord<{ name: string; url: string; }[]> = {}; const kjus: SRecord<{ name: string; url: string; }[]> = {};
async function zahrat(conn: VoiceConnection, url: string, seek?: number) { async function playNext(channel: VoiceBasedChannel, seek?: number) {
try { try {
const src = await stream(url, { seek }); const kju = kjus[channel.guildId]!;
play(conn, { name: src.stream, volume: 1, type: src.type }) const item = kju[0];
const src = await stream(item.url, { seek });
novejPlay(channel, { src: src.stream, volume: 1, type: src.type }, Priority.Music)
.then(state => {
if (state.status != AudioPlayerStatus.Idle) return;
kju.shift();
if (kju.length)
return playNext(channel);
delete kjus[channel.guildId];
})
.catch(e => { .catch(e => {
log("chyba v muziki", e); log("chyba v muziki", e);
}); });
} catch (e) { } catch (e) {
console.log(e); if (e instanceof Error && e.message.startsWith("Seeking beyond limit"))
return stopPlayer(channel.guildId, Priority.Music);
log(e);
const client: Client = module.exports.client; const client: Client = module.exports.client;
adminLog(client, "error v hani muziky"); adminLog(client, "error v hani muziky");
} }
@ -67,22 +84,7 @@ const exp: Modul = {
if (kju.length != 1) return; if (kju.length != 1) return;
configureTimeAnouncment(mes.guildId!, false); playNext(mes.member.voice.channel);
const { conn } = await joinVoice(kanel);
const player = getCurrentPlayer(mes.guildId!)!;
zahrat(conn, ajtem.url);
player.on(AudioPlayerStatus.Idle, () => {
kju.splice(0, 1);
if (kju.length == 0) {
delete kjus[mes.guildId!];
configureTimeAnouncment(mes.guildId!, true);
return;
}
zahrat(conn, kju[0].url);
});
} }
}, },
@ -106,8 +108,7 @@ const exp: Modul = {
const kju = kjus[mes.guildId!]; const kju = kjus[mes.guildId!];
if (!kju) return "nic nehraje"; if (!kju) return "nic nehraje";
const player = getCurrentPlayer(mes.guildId!); stopPlayer(mes.guildId!, Priority.Music);
player?.stop();
mes.react(emouty.d3k); mes.react(emouty.d3k);
} }
}, },
@ -138,8 +139,9 @@ const exp: Modul = {
const kju = kjus[mes.guildId!]; const kju = kjus[mes.guildId!];
if (!kju) return "nehraje nic"; if (!kju) return "nehraje nic";
const conn = getCurrentPlayer(mes.guildId!)!.playable[0]; const channel = mes.client.channels.cache.get(getVoiceConnection(mes.guildId!)?.joinConfig.channelId ?? "") as VoiceBasedChannel;
zahrat(conn, kju[0].url, numero); if (!channel) return "neco nefunguje";
playNext(channel, numero);
mes.react(emouty.d3k); mes.react(emouty.d3k);
} }
} }

View File

@ -1,50 +1,15 @@
// Cokoliv co má něco společnýho s vojsem // Cokoliv co má něco společnýho s vojsem
import { getVoiceConnection, VoiceConnection, VoiceConnectionStatus } from "@discordjs/voice"; import { getVoiceConnection } from "@discordjs/voice";
import { ChannelType, GuildMember, Message, VoiceChannel, VoiceState } from "discord.js"; import { ChannelType, GuildMember, Message, VoiceBasedChannel, VoiceChannel, VoiceState } from "discord.js";
import { emouty } from "../utils/emotes"; import { emouty } from "../utils/emotes";
import { CClient, Modul, MuzikaFace, SRecord } from "../utils/types"; import { CClient, Modul } from "../utils/types";
import { canAnounceTime, handlePrevVoice, joinVoice, log, play, rand } from "../utils/utils"; import { log, rand } from "../utils/utils";
import { handlePrevVoice, Hratelny, novejJoin, novejLeave, novejPlay, Priority } from "../utils/voice";
const timeouty: SRecord<NodeJS.Timeout> = {};
const muty: string[] = []; const muty: string[] = [];
let neodposlouchavani = false; let neodposlouchavani = false;
const vypocitatCas = (guild: string, conn: VoiceConnection) => {
const c = new Date();
const d = new Date();
let hod = d.getHours();
let min = d.getMinutes();
if (min >= 30) {
min = 0;
if (hod == 23) {
hod = 0;
d.setDate(c.getDate() + 1);
} else {
hod++;
}
} else {
min = 30;
}
d.setHours(hod);
d.setMinutes(min);
d.setSeconds(0);
timeouty[conn.joinConfig.guildId] = setTimeout(() => rekniCas(guild, conn, `${nula(hod)}${nula(min)}`), Number(d) - Number(c));
};
const nula = (a: number) => a < 10 ? `0${a}` : a;
const rekniCas = (guild: string, conn: VoiceConnection, cas: string) => {
if (canAnounceTime(guild))
play(conn, [{ name: "zvuky/intro.mp3", volume: 0.8 }, { name: `zvuky/${cas}.mp3`, volume: 2 }, { name: "zvuky/grg.mp3", volume: 0.5 }])
.then(() => {
vypocitatCas(guild, conn);
})
.catch(err => { log("cas error:", err); });
else vypocitatCas(guild, conn);
};
const vytahnout = (member: GuildMember, patro: number) => { const vytahnout = (member: GuildMember, patro: number) => {
const vojs = member.voice.channel; const vojs = member.voice.channel;
if (!vojs) return; if (!vojs) return;
@ -65,14 +30,10 @@ async function playAndFukOff(mes: Message, jinak: string, zvuk: string) {
const channel = mes.member?.voice.channel; const channel = mes.member?.voice.channel;
if (!channel) return jinak; if (!channel) return jinak;
const { conn, prev } = await joinVoice(channel); await novejPlay(channel, zvuk, Priority.Etc, true)
await play(conn, zvuk)
.catch(e => { .catch(e => {
log("Pičo chyba při fukofování", e); log("Pičo chyba při fukofování", e);
}); });
handlePrevVoice(channel.guild, prev);
} }
const exp: Modul = { const exp: Modul = {
@ -90,18 +51,9 @@ const exp: Modul = {
const nahlas = !arg.startsWith("potichu"); const nahlas = !arg.startsWith("potichu");
if (nahlas) mes.channel.send("<@&591306633196339261> vojs"); if (nahlas) mes.channel.send("<@&591306633196339261> vojs");
joinVoice(channel) novejJoin(channel)
.then(obj => { .then(prev => {
const { prev, conn } = obj; if (prev != true && nahlas) novejPlay(channel, "zvuky/nazdar.ogg", Priority.Etc);
if (!timeouty[mes.guildId!]) {
vypocitatCas(mes.guildId!, conn);
conn.on("stateChange", (_, now) => {
if (now.status !== VoiceConnectionStatus.Disconnected && now.status !== VoiceConnectionStatus.Destroyed) return;
clearTimeout(timeouty[mes.guildId!]);
delete timeouty[mes.guildId!];
});
}
if (prev !== true && nahlas) play(conn, "zvuky/nazdar.ogg");
}); });
} }
}, },
@ -110,10 +62,7 @@ const exp: Modul = {
als: ["odejdi", "disconnect", "leave", "odpoj", "votpoj", "vodpoj", "vodprejskni", "tahni"], als: ["odejdi", "disconnect", "leave", "odpoj", "votpoj", "vodpoj", "vodprejskni", "tahni"],
DMUnsafe: true, DMUnsafe: true,
run: mes => { run: mes => {
const vojs = getVoiceConnection(mes.guildId!); if (!novejLeave(mes.guildId!)) return "nejsem ve vojsu";
if (!vojs) return 'nejsem ve vojsu';
vojs.destroy();
mes.react(emouty.purfieRIP); mes.react(emouty.purfieRIP);
} }
}, },
@ -149,11 +98,11 @@ const exp: Modul = {
return `${prefix}${koncovka}`; return `${prefix}${koncovka}`;
}; };
const rozdelAPridej = (cislo: number) => { const rozdelAPridej = (cislo: number) => {
if (cislo == 2) return zvuky.push({ name: `${pre}dvě.mp3`, volume: 2.5 }); if (cislo == 2) return zvuky.push({ src: `${pre}dvě.mp3`, volume: 2.5 });
if (cislo < 20) return zvuky.push({ name: `${pre}${cislo}.mp3`, volume: 2.5 }); if (cislo < 20) return zvuky.push({ src: `${pre}${cislo}.mp3`, volume: 2.5 });
const text = String(cislo); const text = String(cislo);
zvuky.push({ name: `${pre}${text[0]}0.mp3`, volume: 2.5 }); zvuky.push({ src: `${pre}${text[0]}0.mp3`, volume: 2.5 });
if (text[1] != "0") zvuky.push({ name: `${pre}${text[1]}.mp3`, volume: 2.5 }); if (text[1] != "0") zvuky.push({ src: `${pre}${text[1]}.mp3`, volume: 2.5 });
}; };
const hod = addSuffix("hodin", h); const hod = addSuffix("hodin", h);
@ -163,21 +112,21 @@ const exp: Modul = {
const channel = mes.member?.voice.channel; const channel = mes.member?.voice.channel;
if (!channel) return `${je} ${h} ${hod} ${m} ${min}`; if (!channel) return `${je} ${h} ${hod} ${m} ${min}`;
const zvuky: MuzikaFace[] = [{ name: `${pre}${je}.mp3`, volume: 2.5 }]; const zvuky: Hratelny[] = [{ src: `${pre}${je}.mp3`, volume: 2.5 }];
rozdelAPridej(h); rozdelAPridej(h);
zvuky.push({ name: `${pre}${hod}.mp3`, volume: 2.5 }); zvuky.push({ src: `${pre}${hod}.mp3`, volume: 2.5 });
if (m > 0) { if (m > 0) {
rozdelAPridej(m); rozdelAPridej(m);
zvuky.push({ name: `${pre}${min}.mp3`, volume: 2.5 }); zvuky.push({ src: `${pre}${min}.mp3`, volume: 2.5 });
} }
const { conn, prev } = await joinVoice(channel); const prev = await novejJoin(channel);
await play(conn, zvuky) await novejPlay(channel, zvuky, Priority.Etc)
.catch(e => { .catch(e => {
log("chyba tady, si to najdi", e); log("chyba tady, si to najdi", e);
}); });
handlePrevVoice(mes.guild!, prev); handlePrevVoice(prev, mes.guildId!);
} }
}, },
@ -209,8 +158,11 @@ const exp: Modul = {
const conn = getVoiceConnection(aft.guild.id); const conn = getVoiceConnection(aft.guild.id);
if (!aft.channel || !conn || aft.member?.user.id == aft.client.user?.id) return; 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) { if (neodposlouchavani && aft.selfMute && !aft.deaf) {
play(conn, { name: "./zvuky/neodposlouchavej.ogg", volume: 0.38 }) novejPlay(channel, { src: "./zvuky/neodposlouchavej.ogg", volume: 0.38 }, Priority.Etc)
.then(() => { .then(() => {
if (!aft.selfMute) return; if (!aft.selfMute) return;
aft.setDeaf(true, "otposlouchávala ta gadza"); aft.setDeaf(true, "otposlouchávala ta gadza");

View File

@ -1,38 +1,5 @@
import { AudioPlayer, AudioPlayerStatus, AudioResource, createAudioPlayer, createAudioResource, entersState, getVoiceConnection, joinVoiceChannel, PlayerSubscription, StreamType, VoiceConnection, VoiceConnectionStatus } from "@discordjs/voice"; import { ChannelType, Client, User } from "discord.js";
import { ChannelType, Client, Guild, StageChannel, User, VoiceChannel } from "discord.js"; import { existsSync } from "fs";
import { once } from "events";
import { JoinHovna, MuzikaFace, SRecord } from "./types";
import { Readable } from "node:stream";
import { appendFileSync, existsSync } from "fs";
const pripojeni: SRecord<PlayerSubscription> = {};
const timeAnouncability: SRecord<boolean> = {};
const zajimavyUzivatele: SRecord<string> = {
"1002340891178053652": "0",
"362267256329338882": "1",
"321300980249526272": "2",
"403199237170528256": "3",
"404374791253000194": "4",
"435109967222013952": "5",
"478227285158264832": "6",
"272393325808582656": "7",
"222004187494481921": "8",
"858340006783615006": "9",
"688169800710750211": "a",
"474828395654414336": "b",
"221561136028450816": "c",
"287318873286377472": "d",
"519942360520720394": "e",
"861116768747782163": "f",
"414116351863685121": "g",
"307954132638105601": "h",
"440080748251185152": "i",
"355053867265818635": "j",
"471010846668226561": "k",
"442754524697067520": "l",
"489076647727857685": "m"
};
if (!existsSync("config.json")) throw new Error("config.json neexistuje"); if (!existsSync("config.json")) throw new Error("config.json neexistuje");
process.env = { ...process.env, ...require("../../config.json") }; process.env = { ...process.env, ...require("../../config.json") };
@ -93,139 +60,6 @@ export const formatCas = (cas: number) => {
return `${h} hodin ${m} mynut a ${s} se kund`; return `${h} hodin ${m} mynut a ${s} se kund`;
}; };
export async function joinVoice(channel: VoiceChannel | StageChannel): Promise<JoinHovna>;
export async function joinVoice(channel: string, guild: Guild): Promise<JoinHovna>;
export async function joinVoice(channel: VoiceChannel | StageChannel | string, guild?: Guild): Promise<JoinHovna> {
const channelId = typeof channel === "string" ? channel : channel.id;
const guildId = typeof channel === "string" ? guild!.id : channel.guildId;
const guilda = typeof channel === "string" ? guild! : channel.guild;
let conn = getVoiceConnection(guildId);
let prev: boolean | string = false;
if (conn) {
if (conn.joinConfig.channelId == channelId) return { conn, prev: true };
prev = conn.joinConfig.channelId ?? false;
}
conn = joinVoiceChannel({
channelId,
guildId,
selfDeaf: false,
adapterCreator: guilda.voiceAdapterCreator
});
await entersState(conn, VoiceConnectionStatus.Ready, 3e4)
.catch((err: Error) => {
conn!.destroy();
throw err;
});
//#region Dočasná statistika mluvení ve vojsu
if (guildId == "555779161067749446") {
let base: number;
let interval: NodeJS.Timer;
function restart() {
base = Date.now() - 36;
appendFileSync("zaznamy.dlog", `nazdardebile${base.toString(36)}\n`);
interval = setInterval(() => {
base = Date.now() - 36;
appendFileSync("zaznamy.dlog", `hb${base.toString(36)}\n`);
}, 1000 * 60 * 25);
}
restart();
conn.receiver.speaking.on("start", id => {
const ajdy = zajimavyUzivatele[id] ?? id;
const cas = (Date.now() - base).toString(36);
appendFileSync("zaznamy.dlog", `${ajdy} ${cas}\n`);
});
conn.receiver.speaking.on("end", id => {
const ajdy = zajimavyUzivatele[id] ?? id;
const cas = (Date.now() - base).toString(36);
appendFileSync("zaznamy.dlog", `${cas} ${ajdy}\n`);
});
conn.on("stateChange", (prev, now) => {
if (now.status === VoiceConnectionStatus.Ready && prev.status !== VoiceConnectionStatus.Ready) {
clearInterval(interval);
return restart();
}
if (now.status === VoiceConnectionStatus.Disconnected || now.status === VoiceConnectionStatus.Destroyed) {
clearInterval(interval);
appendFileSync("zaznamy.dlog", `odtahnuljsem${Date.now().toString(36)}\n`);
}
});
}
//#endregion
if (!prev) {
const player = createAudioPlayer();
player.on("error", e => log("chyba pri hrani", e));
pripojeni[guildId] = conn.subscribe(player)!;
conn.on("error", e => {
if (e.message.startsWith("Cannot perform IP discovery")) return;
log("error nekde v conn", e);
});
}
return { conn, prev };
}
const makeAudioPlayer = (co: string | MuzikaFace | Readable) => {
if (typeof co == "string" || co instanceof Readable) return createAudioResource(co);
const res = createAudioResource(co.name, { inlineVolume: true, inputType: co.type || StreamType.Arbitrary });
res.volume?.setVolume(co.volume);
return res;
};
export const play = (conn: VoiceConnection, co: string | string[] | MuzikaFace | MuzikaFace[] | Readable) => new Promise<void>(async (res, rej) => {
if (conn.state.status !== VoiceConnectionStatus.Ready) return rej(`conn není ready ale ${conn.state.status}`);
const player = pripojeni[conn.joinConfig.guildId].player;
if (!Array.isArray(co)) {
const aud = makeAudioPlayer(co);
player.play(aud);
player.once(AudioPlayerStatus.Idle, () => res());
return;
}
const resources: AudioResource[] = [];
co.forEach(c => {
const res = makeAudioPlayer(c);
resources.push(res);
});
for (const res of resources) {
await once(res.playStream, "readable");
}
player.play(resources[0]);
let i = 0;
const funkce = () => {
i++;
if (i === resources.length) {
res();
player.removeListener(AudioPlayerStatus.Idle, funkce);
}
else player.play(resources[i]);
};
player.on(AudioPlayerStatus.Idle, funkce);
});
const leave = (guildId: string) => {
const conn = getVoiceConnection(guildId);
if (!conn) return;
conn.destroy();
delete pripojeni[guildId];
};
export const handlePrevVoice = (guild: Guild, prev: boolean | string) => {
if (prev === true) return; // Byl jsem v tomdle vojsu
if (prev === false) return leave(guild.id); // Nebyl jsem ve vojsu vůbec
joinVoice(prev, guild); // Byl jsem jinde
};
/** /**
* Returns absolute date in format `d. m. h:mm:ss` * Returns absolute date in format `d. m. h:mm:ss`
* @param date Date object to convert * @param date Date object to convert
@ -246,16 +80,10 @@ export function adminLog(client: Client, text: string) {
} }
} }
export const getCurrentPlayer = (guildId: string): AudioPlayer | undefined => pripojeni[guildId]?.player; export function log(...content: unknown[]) {
export const configureTimeAnouncment = (guild: string, state: boolean) => { timeAnouncability[guild] = state; };
export const canAnounceTime = (guild: string) => timeAnouncability[guild] ?? true;
export function log(...content: (string | Error)[]) {
const jo = content.map(h => { const jo = content.map(h => {
if (typeof h == "string") return h; if (h instanceof Error) return `\x1b[31m${h.stack || h}\x1b[0m`;
return `\x1b[31m${h.stack || h}\x1b[0m`; return h;
}); });
const d = new Date(); const d = new Date();

236
src/utils/voice.ts Normal file
View File

@ -0,0 +1,236 @@
import { AudioPlayer, AudioPlayerState, AudioPlayerStatus, AudioResource, createAudioPlayer, createAudioResource, entersState, joinVoiceChannel, StreamType, VoiceConnection, VoiceConnectionStatus } from "@discordjs/voice";
import { VoiceBasedChannel } from "discord.js";
import { log } from "./utils";
import { Readable } from "node:stream";
export type Hratelny = string | Readable | {
src: string | Readable;
volume: number;
type?: StreamType;
};
type Vojska = {
channel: VoiceBasedChannel;
conn: VoiceConnection;
players: [AudioPlayer, AudioPlayer, AudioPlayer];
queues: [AudioResource[], AudioResource[], AudioResource[]];
};
export const enum Priority {
Time,
Etc,
Music
}
const vojsy = new Map<string, Vojska>();
let lastId = 0;
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
*/
export const novejJoin = (channel: VoiceBasedChannel) => new Promise<boolean | VoiceBasedChannel>(async (resolve, reject) => {
const guildId = channel.guildId;
let prev: boolean | VoiceBasedChannel = false;
const gildina = vojsy.get(guildId);
if (gildina) {
if (gildina.channel.id == channel.id) {
if (gildina.conn.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;
}
const conn = joinVoiceChannel({
channelId: channel.id,
guildId,
adapterCreator: channel.guild.voiceAdapterCreator
});
await entersState(conn, VoiceConnectionStatus.Ready, 1e4)
.catch(e => {
novejLeave(guildId);
reject(e);
});
conn.on(VoiceConnectionStatus.Disconnected, () => {
// Je možný, že bota někdo "přetáhnul" do jinýho vojsu, v tom případě připojení recyklovat nechem
entersState(conn, VoiceConnectionStatus.Ready, 5e3).catch(() => {
// Taky je možný, že ho někdo fakt odpojil, ale hned znova připojil
// V tom případě už by bylo o recyklaci postaráno
if (conn.state.status != VoiceConnectionStatus.Disconnected) return;
log("recykluju");
novejLeave(guildId);
});
});
const player = createAudioPlayer();
conn.subscribe(player);
vojsy.set(guildId, {
channel,
conn,
players: [createAudioPlayer(), createAudioPlayer(), createAudioPlayer()],
queues: [[], [], []]
});
resolve(prev);
});
export function novejLeave(guildId: string) {
const vojs = vojsy.get(guildId);
if (!vojs) return false;
clearTimeout(timeouty.get(guildId));
timeouty.delete(guildId);
vojs.conn.destroy();
vojsy.delete(guildId);
return true;
}
/**
* Tahle funkce by se měla zavolat pokaždý, když dojde k nějaký změně v queue nebo něco dohraje.
* Řeší hovna typu, přeregistrace jinýho přehrávače, pouštění dalšího resource, a to je asi všechno
* @param vojska Vojska, o kterou se postarat
* @param priority Priorita, na kterou se brát ičkon ohled, je to buďto priorita nově zařazenýho zvuku, nebo prostě toho, s nejvyšší prioritou
*/
function handelieren(vojska: Vojska, priority: Priority) {
for (let i = 0; i < priority; i++) {
if (vojska.players[i].state.status != AudioPlayerStatus.Idle) return;
}
const hrac = vojska.players[priority];
vojska.conn.subscribe(hrac);
const resource = vojska.queues[priority].shift()!;
hrac.play(resource);
function nasafunkca(pred: AudioPlayerState, po: AudioPlayerState) {
if (po.status != AudioPlayerStatus.Idle || pred.status != AudioPlayerStatus.Playing || pred.resource.metadata != resource.metadata) return;
vojska?.players[priority].removeListener("stateChange", nasafunkca);
let nejprioritnejsi = -1;
for (let i = 0; i < 3; i++) {
const hrac = vojska.players[i];
if (hrac.state.status == AudioPlayerStatus.AutoPaused) {
vojska.conn.subscribe(hrac);
return;
}
if (!vojska.queues[i].length) continue;
nejprioritnejsi = i;
break;
}
if (nejprioritnejsi != -1)
handelieren(vojska, nejprioritnejsi);
};
vojska.players[priority].on("stateChange", nasafunkca);
}
function makeResource(co: Hratelny, id: string) {
if (typeof co == "string" || co instanceof Readable) return createAudioResource(co, { metadata: id });
const res = createAudioResource(co.src, { inlineVolume: true, inputType: co.type || StreamType.Arbitrary, metadata: id });
res.volume?.setVolume(co.volume);
return res;
}
function convert(resources: { id: string; res: AudioResource; }[]) {
const odpoved: AudioResource[] = [];
resources.forEach(res => {
odpoved.push(res.res);
});
return odpoved;
}
export const novejPlay = (channel: VoiceBasedChannel, co: Hratelny | Hratelny[], priority: Priority, handlePrev?: true) => new Promise<AudioPlayerState>(async (resolve, reject) => {
let prev: Awaited<ReturnType<typeof novejJoin>>;
if (!(vojsy.get(channel.guildId)?.channel.id == channel.id)) {
prev = await novejJoin(channel);
}
const vojska = vojsy.get(channel.guildId);
if (!vojska) return reject("silenost");
const resources: { id: string; res: AudioResource; }[] = [];
if (!Array.isArray(co)) {
const id = getId();
resources.push({ id, res: makeResource(co, id) });
} else {
co.forEach(c => {
const id = getId();
resources.push({ id, res: makeResource(c, id) });
});
}
// Tuto se možná může někdy hodit, třeba už za 5 minut, třeba nikdy, kdovi
// if (neprepisovat) {
// vojska.queues[priority].push(resource);
// } else {
// vojska.queues[priority] = [resource];
// }
vojska.queues[priority] = convert(resources);
const posledniId = resources.at(-1)?.id;
function nasafunkca(pred: AudioPlayerState, po: AudioPlayerState) {
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) => {
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
};
export function stopPlayer(guildId: string, priorita: Priority) {
const vojs = vojsy.get(guildId);
if (!vojs) return false;
vojs.players[priorita].stop();
return true;
}
// Ohlašování času
const timeouty = new Map<string, NodeJS.Timeout>();
const nula = (a: number) => a < 10 ? `0${a}` : a;
const vypocitatCas = (channel: VoiceBasedChannel) => {
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())));
};
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)
.then(() => {
vypocitatCas(channel);
})
.catch(err => { log("cas error:", err); });
};