mirror of
https://github.com/Myxelium/RandomMemerBot.git
synced 2026-04-13 18:40:36 +00:00
Add authentication endpoints & refactor
This commit is contained in:
6
bot.ts
6
bot.ts
@@ -16,7 +16,6 @@ import {
|
|||||||
} from '@discordjs/voice';
|
} from '@discordjs/voice';
|
||||||
import { ChannelType, Collection, GuildBasedChannel, Snowflake, VoiceChannel, VoiceState } from 'discord.js';
|
import { ChannelType, Collection, GuildBasedChannel, Snowflake, VoiceChannel, VoiceState } from 'discord.js';
|
||||||
import * as dotenv from 'dotenv';
|
import * as dotenv from 'dotenv';
|
||||||
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 { loadAvoidList } from './helpers/load-avoid-list';
|
||||||
import { LoggerColors } from './helpers/logger-colors';
|
import { LoggerColors } from './helpers/logger-colors';
|
||||||
@@ -25,10 +24,11 @@ import { logger } from './helpers/logger';
|
|||||||
import { SetupDiscordCLient } from './helpers/setup-discord-client';
|
import { SetupDiscordCLient } from './helpers/setup-discord-client';
|
||||||
import { convertHoursToMinutes, dateToString } from './helpers/converters';
|
import { convertHoursToMinutes, dateToString } from './helpers/converters';
|
||||||
import { AvoidList } from './models/avoid-list';
|
import { AvoidList } from './models/avoid-list';
|
||||||
|
import { runServer } from './client/router';
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
export var nextPlayBackTime: string = ''; // Export so it can be used in the webserver module aswell.
|
export var nextPlayBackTime: string = 'Never played'; // Export so it can be used in the webserver module aswell.
|
||||||
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.
|
||||||
@@ -39,7 +39,7 @@ discordClient.on('ready', async () => {
|
|||||||
console.log(`Logged in as ${discordClient.user?.tag}!`);
|
console.log(`Logged in as ${discordClient.user?.tag}!`);
|
||||||
|
|
||||||
joinRandomChannel(voiceChannelRetries);
|
joinRandomChannel(voiceChannelRetries);
|
||||||
startWebServer();
|
runServer();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class DiscordService {
|
||||||
|
constructor(private http: HttpClient) {}
|
||||||
|
}
|
||||||
100
client/controllers/authentication.ts
Normal file
100
client/controllers/authentication.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import ip from 'ip';
|
||||||
|
import DiscordOauth2 from 'discord-oauth2';
|
||||||
|
import { ssl } from '../server';
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
const oauth = new DiscordOauth2();
|
||||||
|
|
||||||
|
var clientId = process.env.OAUTH_CLIENT_ID;
|
||||||
|
var clientSecret = process.env.OAUTH_CLIENT_SECRET;
|
||||||
|
var customRedirectUrl = process.env.OAUTH_REDIRECT_URI;
|
||||||
|
var scopes = "identify email guilds";
|
||||||
|
|
||||||
|
router.get('/login', (req, res) => {
|
||||||
|
if(!variablesIsSet(res))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var redirect = `${ssl}://${ip.address()}/oauth`
|
||||||
|
|
||||||
|
if(customRedirectUrl && customRedirectUrl != "default")
|
||||||
|
redirect = `${customRedirectUrl}/oauth`
|
||||||
|
|
||||||
|
res.redirect(`https://discordapp.com/api/oauth2/authorize?client_id=${clientId}&scope=${scopes.replace(' ', '+')}&response_type=code&redirect_uri=${redirect}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/oauth', async (_req: any, res: express.Response) => {
|
||||||
|
if(!variablesIsSet(res))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
url: 'https://discord.com/api/oauth2/token',
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
|
},
|
||||||
|
body: new URLSearchParams({
|
||||||
|
'client_id': clientId!,
|
||||||
|
'client_secret': clientSecret!,
|
||||||
|
'grant_type': 'client_credentials',
|
||||||
|
'code': _req.query.code,
|
||||||
|
'redirect_uri': `${ssl}://${ip.address()}`,
|
||||||
|
'scope': scopes
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var response = await fetch('https://discord.com/api/oauth2/token', options)
|
||||||
|
.then((response) => {
|
||||||
|
return response.json();
|
||||||
|
});
|
||||||
|
|
||||||
|
res.send(response);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/userinfo', async (req, res) => {
|
||||||
|
const token = req.headers.authorization;
|
||||||
|
|
||||||
|
if(token == null || token == undefined)
|
||||||
|
return res.sendStatus(401);
|
||||||
|
|
||||||
|
var cleanToken = token.replace('Bearer ', '');
|
||||||
|
|
||||||
|
const user = await oauth.getUser(cleanToken);
|
||||||
|
|
||||||
|
res.send(user);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/guilds', async (req, res) => {
|
||||||
|
const token = req.headers.authorization;
|
||||||
|
|
||||||
|
if(token == null || token == undefined)
|
||||||
|
return res.sendStatus(401);
|
||||||
|
|
||||||
|
var cleanToken = token.replace('Bearer ', '');
|
||||||
|
|
||||||
|
const guilds = await oauth.getUserGuilds(cleanToken);
|
||||||
|
|
||||||
|
res.send(guilds);
|
||||||
|
});
|
||||||
|
|
||||||
|
function variablesIsSet(response: any): boolean {
|
||||||
|
let errorText = "Invalid configuration. Please check your environment variables.";
|
||||||
|
|
||||||
|
if(clientId == undefined || clientSecret == undefined) {
|
||||||
|
response.send(errorText);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(clientId.length < 5 && clientSecret.length < 5) {
|
||||||
|
response.send(errorText);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default router;
|
||||||
44
client/controllers/index.ts
Normal file
44
client/controllers/index.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import path from 'path';
|
||||||
|
import { Handlers } from "../handlers/index";
|
||||||
|
import { nextPlayBackTime } from '../../bot';
|
||||||
|
import { loadAvoidList } from '../../helpers/load-avoid-list';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the next playback time.
|
||||||
|
* @returns string - The next playback time.
|
||||||
|
*/
|
||||||
|
router.get('/nextplaybacktime', (_req, res) => {
|
||||||
|
res.send(nextPlayBackTime);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index.html file.
|
||||||
|
* @returns index.html - The index.html file.
|
||||||
|
*/
|
||||||
|
router.get('/', (_req, res) => {
|
||||||
|
res.sendFile(path.join(__dirname, '../web/index.html'));
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/avoidlist', (_req, res) => {
|
||||||
|
res.send(loadAvoidList());
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/avoidlist', (req, res) => {
|
||||||
|
Handlers.AddUserToAvoidList(res, req);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.delete('/avoidlist/:user', (req, res) => {
|
||||||
|
Handlers.DeleteUserFromAvoidList(res, req);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/join', (_req, res) => {
|
||||||
|
Handlers.JoinChannel(res);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.use(express.static(path.join(__dirname, "../web")));
|
||||||
|
|
||||||
|
|
||||||
|
export default router;
|
||||||
22
client/controllers/sounds.ts
Normal file
22
client/controllers/sounds.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import path from 'path';
|
||||||
|
import { Handlers } from "../handlers/index";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.delete('/sounds/:filename', (_req, res) => {
|
||||||
|
Handlers.DeleteSoundFile(res, _req);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a file from the sounds folder by filename
|
||||||
|
* @param filename - The name of the file to return.
|
||||||
|
* @returns mp3 - The requested file.
|
||||||
|
*/
|
||||||
|
router.use('/sounds', express.static(path.join(__dirname, '../../sounds')));
|
||||||
|
|
||||||
|
router.get('/sounds', (_req, res: express.Response) => {
|
||||||
|
return Handlers.GetSoundFiles(res);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
44
client/controllers/upload.ts
Normal file
44
client/controllers/upload.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import multer, { diskStorage } from 'multer';
|
||||||
|
import path from 'path';
|
||||||
|
import { Handlers } from "../handlers/index";
|
||||||
|
import { generateFileName } from '../../helpers/generate-file-name';
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
const storage = diskStorage({
|
||||||
|
destination: 'sounds/',
|
||||||
|
filename: function (_req, file, cb) {
|
||||||
|
cb(null, generateFileName(file.originalname));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const upload = multer({
|
||||||
|
storage: storage,
|
||||||
|
limits: { fileSize: 1 * 1024 * 1024 },
|
||||||
|
fileFilter: function (_req, file, cb) {
|
||||||
|
if (path.extname(file.originalname) !== '.mp3') {
|
||||||
|
return cb(new Error('Only .mp3 files are allowed'));
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads a file to the sounds folder.
|
||||||
|
* @Body myFile - The file to upload.
|
||||||
|
*/
|
||||||
|
router.post('/upload', upload.single('myFile'), async (req, res) => {
|
||||||
|
res.send('File uploaded successfully.');
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/youtube', async (req, res) => {
|
||||||
|
await Handlers.UploadYouTubeFile(res, req);
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/upload-youtube', async (req, res) => {
|
||||||
|
await Handlers.UploadYouTubeFile(res, req);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
23
client/router.ts
Normal file
23
client/router.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import express from 'express';
|
||||||
|
import bodyParser from 'body-parser';
|
||||||
|
import { startServer } from './server';
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
|
// Import routes
|
||||||
|
import indexRoutes from './controllers/index';
|
||||||
|
import uploadRoutes from './controllers/upload';
|
||||||
|
import soundsRoutes from './controllers/sounds';
|
||||||
|
import authRoutes from './controllers/authentication';
|
||||||
|
|
||||||
|
// Use routes
|
||||||
|
export function runServer() {
|
||||||
|
app.use('/', indexRoutes);
|
||||||
|
app.use('/', uploadRoutes);
|
||||||
|
app.use('/', soundsRoutes);
|
||||||
|
app.use('/', authRoutes);
|
||||||
|
app.listen(3040);
|
||||||
|
|
||||||
|
startServer(app);
|
||||||
|
}
|
||||||
33
client/server.ts
Normal file
33
client/server.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import https from 'https';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { LoggerColors } from '../helpers/logger-colors';
|
||||||
|
import ip from 'ip';
|
||||||
|
import { Express } from 'express';
|
||||||
|
|
||||||
|
export var ssl: "https" | "http" = "http";
|
||||||
|
|
||||||
|
export function startServer(app: Express) {
|
||||||
|
let port: 80 | 443 = 80;
|
||||||
|
let server;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {
|
||||||
|
requestCert: true,
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
key: fs.readFileSync(path.join(__dirname, '/certs/key.pem')),
|
||||||
|
cert: fs.readFileSync(path.join(__dirname, '/certs/cert.pem')),
|
||||||
|
};
|
||||||
|
server = https.createServer(options, app);
|
||||||
|
ssl = "https";
|
||||||
|
port = 443;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(LoggerColors.Yellow, 'Could not find SSL certificates, falling back to http.');
|
||||||
|
server = app;
|
||||||
|
ssl = "http";
|
||||||
|
}
|
||||||
|
|
||||||
|
server.listen(port, () => {
|
||||||
|
console.log(`Server started at ${ssl}://${ip.address()}:${port}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
import { nextPlayBackTime } from './../bot';
|
|
||||||
import express from 'express';
|
|
||||||
import multer, { diskStorage } from 'multer';
|
|
||||||
import path from 'path';
|
|
||||||
import fs from 'fs';
|
|
||||||
import bodyParser from 'body-parser';
|
|
||||||
import https from 'https';
|
|
||||||
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 storage = diskStorage({
|
|
||||||
destination: 'sounds/',
|
|
||||||
filename: function (_req, file, cb) {
|
|
||||||
cb(null, generateFileName(file.originalname));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(bodyParser.json());
|
|
||||||
|
|
||||||
const upload = multer({
|
|
||||||
storage: storage,
|
|
||||||
limits: { fileSize: 1 * 1024 * 1024 },
|
|
||||||
fileFilter: function (_req, file, cb) {
|
|
||||||
if (path.extname(file.originalname) !== '.mp3') {
|
|
||||||
return cb(new Error('Only .mp3 files are allowed'));
|
|
||||||
}
|
|
||||||
|
|
||||||
cb(null, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the index.html file.
|
|
||||||
* @returns index.html - The index.html file.
|
|
||||||
*/
|
|
||||||
app.get('/', (_req, res) => {
|
|
||||||
res.sendFile(path.join(__dirname, 'web/index.html'));
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads a file to the sounds folder.
|
|
||||||
* @Body myFile - The file to upload.
|
|
||||||
*/
|
|
||||||
app.post('/upload', upload.single('myFile'), async (req, res) => {
|
|
||||||
res.send('File uploaded successfully.');
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/upload-youtube', async (req, res) => {
|
|
||||||
await Handlers.UploadYouTubeFile(res, req);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a file from the sounds folder by filename
|
|
||||||
* @param filename - The name of the file to return.
|
|
||||||
* @returns mp3 - The requested file.
|
|
||||||
*/
|
|
||||||
app.use('/sounds', express.static(path.join(__dirname, '../sounds')));
|
|
||||||
|
|
||||||
app.get('/sounds', (_req, res: express.Response) => {
|
|
||||||
return Handlers.GetSoundFiles(res);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the next playback time.
|
|
||||||
* @returns string - The next playback time.
|
|
||||||
*/
|
|
||||||
app.get('/nextplaybacktime', (_req, res) => {
|
|
||||||
res.send(nextPlayBackTime);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.delete('/sounds/:filename', (_req, res) => {
|
|
||||||
Handlers.DeleteSoundFile(res, _req);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(express.static(path.join(__dirname, "web")));
|
|
||||||
|
|
||||||
app.get('/join', (_req, res) => {
|
|
||||||
Handlers.JoinChannel(res);
|
|
||||||
});
|
|
||||||
|
|
||||||
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());
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the web server on either http or https protocol based on the availability of SSL certificates.
|
|
||||||
* @returns void
|
|
||||||
*/
|
|
||||||
export function startServer() {
|
|
||||||
let port: 80 | 443 = 80;
|
|
||||||
let server;
|
|
||||||
let ssl: "https" | "http" = "http";
|
|
||||||
|
|
||||||
try {
|
|
||||||
const options = {
|
|
||||||
requestCert: true,
|
|
||||||
rejectUnauthorized: false,
|
|
||||||
key: fs.readFileSync(path.join(__dirname, '/certs/key.pem')),
|
|
||||||
cert: fs.readFileSync(path.join(__dirname, '/certs/cert.pem')),
|
|
||||||
};
|
|
||||||
server = https.createServer(options, app);
|
|
||||||
ssl = "https";
|
|
||||||
port = 443;
|
|
||||||
} catch (error) {
|
|
||||||
console.log(LoggerColors.Yellow, 'Could not find SSL certificates, falling back to http.');
|
|
||||||
server = app;
|
|
||||||
ssl = "http";
|
|
||||||
}
|
|
||||||
|
|
||||||
server.listen(port, () => {
|
|
||||||
console.log(`Server started at ${ssl}://${ip.address()}:${port}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"description": "Discord bot randomly plays sounds",
|
"description": "Discord bot randomly plays sounds",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "ts-node bot.ts",
|
"start": "ts-node bot.ts",
|
||||||
"create": "node -e \"require('fs').writeFileSync('.env', 'TOKEN=MY-API-TOKEN\\nINTERVALMIN_MINUTES=10\\nINTERVALMAX_HOURS=6\\nVOICECHANNELRETRIES=12')\"",
|
"create": "node -e \"require('fs').writeFileSync('.env', 'TOKEN=MY-API-TOKEN\\nINTERVALMIN_MINUTES=10\\nINTERVALMAX_HOURS=6\\nVOICECHANNELRETRIES=12\\nOAUTH_CLIENT_ID=\\nOAUTH_CLIENT_SECRET=\\nOAUTH_REDIRECT_URI=\"\"default\"\"')\"",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"author": "SocksOnHead",
|
"author": "SocksOnHead",
|
||||||
@@ -15,6 +15,7 @@
|
|||||||
"@types/multer": "~1.4.8",
|
"@types/multer": "~1.4.8",
|
||||||
"@types/node": "~20.8.2",
|
"@types/node": "~20.8.2",
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
|
"discord-oauth2": "^2.12.1",
|
||||||
"discord.js": "~14.13.0",
|
"discord.js": "~14.13.0",
|
||||||
"dotenv": "~16.3.1",
|
"dotenv": "~16.3.1",
|
||||||
"express": "~4.18.2",
|
"express": "~4.18.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user