diff --git a/.env_example b/.env_example index f312147..e41c215 100644 --- a/.env_example +++ b/.env_example @@ -1,3 +1,2 @@ DISCORD_TOKEN= -RADIOX_STATIONSLISTURL=https://gitea.cwinfo.org/cwchristerw/radio/raw/branch/master/playlist.json -RADIOX_PREFIX=rx- \ No newline at end of file +RADIOX_STATIONSLISTURL=https://gitea.cwinfo.org/cwchristerw/radio/raw/branch/master/playlist.json \ No newline at end of file diff --git a/README.md b/README.md index aa87e67..53033a4 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,9 @@ Internet Radio to your Discord guild ## [Radio Stations List](https://gitea.cwinfo.org/cwchristerw/radio) This bot is using Gitea repo to get radio stations from [playlist.json](https://gitea.cwinfo.org/cwchristerw/radio/raw/branch/master/playlist.json) file. List is currently maintained by Christer Warén. You can use alternative list with same format when using RADIOX_STATIONSLISTURL environment variable. -## PREFIX -Default prefix is "rx-" and you can change it with using RADIOX_PREFIX environment variable. - ## Docker 1. `docker build -t warengroup/eximiabots-radiox .` -2. `docker run --name radiox-dev -d --net host -e DISCORD_TOKEN= -e RADIOX_PREFIX="rx-" -v "$PWD/datastore":/usr/src/app/datastore/ warengroup/eximiabots-radiox` +2. `docker run --name radiox-dev -d --net host -e DISCORD_TOKEN= -v "$PWD/datastore":/usr/src/app/datastore/ warengroup/eximiabots-radiox` ## Join our Discord Server https://discord.gg/rRA65Mn diff --git a/src/Client.ts b/src/Client.ts index 18b97d8..a991ab7 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -1,16 +1,18 @@ import Discord, { Client, Collection } from "discord.js"; import fs from "fs"; -const events = "./client/events/"; import Datastore from "./client/datastore.js"; import { command, radio } from "./client/utils/typings.js"; import config from "./config.js"; import messages from "./client/messages.js"; import path from "path"; +const events = "./client/events/"; + const GatewayIntents = new Discord.Intents(); GatewayIntents.add( 1 << 0, // GUILDS 1 << 7, // GUILD_VOICE_STATES + 1 << 9 // GUILD_MESSAGES ); class RadioClient extends Client { @@ -35,6 +37,15 @@ class RadioClient extends Client { this.funcs.logger = require("./client/funcs/logger.js"); this.funcs.msToTime = require("./client/funcs/msToTime.js"); this.funcs.statisticsUpdate = require("./client/funcs/statisticsUpdate.js"); + this.funcs.saveState = require("./client/funcs/saveState.js"); + this.funcs.loadState = require("./client/funcs/loadState.js"); + + console.log('RadioX ' + this.config.version); + console.log('Internet Radio to your Discord guild'); + console.log('(c)2020-2021 EximiaBots by Warén Group'); + console.log(''); + + this.funcs.logger("Bot", "Starting"); const commandFiles = fs.readdirSync(path.join("./src/client/commands")).filter(f => f.endsWith(".js")); for (const file of commandFiles) { @@ -44,14 +55,32 @@ class RadioClient extends Client { this.on("ready", () => { require(`${events}ready`).execute(this); - this.datastore = new Datastore(); }); + + this.on("messageCreate", msg => { + require(`${events}messageCreate`).execute(this, msg); + }); + + this.on("messageDelete", msg => { + require(`${events}messageDelete`).execute(this, msg); + }); + this.on("interactionCreate", interaction => { require(`${events}interactionCreate`).execute(this, interaction); }); + this.on("voiceStateUpdate", (oldState, newState) => { require(`${events}voiceStateUpdate`).execute(this, oldState, newState); }); + + process.on('SIGINT', () => { + require(`${events}SIGINT`).execute(this); + }); + + process.on('SIGTERM', () => { + require(`${events}SIGTERM`).execute(this); + }); + this.on("error", error => { console.error(error); }); diff --git a/src/client/commands/help.js b/src/client/commands/help.js index 476def6..fc5b94c 100644 --- a/src/client/commands/help.js +++ b/src/client/commands/help.js @@ -19,7 +19,6 @@ module.exports = { message.helpTitle = client.messages.helpTitle.replace("%client.user.username%", client.user.username); message.helpDescription = client.messages.helpDescription.replace("%commands%", commands); - message.helpDescription = message.helpDescription.replace("%client.config.prefix%", client.config.prefix); const embed = new Discord.MessageEmbed() .setTitle(message.helpTitle) diff --git a/src/client/commands/list.js b/src/client/commands/list.js index 40e62da..fd7b412 100644 --- a/src/client/commands/list.js +++ b/src/client/commands/list.js @@ -11,22 +11,71 @@ module.exports = { message.errorToGetPlaylist = client.messages.errorToGetPlaylist.replace("%client.config.supportGuild%", client.config.supportGuild); return interaction.reply(client.messageEmojis["error"] + message.errorToGetPlaylist); } - let stations = `${client.stations.map(s => `**#** ${s.name}`).join('\n')}` - const hashs = stations.split('**#**').length; - for (let i = 0; i < hashs; i++) { - stations = stations.replace('**#**', `**${i + 1}.**`); - } - - const embed = new Discord.MessageEmbed() - .setTitle(client.messages.listTitle) - .setThumbnail("https://cdn.discordapp.com/emojis/" + client.messageEmojis["list"].replace(/[^0-9]+/g, '')) - .setColor(client.config.embedColor) - .setDescription(stations) - .setFooter(client.messages.footerText, "https://cdn.discordapp.com/emojis/" + client.messageEmojis["eximiabots"].replace(/[^0-9]+/g, '')); - interaction.reply({ - embeds: [embed], - ephemeral: true - }); + const radio = client.radio.get(interaction.guild.id); + + if(radio){ + let menu = []; + + let stations = new Array(); + + let options = new Array(); + options[1] = new Array(); + options[2] = new Array(); + + stations[1] = client.stations.slice(0,24).forEach(station => { + station = { + label: station.name, + description: station.owner, + value: station.name + }; + options[1].push(station); + }); + + stations[2] = client.stations.slice(25).forEach(station => { + station = { + label: station.name, + description: station.owner, + value: station.name + }; + options[2].push(station); + }); + + menu = new Discord.MessageActionRow() + .addComponents( + new Discord.MessageSelectMenu() + .setCustomId('play') + .setPlaceholder('Change station') + .addOptions(options[1]) + .addOptions(options[2]) + ); + + stations = null; + options = null; + + interaction.reply({ + content: '**Select station:**', + components: [menu], + ephemeral: true + }); + } else { + let stations = `${client.stations.map(s => `**#** ${s.name}`).join('\n')}` + const hashs = stations.split('**#**').length; + for (let i = 0; i < hashs; i++) { + stations = stations.replace('**#**', `**${i + 1}.**`); + } + + let embed = new Discord.MessageEmbed() + .setTitle(client.messages.listTitle) + .setThumbnail("https://cdn.discordapp.com/emojis/" + client.messageEmojis["list"].replace(/[^0-9]+/g, '')) + .setColor(client.config.embedColor) + .setDescription(stations) + .setFooter(client.messages.footerText, "https://cdn.discordapp.com/emojis/" + client.messageEmojis["eximiabots"].replace(/[^0-9]+/g, '')); + + interaction.reply({ + embeds: [embed], + ephemeral: true + }); + } } }; \ No newline at end of file diff --git a/src/client/commands/maintenance.js b/src/client/commands/maintenance.js index e4b836f..1574c4c 100644 --- a/src/client/commands/maintenance.js +++ b/src/client/commands/maintenance.js @@ -26,18 +26,12 @@ module.exports = { currentRadio.guild = client.datastore.getEntry(radio.value).guild; if(currentRadio){ - client.funcs.statisticsUpdate(client, currentRadio.currentGuild.guild, currentRadio); + client.funcs.statisticsUpdate(client, currentRadio.guild, currentRadio); currentRadio.connection?.destroy(); currentRadio.audioPlayer?.stop(); - const cembed = new Discord.MessageEmbed() - .setTitle(client.messages.maintenanceTitle) - .setThumbnail("https://cdn.discordapp.com/emojis/" + client.messageEmojis["maintenance"].replace(/[^0-9]+/g, '')) - .setColor(client.config.embedColor) - .setDescription(client.messages.sendedMaintenanceMessage) - .setFooter(client.messages.footerText, "https://cdn.discordapp.com/emojis/" + client.messageEmojis["eximiabots"].replace(/[^0-9]+/g, '')); - currentRadio.textChannel.send({ embeds: [cembed] }); + currentRadio.message?.delete(); client.radio.delete(radio.value); - stoppedRadios += "-" + radio.value + ": " + currentRadio.currentGuild.guild.name + "\n"; + stoppedRadios += "-" + radio.value + ": " + currentRadio.guild.name + "\n"; } radio = currentRadios.next(); } diff --git a/src/client/commands/play.js b/src/client/commands/play.js index 2ba4d3e..c0b6d01 100644 --- a/src/client/commands/play.js +++ b/src/client/commands/play.js @@ -137,8 +137,10 @@ module.exports = { client.funcs.statisticsUpdate(client, interaction.guild, radio); radio.audioPlayer.stop(); + let date = new Date(); radio.station = station; radio.textChannel = interaction.channel; + radio.startTime = date.getTime(); play(interaction, interaction.guild, client, url, Discord); return; @@ -214,6 +216,7 @@ async function play(interaction, guild, client, url, Discord) { message.nowplayingDescription = client.messages.nowplayingDescription.replace("%radio.station.name%", radio.station.name); message.nowplayingDescription = message.nowplayingDescription.replace("%radio.station.owner%", radio.station.owner); message.nowplayingDescription = message.nowplayingDescription.replace("%client.funcs.msToTime(completed)%", ""); + message.nowplayingDescription = message.nowplayingDescription.replace("Owner: ", ""); message.nowplayingDescription = message.nowplayingDescription.replace("**", ""); message.nowplayingDescription = message.nowplayingDescription.replace("**", ""); diff --git a/src/client/commands/stop.js b/src/client/commands/stop.js index b2e8641..e0d1779 100644 --- a/src/client/commands/stop.js +++ b/src/client/commands/stop.js @@ -21,13 +21,13 @@ module.exports = { .setFooter(client.messages.footerText, "https://cdn.discordapp.com/emojis/" + client.messageEmojis["eximiabots"].replace(/[^0-9]+/g, '')); if(!radio.message){ - radio.message = await radio.textChannel.send({ embeds: [embed], components: [] }); + radio.message = radio.textChannel.send({ embeds: [embed], components: [] }); } else { radio.message.edit({ embeds: [embed], components: [] }); } - setTimeout(function() { - radio.message.delete(); + setTimeout(async function() { + await radio.message?.delete(); }, 5000); client.radio.delete(interaction.guild.id); diff --git a/src/client/datastore.js b/src/client/datastore.js index dafd80a..dbacf73 100644 --- a/src/client/datastore.js +++ b/src/client/datastore.js @@ -80,6 +80,7 @@ module.exports = class { newData.guild = {}; newData.guild.id = id; newData.statistics = {}; + newData.state = {}; this.map.set(id, newData); this.saveEntry(id, newData); } @@ -90,6 +91,10 @@ module.exports = class { updateEntry(guild, newData) { newData.guild.name = guild.name; + + let date = new Date(); + newData.updated = date.toISOString().substring(0, 10) + this.map.set(guild.id, newData); this.saveEntry(guild.id, newData); //this.showEntry(this.getEntry(guild.id)); @@ -110,6 +115,9 @@ module.exports = class { "time": 0, "used": 0 } + }, + "state": { + } } @@ -118,7 +126,7 @@ module.exports = class { saveEntry(file, data) { data = JSON.stringify(data, null, 4); - + fs.writeFile(path.join(path.dirname(__dirname), '../datastore') + "/" + file + ".json", data, 'utf8', function(err) { if (err) { //console.log(err); diff --git a/src/client/events/SIGINT.js b/src/client/events/SIGINT.js new file mode 100644 index 0000000..dc0bf65 --- /dev/null +++ b/src/client/events/SIGINT.js @@ -0,0 +1,43 @@ +import Discord from "discord.js"; + +module.exports = { + name: 'SIGINT', + async execute(client) { + setTimeout(async function () { + let message = {}; + + if (!client.stations) return process.exit(); + + let currentRadios = client.radio.keys(); + let radio = currentRadios.next(); + + while (!radio.done) { + let currentRadio = client.radio.get(radio.value); + currentRadio.guild = client.datastore.getEntry(radio.value).guild; + + if (currentRadio) { + client.funcs.statisticsUpdate(client, currentRadio.guild, currentRadio); + client.funcs.saveState(client, currentRadio.guild, currentRadio); + currentRadio.connection?.destroy(); + currentRadio.audioPlayer?.stop(); + currentRadio.message?.delete(); + client.radio.delete(radio.value); + } + + radio = currentRadios.next(); + } + + console.log("\n"); + client.funcs.logger("Bot", "Closing"); + console.log("\n"); + + client.user.setStatus('dnd'); + + setInterval(() => { + if(radio.done){ + process.exit(); + } + }, 1000); + }, 5000); + } +} \ No newline at end of file diff --git a/src/client/events/SIGTERM.js b/src/client/events/SIGTERM.js new file mode 100644 index 0000000..52daa75 --- /dev/null +++ b/src/client/events/SIGTERM.js @@ -0,0 +1,6 @@ +module.exports = { + name: 'SIGTERM', + async execute(client) { + process.emit('SIGINT'); + } +} \ No newline at end of file diff --git a/src/client/events/interactionCreate.js b/src/client/events/interactionCreate.js index af105ab..67d2666 100644 --- a/src/client/events/interactionCreate.js +++ b/src/client/events/interactionCreate.js @@ -3,7 +3,6 @@ import Discord from "discord.js"; module.exports = { name: 'interactionCreate', async execute(client, interaction) { - /*if (!interaction.isCommand()) return;*/ const permissions = interaction.channel.permissionsFor(interaction.client.user); if (!permissions.has('EMBED_LINKS')) return interaction.send(client.messages.noPermsEmbed); @@ -16,7 +15,10 @@ module.exports = { try { command.execute(interaction, client, Discord, command); } catch (error) { - interaction.reply(client.messages.runningCommandFailed); + interaction.reply({ + content: client.messages.runningCommandFailed, + ephemeral: true + }); console.error(error); } } else if (interaction.isSelectMenu() || interaction.isButton()){ @@ -27,7 +29,10 @@ module.exports = { try { command.execute(interaction, client, Discord, command); } catch (error) { - interaction.reply(client.messages.runningCommandFailed); + interaction.reply({ + content: client.messages.runningCommandFailed, + ephemeral: true + }); console.error(error); } } diff --git a/src/client/events/messageCreate.js b/src/client/events/messageCreate.js new file mode 100644 index 0000000..49614d1 --- /dev/null +++ b/src/client/events/messageCreate.js @@ -0,0 +1,54 @@ +import Discord from "discord.js"; +module.exports = { + name: 'messageCreate', + async execute(client, msg) { + + if (msg.author.bot || !msg.guild) return; + let prefix = "rx$"; + if(client.user.username == "RadioX"){ + prefix = "rx>"; + } else if (client.user.username == "RadioX Beta"){ + prefix = "rx-"; + } else if (client.user.username == "RadioX Dev"){ + prefix = "rx$"; + } else if(msg.mentions.members.first() && msg.mentions.members.first().user.id === client.user.id){ + prefix = "<@!" + client.user.id + "> "; + } else { + return; + } + + const args = msg.content.slice(prefix.length).split(' '); + if (!msg.content.startsWith(prefix)) return; + if (!args[0]) return; + const commandName = args[0].toLowerCase(); + if (commandName === 'none') return; + const command = client.commands.get(commandName) || client.commands.find(cmd => cmd.aliases && cmd.aliases.includes(commandName)) || client.commandAliases.get(commandName); + if (!command && msg.content !== `${prefix}`) return; + const permissions = msg.channel.permissionsFor(msg.client.user); + if (!permissions.has('EMBED_LINKS')) return msg.channel.send(client.messages.noPermsEmbed); + try { + let message = {}; + + message.messageCommandsDeprecatedTitle = client.messages.messageCommandsDeprecatedTitle.replace("%client.user.username%", client.user.username); + + const embed = new Discord.MessageEmbed() + .setTitle(message.messageCommandsDeprecatedTitle) + .setThumbnail("https://cdn.discordapp.com/emojis/" + client.messageEmojis["logo"].replace(/[^0-9]+/g, '')) + .setColor(client.config.embedColor) + .setDescription(client.messages.messageCommandsDeprecatedDescription) + .setFooter(client.messages.footerText, "https://cdn.discordapp.com/emojis/" + client.messageEmojis["eximiabots"].replace(/[^0-9]+/g, '')); + + msg.channel.send({ embeds: [embed] }); + + setTimeout(function() { + msg.delete(); + }, 30000); + } catch (error) { + msg.reply({ + content: client.messages.runningCommandFailed, + ephemeral: true + }); + console.error(error); + } + } +} \ No newline at end of file diff --git a/src/client/events/messageDelete.js b/src/client/events/messageDelete.js new file mode 100644 index 0000000..9cce584 --- /dev/null +++ b/src/client/events/messageDelete.js @@ -0,0 +1,10 @@ +module.exports = { + name: 'messageDelete', + async execute(client, msg) { + if (!msg.author.bot || !msg.guild) return; + const radio = client.radio.get(msg.guild.id); + if (!radio) return; + if(msg.id != radio.message.id) return; + radio.message = null; + } +} \ No newline at end of file diff --git a/src/client/events/ready.js b/src/client/events/ready.js index 3f051ac..45e4c9d 100644 --- a/src/client/events/ready.js +++ b/src/client/events/ready.js @@ -1,22 +1,24 @@ +import Datastore from "../datastore.js"; const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args)); module.exports = { name: 'ready', async execute(client) { - console.log('RadioX ' + client.config.version); - console.log('Internet Radio to your Discord guild'); - console.log('(c)2020-2021 EximiaBots by Warén Group'); - console.log(''); + client.funcs.logger("Bot", "Ready"); + + /*DATASTORE*/ + client.funcs.logger('Datastore', 'Initialize'); + client.datastore = new Datastore(); /*DEVELOPERS*/ - client.funcs.logger('Developers', 'List'); + client.funcs.logger('Developers'); client.developers = ""; let user = ""; for (let i = 0; i < client.config.devId.length; i++) { user = await client.users.fetch(client.config.devId[i]); - console.log(" - " + user.tag); + console.log("- " + user.tag); if (i == client.config.devId.length - 1) { client.developers += user.tag; } else { @@ -32,9 +34,9 @@ module.exports = { .then(client.funcs.checkFetchStatus) .then(response => response.json()); - client.funcs.logger('Stations', 'List'); + client.funcs.logger('Stations'); client.stations.forEach(station => { - console.log(" - " + station.name); + console.log("- " + station.name); }); console.log("\n"); @@ -65,15 +67,15 @@ module.exports = { /*GUILDS*/ client.funcs.logger('Guilds', 'Started fetching list'); - client.funcs.logger('Guilds', 'List'); + client.funcs.logger('Guilds'); let guilds = await client.guilds.fetch(); guilds.forEach(guild => { - console.log(" - " + guild.id + ": " + guild.name); + console.log("- " + guild.id + ": " + guild.name); }); console.log("\n"); client.funcs.logger('Guilds', 'Successfully fetched list'); - + /*STATISTICS*/ client.datastore.calculateGlobal(client); @@ -83,5 +85,10 @@ module.exports = { /*COMMANDS*/ require(`../commands.js`).execute(client); + setTimeout(function () { + /*RESTORE RADIO*/ + require(`../restoreradio.js`).execute(client, guilds); + }, 5000); + } } \ No newline at end of file diff --git a/src/client/funcs/loadState.js b/src/client/funcs/loadState.js new file mode 100644 index 0000000..f7ff54b --- /dev/null +++ b/src/client/funcs/loadState.js @@ -0,0 +1,10 @@ +module.exports = function loadState(client, guild){ + let data = client.datastore.getEntry(guild.id); + if(!data) return; + let state; + + state = data.state; + data.state = {}; + client.datastore.updateEntry(guild, data); + return state; +} \ No newline at end of file diff --git a/src/client/funcs/logger.js b/src/client/funcs/logger.js index 851a8d1..80ce35c 100644 --- a/src/client/funcs/logger.js +++ b/src/client/funcs/logger.js @@ -1,4 +1,5 @@ module.exports = function (area, text){ let date = new Date(); - console.log('[' + area + '] – ' + date.toISOString() + '\n' + text + '\n'); + console.log('[' + area + '] – ' + date.toISOString()); + if(text) console.log(text + '\n'); } \ No newline at end of file diff --git a/src/client/funcs/saveState.js b/src/client/funcs/saveState.js new file mode 100644 index 0000000..eb59553 --- /dev/null +++ b/src/client/funcs/saveState.js @@ -0,0 +1,18 @@ +module.exports = function saveState(client, guild, radio){ + client.datastore.checkEntry(guild.id); + + let date = new Date(); + + let data = client.datastore.getEntry(guild.id); + + data.state = {}; + data.state.channels = {}; + data.state.channels.text = radio.textChannel.id; + data.state.channels.voice = radio.voiceChannel.id; + data.state.date = date.toISOString(); + data.state.station = {}; + data.state.station.name = radio.station.name; + data.state.station.owner = radio.station.owner; + + client.datastore.updateEntry(guild, data); +} \ No newline at end of file diff --git a/src/client/funcs/statisticsUpdate.js b/src/client/funcs/statisticsUpdate.js index 2ca66c6..4fcf09c 100644 --- a/src/client/funcs/statisticsUpdate.js +++ b/src/client/funcs/statisticsUpdate.js @@ -2,21 +2,21 @@ module.exports = function statisticsUpdate(client, guild, radio) { client.datastore.checkEntry(guild.id); - radio.currentGuild = client.datastore.getEntry(guild.id); + radio.datastore = client.datastore.getEntry(guild.id); - if(!radio.currentGuild.statistics[radio.station.name]){ - radio.currentGuild.statistics[radio.station.name] = {}; - radio.currentGuild.statistics[radio.station.name].time = 0; - radio.currentGuild.statistics[radio.station.name].used = 0; - client.datastore.updateEntry(guild, radio.currentGuild); + if(!radio.datastore.statistics[radio.station.name]){ + radio.datastore.statistics[radio.station.name] = {}; + radio.datastore.statistics[radio.station.name].time = 0; + radio.datastore.statistics[radio.station.name].used = 0; + client.datastore.updateEntry(guild, radio.datastore); } let date = new Date(); radio.currentTime = date.getTime(); radio.playTime = parseInt(radio.currentTime)-parseInt(radio.startTime); - radio.currentGuild.statistics[radio.station.name].time = parseInt(radio.currentGuild.statistics[radio.station.name].time)+parseInt(radio.playTime); + radio.datastore.statistics[radio.station.name].time = parseInt(radio.datastore.statistics[radio.station.name].time)+parseInt(radio.playTime); - radio.currentGuild.statistics[radio.station.name].used = parseInt(radio.currentGuild.statistics[radio.station.name].used)+1; - client.datastore.updateEntry(guild, radio.currentGuild); + radio.datastore.statistics[radio.station.name].used = parseInt(radio.datastore.statistics[radio.station.name].used)+1; + client.datastore.updateEntry(guild, radio.datastore); client.datastore.calculateGlobal(client); } \ No newline at end of file diff --git a/src/client/messages.js b/src/client/messages.js index db7350c..46eda2d 100644 --- a/src/client/messages.js +++ b/src/client/messages.js @@ -8,8 +8,6 @@ module.exports = { bugDescription: "Join the support server \n %client.config.supportGuild%", helpTitle: "%client.user.username% help:", helpDescription: "%commands%", - helpCommandTitle: "%client.config.prefix%%command.name% %command.usage%", - helpCommandDescription: "%command.description% \n Command Alias: %command.alias%", inviteTitle: "Invite %client.user.username% to your Discord server!", listTitle: "Radio Stations", nowplayingTitle: "Now Playing", @@ -36,5 +34,7 @@ module.exports = { statusField3: "Uptime", statusField4: "Version", statusField5: "Hosted by", - errorStationURL: "Station can't be URL" + errorStationURL: "Station can't be URL", + messageCommandsDeprecatedTitle: "%client.user.username%", + messageCommandsDeprecatedDescription: "We recommend you to reauthorize our bot by clicking the invite link down below, because Discord is planning to remove message content from verified bots [Read More](https://support-dev.discord.com/hc/en-us/articles/4404772028055) \n\n **Invite Bot** \n https://wgi.fi/radiox_invite \n\n This bot now supports slash commands, you should start using them instead. Type / into the message box and select the bot you wish to use. Remember to be careful as there are a few bugs here and there on Discord. \n\n We will remove this deprecation message in March of 2022 when RadioX 1.0.0 is released." }; \ No newline at end of file diff --git a/src/client/restoreradio.js b/src/client/restoreradio.js new file mode 100644 index 0000000..74fdaac --- /dev/null +++ b/src/client/restoreradio.js @@ -0,0 +1,207 @@ +import Discord from "discord.js"; +const { + createAudioPlayer, + createAudioResource, + getVoiceConnection, + joinVoiceChannel +} = require("@discordjs/voice"); + +module.exports = { + async execute(client, guilds) { + guilds.forEach(async guild => { + let state = client.funcs.loadState(client, guild); + if(!state) return; + if(!state.station || !state.channels.voice || !state.channels.text) return; + + const sstation = await searchStation(state.station.name, client); + let url = sstation.stream[sstation.stream.default]; + let station = sstation; + + const construct = { + textChannel: client.channels.cache.get(state.channels.text), + voiceChannel: client.channels.cache.get(state.channels.voice), + connection: null, + message: null, + audioPlayer: createAudioPlayer(), + station: station + }; + client.radio.set(guild.id, construct); + + try { + let voiceChannel = client.channels.cache.get(state.channels.voice); + const connection = + getVoiceConnection(guild.id) ?? + joinVoiceChannel({ + channelId: voiceChannel.id, + guildId: voiceChannel.guild.id, + adapterCreator: voiceChannel.guild.voiceAdapterCreator + }); + + construct.connection = connection; + let date = new Date(); + construct.startTime = date.getTime(); + + play(null, guild, client, url, Discord); + + client.datastore.checkEntry(guild.id); + construct.datastore = client.datastore.getEntry(guild.id); + + if (!construct.datastore.statistics[construct.station.name]) { + construct.datastore.statistics[construct.station.name] = {}; + construct.datastore.statistics[construct.station.name].time = 0; + construct.datastore.statistics[construct.station.name].used = 0; + client.datastore.updateEntry(guild, construct.datastore); + } + } catch (error) { + console.log(error); + } + }); + } +} + +async function play(interaction, guild, client, url, Discord) { + 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); + }); + + message.nowplayingDescription = client.messages.nowplayingDescription.replace("%radio.station.name%", radio.station.name); + message.nowplayingDescription = message.nowplayingDescription.replace("%radio.station.owner%", radio.station.owner); + message.nowplayingDescription = message.nowplayingDescription.replace("%client.funcs.msToTime(completed)%", ""); + message.nowplayingDescription = message.nowplayingDescription.replace("Owner: ", ""); + message.nowplayingDescription = message.nowplayingDescription.replace("**", ""); + message.nowplayingDescription = message.nowplayingDescription.replace("**", ""); + + const embed = new Discord.MessageEmbed() + .setTitle(client.user.username) + .setThumbnail("https://cdn.discordapp.com/emojis/" + client.messageEmojis["play"].replace(/[^0-9]+/g, '')) + .setColor(client.config.embedColor) + .addField(client.messages.nowplayingTitle, message.nowplayingDescription, true) + .setFooter(client.messages.footerText, "https://cdn.discordapp.com/emojis/" + client.messageEmojis["eximiabots"].replace(/[^0-9]+/g, '')); + + const buttons = new Discord.MessageActionRow() + .addComponents( + new Discord.MessageButton() + .setCustomId('list') + .setEmoji(client.messageEmojis["list"]) + .setStyle('SECONDARY') + ) + .addComponents( + new Discord.MessageButton() + .setCustomId('prev') + .setEmoji(client.messageEmojis["prev"]) + .setStyle('SECONDARY') + .setDisabled(true) + ) + .addComponents( + new Discord.MessageButton() + .setCustomId('stop') + .setEmoji(client.messageEmojis["stop"]) + .setStyle('SECONDARY') + ) + .addComponents( + new Discord.MessageButton() + .setCustomId('next') + .setEmoji(client.messageEmojis["next"]) + .setStyle('SECONDARY') + .setDisabled(true) + ) + .addComponents( + new Discord.MessageButton() + .setCustomId('statistics') + .setEmoji(client.messageEmojis["statistics"]) + .setStyle('SECONDARY') + ); + + if(!radio.message){ + radio.message = await radio.textChannel.send({ embeds: [embed], components: [buttons] }); + } else { + radio.message.edit({ embeds: [embed], components: [buttons] }); + } + + message.play = client.messages.play.replace("%radio.station.name%", radio.station.name); + +} + +function searchStation(key, client) { + if (client.stations === null) return false; + let foundStations = []; + if (!key) return false; + if (key == "radio") return false; + + client.stations + .filter( + x => x.name.toUpperCase().includes(key.toUpperCase()) || x === key + ) + .forEach(x => + foundStations.push({ station: x, name: x.name, probability: 100 }) + ); + + 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; +} \ No newline at end of file diff --git a/src/config.js b/src/config.js index 41089fe..2aa884d 100644 --- a/src/config.js +++ b/src/config.js @@ -20,7 +20,6 @@ module.exports = { hostedBy: "[Warén Group](https://waren.io)", //Settings - prefix: process.env.RADIOX_PREFIX || "rx-", version: process.env.RADIOX_VERSION || process.env.npm_package_version, debug: process.env.DEBUG_MODE || false