mirror of
https://github.com/Myxelium/RandomMemerBot.git
synced 2026-04-09 08:59:39 +00:00
Larger refactoring and added avoidlist
This commit is contained in:
3
avoid-list.json
Normal file
3
avoid-list.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"avoidUsers": []
|
||||||
|
}
|
||||||
130
bot.ts
130
bot.ts
@@ -14,11 +14,17 @@ import {
|
|||||||
VoiceConnectionStatus,
|
VoiceConnectionStatus,
|
||||||
VoiceConnection
|
VoiceConnection
|
||||||
} from '@discordjs/voice';
|
} from '@discordjs/voice';
|
||||||
import { ChannelType, Client, GatewayIntentBits, GuildBasedChannel } from 'discord.js';
|
import { ChannelType, Collection, GuildBasedChannel, Snowflake, VoiceChannel, VoiceState } from 'discord.js';
|
||||||
import * as dotenv from 'dotenv';
|
import * as dotenv from 'dotenv';
|
||||||
import * as fileSystem from 'fs';
|
|
||||||
import { startServer as startWebServer } from './client/webserver';
|
import { startServer as startWebServer } from './client/webserver';
|
||||||
import * as schedule from 'node-schedule';
|
import * as schedule from 'node-schedule';
|
||||||
|
import { loadAvoidList } from './helpers/load-avoid-list';
|
||||||
|
import { LoggerColors } from './helpers/logger-colors';
|
||||||
|
import { getRandomSoundFilePath } from './helpers/get-random-sound-file-path';
|
||||||
|
import { logger } from './helpers/logger';
|
||||||
|
import { SetupDiscordCLient } from './helpers/setup-discord-client';
|
||||||
|
import { convertHoursToMinutes, dateToString } from './helpers/converters';
|
||||||
|
import { AvoidList } from './models/avoid-list';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
@@ -26,28 +32,7 @@ export var nextPlayBackTime: string = ''; // Export so it can be used in the web
|
|||||||
const minTimeInterval = parseInt(process.env.INTERVALMIN_MINUTES!, 10); // Minimum interval in minutes.
|
const minTimeInterval = parseInt(process.env.INTERVALMIN_MINUTES!, 10); // Minimum interval in minutes.
|
||||||
const maxTimeInterval = convertHoursToMinutes(parseFloat(process.env.INTERVALMAX_HOURS!)); // Maximum interval in minutes.
|
const maxTimeInterval = convertHoursToMinutes(parseFloat(process.env.INTERVALMAX_HOURS!)); // Maximum interval in minutes.
|
||||||
const voiceChannelRetries = parseInt(process.env.VOICECHANNELRETRIES!, 10); // Number of retries to find a voice channel with members in it.
|
const voiceChannelRetries = parseInt(process.env.VOICECHANNELRETRIES!, 10); // Number of retries to find a voice channel with members in it.
|
||||||
|
const discordClient = SetupDiscordCLient();
|
||||||
const discordApplicationToken = process.env.TOKEN; // Discord bot token from .env file (required) More info: https://discordgsm.com/guide/how-to-get-a-discord-bot-token
|
|
||||||
const soundsDirectory = './sounds/';
|
|
||||||
const discordClient = new Client({
|
|
||||||
intents: [
|
|
||||||
GatewayIntentBits.Guilds,
|
|
||||||
GatewayIntentBits.GuildMessages,
|
|
||||||
GatewayIntentBits.MessageContent,
|
|
||||||
GatewayIntentBits.GuildVoiceStates,
|
|
||||||
GatewayIntentBits.GuildIntegrations,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
export class LoggerColors {
|
|
||||||
public static readonly Green: string = "\x1b[32m%s\x1b[0m";
|
|
||||||
public static readonly Yellow: string = "\x1b[33m%s\x1b[0m";
|
|
||||||
public static readonly Cyan: string = "\x1b[36m%s\x1b[0m";
|
|
||||||
public static readonly Red: string = "\x1b[31m%s\x1b[0m";
|
|
||||||
public static readonly Teal: string = "\x1b[35m%s\x1b[0m";
|
|
||||||
}
|
|
||||||
|
|
||||||
discordClient.login(discordApplicationToken);
|
|
||||||
|
|
||||||
discordClient.on('ready', async () => {
|
discordClient.on('ready', async () => {
|
||||||
console.log(LoggerColors.Green, `Add to server by: https://discord.com/oauth2/authorize?client_id=${discordClient.application?.id}&permissions=70379584&scope=bot`);
|
console.log(LoggerColors.Green, `Add to server by: https://discord.com/oauth2/authorize?client_id=${discordClient.application?.id}&permissions=70379584&scope=bot`);
|
||||||
@@ -56,7 +41,7 @@ discordClient.on('ready', async () => {
|
|||||||
joinRandomChannel(voiceChannelRetries);
|
joinRandomChannel(voiceChannelRetries);
|
||||||
startWebServer();
|
startWebServer();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Joins a random voice channel in a random guild and plays a random sound file.
|
* 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.
|
* @param retries - The number of retries to attempt if no voice channels are found.
|
||||||
@@ -89,29 +74,31 @@ export async function joinRandomChannel(retries = 12) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const randomlyPickedVoiceChannel = accessableVoiceChannels.random();
|
const randomlyPickedVoiceChannel = accessableVoiceChannels.random();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Join the voice channel
|
// Join the voice channel
|
||||||
const voiceChannelConnection = joinVoiceChannel({
|
if(isUserFromAvoidListNotInVoiceChannel(randomlyPickedVoiceChannel!)) {
|
||||||
channelId: randomlyPickedVoiceChannel!.id,
|
|
||||||
guildId: randomlyPickedVoiceChannel!.guild.id,
|
const voiceChannelConnection = joinVoiceChannel({
|
||||||
adapterCreator: randomlyPickedVoiceChannel!.guild.voiceAdapterCreator,
|
channelId: randomlyPickedVoiceChannel!.id,
|
||||||
});
|
guildId: randomlyPickedVoiceChannel!.guild.id,
|
||||||
|
adapterCreator: randomlyPickedVoiceChannel!.guild.voiceAdapterCreator,
|
||||||
|
});
|
||||||
|
|
||||||
await entersState(voiceChannelConnection, VoiceConnectionStatus.Ready, 30e3);
|
await entersState(voiceChannelConnection, VoiceConnectionStatus.Ready, 30e3);
|
||||||
const soundFilePath = getRandomSoundFilePath();
|
const soundFilePath = getRandomSoundFilePath();
|
||||||
|
|
||||||
if(!soundFilePath) {
|
if(!soundFilePath) {
|
||||||
console.log(LoggerColors.Red, 'No sound files found');
|
console.log(LoggerColors.Red, 'No sound files found');
|
||||||
scheduleNextJoin();
|
scheduleNextJoin();
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await playSoundFile(
|
||||||
|
soundFilePath,
|
||||||
|
randomlyPickedVoiceChannel,
|
||||||
|
voiceChannelConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
await playSoundFile(
|
|
||||||
soundFilePath,
|
|
||||||
randomlyPickedVoiceChannel,
|
|
||||||
voiceChannelConnection);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
}
|
}
|
||||||
@@ -143,15 +130,6 @@ async function playSoundFile(
|
|||||||
voiceChannelConnection.destroy();
|
voiceChannelConnection.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a random sound file from the sounds directory.
|
|
||||||
* @returns string - The path to a random sound file.
|
|
||||||
*/
|
|
||||||
function getRandomSoundFilePath(): string {
|
|
||||||
const allSoundFilesAsArray = fileSystem.readdirSync(soundsDirectory).filter(file => file.endsWith('.mp3'));
|
|
||||||
return soundsDirectory + allSoundFilesAsArray[Math.floor(Math.random() * allSoundFilesAsArray.length)];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedules the next join to a random channel. Using a random interval between minTime and maxTime.
|
* Schedules the next join to a random channel. Using a random interval between minTime and maxTime.
|
||||||
* It clears the previous schedule before scheduling the next join, to avoid multiple schedules.
|
* It clears the previous schedule before scheduling the next join, to avoid multiple schedules.
|
||||||
@@ -172,36 +150,34 @@ function scheduleNextJoin(): void {
|
|||||||
let nextPlaybackDate = schedule.scheduledJobs[jobName].nextInvocation();
|
let nextPlaybackDate = schedule.scheduledJobs[jobName].nextInvocation();
|
||||||
|
|
||||||
nextPlayBackTime = dateToString(nextPlaybackDate) ?? '';
|
nextPlayBackTime = dateToString(nextPlaybackDate) ?? '';
|
||||||
log(nextPlaybackDate, hours, minutes);
|
logger(nextPlaybackDate, hours, minutes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertHoursToMinutes(hours: number): number {
|
|
||||||
return hours * 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
function isUserFromAvoidListNotInVoiceChannel(channel: GuildBasedChannel): boolean {
|
||||||
* Logs the wait time, current time, next join time, and cron schedule in the console.
|
const avoidList: AvoidList = loadAvoidList();
|
||||||
* @param waitTime - The time to wait until the next join.
|
const voiceChannel = channel as VoiceChannel;
|
||||||
* @param hour - The hour of the cron schedule.
|
const voiceStates: Collection<Snowflake, VoiceState> = voiceChannel.guild.voiceStates.cache;
|
||||||
* @param minute - The minute of the cron schedule.
|
const membersInVoiceChannel = voiceStates.filter(voiceState => voiceState.channelId === voiceChannel.id);
|
||||||
*/
|
|
||||||
function log(
|
|
||||||
waitTime: Date,
|
|
||||||
hour: number,
|
|
||||||
minute: number
|
|
||||||
){
|
|
||||||
const currentTime = new Date();
|
|
||||||
|
|
||||||
console.log(
|
if(avoidList.avoidUsers.length === 0)
|
||||||
LoggerColors.Cyan, `
|
return true;
|
||||||
Wait time: ${(waitTime.getTime() - currentTime.getTime()) / 60000} minutes,
|
|
||||||
Current time: ${dateToString(currentTime)},
|
if (channel.type !== ChannelType.GuildVoice)
|
||||||
Next join time: ${dateToString(waitTime)},
|
return true;
|
||||||
Cron: ${Math.floor(minute)} ${Math.floor(hour) == 0 ? '*' : Math.floor(hour) } * * *`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function dateToString(date: Date): string {
|
// Check if any member from the avoid list is in the voice channel
|
||||||
return date.toLocaleString('sv-SE', { timeZone: 'Europe/Stockholm' });
|
for (const voiceState of membersInVoiceChannel.values()) {
|
||||||
|
if(!voiceState.member)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (avoidList.avoidUsers.includes(voiceState.member.user.username)) {
|
||||||
|
console.log(LoggerColors.Yellow, `${voiceState.member.user.username} is in the avoid list, skipping...`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No member from the avoid list is in the voice channel
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
20
client/handlers/addUserToAvoidList.ts
Normal file
20
client/handlers/addUserToAvoidList.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import * as fileSystem from 'fs';
|
||||||
|
import express from 'express';
|
||||||
|
import { loadAvoidList } from '../../helpers/load-avoid-list';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a user to the avoid list.
|
||||||
|
* @param user - The user to add to the avoid list.
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function AddUserToAvoidList(response: express.Response, request: express.Request) {
|
||||||
|
const avoidList = loadAvoidList();
|
||||||
|
|
||||||
|
if (avoidList.avoidUsers.includes(request.body.user)) {
|
||||||
|
response.send('User already in avoid list.');
|
||||||
|
} else {
|
||||||
|
avoidList.avoidUsers.push(request.body.user);
|
||||||
|
fileSystem.writeFileSync('avoid-list.json', JSON.stringify(avoidList, null, "\t"));
|
||||||
|
response.send('User added to avoid list.');
|
||||||
|
}
|
||||||
|
}
|
||||||
19
client/handlers/deleteSoundFile.ts
Normal file
19
client/handlers/deleteSoundFile.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import path from 'path';
|
||||||
|
import * as fileSystem from 'fs';
|
||||||
|
import { LoggerColors } from '../../helpers/logger-colors';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a file from the sounds folder by filename
|
||||||
|
*/
|
||||||
|
export function DeleteSoundFile(response: express.Response, request: express.Request) {
|
||||||
|
const directoryPath = path.join(__dirname, '../../sounds');
|
||||||
|
const filePath = directoryPath + '/' + request.params.filename;
|
||||||
|
|
||||||
|
fileSystem.unlink(filePath, (err: any) => {
|
||||||
|
if (err) {
|
||||||
|
return console.log(LoggerColors.Red, 'Unable to delete file: ' + err);
|
||||||
|
}
|
||||||
|
response.send('File deleted successfully.');
|
||||||
|
});
|
||||||
|
}
|
||||||
20
client/handlers/deleteUserFromAvoidList.ts
Normal file
20
client/handlers/deleteUserFromAvoidList.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import fs from 'fs';
|
||||||
|
import { loadAvoidList } from '../../helpers/load-avoid-list';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a user from the avoid list.
|
||||||
|
* @param user - The user to remove from the avoid list.
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function DeleteUserFromAvoidList(response: express.Response, request: express.Request) {
|
||||||
|
const avoidList = loadAvoidList();
|
||||||
|
|
||||||
|
if (avoidList.avoidUsers.includes(request.params.user)) {
|
||||||
|
avoidList.avoidUsers = avoidList.avoidUsers.filter(user => user !== request.params.user);
|
||||||
|
fs.writeFileSync('avoid-list.json', JSON.stringify(avoidList, null, "\t"));
|
||||||
|
response.send('User removed from avoid list.');
|
||||||
|
} else {
|
||||||
|
response.send('User not in avoid list.');
|
||||||
|
}
|
||||||
|
}
|
||||||
18
client/handlers/getSoundFiles.ts
Normal file
18
client/handlers/getSoundFiles.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import path from 'path';
|
||||||
|
import * as fileSystem from 'fs';
|
||||||
|
import { LoggerColors } from '../../helpers/logger-colors';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all sound files in the sounds directory.
|
||||||
|
* @returns string[] - An array of all sound files in the sounds directory.
|
||||||
|
*/
|
||||||
|
export function GetSoundFiles(response: express.Response) {
|
||||||
|
const directoryPath = path.join(__dirname, '../../sounds');
|
||||||
|
fileSystem.readdir(directoryPath, function (error: any, files: any[]) {
|
||||||
|
if (error) {
|
||||||
|
return console.log(LoggerColors.Red, 'Unable to scan directory: ' + error);
|
||||||
|
}
|
||||||
|
response.send(files);
|
||||||
|
});
|
||||||
|
}
|
||||||
15
client/handlers/index.ts
Normal file
15
client/handlers/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { AddUserToAvoidList } from './addUserToAvoidList';
|
||||||
|
import { DeleteSoundFile } from './deleteSoundFile';
|
||||||
|
import { GetSoundFiles } from './getSoundFiles';
|
||||||
|
import { UploadYouTubeFile } from './uploadYouTubeFile';
|
||||||
|
import { DeleteUserFromAvoidList } from './deleteUserFromAvoidList';
|
||||||
|
import { JoinChannel } from './joinChannel';
|
||||||
|
|
||||||
|
export class Handlers {
|
||||||
|
public static AddUserToAvoidList = AddUserToAvoidList;
|
||||||
|
public static DeleteSoundFile = DeleteSoundFile;
|
||||||
|
public static GetSoundFiles = GetSoundFiles;
|
||||||
|
public static UploadYouTubeFile = UploadYouTubeFile;
|
||||||
|
public static DeleteUserFromAvoidList = DeleteUserFromAvoidList;
|
||||||
|
public static JoinChannel = JoinChannel;
|
||||||
|
}
|
||||||
11
client/handlers/joinChannel.ts
Normal file
11
client/handlers/joinChannel.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { joinRandomChannel } from "../../bot";
|
||||||
|
import express from 'express';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Joins a random channel and plays a random sound file.
|
||||||
|
* @returns void
|
||||||
|
*/
|
||||||
|
export function JoinChannel(response: express.Response) {
|
||||||
|
joinRandomChannel();
|
||||||
|
response.send('Joining random channel.');
|
||||||
|
}
|
||||||
60
client/handlers/uploadYouTubeFile.ts
Normal file
60
client/handlers/uploadYouTubeFile.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import path from 'path';
|
||||||
|
import ytdl from 'ytdl-core';
|
||||||
|
import * as fileSystem from 'fs';
|
||||||
|
import ffmpeg from 'fluent-ffmpeg';
|
||||||
|
import { generateFileName } from '../../helpers/generate-file-name';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a YouTube video as an mp3 file to the sounds folder.
|
||||||
|
* The video must be shorter than 10 seconds.
|
||||||
|
* @Body url - The YouTube video url.
|
||||||
|
*/
|
||||||
|
export async function UploadYouTubeFile(response: express.Response, request: express.Request) {
|
||||||
|
const url = request.body.url;
|
||||||
|
|
||||||
|
if (ytdl.validateURL(url)) {
|
||||||
|
const info = await ytdl.getInfo(url);
|
||||||
|
// remove special characters from the title and white spaces
|
||||||
|
const title = info.videoDetails.title.replace(/[^a-zA-Z ]/g, "").replace(/\s+/g, '-').toLowerCase();
|
||||||
|
|
||||||
|
// Create a temporary directory to store the uploaded file so validation can be done
|
||||||
|
const tempDir = fileSystem.mkdtempSync('temp');
|
||||||
|
const outputFilePath = path.resolve(tempDir, generateFileName(title));
|
||||||
|
|
||||||
|
const videoReadableStream = ytdl(url, { filter: 'audioonly' });
|
||||||
|
const fileWritableStream = fileSystem.createWriteStream(outputFilePath);
|
||||||
|
|
||||||
|
videoReadableStream.pipe(fileWritableStream);
|
||||||
|
|
||||||
|
fileWritableStream.on('finish', () => {
|
||||||
|
ffmpeg.ffprobe(outputFilePath, function (err, metadata) {
|
||||||
|
if (err) {
|
||||||
|
fileSystem.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
return response.status(500).send('Error occurred during processing.');
|
||||||
|
}
|
||||||
|
const duration = metadata.format.duration;
|
||||||
|
|
||||||
|
if (duration == undefined) {
|
||||||
|
fileSystem.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
return response.status(400).send('Something went wrong.');
|
||||||
|
}
|
||||||
|
if (duration > 10) {
|
||||||
|
fileSystem.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
return response.status(400).send('File is longer than 10 seconds.');
|
||||||
|
} else {
|
||||||
|
// Move the file from the temporary directory to its final destination
|
||||||
|
const finalFilePath = path.resolve(__dirname, '../../sounds/', generateFileName(title));
|
||||||
|
fileSystem.renameSync(outputFilePath, finalFilePath);
|
||||||
|
|
||||||
|
response.send('File uploaded successfully.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the temporary directory and its contents once done
|
||||||
|
fileSystem.rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
response.status(400).send('Invalid url provided.');
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
client/web/assets/favicon.ico
Normal file
BIN
client/web/assets/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
@@ -106,6 +106,7 @@ function loadFiles() {
|
|||||||
// Call loadFiles when the script is loaded
|
// Call loadFiles when the script is loaded
|
||||||
loadFiles();
|
loadFiles();
|
||||||
loadNextPlaybackTime();
|
loadNextPlaybackTime();
|
||||||
|
updateAvoidList();
|
||||||
|
|
||||||
document.getElementById('uploadForm').addEventListener('submit', function(event) {
|
document.getElementById('uploadForm').addEventListener('submit', function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -185,3 +186,79 @@ document.getElementById('uploadForm').addEventListener('submit', function(event)
|
|||||||
alert('Please select a file or paste a YouTube link.');
|
alert('Please select a file or paste a YouTube link.');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document.getElementById('avoidForm').addEventListener('submit', function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const user = document.getElementById('avoidUser').value;
|
||||||
|
|
||||||
|
fetch('/avoidlist', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ user }),
|
||||||
|
})
|
||||||
|
.then(response => response.text())
|
||||||
|
.then((data) => {
|
||||||
|
updateAvoidList();
|
||||||
|
document.getElementById('avoidUser').value = '';
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('removeUser').addEventListener('click', function() {
|
||||||
|
const user = document.getElementById('avoidUser').value;
|
||||||
|
|
||||||
|
fetch(`/avoidlist/${user}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
.then(_ => {
|
||||||
|
updateAvoidList();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateAvoidList() {
|
||||||
|
fetch('/avoidlist')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const avoidListElement = document.getElementById('avoidList');
|
||||||
|
|
||||||
|
// Clear the avoid list.
|
||||||
|
avoidListElement.innerHTML = '';
|
||||||
|
|
||||||
|
// Add each user in the avoid list to the UI.
|
||||||
|
data.avoidUsers.forEach(user => {
|
||||||
|
const listItem = document.createElement('li');
|
||||||
|
listItem.textContent = user;
|
||||||
|
listItem.className = 'list-group-item list-group-item-action d-flex justify-content-between align-items-center';
|
||||||
|
|
||||||
|
// Add a button to remove the user from the avoid list.
|
||||||
|
const removeButton = document.createElement('button');
|
||||||
|
removeButton.textContent = 'Remove';
|
||||||
|
removeButton.className = 'btn btn-success';
|
||||||
|
removeButton.addEventListener('click', function() {
|
||||||
|
fetch(`/avoidlist/${user}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
.then(_ => {
|
||||||
|
updateAvoidList();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
listItem.appendChild(removeButton);
|
||||||
|
avoidListElement.appendChild(listItem);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,46 +1,66 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>RandomMemer upload sounds</title>
|
<title>RandomMemer upload sounds</title>
|
||||||
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" rel="stylesheet">
|
||||||
</head>
|
<link rel="icon" href="assets/favicon.ico" type="image/x-icon"/>
|
||||||
<body class="bg-dark text-white">
|
</head>
|
||||||
<div class="container py-5">
|
<body class="bg-dark text-white">
|
||||||
<div class="row justify-content-center">
|
<div class="container py-5">
|
||||||
<div class="col-md-6">
|
<div class="row justify-content-center">
|
||||||
<img src="logo.svg" alt="Logo" class="img-fluid mx-auto d-block mb-5">
|
<div class="col-md-6">
|
||||||
<form id="uploadForm" enctype="multipart/form-data" class="mb-4 dropzone dz-clickable">
|
<img src="assets/logo.svg" alt="Logo" class="img-fluid mx-auto d-block mb-5">
|
||||||
<h3>Upload a sound</h3>
|
<form id="uploadForm" enctype="multipart/form-data" class="mb-4 dropzone dz-clickable">
|
||||||
<div class="form-group">
|
<h3>Upload a sound</h3>
|
||||||
<div class="input-group">
|
<div class="form-group">
|
||||||
<div class="custom-file">
|
<div class="input-group">
|
||||||
<input type="file" id="myFile" class="custom-file-input">
|
<div class="custom-file">
|
||||||
<label class="custom-file-label" for="myFile">Choose file</label>
|
<input type="file" id="myFile" class="custom-file-input">
|
||||||
|
<label class="custom-file-label" for="myFile">Choose file</label>
|
||||||
|
</div>
|
||||||
|
<input type="text" id="youtubeLink" class="form-control" placeholder="Paste YouTube link here">
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="youtubeLink" class="form-control" placeholder="Paste YouTube link here">
|
|
||||||
</div>
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary btn-block">Upload</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<button id="joinButton" class="btn btn-info btn-block">Trigger a join</button>
|
||||||
|
|
||||||
|
<div id="nextPlaybackTime" class="mb-4">
|
||||||
|
Endpoint not loading!?
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary btn-block">Upload</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<button id="joinButton" class="btn btn-info btn-block">Join</button>
|
<div>
|
||||||
|
<h3>Avoid list</h3>
|
||||||
|
<p>A list of users the bot should never be around</p>
|
||||||
|
<form id="avoidForm" class="mb-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<input type="text" id="avoidUser" class="form-control" placeholder="Enter user to avoid">
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-info btn-block">Add</button>
|
||||||
|
</form>
|
||||||
|
<ul id="avoidList" class="list-group"></ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="nextPlaybackTime" class="mb-4">
|
<hr class="my-4">
|
||||||
Endpoint not loading!?
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h3>Uploaded Files</h3>
|
<h3>Uploaded Files</h3>
|
||||||
<ul id="fileList" class="list-group"></ul>
|
<ul id="fileList" class="list-group"></ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Include Dropzone JS -->
|
<!-- Include Dropzone JS -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.0/dropzone.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.0/dropzone.js"></script>
|
||||||
<script src="client.js"></script>
|
<script src="scripts/avoid-list.js" type="module"></script>
|
||||||
</body>
|
<script src="scripts/avoid-form.js" type="module"></script>
|
||||||
|
<script src="scripts/upload.js" type="module"></script>
|
||||||
|
<script src="scripts/file-list.js" type="module"></script>
|
||||||
|
<script src="scripts/index.js" type="module"></script>
|
||||||
|
<script src="scripts/join.js" type="module"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
48
client/web/scripts/avoid-form.js
Normal file
48
client/web/scripts/avoid-form.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { updateAvoidList } from './avoid-list.js';
|
||||||
|
|
||||||
|
const avoidForm = document.getElementById('avoidForm');
|
||||||
|
if (avoidForm) {
|
||||||
|
avoidForm.addEventListener('submit', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const user = document.getElementById('avoidUser').value;
|
||||||
|
|
||||||
|
if(!user)
|
||||||
|
return;
|
||||||
|
|
||||||
|
fetch('/avoidlist', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
user
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(response => response.text())
|
||||||
|
.then((data) => {
|
||||||
|
updateAvoidList();
|
||||||
|
document.getElementById('avoidUser').value = '';
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const removeUser = document.getElementById('removeUser');
|
||||||
|
if(removeUser){
|
||||||
|
document.getElementById('removeUser').addEventListener('click', function () {
|
||||||
|
const user = document.getElementById('avoidUser').value;
|
||||||
|
|
||||||
|
fetch(`/avoidlist/${user}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
.then(_ => {
|
||||||
|
updateAvoidList();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
39
client/web/scripts/avoid-list.js
Normal file
39
client/web/scripts/avoid-list.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
export function updateAvoidList() {
|
||||||
|
fetch('/avoidlist')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
const avoidListElement = document.getElementById('avoidList');
|
||||||
|
|
||||||
|
// Clear the avoid list.
|
||||||
|
avoidListElement.innerHTML = '';
|
||||||
|
|
||||||
|
// Add each user in the avoid list to the UI.
|
||||||
|
data.avoidUsers.forEach(user => {
|
||||||
|
const listItem = document.createElement('li');
|
||||||
|
listItem.textContent = user;
|
||||||
|
listItem.className = 'list-group-item list-group-item-action d-flex justify-content-between align-items-center';
|
||||||
|
|
||||||
|
// Add a button to remove the user from the avoid list.
|
||||||
|
const removeButton = document.createElement('button');
|
||||||
|
removeButton.textContent = 'Remove';
|
||||||
|
removeButton.className = 'btn btn-success';
|
||||||
|
removeButton.addEventListener('click', function () {
|
||||||
|
fetch(`/avoidlist/${user}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
.then(_ => {
|
||||||
|
updateAvoidList();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
listItem.appendChild(removeButton);
|
||||||
|
avoidListElement.appendChild(listItem);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
67
client/web/scripts/file-list.js
Normal file
67
client/web/scripts/file-list.js
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
export 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 = 'list-group-item list-group-item-action d-flex justify-content-between align-items-center'
|
||||||
|
|
||||||
|
// Create a div for the file name and add it to the list item
|
||||||
|
const fileNameDiv = document.createElement('div');
|
||||||
|
fileNameDiv.textContent = file;
|
||||||
|
li.appendChild(fileNameDiv);
|
||||||
|
|
||||||
|
// Create a div for the icons and add it to the list item
|
||||||
|
const iconDiv = document.createElement('div');
|
||||||
|
li.appendChild(iconDiv);
|
||||||
|
|
||||||
|
// Create a div for the play icon and add it to the icon div
|
||||||
|
const playIconDiv = document.createElement('span');
|
||||||
|
playIconDiv.style.cursor = 'pointer';
|
||||||
|
playIconDiv.textContent = '▶️';
|
||||||
|
iconDiv.appendChild(playIconDiv);
|
||||||
|
|
||||||
|
// Attach a click event listener to the play icon div
|
||||||
|
playIconDiv.addEventListener('click', () => {
|
||||||
|
// Create a new audio object and play the file
|
||||||
|
let audio = new Audio('/sounds/' + file);
|
||||||
|
audio.play();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create a div for the trash icon and add it to the icon div
|
||||||
|
const trashIconDiv = document.createElement('span');
|
||||||
|
trashIconDiv.style.cursor = 'pointer';
|
||||||
|
trashIconDiv.textContent = '🗑️';
|
||||||
|
iconDiv.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));
|
||||||
|
}
|
||||||
8
client/web/scripts/index.js
Normal file
8
client/web/scripts/index.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { loadFiles } from './file-list.js';
|
||||||
|
import { updateAvoidList } from './avoid-list.js';
|
||||||
|
import { loadNextPlaybackTime } from './upload.js';
|
||||||
|
|
||||||
|
// Call loadFiles when the script is loaded
|
||||||
|
loadFiles();
|
||||||
|
loadNextPlaybackTime();
|
||||||
|
updateAvoidList();
|
||||||
18
client/web/scripts/join.js
Normal file
18
client/web/scripts/join.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
document.getElementById('joinButton').addEventListener('click', function() {
|
||||||
|
this.disabled = true;
|
||||||
|
|
||||||
|
fetch('/join')
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.disabled = false;
|
||||||
|
}, 40000);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error:', error);
|
||||||
|
setTimeout(() => {
|
||||||
|
this.disabled = false;
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
|
});
|
||||||
100
client/web/scripts/upload.js
Normal file
100
client/web/scripts/upload.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { loadFiles } from "./file-list.js";
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', (event) => {
|
||||||
|
document.querySelector('#myFile').addEventListener('change', function(e) {
|
||||||
|
var fileName = e.target.files[0].name;
|
||||||
|
var nextSibling = e.target.nextElementSibling
|
||||||
|
nextSibling.innerText = fileName
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export function loadNextPlaybackTime() {
|
||||||
|
fetch('/nextplaybacktime')
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(data => {
|
||||||
|
const nextPlaybackTime = document.getElementById('nextPlaybackTime');
|
||||||
|
nextPlaybackTime.textContent = `Playing next time: ${data}`;
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('uploadForm').addEventListener('submit', function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
var fileInput = document.getElementById('myFile');
|
||||||
|
var file = fileInput.files[0];
|
||||||
|
var youtubeLink = document.getElementById('youtubeLink').value;
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
var objectURL = URL.createObjectURL(file);
|
||||||
|
var audio = new Audio(objectURL);
|
||||||
|
|
||||||
|
audio.addEventListener('loadedmetadata', function () {
|
||||||
|
var duration = audio.duration;
|
||||||
|
console.log(duration);
|
||||||
|
|
||||||
|
if (duration > 10) {
|
||||||
|
alert('File is longer than 10 seconds.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.size > 1024 * 1024) {
|
||||||
|
alert('File is larger than 1MB.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file.name.split('.').pop().toLowerCase() !== 'mp3') {
|
||||||
|
alert('Only .mp3 files are allowed.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var formData = new FormData();
|
||||||
|
formData.append('myFile', file);
|
||||||
|
|
||||||
|
fetch('/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data);
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
alert('An error occurred while uploading the file.');
|
||||||
|
}).finally(() => {
|
||||||
|
fileInput.value = '';
|
||||||
|
loadFiles();
|
||||||
|
alert('File uploaded successfully.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (youtubeLink) {
|
||||||
|
console.log(youtubeLink);
|
||||||
|
fetch('/upload-youtube', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
url: youtubeLink
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(data => {
|
||||||
|
console.log(data);
|
||||||
|
// if response is not ok, alert
|
||||||
|
if (data !== 'ok') {
|
||||||
|
alert(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
alert('File uploaded successfully.');
|
||||||
|
loadFiles();
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error);
|
||||||
|
alert('An error occurred while uploading the file.');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
alert('Please select a file or paste a YouTube link.');
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
import { joinRandomChannel, nextPlayBackTime } from './../bot';
|
import { nextPlayBackTime } from './../bot';
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import multer, { diskStorage } from 'multer';
|
import multer, { diskStorage } from 'multer';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { LoggerColors } from '../bot';
|
|
||||||
import ytdl from 'ytdl-core';
|
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import bodyParser from 'body-parser';
|
import bodyParser from 'body-parser';
|
||||||
import ffmpeg from 'fluent-ffmpeg';
|
|
||||||
import https from 'https';
|
import https from 'https';
|
||||||
import ip from 'ip';
|
import ip from 'ip';
|
||||||
|
|
||||||
|
import { Handlers } from "./handlers/index"
|
||||||
|
import { loadAvoidList } from '../helpers/load-avoid-list';
|
||||||
|
import { LoggerColors } from '../helpers/logger-colors';
|
||||||
|
import { generateFileName } from '../helpers/generate-file-name';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const storage = diskStorage({
|
const storage = diskStorage({
|
||||||
destination: 'sounds/',
|
destination: 'sounds/',
|
||||||
@@ -48,58 +50,8 @@ app.post('/upload', upload.single('myFile'), async (req, res) => {
|
|||||||
res.send('File uploaded successfully.');
|
res.send('File uploaded successfully.');
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads a YouTube video as an mp3 file to the sounds folder.
|
|
||||||
* The video must be shorter than 10 seconds.
|
|
||||||
* @Body url - The YouTube video url.
|
|
||||||
*/
|
|
||||||
app.post('/upload-youtube', async (req, res) => {
|
app.post('/upload-youtube', async (req, res) => {
|
||||||
const url = req.body.url;
|
await Handlers.UploadYouTubeFile(res, req);
|
||||||
|
|
||||||
if (ytdl.validateURL(url)) {
|
|
||||||
const info = await ytdl.getInfo(url);
|
|
||||||
// remove special characters from the title and white spaces
|
|
||||||
const title = info.videoDetails.title.replace(/[^a-zA-Z ]/g, "").replace(/\s+/g, '-').toLowerCase();
|
|
||||||
|
|
||||||
// Create a temporary directory to store the uploaded file so validation can be done
|
|
||||||
const tempDir = fs.mkdtempSync('temp');
|
|
||||||
const outputFilePath = path.resolve(tempDir, generateFileName(title));
|
|
||||||
|
|
||||||
const videoReadableStream = ytdl(url, { filter: 'audioonly' });
|
|
||||||
const fileWritableStream = fs.createWriteStream(outputFilePath);
|
|
||||||
|
|
||||||
videoReadableStream.pipe(fileWritableStream);
|
|
||||||
|
|
||||||
fileWritableStream.on('finish', () => {
|
|
||||||
ffmpeg.ffprobe(outputFilePath, function(err, metadata) {
|
|
||||||
if (err) {
|
|
||||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
||||||
return res.status(500).send('Error occurred during processing.');
|
|
||||||
}
|
|
||||||
const duration = metadata.format.duration;
|
|
||||||
|
|
||||||
if (duration == undefined) {
|
|
||||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
||||||
return res.status(400).send('Something went wrong.');
|
|
||||||
}
|
|
||||||
if (duration > 10) {
|
|
||||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
||||||
return res.status(400).send('File is longer than 10 seconds.');
|
|
||||||
} else {
|
|
||||||
// Move the file from the temporary directory to its final destination
|
|
||||||
const finalFilePath = path.resolve(__dirname, '../sounds/', generateFileName(title));
|
|
||||||
fs.renameSync(outputFilePath, finalFilePath);
|
|
||||||
|
|
||||||
res.send('File uploaded successfully.');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the temporary directory and its contents once done
|
|
||||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.status(400).send('Invalid url provided.');
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,19 +61,8 @@ app.post('/upload-youtube', async (req, res) => {
|
|||||||
*/
|
*/
|
||||||
app.use('/sounds', express.static(path.join(__dirname, '../sounds')));
|
app.use('/sounds', express.static(path.join(__dirname, '../sounds')));
|
||||||
|
|
||||||
/**
|
app.get('/sounds', (_req, res: express.Response) => {
|
||||||
* Returns a list of all sound files in the sounds directory.
|
return Handlers.GetSoundFiles(res);
|
||||||
* @returns string[] - An array of all sound files in the sounds directory.
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -132,26 +73,26 @@ app.get('/nextplaybacktime', (_req, res) => {
|
|||||||
res.send(nextPlayBackTime);
|
res.send(nextPlayBackTime);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
app.delete('/sounds/:filename', (_req, res) => {
|
||||||
* Deletes a file from the sounds folder by filename
|
Handlers.DeleteSoundFile(res, _req);
|
||||||
*/
|
|
||||||
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")));
|
app.use(express.static(path.join(__dirname, "web")));
|
||||||
|
|
||||||
app.get('/join', (_req, res) => {
|
app.get('/join', (_req, res) => {
|
||||||
joinRandomChannel();
|
Handlers.JoinChannel(res);
|
||||||
res.send('Joining random channel.');
|
});
|
||||||
|
|
||||||
|
app.post('/avoidlist', (req, res) => {
|
||||||
|
Handlers.AddUserToAvoidList(res, req);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete('/avoidlist/:user', (req, res) => {
|
||||||
|
Handlers.DeleteUserFromAvoidList(res, req);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/avoidlist', (_req, res) => {
|
||||||
|
res.send(loadAvoidList());
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -182,22 +123,4 @@ export function startServer() {
|
|||||||
server.listen(port, () => {
|
server.listen(port, () => {
|
||||||
console.log(`Server started at ${ssl}://${ip.address()}:${port}`);
|
console.log(`Server started at ${ssl}://${ip.address()}:${port}`);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a random file name based on the provided name.
|
|
||||||
* @param name - The name to generate a file name for.
|
|
||||||
* @returns string - The generated file name.
|
|
||||||
*/
|
|
||||||
function generateFileName(name: string) {
|
|
||||||
const randomHex = [...Array(3)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
|
|
||||||
|
|
||||||
const formattedName = name
|
|
||||||
.replace(/\(.*?\)|\[.*?\]/g, '')
|
|
||||||
.split(' ')
|
|
||||||
.filter(word => /^[a-zA-Z0-9]/.test(word))
|
|
||||||
.join(' ')
|
|
||||||
.replace(/\s+/g, ' ');
|
|
||||||
|
|
||||||
return `${formattedName}-${randomHex}.mp3`;
|
|
||||||
}
|
|
||||||
7
helpers/converters.ts
Normal file
7
helpers/converters.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export function dateToString(date: Date): string {
|
||||||
|
return date.toLocaleString('sv-SE', { timeZone: 'Europe/Stockholm' });
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertHoursToMinutes(hours: number): number {
|
||||||
|
return hours * 60;
|
||||||
|
}
|
||||||
18
helpers/generate-file-name.ts
Normal file
18
helpers/generate-file-name.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Formats a name into a file name with random generated value at the end.
|
||||||
|
* @param name - The name to generate a file name for.
|
||||||
|
* @returns string - The generated file name.
|
||||||
|
*/
|
||||||
|
export function generateFileName(name: string): string {
|
||||||
|
const randomHex = [...Array(3)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
|
||||||
|
|
||||||
|
const formattedName = name
|
||||||
|
.replace(/\(.*?\)|\[.*?\]/g, '')
|
||||||
|
.split(' ')
|
||||||
|
.filter(word => /^[a-zA-Z0-9]/.test(word))
|
||||||
|
.join(' ')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.replace('.mp3', '');
|
||||||
|
|
||||||
|
return `${formattedName}-${randomHex}.mp3`;
|
||||||
|
}
|
||||||
11
helpers/get-random-sound-file-path.ts
Normal file
11
helpers/get-random-sound-file-path.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import * as fileSystem from 'fs';
|
||||||
|
|
||||||
|
export const soundsDirectory = './sounds/';
|
||||||
|
/**
|
||||||
|
* Returns a random sound file from the sounds directory.
|
||||||
|
* @returns string - The path to a random sound file.
|
||||||
|
*/
|
||||||
|
export function getRandomSoundFilePath(): string {
|
||||||
|
const allSoundFilesAsArray = fileSystem.readdirSync(soundsDirectory).filter(file => file.endsWith('.mp3'));
|
||||||
|
return soundsDirectory + allSoundFilesAsArray[Math.floor(Math.random() * allSoundFilesAsArray.length)];
|
||||||
|
}
|
||||||
10
helpers/load-avoid-list.ts
Normal file
10
helpers/load-avoid-list.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import * as fileSystem from 'fs';
|
||||||
|
import { AvoidList } from '../models/avoid-list';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of users that the bot should avoid being in same channel as.
|
||||||
|
* @returns avoidList
|
||||||
|
*/
|
||||||
|
export function loadAvoidList(): AvoidList {
|
||||||
|
return JSON.parse(fileSystem.readFileSync("avoid-list.json", 'utf-8'));
|
||||||
|
}
|
||||||
7
helpers/logger-colors.ts
Normal file
7
helpers/logger-colors.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export class LoggerColors {
|
||||||
|
public static readonly Green: string = "\x1b[32m%s\x1b[0m";
|
||||||
|
public static readonly Yellow: string = "\x1b[33m%s\x1b[0m";
|
||||||
|
public static readonly Cyan: string = "\x1b[36m%s\x1b[0m";
|
||||||
|
public static readonly Red: string = "\x1b[31m%s\x1b[0m";
|
||||||
|
public static readonly Teal: string = "\x1b[35m%s\x1b[0m";
|
||||||
|
}
|
||||||
24
helpers/logger.ts
Normal file
24
helpers/logger.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { dateToString } from "./converters";
|
||||||
|
import { LoggerColors } from "./logger-colors";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logs the wait time, current time, next join time, and cron schedule in the console.
|
||||||
|
* @param waitTime - The time to wait until the next join.
|
||||||
|
* @param hour - The hour of the cron schedule.
|
||||||
|
* @param minute - The minute of the cron schedule.
|
||||||
|
*/
|
||||||
|
export function logger(
|
||||||
|
waitTime: Date,
|
||||||
|
hour: number,
|
||||||
|
minute: number
|
||||||
|
){
|
||||||
|
const currentTime = new Date();
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
LoggerColors.Cyan, `
|
||||||
|
Wait time: ${(waitTime.getTime() - currentTime.getTime()) / 60000} minutes,
|
||||||
|
Current time: ${dateToString(currentTime)},
|
||||||
|
Next join time: ${dateToString(waitTime)},
|
||||||
|
Cron: ${Math.floor(minute)} ${Math.floor(hour) == 0 ? '*' : Math.floor(hour) } * * *`
|
||||||
|
);
|
||||||
|
}
|
||||||
18
helpers/setup-discord-client.ts
Normal file
18
helpers/setup-discord-client.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Client, GatewayIntentBits } from "discord.js";
|
||||||
|
|
||||||
|
export function SetupDiscordCLient(): Client{
|
||||||
|
const discordClient = new Client({
|
||||||
|
intents: [
|
||||||
|
GatewayIntentBits.Guilds,
|
||||||
|
GatewayIntentBits.GuildMessages,
|
||||||
|
GatewayIntentBits.MessageContent,
|
||||||
|
GatewayIntentBits.GuildVoiceStates,
|
||||||
|
GatewayIntentBits.GuildIntegrations,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const discordApplicationToken = process.env.TOKEN; // Discord bot token from .env file (required) More info: https://discordgsm.com/guide/how-to-get-a-discord-bot-token
|
||||||
|
|
||||||
|
discordClient.login(discordApplicationToken);
|
||||||
|
|
||||||
|
return discordClient;
|
||||||
|
}
|
||||||
3
models/avoid-list.ts
Normal file
3
models/avoid-list.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface AvoidList {
|
||||||
|
avoidUsers: string[];
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||

|

|
||||||
|
|
||||||
# What is this!?
|
# What is this!?
|
||||||
this is a discord bot that joins a random voice channel in a random guild and plays a random sound file (mp3). it uses the `@discordjs/voice` library for voice connections and the `node-schedule` library for scheduling the next join. the bot also starts a web server using the `startserver` function from the `webserver` module.
|
this is a discord bot that joins a random voice channel in a random guild and plays a random sound file (mp3). it uses the `@discordjs/voice` library for voice connections and the `node-schedule` library for scheduling the next join. the bot also starts a web server using the `startserver` function from the `webserver` module.
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user