Konečně přepis vojsování doprele e e e
This commit is contained in:
parent
30f68e212d
commit
ae353a0fec
992
package-lock.json
generated
992
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "denim_3001",
|
||||
"version": "3001.51.1",
|
||||
"version": "3001.52.0",
|
||||
"description": "Toto je velmi kvalitní bot.",
|
||||
"repository": {
|
||||
"url": "https://github.com/Histmy/Denim-Bot/"
|
||||
@ -14,21 +14,19 @@
|
||||
"author": "Histmy + det-fys",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@discordjs/voice": "^0.15.0",
|
||||
"discord.js": "^14.8.0",
|
||||
"@discordjs/voice": "^0.16.0",
|
||||
"discord.js": "^14.11.0",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
"node-fetch": "^2.6.1",
|
||||
"opusscript": "^0.0.8",
|
||||
"opusscript": "^0.1.0",
|
||||
"play-dl": "^1.9.6",
|
||||
"tiny-typed-emitter": "^2.1.0",
|
||||
"tweetnacl": "^1.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/js-levenshtein": "^1.1.1",
|
||||
"@types/node-fetch": "^2.6.2",
|
||||
"jest": "^29.5.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "^4.1.2"
|
||||
"@types/node-fetch": "^2.6.4",
|
||||
"jest": "^29.6.1"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,23 +1,40 @@
|
||||
// Tady bude muzika, vole
|
||||
|
||||
import { AudioPlayerStatus, VoiceConnection } from "@discordjs/voice";
|
||||
import { Client } from "discord.js";
|
||||
import { AudioPlayerStatus, getVoiceConnection } from "@discordjs/voice";
|
||||
import { Client, VoiceBasedChannel } 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 { 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; }[]> = {};
|
||||
|
||||
async function zahrat(conn: VoiceConnection, url: string, seek?: number) {
|
||||
async function playNext(channel: VoiceBasedChannel, seek?: number) {
|
||||
try {
|
||||
const src = await stream(url, { seek });
|
||||
play(conn, { name: src.stream, volume: 1, type: src.type })
|
||||
const kju = kjus[channel.guildId]!;
|
||||
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 => {
|
||||
log("chyba v muziki", 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;
|
||||
adminLog(client, "error v hani muziky");
|
||||
}
|
||||
@ -67,22 +84,7 @@ const exp: Modul = {
|
||||
|
||||
if (kju.length != 1) return;
|
||||
|
||||
configureTimeAnouncment(mes.guildId!, false);
|
||||
|
||||
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);
|
||||
});
|
||||
playNext(mes.member.voice.channel);
|
||||
}
|
||||
},
|
||||
|
||||
@ -106,8 +108,7 @@ const exp: Modul = {
|
||||
const kju = kjus[mes.guildId!];
|
||||
if (!kju) return "nic nehraje";
|
||||
|
||||
const player = getCurrentPlayer(mes.guildId!);
|
||||
player?.stop();
|
||||
stopPlayer(mes.guildId!, Priority.Music);
|
||||
mes.react(emouty.d3k);
|
||||
}
|
||||
},
|
||||
@ -138,8 +139,9 @@ const exp: Modul = {
|
||||
const kju = kjus[mes.guildId!];
|
||||
if (!kju) return "nehraje nic";
|
||||
|
||||
const conn = getCurrentPlayer(mes.guildId!)!.playable[0];
|
||||
zahrat(conn, kju[0].url, numero);
|
||||
const channel = mes.client.channels.cache.get(getVoiceConnection(mes.guildId!)?.joinConfig.channelId ?? "") as VoiceBasedChannel;
|
||||
if (!channel) return "neco nefunguje";
|
||||
playNext(channel, numero);
|
||||
mes.react(emouty.d3k);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,50 +1,15 @@
|
||||
// Cokoliv co má něco společnýho s vojsem
|
||||
|
||||
import { getVoiceConnection, VoiceConnection, VoiceConnectionStatus } from "@discordjs/voice";
|
||||
import { ChannelType, GuildMember, Message, VoiceChannel, VoiceState } from "discord.js";
|
||||
import { getVoiceConnection } from "@discordjs/voice";
|
||||
import { ChannelType, GuildMember, Message, VoiceBasedChannel, VoiceChannel, VoiceState } from "discord.js";
|
||||
import { emouty } from "../utils/emotes";
|
||||
import { CClient, Modul, MuzikaFace, SRecord } from "../utils/types";
|
||||
import { canAnounceTime, handlePrevVoice, joinVoice, log, play, rand } from "../utils/utils";
|
||||
import { CClient, Modul } from "../utils/types";
|
||||
import { log, rand } from "../utils/utils";
|
||||
import { handlePrevVoice, Hratelny, novejJoin, novejLeave, novejPlay, Priority } from "../utils/voice";
|
||||
|
||||
const timeouty: SRecord<NodeJS.Timeout> = {};
|
||||
const muty: string[] = [];
|
||||
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 vojs = member.voice.channel;
|
||||
if (!vojs) return;
|
||||
@ -65,14 +30,10 @@ async function playAndFukOff(mes: Message, jinak: string, zvuk: string) {
|
||||
const channel = mes.member?.voice.channel;
|
||||
if (!channel) return jinak;
|
||||
|
||||
const { conn, prev } = await joinVoice(channel);
|
||||
|
||||
await play(conn, zvuk)
|
||||
await novejPlay(channel, zvuk, Priority.Etc, true)
|
||||
.catch(e => {
|
||||
log("Pičo chyba při fukofování", e);
|
||||
});
|
||||
|
||||
handlePrevVoice(channel.guild, prev);
|
||||
}
|
||||
|
||||
const exp: Modul = {
|
||||
@ -90,18 +51,9 @@ const exp: Modul = {
|
||||
const nahlas = !arg.startsWith("potichu");
|
||||
if (nahlas) mes.channel.send("<@&591306633196339261> vojs");
|
||||
|
||||
joinVoice(channel)
|
||||
.then(obj => {
|
||||
const { prev, conn } = obj;
|
||||
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");
|
||||
novejJoin(channel)
|
||||
.then(prev => {
|
||||
if (prev != true && nahlas) novejPlay(channel, "zvuky/nazdar.ogg", Priority.Etc);
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -110,10 +62,7 @@ const exp: Modul = {
|
||||
als: ["odejdi", "disconnect", "leave", "odpoj", "votpoj", "vodpoj", "vodprejskni", "tahni"],
|
||||
DMUnsafe: true,
|
||||
run: mes => {
|
||||
const vojs = getVoiceConnection(mes.guildId!);
|
||||
if (!vojs) return 'nejsem ve vojsu';
|
||||
|
||||
vojs.destroy();
|
||||
if (!novejLeave(mes.guildId!)) return "nejsem ve vojsu";
|
||||
mes.react(emouty.purfieRIP);
|
||||
}
|
||||
},
|
||||
@ -149,11 +98,11 @@ const exp: Modul = {
|
||||
return `${prefix}${koncovka}`;
|
||||
};
|
||||
const rozdelAPridej = (cislo: number) => {
|
||||
if (cislo == 2) return zvuky.push({ name: `${pre}dvě.mp3`, volume: 2.5 });
|
||||
if (cislo < 20) return zvuky.push({ name: `${pre}${cislo}.mp3`, volume: 2.5 });
|
||||
if (cislo == 2) return zvuky.push({ src: `${pre}dvě.mp3`, volume: 2.5 });
|
||||
if (cislo < 20) return zvuky.push({ src: `${pre}${cislo}.mp3`, volume: 2.5 });
|
||||
const text = String(cislo);
|
||||
zvuky.push({ name: `${pre}${text[0]}0.mp3`, volume: 2.5 });
|
||||
if (text[1] != "0") zvuky.push({ name: `${pre}${text[1]}.mp3`, volume: 2.5 });
|
||||
zvuky.push({ src: `${pre}${text[0]}0.mp3`, volume: 2.5 });
|
||||
if (text[1] != "0") zvuky.push({ src: `${pre}${text[1]}.mp3`, volume: 2.5 });
|
||||
};
|
||||
|
||||
const hod = addSuffix("hodin", h);
|
||||
@ -163,21 +112,21 @@ const exp: Modul = {
|
||||
const channel = mes.member?.voice.channel;
|
||||
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);
|
||||
zvuky.push({ name: `${pre}${hod}.mp3`, volume: 2.5 });
|
||||
zvuky.push({ src: `${pre}${hod}.mp3`, volume: 2.5 });
|
||||
if (m > 0) {
|
||||
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);
|
||||
await play(conn, zvuky)
|
||||
const prev = await novejJoin(channel);
|
||||
await novejPlay(channel, zvuky, Priority.Etc)
|
||||
.catch(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);
|
||||
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) {
|
||||
play(conn, { name: "./zvuky/neodposlouchavej.ogg", volume: 0.38 })
|
||||
novejPlay(channel, { src: "./zvuky/neodposlouchavej.ogg", volume: 0.38 }, Priority.Etc)
|
||||
.then(() => {
|
||||
if (!aft.selfMute) return;
|
||||
aft.setDeaf(true, "otposlouchávala ta gadza");
|
||||
|
||||
@ -1,38 +1,5 @@
|
||||
import { AudioPlayer, AudioPlayerStatus, AudioResource, createAudioPlayer, createAudioResource, entersState, getVoiceConnection, joinVoiceChannel, PlayerSubscription, StreamType, VoiceConnection, VoiceConnectionStatus } from "@discordjs/voice";
|
||||
import { ChannelType, Client, Guild, StageChannel, User, VoiceChannel } from "discord.js";
|
||||
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"
|
||||
};
|
||||
import { ChannelType, Client, User } from "discord.js";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
if (!existsSync("config.json")) throw new Error("config.json neexistuje");
|
||||
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`;
|
||||
};
|
||||
|
||||
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`
|
||||
* @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 const configureTimeAnouncment = (guild: string, state: boolean) => { timeAnouncability[guild] = state; };
|
||||
|
||||
export const canAnounceTime = (guild: string) => timeAnouncability[guild] ?? true;
|
||||
|
||||
export function log(...content: (string | Error)[]) {
|
||||
export function log(...content: unknown[]) {
|
||||
const jo = content.map(h => {
|
||||
if (typeof h == "string") return h;
|
||||
return `\x1b[31m${h.stack || h}\x1b[0m`;
|
||||
if (h instanceof Error) return `\x1b[31m${h.stack || h}\x1b[0m`;
|
||||
return h;
|
||||
});
|
||||
|
||||
const d = new Date();
|
||||
|
||||
236
src/utils/voice.ts
Normal file
236
src/utils/voice.ts
Normal 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 má postarat
|
||||
* @param priority Priorita, na kterou se má 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); });
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user