const { createAudioPlayer, createAudioResource, getVoiceConnection, joinVoiceChannel } = require("@discordjs/voice"); const { SlashCommandBuilder } = require('@discordjs/builders'); const { createDiscordJSAdapter } = require("../utils/adapter"); module.exports = { name: "play", alias: "p", usage: "<song name>", description: "Play radio.", permission: "none", category: "radio", data: new SlashCommandBuilder() .setName('play') .setDescription('Play radio.') .addStringOption(option => option.setName('query') .setDescription('Select station') .setRequired(true)), async execute(interaction, client, Discord, command) { let message = {}; let query = interaction.options.getString("query"); let url = query ? query.replace(/<(.+)>/g, "$1") : ""; const radio = client.radio.get(interaction.guild.id); const voiceChannel = interaction.member.voice.channel; if (!radio) { if (!interaction.member.voice.channel) return interaction.reply( client.messageEmojis["error"] + client.messages.noVoiceChannel ); } else { if (voiceChannel !== radio.voiceChannel) return interaction.reply( client.messageEmojis["error"] + client.messages.wrongVoiceChannel ); } if (!client.stations) { message.errorToGetPlaylist = client.messages.errorToGetPlaylist.replace( "%client.config.supportGuild%", client.config.supportGuild ); return interaction.reply(client.messageEmojis["error"] + message.errorToGetPlaylist); } if (!query) return interaction.reply(client.messages.noQuery); const permissions = voiceChannel.permissionsFor(interaction.client.user); if (!permissions.has("CONNECT")) { return interaction.reply(client.messageEmojis["error"] + client.messages.noPermsConnect); } if (!permissions.has("SPEAK")) { return interaction.reply(client.messageEmojis["error"] + client.messages.noPermsSpeak); } let station; const number = parseInt(query - 1); if (url.startsWith("http")) { return interaction.reply( client.messageEmojis["error"] + client.messages.errorStationURL ); } else if (!isNaN(number)) { if (number > client.stations.length - 1) { return interaction.reply( client.messageEmojis["error"] + client.messages.wrongStationNumber ); } else { url = client.stations[number].stream[client.stations[number].stream.default]; station = client.stations[number]; } } else { if (query.length < 3) return interaction.reply( client.messageEmojis["error"] + client.messages.tooShortSearch ); const sstation = await searchStation(query.slice(1), client); if (!sstation) return interaction.reply( client.messageEmojis["error"] + client.messages.noSearchResults ); url = sstation.stream[sstation.stream.default]; station = sstation; } if (radio) { client.funcs.statisticsUpdate(client, interaction.guild, radio); radio.audioPlayer.stop(); radio.station = station; radio.textChannel = interaction.channel; play(interaction, interaction.guild, client, url); return; } const construct = { textChannel: interaction.channel, voiceChannel: voiceChannel, connection: null, audioPlayer: createAudioPlayer(), station: station }; client.radio.set(interaction.guild.id, construct); try { const connection = getVoiceConnection(voiceChannel.guild.id) ?? joinVoiceChannel({ channelId: voiceChannel.id, guildId: voiceChannel.guild.id, adapterCreator: createDiscordJSAdapter(voiceChannel) }); construct.connection = connection; let date = new Date(); construct.startTime = date.getTime(); play(interaction, interaction.guild, client, url); client.datastore.checkEntry(interaction.guild.id); construct.currentGuild = client.datastore.getEntry(interaction.guild.id); if (!construct.currentGuild.statistics[construct.station.name]) { construct.currentGuild.statistics[construct.station.name] = {}; construct.currentGuild.statistics[construct.station.name].time = 0; construct.currentGuild.statistics[construct.station.name].used = 0; client.datastore.updateEntry(interaction.guild, construct.currentGuild); } } catch (error) { console.log(error); client.radio.delete(interaction.guild.id); return interaction.reply(client.messageEmojis["error"] + `An error occured: ${error}`); } } }; function play(interaction, guild, client, url) { let message = {}; const radio = client.radio.get(guild.id); const resource = createAudioResource(url); radio.connection.subscribe(radio.audioPlayer); radio.audioPlayer.play(resource); resource.playStream .on("readable", () => { client.funcs.logger('Radio', 'Stream started' + " / " + guild.id + " / " + radio.station.name); }) .on("finish", () => { client.funcs.logger('Radio', 'Stream finished' + " / " + guild.id); client.funcs.statisticsUpdate(client, guild, radio); radio.connection?.destroy(); radio.audioPlayer?.stop(); client.radio.delete(guild.id); return; }) .on("error", error => { client.funcs.logger('Radio', 'Stream errored'); console.error(error); radio.connection?.destroy(); radio.audioPlayer?.stop(); client.radio.delete(guild.id); return interaction.reply(client.messages.errorPlaying); }); message.play = client.messages.play.replace("%radio.station.name%", radio.station.name); interaction.reply(client.messageEmojis["play"] + message.play); } function searchStation(key, client) { if (client.stations === null) return false; let foundStations = []; if (!key) return false; if (key == "radio") return false; if (key.startsWith("radio ")) key = key.slice(6); const probabilityIncrement = 100 / key.split(" ").length / 2; for (let i = 0; i < key.split(" ").length; i++) { client.stations .filter( x => x.name.toUpperCase().includes(key.split(" ")[i].toUpperCase()) || x === key ) .forEach(x => foundStations.push({ station: x, name: x.name, probability: probabilityIncrement }) ); } if (foundStations.length === 0) return false; for (let i = 0; i < foundStations.length; i++) { for (let j = 0; j < foundStations.length; j++) { if (foundStations[i] === foundStations[j] && i !== j) foundStations.splice(i, 1); } } for (let i = 0; i < foundStations.length; i++) { if (foundStations[i].name.length > key.length) { foundStations[i].probability -= (foundStations[i].name.split(" ").length - key.split(" ").length) * (probabilityIncrement * 0.5); } else if (foundStations[i].name.length === key.length) { foundStations[i].probability += probabilityIncrement * 0.9; } for (let j = 0; j < key.split(" ").length; j++) { if (!foundStations[i].name.toUpperCase().includes(key.toUpperCase().split(" ")[j])) { foundStations[i].probability -= probabilityIncrement * 0.5; } } } let highestProbabilityStation; for (let i = 0; i < foundStations.length; i++) { if ( !highestProbabilityStation || highestProbabilityStation.probability < foundStations[i].probability ) highestProbabilityStation = foundStations[i]; if ( highestProbabilityStation && highestProbabilityStation.probability === foundStations[i].probability ) { highestProbabilityStation = foundStations[i].station; } } return highestProbabilityStation; }