Added cron job, updated webserver, added retry functionality

This commit is contained in:
Myx
2023-10-13 02:42:04 +02:00
parent e3f9dc2899
commit d25d2c8f54
7 changed files with 219 additions and 31 deletions

72
bot.ts
View File

@@ -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,21 +34,31 @@ 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 =>
@@ -51,13 +66,23 @@ async function joinRandomChannel() {
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()}`
);
}

13
client/readme.md Normal file
View File

@@ -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.

View File

@@ -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,6 +91,9 @@ 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);

View File

@@ -23,6 +23,13 @@
<button class="button expanded" type="submit">Upload</button>
</div>
</form>
<div class="callout">
<h3>Uploaded Files</h3>
<div style="height:200px;overflow:auto;">
<ul id="fileList" class="vertical menu"></ul>
</div>
</div>
</div>
</div>
</div>

View File

@@ -30,6 +30,29 @@ app.post('/upload', upload.single('myFile'), async (req, res) => {
res.send('File uploaded successfully.');
});
app.get('/sounds', (_req, res) => {
const fs = require('fs');
const directoryPath = path.join(__dirname, '../sounds');
fs.readdir(directoryPath, function (err: any, files: any[]) {
if (err) {
return console.log(LoggerColors.Red, 'Unable to scan directory: ' + err);
}
res.send(files);
});
});
app.delete('/sounds/:filename', (req, res) => {
const fs = require('fs');
const directoryPath = path.join(__dirname, '../sounds');
const filePath = directoryPath + '/' + req.params.filename;
fs.unlink(filePath, (err: any) => {
if (err) {
return console.log(LoggerColors.Red, 'Unable to delete file: ' + err);
}
res.send('File deleted successfully.');
});
});
app.use(express.static(path.join(__dirname, "web")));
export function startServer() {

61
package-lock.json generated
View File

@@ -16,13 +16,14 @@
"dotenv": "~16.3.1",
"express": "~4.18.2",
"multer": "~1.4.5-lts.1",
"node-schedule": "^2.1.1",
"sodium": "~3.0.2",
"ts-dotenv": "^0.9.1",
"ts-node": "~10.9.1",
"typescript": "~5.2.2"
},
"devDependencies": {
"@types/express": "~4.17.18"
"@types/express": "~4.17.18",
"@types/node-schedule": "^2.1.1"
}
},
"node_modules/@cspotcode/source-map-support": {
@@ -268,6 +269,15 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.2.tgz",
"integrity": "sha512-Vvycsc9FQdwhxE3y3DzeIxuEJbWGDsnrxvMADzTDF/lcdR9/K+AQIeAghTQsHtotg/q0j3WEOYS/jQgSdWue3w=="
},
"node_modules/@types/node-schedule": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@types/node-schedule/-/node-schedule-2.1.1.tgz",
"integrity": "sha512-FaqkbBizA+DinA0XWtAhdbEXykUkkqzBWT4BSnhn71z9C+vvcDgNcHvTP59nBhMg3o39E/ZY8zB/AQ6/HGuRag==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/qs": {
"version": "6.9.8",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz",
@@ -475,6 +485,17 @@
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="
},
"node_modules/cron-parser": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz",
"integrity": "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==",
"dependencies": {
"luxon": "^3.2.1"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -756,6 +777,19 @@
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
"integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw=="
},
"node_modules/long-timeout": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz",
"integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w=="
},
"node_modules/luxon": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.3.tgz",
"integrity": "sha512-tFWBiv3h7z+T/tDaoxA8rqTxy1CHV6gHS//QdaH4pulbq/JuBSGgQspQQqcgnwdAx6pNI7cmvz5Sv/addzHmUg==",
"engines": {
"node": ">=12"
}
},
"node_modules/magic-bytes.js": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.5.0.tgz",
@@ -871,6 +905,19 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz",
"integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA=="
},
"node_modules/node-schedule": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.1.tgz",
"integrity": "sha512-OXdegQq03OmXEjt2hZP33W2YPs/E5BcFQks46+G2gAxs4gHOIVD1u7EqlYLYSKsaIpyKCK9Gbk0ta1/gjRSMRQ==",
"dependencies": {
"cron-parser": "^4.2.0",
"long-timeout": "0.1.1",
"sorted-array-functions": "^1.3.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -1101,6 +1148,11 @@
"node-addon-api": "*"
}
},
"node_modules/sorted-array-functions": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz",
"integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA=="
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@@ -1138,11 +1190,6 @@
"node": ">=0.6"
}
},
"node_modules/ts-dotenv": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/ts-dotenv/-/ts-dotenv-0.9.1.tgz",
"integrity": "sha512-oTTXmjSMkCIK8d+uKbQnHh7JS9zEYGn3pq7nE0qHVERq+6Mx9wZKcXVAT+7X0qhOWpRz10dqrJCFFcKwunYz5w=="
},
"node_modules/ts-mixer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.3.tgz",

View File

@@ -4,7 +4,7 @@
"description": "Discord bot randomly plays sounds",
"scripts": {
"start": "ts-node bot.ts",
"create": "echo TOKEN=MY-API-TOKEN > .env",
"create": "node -e \"require('fs').writeFileSync('.env', 'TOKEN=MY-API-TOKEN\\nINTERVALMIN_MINUTES=10\\nINTERVALMAX_HOURS=6\\nVOICECHANNELRETRIES=12')\"",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "SocksOnHead",
@@ -17,11 +17,14 @@
"dotenv": "~16.3.1",
"express": "~4.18.2",
"multer": "~1.4.5-lts.1",
"node-schedule": "~2.1.1",
"libsodium-wrappers": "~0.7.13",
"sodium": "~3.0.2",
"ts-node": "~10.9.1",
"typescript": "~5.2.2"
},
"devDependencies": {
"@types/express": "~4.17.18"
"@types/express": "~4.17.18",
"@types/node-schedule": "^2.1.1"
}
}