Web dashboard for discord.js

Hello. I’m trying to create a web dashboard to my team’s discord.js bot, but we don’t know how to do it. Any help?

what are u asking for help on? like launching it or what? the one i use is using ejs. ill send u the guide my friend made

If you can, please send it. It should help.

Bunch of questions:

  1. What framework for frontend?
    a) React / Vue / Angular
    b) Everything else (html rendering nor js rendering*)
  2. What framework and PL for backend?
    a) Node.JS - Express, restify over express , etc.
    b) Other languages - other frameworks, but the idea is a REST API for your backend

Next, you start making an API for everything you need to display - stats, bot’s commands, etc. If you’ll be using Node and Express, then check some articles about code splitting for routes in Express (just google ‘router express nodejs’)

And then - get and display all stuff on your webpage. Easy.

* js rendering - here I mean that all the collected stuff you’ll get from your DB will be put to the page through JS script

I don’t know anything about creating an dashboard for my team’s bot. What we want:

  • Modify the ban, warn, kick, etc messages;
  • Change the prefix;
  • Give the info about each server,
  • Add custom commands.

Man, that’s your problem and also your team’s problem that you cannot code. Noone here would be a free-programming-teacher, so you have to develop by yourself.
There is a plenty of books and courses right on your fingertips. Go ahead.

Saying “what we want” means that you are hiring someone? Is it polite there?

We’re not hiring. I just want to know HOW THE HELL can we connect the bot files to the dashboard?

EDIT: And that was a list, so you can know what we need help, and what we want to know how to link.

You don’t have to.

All you need is some kind of API that will run in the bot’s project. It will be changing the data, reading it / writing it.

Then, on the web page you fetch the data using the API you’d created. You make a request - get a response and visualize it. Is it that hard?

You could use socket.io, I did it one time and it was working well:)

Ok. Do you know any good tutorials for html?

You should check Youtube or like https://www.w3schools.com/html which is a great website for all your questions:)

check this out. i posted the guide here

It doesnt’t work. :confused: It just gives me a starting page

Nevermind. It loaded now.

Ok. I found a template on github and made my bot with it, but the dashboard isn’t working.

Here’s my code:

Config.js:

/* eslint-disable */
var config = { // NOTE: DO NOT LEAVE ANYTHING BLANK
	// ALL settings are strings. Do NOT just use true or false, use these in strings such as 'true' or 'false'. This is due to how some code works when changing the settings
	ownerID: '495953543543521280', // Your ID here
  subownerID: "472720369346936842",
	token: 'NjQ5MjcxMDY0MzI4NjY3MjA3.Xd6XNg.E1NSlZ3zi-NUBFeLuUwZFZBvSP0', // Your bot token here
	status: 'dnd', // Bot status [online/idle/invisible/dnd]
	debug: 'false', // This is used to output some debug info if needed. The token will be in the console and other information could be in the console
	playingGame: '{{prefix}}help | {{guilds}} guilds | v{{version}}', // The game you want the bot to play. {{prefix}} is replaced with the default prefix below, {{guilds}} is replaced with the guild count and {{version}} is replaced with the bot version. Leave blank to disable
	purgeLogFormat: '\n Message ID: {{mID}} | Message Timestamp: {{mTS}} | Content: {{mC}} \n', // {{mID}}: Message ID; {{mTS}} Message Timestamp; {{mC}}: Message Content;
	eightBallResponses: ['Yes', 'No', 'Certainly', 'My sources say yes', 'Try again later', 'Without a doubt', 'Better not to tell you now'], // An array of responses for the 8ball command
	
	//------------------------------------- Old Global Protector's config --------------------------------------
	// Support Configuration
  	everyoneRole: "591267505876631560",
  	member: "609759213408878630",
  	support: "609762077917052967",
	
	//Bot Admins and Support
	// Bot Admins, level 9 by default.
  	 admins: ["188661388632260608", "630392442197245970", "317731855317336067", "391210867016073217", "344878404991975427"], // chonc, scrumpy, ollieg3, minion3665, flamingfondue, chromebook777 | "472720369346936842",

  	// Bot Support, level 8 by default.
 	 support: [], 
	//-------------------------------------------- End of Old Config -------------------------------------------
	cleverbotToken: '', // API Token for CleverBot
  googleAPIToken: 'AIzaSyCF4EfSw_IDW96PgKLC-NxNptMqc-F0lzc', // Used for link shortener and music features. You need to have these APIs enabled.
  logTimeFormat: 'D MMM YYYY HH:mm:ss ZZ',
  musicEnabled: 'true',
	defaultSettings: {
		prefix: '!',
		modLogChannel: 'mod-log',
		modRole: 'Moderator',
		adminRole: 'Admin',
		welcomeChannel: 'general',
		welcomeMessage: 'Welcome {{user}}!',
		welcomeEnabled: 'false',
		inviteFilterEnabled: 'false',
		inviteWhitelist: ['discord-testers', 'discord-developers'], // This can be changed, these are just defaults as an example
		facepalms: 'true', // If enabled, the bot will reply with the facepalm emoji whenever a message contains 'facepalm'
    xds: 'true',
		swearFilter: 'false',
		swearWords: ['damn'], // An array of swear words. These should be lowercase. (of course, I have not included much for certain reasons...)
		logDeletes: 'true',
		logNewMember: 'true',
		logMemberLeave: 'true',
		logCommandUsage: 'true',
		logPurge: 'true',
		commandTimeout: '2500',
		sendHelp: 'channel' // Available options: channel, dm
	},
	dashboard: {
		enabled: 'true', // This setting controls whether the dashboard is enabled or not.
		oauthSecret: 'XueEt693BtM2_LWPtzacdamuPap8_t9R', // The client secret from the Discord bot page
		secure: 'false', // HTTPS: 'true' for true, 'false' for false
		sessionSecret: '179324865JUSTg0t0h31l', // Go crazy on the keyboard here, this is used as a session secret
		domain: 'global-protector-test.glitch.me', // Domain name (with port if not running behind proxy running on port 80). Example: 'domain': 'dashboard.bot-website.com' OR 'domain': 'localhost:33445'
		port: '33445', // The port that it should run on
		invitePerm: '536079575',
		protectStats: 'false',
		borderedStats: 'false', // Controls whether stats in the dashboard should have a border or not
		legalTemplates: {
			contactEmail: 'thebigergameroficial@gmail.com', // This email will be used in the legal page of the dashboard if someone needs to contact you for any reason regarding this page
			lastEdited: '27 November 2019' // Change this if you update the `TERMS.md` or `PRIVACY.md` files in `dashboard/public/`
		}
	}
};

module.exports = config;

dashboard.js:

/*
DASHBOARD EXAMPLE

This is a very simple dashboard example, but even in its simple state, there are still a
lot of moving parts working together to make this a reality. I shall attempt to explain
those parts in as much details as possible, but be aware: there's still a lot of complexity
and you shouldn't expect to really understand all of it instantly.

Pay attention, be aware of the details, and read the comments.

Note that this *could* be split into multiple files, but for the purpose of this
example, putting it in one file is a little simpler. Just *a little*.
*/

// Native Node Imports
const url = require('url');
const path = require('path');
const fs = require('fs');

// Used for Permission Resolving...
const Discord = require('discord.js');

// Express Session
const express = require('express');
const app = express();

// Express Plugins
// Specifically, passport helps with oauth2 in general.
// passport-discord is a plugin for passport that handles Discord's specific implementation.
const passport = require('passport');
const session = require('express-session');
const Strategy = require('passport-discord').Strategy;

// Helmet is a security plugin
//const helmet = require('helmet');

// Used to parse Markdown from things like ExtendedHelp
const md = require('marked');

// For logging
const morgan = require('morgan');

// For stats
const moment = require('moment');
require('moment-duration-format');

module.exports = (client) => {

	if (client.config.dashboard.enabled !== 'true') return client.log('log', 'Dashboard disabled', 'INFO');
	// It's easier to deal with complex paths.
	// This resolves to: yourbotdir/dashboard/
	const dataDir = path.resolve(`${process.cwd()}${path.sep}dashboard`);

	// This resolves to: yourbotdir/dashboard/templates/
	// which is the folder that stores all the internal template files.
	const templateDir = path.resolve(`${dataDir}${path.sep}templates`);

	app.set('trust proxy', 5); // Proxy support
	// The public data directory, which is accessible from the *browser*.
	// It contains all css, client javascript, and images needed for the site.
	app.use('/public', express.static(path.resolve(`${dataDir}${path.sep}public`), { maxAge: '10d' }));
	app.use(morgan('combined')); // Logger

	// uhhhh check what these do.
	passport.serializeUser((user, done) => {
		done(null, user);
	});
	passport.deserializeUser((obj, done) => {
		done(null, obj);
	});

	/*
	This defines the **Passport** oauth2 data. A few things are necessary here.

	clientID = Your bot's client ID, at the top of your app page. Please note,
		older bots have BOTH a client ID and a Bot ID. Use the Client one.
	clientSecret: The secret code at the top of the app page that you have to
		click to reveal. Yes that one we told you you'd never use.
	callbackURL: The URL that will be called after the login. This URL must be
		available from your PC for now, but must be available publically if you're
		ever to use this dashboard in an actual bot.
	scope: The data scopes we need for data. identify and guilds are sufficient
		for most purposes. You might have to add more if you want access to more
		stuff from the user. See: https://discordapp.com/developers/docs/topics/oauth2

	See config.js.example to set these up.
	*/

	var protocol;

	if (client.config.dashboard.secure === 'true') {
		client.protocol = 'https://';
	} else {
		client.protocol = 'http://';
	}

	protocol = client.protocol;

	client.callbackURL = `${protocol}${client.config.dashboard.domain}/callback`;
	client.log('log', `Callback URL: ${client.callbackURL}`, 'INFO');
	passport.use(new Strategy({
		clientID: client.appInfo.id,
		clientSecret: client.config.dashboard.oauthSecret,
		callbackURL: client.callbackURL,
		scope: ['identify', 'guilds']
	},
	(accessToken, refreshToken, profile, done) => {
		process.nextTick(() => done(null, profile));
	}));


	// Session data, used for temporary storage of your visitor's session information.
	// the `secret` is in fact a 'salt' for the data, and should not be shared publicly.
	app.use(session({
		secret: client.config.dashboard.sessionSecret,
		resave: false,
		saveUninitialized: false,
	}));

	// Initializes passport and session.
	app.use(passport.initialize());
	app.use(passport.session());

	// The domain name used in various endpoints to link between pages.
	app.locals.domain = client.config.dashboard.domain;

	// The EJS templating engine gives us more power
	app.engine('html', require('ejs').renderFile);
	app.set('view engine', 'html');

	// body-parser reads incoming JSON or FORM data and simplifies their
	// use in code.
	var bodyParser = require('body-parser');
	app.use(bodyParser.json()); // to support JSON-encoded bodies
	app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
		extended: true
	}));

	/*
	Authentication Checks. checkAuth verifies regular authentication,
	whereas checkAdmin verifies the bot owner. Those are used in url
	endpoints to give specific permissions.
	*/
	function checkAuth(req, res, next) {
		if (req.isAuthenticated()) return next();
		req.session.backURL = req.url;
		res.redirect('/login');
	}

	function cAuth(req, res) {
		if (req.isAuthenticated()) return;
		req.session.backURL = req.url;
		res.redirect('/login');
	}

	function checkAdmin(req, res, next) {
		if (req.isAuthenticated() && req.user.id === client.config.ownerID) return next();
		req.session.backURL = req.originalURL;
		res.redirect('/');
	}

	var privacyMD = '';
	fs.readFile(`${process.cwd()}${path.sep}dashboard${path.sep}public${path.sep}PRIVACY.md`, function(err, data) {
		if (err) {
			console.log(err);
			privacyMD = 'Error';
			return;
		}
		privacyMD = data.toString().replace(/\{\{botName\}\}/g, client.user.username).replace(/\{\{email\}\}/g, client.config.dashboard.legalTemplates.contactEmail);
		if (client.config.dashboard.secure !== 'true') {
			privacyMD = privacyMD.replace('Sensitive and private data exchange between the Site and its Users happens over a SSL secured communication channel and is encrypted and protected with digital signatures.', '');
		}
	});

	var termsMD = '';
	fs.readFile(`${process.cwd()}${path.sep}dashboard${path.sep}public${path.sep}TERMS.md`, function(err, data) {
		if (err) {
			console.log(err);
			privacyMD = 'Error';
			return;
		}
		termsMD = data.toString().replace(/\{\{botName\}\}/g, client.user.username).replace(/\{\{email\}\}/g, client.config.dashboard.legalTemplates.contactEmail);
	});

	// Index page. If the user is authenticated, it shows their info
	// at the top right of the screen.
	app.get('/', (req, res) => {
		if (req.isAuthenticated()) {
			res.render(path.resolve(`${templateDir}${path.sep}index.ejs`), {
				bot: client,
				auth: true,
				user: req.user
			});
		} else {
			res.render(path.resolve(`${templateDir}${path.sep}index.ejs`), {
				bot: client,
				auth: false,
				user: null
			});
		}
	});


	app.get('/stats', (req, res) => {
		if (client.config.dashboard.protectStats === 'true') {
			cAuth(req, res);
		}
		const duration = moment.duration(client.uptime).format(' D [days], H [hrs], m [mins], s [secs]');
		//const members = client.guilds.reduce((p, c) => p + c.memberCount, 0);
		const members = `${client.users.filter(u => u.id !== '1').size} (${client.users.filter(u => u.id !== '1').filter(u => u.bot).size} bots)`;
		const textChannels = client.channels.filter(c => c.type === 'text').size;
		const voiceChannels = client.channels.filter(c => c.type === 'voice').size;
		const guilds = client.guilds.size;
		res.render(path.resolve(`${templateDir}${path.sep}stats.ejs`), {
			bot: client,
			auth: req.isAuthenticated() ? true : false,
			user: req.isAuthenticated() ? req.user : null,
			stats: {
				servers: guilds,
				members: members,
				text: textChannels,
				voice: voiceChannels,
				uptime: duration,
				commands: client.commandsNumber,
				memoryUsage: (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2),
				dVersion: Discord.version,
				nVersion: process.version,
				bVersion: client.version
			}
		});
	});

	app.get('/legal', function (req, res) {

		md.setOptions({
			renderer: new md.Renderer(),
			gfm: true,
			tables: true,
			breaks: false,
			pedantic: false,
			sanitize: false,
			smartLists: true,
			smartypants: false
		});

		/*var showdown	= require('showdown');
		var	converter = new showdown.Converter(),
			textPr			= privacyMD,
			htmlPr			= converter.makeHtml(textPr),
			textTe			= termsMD,
			htmlTe			= converter.makeHtml(textTe);
		res.render(path.resolve(`${templateDir}${path.sep}legal.ejs`), {
			bot: client,
			auth: req.isAuthenticated() ? true : false,
			user: req.isAuthenticated() ? req.user : null,
			privacy: htmlPr.replace(/\\'/g, `'`),
			terms: htmlTe.replace(/\\'/g, `'`),
			edited: client.config.dashboard.legalTemplates.lastEdited
		});*/

		res.render(path.resolve(`${templateDir}${path.sep}legal.ejs`), {
			bot: client,
			auth: req.isAuthenticated() ? true : false,
			user: req.isAuthenticated() ? req.user : null,
			privacy: md(privacyMD),
			terms: md(termsMD),
			edited: client.config.dashboard.legalTemplates.lastEdited
		});
	});

	// The login page saves the page the person was on in the session,
	// then throws the user to the Discord OAuth2 login page.
	app.get('/login', (req, res, next) => {
		if (req.session.backURL) {
			req.session.backURL = req.session.backURL;
		} else if (req.headers.referer) {
			const parsed = url.parse(req.headers.referer);
			if (parsed.hostname === app.locals.domain) {
				req.session.backURL = parsed.path;
			}
		} else {
			req.session.backURL = '/';
		}
		next();
	},
	passport.authenticate('discord'));

	app.get('/callback', passport.authenticate('discord', {
		failureRedirect: '/'
	}), (req, res) => {
		if (req.session.backURL) {
			res.redirect(req.session.backURL);
			req.session.backURL = null;
		} else {
			res.redirect('/');
		}
	});

	app.get('/admin', checkAdmin, (req, res) => {
		res.render(path.resolve(`${templateDir}${path.sep}admin.ejs`), {
			bot: client,
			user: req.user,
			auth: true
		});
	});

	app.get('/dashboard', checkAuth, (req, res) => {
		const perms = Discord.EvaluatedPermissions;
		res.render(path.resolve(`${templateDir}${path.sep}dashboard.ejs`), {
			perms: perms,
			bot: client,
			user: req.user,
			auth: true
		});
	});

	app.get('/add/:guildID', checkAuth, (req, res) => {
		req.session.backURL = '/dashboard';
		var invitePerm = client.config.dashboard.invitePerm;
		var inviteURL = `https://discordapp.com/oauth2/authorize?client_id=${client.appInfo.id}&scope=bot&guild_id=${req.params.guildID}&response_type=code&redirect_uri=${encodeURIComponent(`${client.callbackURL}`)}&permissions=${invitePerm}`;
		if (client.guilds.has(req.params.guildID)) {
			res.send('<p>The bot is already there... <script>setTimeout(function () { window.location="/dashboard"; }, 1000);</script><noscript><meta http-equiv="refresh" content="1; url=/dashboard" /></noscript>');
		} else {
			res.redirect(inviteURL);
		}
	});

	app.post('/manage/:guildID', checkAuth, (req, res) => {
		const guild = client.guilds.get(req.params.guildID);
		if (!guild) return res.status(404);
		const isManaged = guild && !!guild.member(req.user.id) ? guild.member(req.user.id).permissions.has('MANAGE_GUILD') : false;
		if (req.user.id === client.config.ownerID) {
			console.log(`Admin bypass for managing server: ${req.params.guildID}`);
		} else if (!isManaged) {
			res.redirect('/');
		}
		const settings = client.settings.get(guild.id);
		for (const key in settings) {
			var value = req.body[key];
			//console.log(typeof value);
			//console.log(value);
			/*if (value.length > 1) {
				for (var i = 0; i < value.length; i++) {
					console.log(value[i]);
					value[i] = value[i].replace(',', '');
					console.log(value[i]);
				}
			} else {*/
			if (value.indexOf(',') > -1) {
				settings[key] = value.split(',');
				//console.log('S: ' + settings[key]);
				//console.log(typeof settings[key]);
				//console.log('Split');
				//console.log(typeof value);
				//console.log(value);

			} else if (key === "inviteWhitelist") {
					var iWArray = [];
					value = value.replace(/\s/g, '');
					value.indexOf(',') > -1 ? iWArray = value.split(',') : iWArray.push(value);
					settings[key] = iWArray;
				} if (key === "swearWords") {
					var sWArray = [];
				 	value = value.replace(/\s/g, '');
					value.indexOf(',') > -1 ? sWArray = value.split(',') : sWArray.push(value);
				 	settings[key] = sWArray;
				} else {
					settings[key] = value;
					//console.log(typeof value);
					//console.log(value);
				}
			//settings[key] = req.body[key];
		}
		client.settings.set(guild.id, settings);
		res.redirect(`/manage/${req.params.guildID}`);
	});

	app.get('/manage/:guildID', checkAuth, (req, res) => {
		const guild = client.guilds.get(req.params.guildID);
		if (!guild) return res.status(404);
		const isManaged = guild && !!guild.member(req.user.id) ? guild.member(req.user.id).permissions.has('MANAGE_GUILD') : false;
		if (req.user.id === client.config.ownerID) {
			console.log(`Admin bypass for managing server: ${req.params.guildID}`);
		} else if (!isManaged) {
			res.redirect('/dashboard');
		}
		res.render(path.resolve(`${templateDir}${path.sep}manage.ejs`), {
			bot: client,
			guild: guild,
			user: req.user,
			auth: true
		});
	});

	app.get('/leave/:guildID', checkAuth, async (req, res) => {
		const guild = client.guilds.get(req.params.guildID);
		if (!guild) return res.status(404);
		const isManaged = guild && !!guild.member(req.user.id) ? guild.member(req.user.id).permissions.has('MANAGE_GUILD') : false;
		if (req.user.id === client.config.ownerID) {
			console.log(`Admin bypass for managing server: ${req.params.guildID}`);
		} else if (!isManaged) {
			res.redirect('/dashboard');
		}
		await guild.leave();
		if (req.user.id === client.config.ownerID) {
			return res.redirect('/admin');
		}
		res.redirect('/dashboard');
	});

	app.get('/reset/:guildID', checkAuth, async (req, res) => {
		const guild = client.guilds.get(req.params.guildID);
		if (!guild) return res.status(404);
		const isManaged = guild && !!guild.member(req.user.id) ? guild.member(req.user.id).permissions.has('MANAGE_GUILD') : false;
		if (req.user.id === client.config.ownerID) {
			console.log(`Admin bypass for managing server: ${req.params.guildID}`);
		} else if (!isManaged) {
			res.redirect('/dashboard');
		}
		client.settings.set(guild.id, client.config.defaultSettings);
		res.redirect(`/manage/${req.params.guildID}`);
	});


	app.get('/commands', (req, res) => {
		if (req.isAuthenticated()) {
			res.render(path.resolve(`${templateDir}${path.sep}commands.ejs`), {
				bot: client,
				auth: true,
				user: req.user,
				md: md
			});
		} else {
			res.render(path.resolve(`${templateDir}${path.sep}commands.ejs`), {
				bot: client,
				auth: false,
				user: null,
				md: md
			});
		}
	});

	app.get('/logout', function (req, res) {
		req.logout();
		res.redirect('/');
	});

	app.get('*', function(req, res) { // Catch-all 404
		res.send('<p>404 File Not Found. Please wait...<p> <script>setTimeout(function () { window.location = "/"; }, 1000);</script><noscript><meta http-equiv="refresh" content="1; url=/" /></noscript>');
	});

	client.site = app.listen(client.config.dashboard.port, function() {
		client.log('log', `Dashboard running on port ${client.config.dashboard.port}`, 'INFO');
	}).on('error', (err) => {
		client.log('ERROR', `Error with starting dashboard: ${err.code}`);
		return process.exit(0);
	});
};

I think the problem is with the port that glitch uses, but idk. Can you help me please?

EDIT: Also, it never gives an error.

Not really a good idea to leak your bot token. Hopefully that is an invalid token
Happy Glitching! - tech_dude1

It was an old token from a backup. It is invalid and it was regenerated 3 times since then.

I am happy that was the case.

Happy Glitching! - tech_dude1

@tech_dude1 thanks. But do you know how to fix my problem?

EDIT: I went to the console and typed curl localhost:33445 and it gave me the main page code. But why it isn’t showing it?

You have to change your port to 3000 in config because of Glitch’s project politics.

2 Likes

IT WORKED!!! OMG! THANK YOU!!!
Thank you all!!!