diff --git a/bot.ts b/bot.ts index d0f026f..23c16af 100644 --- a/bot.ts +++ b/bot.ts @@ -9,10 +9,15 @@ import { import { ChannelType, Client, GatewayIntentBits } from 'discord.js'; import * as dotenv from 'dotenv'; import * as fileSystem from 'fs'; -import { startServer } from './client/webserver'; +import { startServer as startWebServer } from './client/webserver'; +import * as schedule from 'node-schedule'; dotenv.config(); +const minTime = parseInt(process.env.INTERVALMIN_MINUTES!, 10); // Minimum interval in minutes +const maxTime = convertHoursToMinutes(parseInt(process.env.INTERVALMAX_HOURS!, 10)); // Maximum interval in minutes +const voiceChannelRetries = parseInt(process.env.VOICECHANNELRETRIES!, 10); // Number of retries to find a voice channel with members in it + const token = process.env.TOKEN; const soundsDir = './sounds/'; const client = new Client({ @@ -29,35 +34,55 @@ export class LoggerColors { public static readonly Green = "\x1b[32m%s\x1b[0m"; public static readonly Yellow = "\x1b[33m%s\x1b[0m"; public static readonly Cyan = "\x1b[36m%s\x1b[0m"; + public static readonly Red = "\x1b[31m%s\x1b[0m"; + public static readonly Teal = "\x1b[35m%s\x1b[0m"; } client.login(token); -client.on('ready', () => { - +client.on('ready', async () => { console.log(LoggerColors.Green, `Add to server by: https://discord.com/oauth2/authorize?client_id=${client.application?.id}&permissions=70379584&scope=bot`); - startServer(); console.log(`Logged in as ${client.user?.tag}!`); - joinRandomChannel(); + + joinRandomChannel(voiceChannelRetries); + startWebServer(); }); -async function joinRandomChannel() { - if (!client.guilds.cache.size) +/** + * Joins a random voice channel in a random guild and plays a random sound file. + * @param retries - The number of retries to attempt if no voice channels are found. + * @returns void + */ +async function joinRandomChannel(retries = 12) { + if (!client.guilds.cache.size) { + console.log(LoggerColors.Red, 'No guilds found'); + scheduleNextJoin(); return; - + } + const guild = client.guilds.cache.random(); const voiceChannels = guild?.channels.cache.filter(channel => channel.type === ChannelType.GuildVoice && channel.members.size > 0 ); - if (!voiceChannels?.size) + if (!voiceChannels?.size) { + if (retries > 0) { + console.log(LoggerColors.Yellow, `No voice channels found, retrying in 5 seconds... (${retries} retries left)`); + setTimeout(() => joinRandomChannel(retries - 1), 5000); // Wait for 5 seconds before retrying + } + + if(retries === 0) { + console.log(LoggerColors.Red, 'No voice channels found'); + scheduleNextJoin(); + } return; + } const voiceChannel = voiceChannels.random(); - + try { - const connection = await joinVoiceChannel({ + const connection = joinVoiceChannel({ channelId: voiceChannel!.id, guildId: voiceChannel!.guild.id, adapterCreator: voiceChannel!.guild.voiceAdapterCreator, @@ -69,7 +94,7 @@ async function joinRandomChannel() { const resource = createAudioResource(soundFile); const player = createAudioPlayer(); - console.log(LoggerColors.Yellow, `Playing ${soundFile} in ${voiceChannel?.name}...`); + console.log(LoggerColors.Teal, `Playing ${soundFile} in ${voiceChannel?.name}...`); player.play(resource); connection.subscribe(player); @@ -80,17 +105,30 @@ async function joinRandomChannel() { console.error(error); } - const waitTime = Math.floor(Math.random() * (43200000 - 10000 + 1)) + 10000; - log(waitTime); - setTimeout(joinRandomChannel, waitTime); + scheduleNextJoin(); +} + +function scheduleNextJoin(){ + const randomInterval = Math.floor(Math.random() * (maxTime - minTime + 1)) + minTime; + log(randomInterval); + + schedule.scheduleJob(`*/${randomInterval} * * * *`, function(){ + joinRandomChannel(); + }); +} + +function convertHoursToMinutes(hours: number){ + return hours * 60; } function log(waitTime: number){ + const currentTime = new Date(); + const nextJoinTime = new Date(currentTime.getTime() + waitTime * 60 * 1000); // Convert waitTime from minutes to milliseconds + console.log( LoggerColors.Cyan, ` - Wait time: ${(waitTime / 1000 / 60 > 60) ? (waitTime / 1000 / 60 / 60 + ' hours') : (waitTime / 1000 / 60 + ' minutes')}, - Current time: ${new Date().toLocaleTimeString()}, - Next join time: ${new Date(Date.now() + waitTime) - .toLocaleTimeString()}` + Wait time: ${waitTime} minutes, + Current time: ${currentTime.toLocaleTimeString()}, + Next join time: ${nextJoinTime.toLocaleTimeString()}` ); } \ No newline at end of file diff --git a/client/readme.md b/client/readme.md new file mode 100644 index 0000000..678a4e5 --- /dev/null +++ b/client/readme.md @@ -0,0 +1,13 @@ +### Webserver +A simple webserver for managing sounds played by the bot. + +``I havent paid much attention to this and it can be refactored, rewritten and just more structured. I didn't feel like i bothered too much so i gave it minimal attention and left it at "I just works" stage.`` + + +//Feels weird to write basic js in the browser since it was so long ago.. + + +## How it works, +the bot should give you an address on startup, but if you miss it, it should be http://localhost:8080 if you host it somewhere its the ip-address of the server instead of localhost. + +i tested ``foundation`` because the sake of it for the design, i dont think i will use it again on a project. \ No newline at end of file diff --git a/client/web/client.js b/client/web/client.js index 9e24010..9347f11 100644 --- a/client/web/client.js +++ b/client/web/client.js @@ -1,3 +1,57 @@ +function loadFiles() { + // Fetch the JSON data from the /sounds endpoint + fetch('/sounds') + .then(response => response.json()) + .then(data => { + // Get the fileList element + const fileList = document.getElementById('fileList'); + + // Clear the current list + fileList.innerHTML = ''; + + // Add each file to the list + data.forEach(file => { + // Create a new list item + const li = document.createElement('li'); + li.className = 'grid-x'; + + // Create a div for the file name and add it to the list item + const fileNameDiv = document.createElement('div'); + fileNameDiv.className = 'cell auto'; + fileNameDiv.textContent = file; + li.appendChild(fileNameDiv); + + // Create a div for the trash icon and add it to the list item + const trashIconDiv = document.createElement('div'); + trashIconDiv.className = 'cell shrink'; + trashIconDiv.style.cursor = 'pointer'; + trashIconDiv.textContent = '🗑️'; + li.appendChild(trashIconDiv); + + // Attach a click event listener to the trash icon div + trashIconDiv.addEventListener('click', () => { + // Send a DELETE request to the server to remove the file + fetch('/sounds/' + file, { method: 'DELETE' }) + .then(response => response.text()) + .then(message => { + console.log(message); + + // Remove the list item from the fileList element + fileList.removeChild(li); + }) + .catch(error => console.error('Error:', error)); + }); + + // Add the list item to the fileList element + fileList.appendChild(li); + }); + }) + .catch(error => console.error('Error:', error)); +} + +// Call loadFiles when the script is loaded +loadFiles(); + document.getElementById('uploadForm').addEventListener('submit', function(event) { event.preventDefault(); @@ -37,10 +91,13 @@ document.getElementById('uploadForm').addEventListener('submit', function(event) .then(data => { console.log(data); alert('File uploaded successfully.'); + + // Call loadFiles again to update the file list + loadFiles(); }) .catch(error => { console.error(error); alert('An error occurred while uploading the file.'); }); }); -}); \ No newline at end of file +}); diff --git a/client/web/index.html b/client/web/index.html index e45c034..60a8383 100644 --- a/client/web/index.html +++ b/client/web/index.html @@ -23,10 +23,17 @@ + +