mirror of
https://github.com/Myxelium/RandomMemerBot.git
synced 2026-04-09 08:59:39 +00:00
Added cron job, updated webserver, added retry functionality
This commit is contained in:
72
bot.ts
72
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,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
13
client/readme.md
Normal 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.
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
61
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user