diff --git a/dashboard/.env b/dashboard/.env new file mode 100644 index 0000000..279c513 --- /dev/null +++ b/dashboard/.env @@ -0,0 +1,12 @@ +API_URL=localhost:4000/api/v1, +BOT_ROLE_ID=861875883190255646, +BOT_TOKEN=ODYxODg2OTEyNjE2OTg4Njcy.YOQUvQ._nTVCqkKoQFkYLLqK9VwWefVGBQ, +CLIENT_ID=861886912616988672, +CLIENT_SECRET=861886912616988672, +DASHBOARD_URL=localhost:3000, +DEV_ROLE_ID=861876106130489415, +DOWNTIME_CHANNEL_ID=861879504447995925, +GUILD_ID=861875493245419521, +LOG_CHANNEL_ID=861879665232576534, +MONGO_URI=mongodb+srv://wumpusdiscordbotlist:1234S978@classic.as7wj.mongodb.net/myFirstDatabase?retryWrites=true&w=majority, +PORT=3000 \ No newline at end of file diff --git a/dashboard/assets/css/charts.css b/dashboard/assets/css/charts.css new file mode 100644 index 0000000..a932ba0 --- /dev/null +++ b/dashboard/assets/css/charts.css @@ -0,0 +1,37 @@ +.charts { + display: flex; + justify-content: space-between; + text-align: center; +} + +.charts svg { + display: flex; + float: left; +} + +.ct-series-a .ct-line, +.ct-series-a .ct-point { + stroke: var(--success); + stroke-width: 4px; +} +.ct-series-b .ct-line, +.ct-series-b .ct-point { + stroke: var(--danger); + stroke-width: 4px; +} + +.ct-series-a .ct-area { + fill: var(--success); +} +.ct-series-b .ct-area { + fill: var(--danger); +} + +.ct-grids line, +.ct-labels span, +.ct-grid.ct-horizontal { + color: var(--font); +} +.ct-grid { + stroke: transparent; +} \ No newline at end of file diff --git a/dashboard/assets/css/commands.css b/dashboard/assets/css/commands.css new file mode 100644 index 0000000..de6361a --- /dev/null +++ b/dashboard/assets/css/commands.css @@ -0,0 +1,20 @@ +#search { + max-width: 300px; +} +#search + button { + height: 64px; +} + +li { + cursor: pointer; +} + +.commands li { + margin: 5px 0; + border-radius: 5px; + border: 1px solid var(--font) !important; +} + +.list-group-item { + background-color: inherit; +} \ No newline at end of file diff --git a/dashboard/assets/css/index.css b/dashboard/assets/css/index.css new file mode 100644 index 0000000..46f298c --- /dev/null +++ b/dashboard/assets/css/index.css @@ -0,0 +1,3 @@ +.jumbotron { + margin-top: 25vh; +} \ No newline at end of file diff --git a/dashboard/assets/css/leaderboard.css b/dashboard/assets/css/leaderboard.css new file mode 100644 index 0000000..3f57a7e --- /dev/null +++ b/dashboard/assets/css/leaderboard.css @@ -0,0 +1,21 @@ +h1 img { + height: 64px; +} + +li.list-group-item { + background-color: var(--background-secondary); + color: var(--heading); +} + +li:nth-of-type(1) { + background: linear-gradient(to bottom, #C5B358 0%, #B6A449 100%); + color: white; +} +li:nth-of-type(2) { + background: linear-gradient(to bottom, #C0C0C0 0%, #B1B1B1 100%); + color: white; +} +li:nth-of-type(3) { + background: linear-gradient(to bottom, #CD7F32 0%, #BE7023 100%); + color: white; +} diff --git a/dashboard/assets/css/main.css b/dashboard/assets/css/main.css new file mode 100644 index 0000000..5b21299 --- /dev/null +++ b/dashboard/assets/css/main.css @@ -0,0 +1,29 @@ +body { + overflow-x: hidden; + background-color: var(--background-primary); + color: var(--font); +} + +h1, h2, h3, h4, h5 { + color: var(--heading); +} + +code.hljs { + background-color: var(--background-secondary); +} + +.user-avatar { + max-width: 36px; +} + +.navbar a { + color: var(--heading); +} + +.table { + color: var(--heading); +} + +.dropdown-menu { + background-color: inherit; +} \ No newline at end of file diff --git a/dashboard/assets/css/music.css b/dashboard/assets/css/music.css new file mode 100644 index 0000000..dadbd34 --- /dev/null +++ b/dashboard/assets/css/music.css @@ -0,0 +1,45 @@ +.card-header img { + width: 128px; + height: 128px; + object-fit: cover; + border-radius: 15px; +} + +.buttons, +#volume { + text-align: center; +} +.q-control, +.position { + display: flex; + justify-content: center; +} + +.form-control, +.btn { + box-shadow: none !important; + outline: none !important; +} + +.track-list img { + width: 64px; + height: 64px; + object-fit: cover; + border-radius: 10px; +} + +#trackSearch { + margin-top: 20px; + max-height: 42px; +} + +#volume { + cursor: pointer; +} +#volume input { + opacity: 0; + transition: opacity .3s ease-in-out; +} +#volume:hover input { + opacity: 1; +} diff --git a/dashboard/assets/css/sidebar.css b/dashboard/assets/css/sidebar.css new file mode 100644 index 0000000..7609f05 --- /dev/null +++ b/dashboard/assets/css/sidebar.css @@ -0,0 +1,97 @@ +.icon, .icon img { + font-size: x-large; + + height: 52px; + width: 52px; + + transition: border-radius .3s ease-in-out; +} +.icon:hover, .icon img:hover { + border-radius: 15% !important; +} +.abbr { + padding-top: 7.5px; +} + +.hamburger { + cursor: pointer; + font-size: xxx-large; + + bottom: 0; + position: absolute; +} + +#sidebar { + padding: 10px; + background-color: var(--background-tertiary); +} +#sidebar, +#sidebarExtension { + height: 100vh; + float: left; +} + +/* extension */ +#sidebarExtension { + width: 275px; + transition: .3s; + opacity: 1; + background-color: var(--background-secondary); +} +#sidebarExtension .large-icon { + display: block; + margin-left: auto; + margin-right: auto; + width: 50%; +} +#sidebarExtension .large-icon, +#sidebarExtension img { + font-size: xx-large; + + width: 96px; + height: 96px; +} +#sidebarExtension .abbr { + padding-top: 25px; +} +#sidebarExtension h4 { + cursor: pointer; +} + +#sidebarExtension.closed { + width: 0; + padding: 0px 0; +} +#sidebarExtension.closed { + opacity: 0; +} + +/* tabs */ +.tabs a { + margin: 2px 10px; + padding: 5px; + padding-left: 10px !important; + border-radius: 5px; + + text-decoration: none; + color: var(--font); +} +.tabs a.active, +.tabs a:hover { + background-color: var(--background-primary); +} + +.tabs .category { + padding: 24px 0 4px 24px; + font-size: 12px; + text-transform: uppercase; + font-weight: 700; + + color: gray; + cursor: pointer; + + transition: color .3s ease-in-out; +} +.tabs .category:hover { + color: var(--heading); +} \ No newline at end of file diff --git a/dashboard/assets/css/theme.css b/dashboard/assets/css/theme.css new file mode 100644 index 0000000..1f8c5fa --- /dev/null +++ b/dashboard/assets/css/theme.css @@ -0,0 +1,22 @@ +:root, +:root[theme='ASCETIC'] { + --primary: black; + + --heading: black; + --font: black; + + --background-primary: white; + --background-secondary: #F8F9FA; + --background-tertiary: #F8F9FA; +} + +:root[theme='DISCORD'] { + --primary: #7289DA; + + --heading: white; + --font: #99AAB5; + + --background-primary: #36393F; + --background-secondary: #2F3136; + --background-tertiary: #202225; +} \ No newline at end of file diff --git a/dashboard/assets/css/utils.css b/dashboard/assets/css/utils.css new file mode 100644 index 0000000..95d65fd --- /dev/null +++ b/dashboard/assets/css/utils.css @@ -0,0 +1,28 @@ +.round { + border-radius: 50% !important; +} + +.shadow { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06) !important; +} + +.cursor-pointer { + cursor: pointer !important; +} + +/* buttons */ +.btn-gradient { + background-image: linear-gradient(135deg, var(--secondary), var(--info)); + color: white; +} +.btn-gradient:hover { + color: white; +} + +/* text */ +.text-gradient { + background: linear-gradient(135deg, var(--secondary), var(--info)); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} \ No newline at end of file diff --git a/dashboard/assets/js/charts.js b/dashboard/assets/js/charts.js new file mode 100644 index 0000000..a0d89f5 --- /dev/null +++ b/dashboard/assets/js/charts.js @@ -0,0 +1,11 @@ +function initChart(selector, ...log) { + new Chartist.Line(selector, { + labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], + series: [...log] + }, { low: Math.min(log), showArea: true }); +} + +initChart('.joins-chart', joinsLog, leavesLog); +initChart('.punishments-chart', punishmentsLog); +initChart('.messages-chart', messagesLog); +initChart('.commands-chart', commandsLog); \ No newline at end of file diff --git a/dashboard/assets/js/commands.js b/dashboard/assets/js/commands.js new file mode 100644 index 0000000..b95aa99 --- /dev/null +++ b/dashboard/assets/js/commands.js @@ -0,0 +1,51 @@ +$('.categories li').on('click', setCategory); + +function setCategory() { + blank(); + + const selected = $(this); + selected.addClass('active'); + + const categoryCommands = $(`.commands .${selected[0].id}`); + categoryCommands.show(); + + updateResultsText(categoryCommands); +} +function blank() { + $('.categories li').removeClass('active'); + $('.commands li').hide(); +} + +$('#search + button').on('click', () => { + const query = $('#search input').val(); + if (!query.trim()) { + updateResultsText(commands); + return $('.commands li').show(); + } + + const results = new Fuse(commands, { + isCaseSensitive: false, + keys: [ + { name: 'name', weight: 1 }, + { name: 'category', weight: 0.5 } + ] + }) + .search(query) + .map(r => r.item); + + blank(); + + for (const command of results) + $(`#${command.name}Command`).show(); + + updateResultsText(results); +}); + +function updateResultsText(arr) { + $('#commandError').text( + (arr.length <= 0) + ? 'There is nothing to see here.' + : ''); +} + +setCategory.bind($('.categories li')[0])(); \ No newline at end of file diff --git a/dashboard/assets/js/guild.js b/dashboard/assets/js/guild.js new file mode 100644 index 0000000..254de2a --- /dev/null +++ b/dashboard/assets/js/guild.js @@ -0,0 +1,23 @@ +$('.tabs a, #sidebarExtension h4').on('click', function() { + $('.tabs a').removeClass('active'); + setModule($(this).attr('id')); +}); + +function setModule(name) { + $('.module').hide(); + $(`#${name}Module`).show(); + $(`#${name}`).addClass('active'); +} + +$('input').on('input', function() { + $(this)[0].checkValidity() + ? $(this).removeClass('border border-danger') + : $(this).addClass('border border-danger'); + + $('button.btn.btn-success') + .attr('disabled', !$('form')[0].checkValidity()); +}); + +setModule('overview'); + +hljs.initHighlightingOnLoad(); \ No newline at end of file diff --git a/dashboard/assets/js/main.js b/dashboard/assets/js/main.js new file mode 100644 index 0000000..04fdde1 --- /dev/null +++ b/dashboard/assets/js/main.js @@ -0,0 +1,3 @@ +$(function () { + $('[data-toggle="tooltip"]').tooltip() +}) \ No newline at end of file diff --git a/dashboard/assets/js/music/html-music-wrapper.js b/dashboard/assets/js/music/html-music-wrapper.js new file mode 100644 index 0000000..1eaa143 --- /dev/null +++ b/dashboard/assets/js/music/html-music-wrapper.js @@ -0,0 +1,92 @@ +class HTMLMusicWrapper { + #music; + + get currentTimestamp() { + const position = this.#music.position; + + const minutes = Math.floor(position / 60).toString().padStart(2, '0'); + const seconds = Math.floor(position - (minutes * 60)).toString().padStart(2, '0'); + return `${minutes}:${seconds}`; + } + + set apiError(error) { + if (!error) + return $('#musicAPIError').addClass('d-none'); + + $('#musicAPIError').removeClass('d-none'); + $('#musicAPIError').text(error.message ?? 'Unknown error.'); + } + + constructor(musicClient) { + this.#music = musicClient; + + setInterval(() => this.#updateSeeker(), 1000); + } + + #updateSeeker() { + if (!this.#music.isPlaying || this.#music.isPaused) return; + + this.#music.position++; + + $('#seekTrack input').val(this.#music.position); + $('.current').text(this.currentTimestamp); + } + + updateList() { + $('.now-playing').html(this.#nowPlaying()); + + const track = (this.#music.isPlaying) ? this.#music.list[0] : null; + if (track) { + $('.current').text(this.currentTimestamp); + $('.duration').text(track.duration.timestamp); + $('#seekTrack input').attr('max', track.duration.seconds); + } else { + $('.current, .duration').text(`00:00`); + } + + $('.track-list').html( + (!this.#music.isPlaying) + ? '

No tracks here.

' + : this.#music.list + .map(this.#htmlTrack) + .join() + ); + + $('.track .remove').on('click', async () => { + const index = $('.track .remove').index('.remove'); + await this.#music.remove(index); + }); + } + + toggle() { + $('#toggleTrack i').toggleClass('fa-pause'); + $('#toggleTrack i').toggleClass('fa-play'); + } + + #nowPlaying() { + if (!this.#music.isPlaying) return ``; + + const track = this.#music.list[0]; + return ` + +

${track.title}

+

${track.author.name}

+ `; + } + + #htmlTrack(track) { + return ` +
+ + +
${track.title}
+

${track.author.name}

+
+
+ ${track.duration.timestamp} + +
+
+ `; + } +} diff --git a/dashboard/assets/js/music/music-wrapper.js b/dashboard/assets/js/music/music-wrapper.js new file mode 100644 index 0000000..624d7ff --- /dev/null +++ b/dashboard/assets/js/music/music-wrapper.js @@ -0,0 +1,110 @@ +class MusicWrapper { + #endpoint = `/api/guilds/${guildId}/music`; + #html = new HTMLMusicWrapper(this); + + isPaused = $('#toggleTrack i').hasClass('fa-play'); + list = []; + position = +$('#seekTrack input').val(); + + get isPlaying() { + return this.list.length > 0; + } + + async #fetch(action) { + try { + const res = await fetch(`${this.#endpoint}/${action}`, { + headers: { Authorization: key } + }); + const json = await res.json(); + if (!res.ok) + throw json; + + return json; + } catch (error) { + this.#html.apiError = error; + throw error; + } + } + + async play(query) { + try { + await this.#fetch(`play?q=${query}`); + this.#html.apiError = null; + } catch {} + await this.updateList(); + } + + async stop() { + try { + await this.#fetch(`stop`); + this.#html.apiError = null; + this.position = 0; + } catch {} + await this.updateList(); + } + + async toggle() { + try { + await this.#fetch(`toggle`); + this.#html.apiError = null; + } catch {} + } + + async setVolume(value) { + try { + await this.#fetch(`volume?v=${value}`); + this.#html.apiError = null; + } catch {} + } + + async seek(to) { + try { + await this.#fetch(`seek?to=${to}`); + this.position = to; + + this.#html.apiError = null; + } catch {} + } + + async toggle() { + try { + await this.#fetch(`toggle`); + this.isPaused = !this.isPaused; + + this.#html.apiError = null; + this.#html.toggle(); + } catch {} + } + + async remove(index) { + try { + const list = await this.#fetch(`remove?i=${index}`); + + this.#html.apiError = null; + await this.updateList(list); + } catch {} + } + + async shuffle() { + try { + const list = await this.#fetch(`shuffle`); + + this.#html.apiError = null; + await this.updateList(list); + } catch {} + } + + async skip() { + try { + const list = await this.#fetch(`skip`); + + this.#html.apiError = null; + await this.updateList(list); + } catch {} + } + + async updateList(list = null) { + this.list = list ?? await this.#fetch('list'); + this.#html.updateList(); + } +} diff --git a/dashboard/assets/js/music/music.js b/dashboard/assets/js/music/music.js new file mode 100644 index 0000000..d49ed16 --- /dev/null +++ b/dashboard/assets/js/music/music.js @@ -0,0 +1,22 @@ +$(async () => { + const music = new MusicWrapper(); + await music.updateList(); + + $('#skipTrack').on('click', () => music.skip()); + $('#toggleTrack').on('click', () => music.toggle()); + $('#shuffleList').on('click', () => music.shuffle()); + $('#stopTrack').on('click', () => music.stop()); + $('#trackSearch').on('click', async () => { + const query = $('.q-control input').val(); + await music.play(query); + }); + + $('#seekTrack input').on('input', async function() { + const to = +$(this).val(); + await music.seek(to); + }); + $('#volume input').on('input', async function() { + const value = +$(this).val(); + await music.setVolume(value); + }); +}); diff --git a/dashboard/assets/js/sidebar.js b/dashboard/assets/js/sidebar.js new file mode 100644 index 0000000..a10d518 --- /dev/null +++ b/dashboard/assets/js/sidebar.js @@ -0,0 +1,3 @@ +$('.hamburger').on('click', function() { + $('#sidebarExtension').toggleClass('closed'); +}); \ No newline at end of file diff --git a/dashboard/assets/js/theme.js b/dashboard/assets/js/theme.js new file mode 100644 index 0000000..79545f5 --- /dev/null +++ b/dashboard/assets/js/theme.js @@ -0,0 +1,11 @@ +$('#themeSelect').on('change', function() { + setTheme($(this).val()); +}); + +function setTheme(theme) { + localStorage.setItem('theme', theme); + $('#themeSelect').val(theme); + $('html').attr('theme', theme); +} + +setTheme(localStorage.getItem('theme')); diff --git a/dashboard/modules/api-utils.js b/dashboard/modules/api-utils.js new file mode 100644 index 0000000..14e9cba --- /dev/null +++ b/dashboard/modules/api-utils.js @@ -0,0 +1,4 @@ +module.exports.sendError = (res, { + code = 400, + message = 'An unknown error occurred.' +}) => res.status(400).json({ code, message }); diff --git a/dashboard/modules/audit-logger.js b/dashboard/modules/audit-logger.js new file mode 100644 index 0000000..31737a5 --- /dev/null +++ b/dashboard/modules/audit-logger.js @@ -0,0 +1,9 @@ +const logs = require('../../data/logs'); + +module.exports = new class { + async change(id, change) { + const log = await logs.get(id); + log.changes.push(change); + await log.save(); + } +} diff --git a/dashboard/modules/auth-client.js b/dashboard/modules/auth-client.js new file mode 100644 index 0000000..17b97af --- /dev/null +++ b/dashboard/modules/auth-client.js @@ -0,0 +1,8 @@ +const OAuthClient = require('disco-oauth'); +const config = require('../../config.js'); + +const client = new OAuthClient(config.bid, config.secret); +client.setRedirect(`${config.dashboardURL}/auth`); +client.setScopes('identify', 'guilds'); + +module.exports = client; \ No newline at end of file diff --git a/dashboard/modules/middleware.js b/dashboard/modules/middleware.js new file mode 100644 index 0000000..f8902e6 --- /dev/null +++ b/dashboard/modules/middleware.js @@ -0,0 +1,137 @@ +const sessions = require('./sessions'); +const { sendError } = require('./api-utils'); +const bot = require('../../xp'); +const musicHandler = require('../../handlers/music-handler'); + +module.exports.updateGuilds = async (req, res, next) => { + try { + const key = res.cookies.get('key') + || req.get('Authorization'); + if (key) { + const { guilds } = await sessions.get(key); + res.locals.guilds = guilds; + } + } finally { + return next(); + } +}; + +module.exports.updateUser = async (req, res, next) => { + try { + const key = res.cookies.get('key') + req.get('Authorization'); + if (key) { + const { authUser } = await sessions.get(key); + res.locals.user = authUser; + } + } finally { + return next(); + } +}; + +module.exports.updateMusicPlayer = async (req, res, next) => { + try { + const requestor = bot.guilds.cache + .get(req.params.id).members.cache.get(res.locals.user.id) + + if (!requestor) + throw new TypeError('You shall not pass.'); + + res.locals.requestor = requestor; + res.locals.player = function(options){ + musicHandler.get({ + guildId: req.params.id, + voiceChannel: !requestor.voice ? null : requestor.voice.channel, + userxd : requestor, + track: options.songname, + res: res + }); + } + res.locals.player.play = function(options){ + + + musicHandler.play({ + guildId: req.params.id, + voiceChannel: !requestor.voice ? null : requestor.voice.channel, + track: options.songname, + userxd : requestor, + res: res + }); + } + + res.locals.player.isPaused = function(){ + let queue = bot.queue.get(req.params.id); + if(queue.playing !== false) + { + return false; + } + else { + return true; + } + + } + res.locals.player.shuffle = function() + { + let queue = bot.queue.get(req.params.id); + + let songs = queue.songs; + for (let i = songs.length - 1; i > 1; i--) { + let j = 1 + Math.floor(Math.random() * i); + [songs[i], songs[j]] = [songs[j], songs[i]]; + } + queue.songs = songs; + bot.queue.set(req.params.id, queue); + } + res.locals.player.settings = function(){ + let queue = bot.queue.get(req.params.id); + return queue; + } + + res.locals.player.skip = function(){ + let queue = bot.queue.get(req.params.id); + queue.connection.dispatcher.end('Okie skipped!') + } + res.locals.player.settings.volume = function(options){ + let queue = bot.queue.get(req.params.id); + queue.connection.dispatcher.setVolumeLogarithmic(options.rate); + } + res.locals.player.end = function(){ + let queue = bot.queue.get(req.params.id); + queue.songs = [] + queue.connection.dispatcher.end("done"); + } + res.locals.player.queue = function(){ + let queue = bot.queue.get(req.params.id); + return queue.songs; + } + res.locals.player.pause = function(){ + let queue = bot.queue.get(req.params.id); + queue.connection.dispatcher.pause(); + } + res.locals.player.resume = function(){ + let queue = bot.queue.get(req.params.id); + queue.connection.dispatcher.resume(); + } + res.locals.player.stop = function(){ + let queue = bot.queue.get(req.params.id); + queue.connection.dispatcher.end("done"); + } + return next(); + } catch (error) { + + sendError(res, { message: error.message }); + } +}; + +module.exports.validateGuild = async (req, res, next) => { + res.locals.guild = res.locals.guilds.find(g => g.id === req.params.id); + return (res.locals.guild) + ? next() + : res.render('errors/404'); +}; + +module.exports.validateUser = async (req, res, next) => { + return (res.locals.user) + ? next() + : res.render('errors/401'); +}; \ No newline at end of file diff --git a/dashboard/modules/rate-limiter.js b/dashboard/modules/rate-limiter.js new file mode 100644 index 0000000..2d5bec7 --- /dev/null +++ b/dashboard/modules/rate-limiter.js @@ -0,0 +1,10 @@ +const rateLimit = require('express-rate-limit'); +const RateLimitStore = require('rate-limit-mongo'); +const config = require('../../config.js'); + +module.exports = rateLimit({ + max: 300, + message: 'You are being rate limited.', + store: new RateLimitStore({ uri: config.mongourl }), + windowMs: 60 * 1000 +}); diff --git a/dashboard/modules/sessions.js b/dashboard/modules/sessions.js new file mode 100644 index 0000000..e72ed51 --- /dev/null +++ b/dashboard/modules/sessions.js @@ -0,0 +1,40 @@ +const authClient = require('./auth-client'); +const bot = require('../../xp'); + +const sessions = new Map(); + +function get(key) { + return sessions.get(key) || create(key); +} + +async function create(key) { + setTimeout(() => sessions.delete(key), 5 * 60 * 1000); + await update(key); + + return sessions.get(key); +} + +async function update(key) { + return sessions + .set(key, { + authUser: await authClient.getUser(key), + guilds: getManageableGuilds(await authClient.getGuilds(key)) + }); +} + +function getManageableGuilds(authGuilds) { + const guilds = []; + for (const id of authGuilds.keys()) { + const isManager = authGuilds + .get(id).permissions + .includes('MANAGE_GUILD'); + const guild = bot.guilds.cache.get(id); + if (!guild || !isManager) continue; + + guilds.push(guild); + } + return guilds; +} + +module.exports.get = get; +module.exports.update = update; \ No newline at end of file diff --git a/dashboard/routes/auth-routes.js b/dashboard/routes/auth-routes.js new file mode 100644 index 0000000..144ced8 --- /dev/null +++ b/dashboard/routes/auth-routes.js @@ -0,0 +1,41 @@ +const config = require('../../config.js'); +const express = require('express'); +const authClient = require('../modules/auth-client'); +const sessions = require('../modules/sessions'); + +const router = express.Router(); + +router.get('/invite', (req, res) => + res.redirect(`https://discord.com/api/oauth2/authorize?client_id=874338511464046622&permissions=0&redirect_uri=https%3A%2F%2Fdyno-clone.dhvitop.repl.co%2Fauth&scope=bot`)); + +router.get('/login', (req, res) => + res.redirect(`https://discord.com/api/oauth2/authorize?client_id=874338511464046622&redirect_uri=https%3A%2F%2Fdyno-clone.dhvitop.repl.co%2Fauth&response_type=code&scope=identify%20guilds`)); + +router.get('/auth-guild', async (req, res) => { + try { + const key = res.cookies.get('key'); + await sessions.update(key); + } finally { + res.redirect('/dashboard'); + } +}); + +router.get('/auth', async (req, res) => { + try { + const code = req.query.code; + const key = await authClient.getAccess(code); + + res.cookies.set('key', key); + res.redirect('/dashboard'); + } catch { + res.redirect('/'); + } +}); + +router.get('/logout', (req, res) => { + res.cookies.set('key', ''); + + res.redirect('/'); +}); + +module.exports = router; \ No newline at end of file diff --git a/dashboard/routes/dashboard-routes.js b/dashboard/routes/dashboard-routes.js new file mode 100644 index 0000000..420bfbf --- /dev/null +++ b/dashboard/routes/dashboard-routes.js @@ -0,0 +1,44 @@ +const express = require('express'); +const { validateGuild, updateMusicPlayer } = require('../modules/middleware'); +const log = require('../modules/audit-logger'); +const guilds = require('../../data/guilds'); +const logs = require('../../data/logs'); +const bot = require('../../xp'); + +const router = express.Router(); + +router.get('/dashboard', (req, res) => res.render('dashboard/index')); + +router.get('/servers/:id', validateGuild, updateMusicPlayer, + async (req, res) => res.render('dashboard/show', { + savedGuild: await guilds.get(req.params.id), + savedLog: await logs.get(req.params.id), + users: bot.users.cache, + player: res.locals.player, + key: res.cookies.get('key') + })); + +router.put('/servers/:id/:module', validateGuild, async (req, res) => { + try { + const { id, module } = req.params; + + const savedGuild = await guilds.get(id); + + await log.change(id, { + at: new Date(), + by: res.locals.user.id, + module, + new: {...savedGuild[module]}, + old: {...req.body} + }); + + savedGuild[module] = req.body; + await savedGuild.save(); + + res.redirect(`/servers/${id}`); + } catch { + res.render('errors/400'); + } +}); + +module.exports = router; \ No newline at end of file diff --git a/dashboard/routes/music-routes.js b/dashboard/routes/music-routes.js new file mode 100644 index 0000000..6ed9128 --- /dev/null +++ b/dashboard/routes/music-routes.js @@ -0,0 +1,123 @@ +const express = require('express'); +const { sendError } = require('../modules/api-utils'); + +const router = express.Router({ mergeParams: true }); + +router.get('/play', async (req, res) => { + try { + let test = !res.locals.requestor.voice ? false : true; + if(test === false) + { + + + sendError(res, { message: "You Should be Connected to Any Voice Channel on The Selected Guild" }); + } + const track = await res.locals.player.play({songname : req.query.q, userxd: res.locals.requestor}); + res.json({ message: `${track}`}); + } catch (error) { + console.log(error); + sendError(res, error); + } +}); + +router.get('/stop', async (req, res) => { + try { + res.locals.player.end(); + + res.json({ code: 200, message: 'Success!' }); + } catch (error) { + console.log(error); + sendError(res, error); + } +}); + +router.get('/toggle', async (req, res) => { + try { + + if(res.locals.player.isPaused === true) + { + + res.locals.player.resume(); + } + else { + res.locals.player.pause(); + } + + + + res.json({ message: 'Success' }); + } catch (error) { + console.log(error); + sendError(res, error); + } +}); + +router.get('/volume', async (req, res) => { + try { + res.locals.player.settings.volume({rate:req.query.v}); + + res.json({ message: 'Success' }); + } catch (error) { + console.log(error); + sendError(res, error); + } +}); + +router.get('/seek', async (req, res) => { + try { + + let queue = res.locals.player.settings; + queue.connection.dispatcher.end('Okie skipped!') + + res.json({ message: 'Success' }); + } catch (error) { + console.log(error); + sendError(res, error); + } +}); + +router.get('/list', async (req, res) => { + try { + res.json({ message: `${res.locals.player.queue}`}); + } catch (error) { + console.log(error); + sendError(res, error); + } +}); + +router.get('/remove', async (req, res) => { + try { + const index = +req.query.i; + let queue = res.locals.player.settings; + queue.connection.dispatcher.stop('Okie skipped!') + + res.json({ message: 'Success' }); + } catch (error) { + console.log(error); + sendError(res, error); + } +}); + +router.get('/shuffle', async (req, res) => { + try { + res.locals.player.shuffle(); + + res.json({ message: `${res.locals.player.settings.songs}` }); + } catch (error) { + console.log(error); + sendError(res, error); + } +}); + +router.get('/skip', async (req, res) => { + try { + await res.locals.player.skip(); + + res.json({ message: `${res.locals.player.settings.songs}` }); + } catch (error) { + console.log(error); + sendError(res, error); + } +}); + +module.exports = router; diff --git a/dashboard/routes/root-routes.js b/dashboard/routes/root-routes.js new file mode 100644 index 0000000..e22773c --- /dev/null +++ b/dashboard/routes/root-routes.js @@ -0,0 +1,48 @@ +const express = require('express'); +const {readdirSync} = require("fs"); +const bot = require('../../xp'); +const users = require('../../data/users'); +const commands = new Map(); + readdirSync('./commands/').forEach(dir => { + + const commandsxd = readdirSync(`./commands/${dir}/`).filter(file => file.endsWith('.js')); + for(let file of commandsxd){ + let pull = require(`../../commands/${dir}/${file}`); + + if(pull){ + commands.set(pull.name, pull); + + + } + } + }); + +const router = express.Router(); + +router.get('/', (req, res) => res.render('index')); + +router.get('/commands', (req, res) => res.render('commands', { + subtitle: 'Commands', + categories: [ + { name: 'Auto Mod', icon: 'fas fa-gavel' }, + { name: 'Economy', icon: 'fas fa-coins' }, + { name: 'General', icon: 'fas fa-star' }, + { name: 'Music', icon: 'fas fa-music' } + ], + commands: Array.from(commands.values()), + commandsString: JSON.stringify(Array.from(commands.values())) +})); + +router.get('/leaderboard/:id', async (req, res) => { + const guildxd = bot.guilds.cache.get(req.params.id); + if (!guildxd) + return res.render('errors/404'); + + const savedUsers = (await users.getInGuild(req.params.id)) + .sort((a, b) => (a.coins < b.coins) ? 1 : -1) + .slice(0, 100); + + res.render('dashboard/leaderboard', { guildxd, savedUsers }); +}); + +module.exports = router; diff --git a/dashboard/server.js b/dashboard/server.js new file mode 100644 index 0000000..1c6a9a9 --- /dev/null +++ b/dashboard/server.js @@ -0,0 +1,46 @@ +const bodyParser = require('body-parser'); +const cookies = require('cookies'); +const express = require('express'); +const methodOverride = require('method-override'); +const middleware = require('./modules/middleware'); +const rateLimit = require('./modules/rate-limiter'); +const { sendError } = require('./modules/api-utils'); + +const authRoutes = require('./routes/auth-routes'); +const dashboardRoutes = require('./routes/dashboard-routes'); +const rootRoutes = require('./routes/root-routes'); +const musicRoutes = require('./routes/music-routes'); + +const app = express(); + +app.set('views', __dirname + '/views'); +app.set('view engine', 'pug'); + +app.use(rateLimit); +app.use(bodyParser.urlencoded({ extended: true })); +app.use(methodOverride('_method')); +app.use(cookies.express('a', 'b', 'c')); + +app.use(express.static(`${__dirname}/assets`)); +app.locals.basedir = `${__dirname}/assets`; + +app.use('/api/guilds/:id/music', + middleware.updateUser, + middleware.validateUser, + middleware.updateGuilds, + middleware.validateGuild, + middleware.updateMusicPlayer, + musicRoutes +); +app.use('/api', (req, res) => res.json({ hello: 'earth' })); +app.use('/api/*', (req, res) => sendError(res, { code: 404, message: 'Not found.' })); + +app.use('/', + middleware.updateUser, rootRoutes, + authRoutes, + middleware.validateUser, middleware.updateGuilds, dashboardRoutes +); +app.all('*', (req, res) => res.render('errors/404')); + +const port = process.env.PORT || 3000; +app.listen(port, () => console.log(`Server is live on port ${port}`)); \ No newline at end of file diff --git a/dashboard/views/commands.pug b/dashboard/views/commands.pug new file mode 100644 index 0000000..26bc90e --- /dev/null +++ b/dashboard/views/commands.pug @@ -0,0 +1,37 @@ +include includes/mixins + +doctype +html(lang='en') + head + include includes/header.pug + + script(src='https://cdn.jsdelivr.net/npm/fuse.js/dist/fuse.js', defer) + script(src='/js/commands.js', defer) + script(defer). + let commands = !{commandsString}; + + link(rel='stylesheet', href='/css/commands.css') + body + include includes/navbar.pug + .container + .jumbotron.text-center.bg-transparent + h1.display-3 Commands + p.lead View the commands for 1PG. + hr + + section#commands + .d-flex.justify-content-center + #search.form-group.p-3 + input.form-control(type='search') + button.btn + i.fas.fa-search + .row + .col-sm-3.categories + ul.list-group.mb-2 + each category in categories + +category(category) + .col-sm-9.commands + ul.list-group.mb-2 + each command in commands + +command(command) + p#commandError \ No newline at end of file diff --git a/dashboard/views/dashboard/extensions/audit-log.pug b/dashboard/views/dashboard/extensions/audit-log.pug new file mode 100644 index 0000000..54211a0 --- /dev/null +++ b/dashboard/views/dashboard/extensions/audit-log.pug @@ -0,0 +1,21 @@ +section#auditLogModule.module.container.px-5 + .jumbotron.bg-transparent.pb-0 + h1.display-4.text-center Audit Log + .px-5.mt-5.d-flex + table.table.table-responsive-xl + thead + tr + th(scope='col') # + th(scope='col') At + th(scope='col') By + th(scope='col') Old + th(scope='col') New + tbody + each change, i in savedLog.changes.reverse() + tr + if i < 3 + th(scope='row') #{savedLog.changes.length - i} + td #{new Date(change.at).toLocaleString()} + td #[+user(users.get(change.by))] + td #[pre #[code #{JSON.stringify(change.old, null, 2)}]] + td #[pre #[code #{JSON.stringify(change.new, null, 2)}]] \ No newline at end of file diff --git a/dashboard/views/dashboard/index.pug b/dashboard/views/dashboard/index.pug new file mode 100644 index 0000000..1dafc3c --- /dev/null +++ b/dashboard/views/dashboard/index.pug @@ -0,0 +1,29 @@ +doctype +html(lang='en') + head + include ../includes/header.pug + + script(src='/js/sidebar.js', defer) + link(rel='stylesheet', href='/css/sidebar.css') + body + include ../includes/sidebar.pug + + #sidebarExtension + header.text-center.pt-4 + .large-icon.bg-white.round + img.round(src=user.avatarUrl(128), alt=user.username) + h4.pt-2 #{user.username} + span.text-muted #{'#' + user.discriminator.toString().padStart(4, '0')} + + .p-4 + label Theme + select#themeSelect.form-control + option(value='ASCETIC', selected) Ascetic (Default) + option(value='DISCORD') Discord + + include ../includes/navbar.pug + + .container.jumbotron.text-center.bg-transparent + h1.display-3 Dashboard + hr + p.lead Manage your servers with the 1PG dashboard. \ No newline at end of file diff --git a/dashboard/views/dashboard/leaderboard.pug b/dashboard/views/dashboard/leaderboard.pug new file mode 100644 index 0000000..c246498 --- /dev/null +++ b/dashboard/views/dashboard/leaderboard.pug @@ -0,0 +1,25 @@ +doctype +html(lang='en') + head + include ../includes/header.pug + + link(rel='stylesheet', href='/css/leaderboard.css') + body + include ../includes/navbar.pug + header.text-center + h1 + + span #{guildxd.name} Leaderboard + p.lead View the top 100 richest users in #{guildxd.name}. + + ul.list-group.container.mt-5 + each savedUser, index in savedUsers + - var user = guildxd.members.cache.get(savedUser.id).user; + if user + li.list-group-item + span + strong.round.mr-3 #{index + 1} + img.round.user-avatar.mr-2(src=user.displayAvatarURL({ dynamic: true })) + span.lead #{user.username} + span.text-muted ##{user.discriminator} + span.float-right.pt-2 #{savedUser.coins} #[i.fas.fa-coins.text-warning] \ No newline at end of file diff --git a/dashboard/views/dashboard/modules/auto-mod.pug b/dashboard/views/dashboard/modules/auto-mod.pug new file mode 100644 index 0000000..adb2b6e --- /dev/null +++ b/dashboard/views/dashboard/modules/auto-mod.pug @@ -0,0 +1,5 @@ +section#autoModModule.module.container.px-5 + .jumbotron.bg-transparent.pb-0 + h1.display-4.text-center Auto-mod + .form-group.mt-5 + .row \ No newline at end of file diff --git a/dashboard/views/dashboard/modules/economy.pug b/dashboard/views/dashboard/modules/economy.pug new file mode 100644 index 0000000..100d1aa --- /dev/null +++ b/dashboard/views/dashboard/modules/economy.pug @@ -0,0 +1,5 @@ +section#economyModule.module.container.px-5 + .jumbotron.bg-transparent.pb-0 + h1.display-4.text-center Economy + .form-group.mt-5 + .row \ No newline at end of file diff --git a/dashboard/views/dashboard/modules/general.pug b/dashboard/views/dashboard/modules/general.pug new file mode 100644 index 0000000..4e40e3a --- /dev/null +++ b/dashboard/views/dashboard/modules/general.pug @@ -0,0 +1,22 @@ +section#generalModule.module.container.px-5 + form(action='/servers/' + guild.id + '/general?_method=PUT', method='POST') + .jumbotron.bg-transparent.pb-0 + h1.display-4.text-center General + .form-group.my-5 + .row + .col-4 + label Prefix + input.form-control(type='text', name='prefix', value=savedGuild.general.prefix, + maxlength='5', required) + .col-4 + label Blacklisted Channels + input(name='blacklistedChannelIds', type='hidden', value='') + select.form-control(name='blacklistedChannelIds[]', multiple) + each channel of Array.from(guild.channels.cache.filter(c => c.type === 'text').values()) + if savedGuild.general.blacklistedChannelIds.includes(channel.id) + option(value=channel.id, selected) ##{channel.name} + else + option(value=channel.id) ##{channel.name} + .col-4 + .d-flex.justify-content-center + button.btn.btn-success #[i.fas.fa-rocket] Save \ No newline at end of file diff --git a/dashboard/views/dashboard/modules/music.pug b/dashboard/views/dashboard/modules/music.pug new file mode 100644 index 0000000..4a66f62 --- /dev/null +++ b/dashboard/views/dashboard/modules/music.pug @@ -0,0 +1,38 @@ +section#musicModule.module.container.px-5 + .jumbotron.bg-transparent.pb-0 + h1.display-4.text-center Music + .form-group.mt-5 + + - const track = ((!player.settings.songs ? 0 : player.settings.songs.length) > 0) ? player.settings.songs[0].title : null; + .card.bg-transparent.shadow.p-3 + #musicAPIError.alert.alert-danger.d-none + .card-header.bg-transparent.row + .col + h5 Player + .now-playing + .col.mt-5 + .controls.row + .col-3 + .col-6.buttons + button#toggleTrack.btn.text-gradient #[i.fas(class=player.isPaused ? 'fa-play' : 'fa-pause')] + button#stopTrack.btn.text-gradient #[i.fas.fa-square] + button#skipTrack.btn.text-gradient #[i.fas.fa-forward] + button#shuffleList.btn.text-gradient #[i.fas.fa-random] + #volume.col-3 + i.fas.fa-volume-up.text-gradient + input.form-control(type='range', value='0', max='1', step='0.1') + #seekTrack + input.form-control(type='range', value=(Math.floor(player.position || 0) / 1000)) + .position + span.current 00:00 + span.text-muted.px-1 / + span.duration.text-muted 00:00 + + .card-body + h5 Queue + .q-control.mb-3 + label Search + input.form-control + button#trackSearch.ml-2.mb-1.btn.btn-gradient + + .track-list + \ No newline at end of file diff --git a/dashboard/views/dashboard/modules/overview.pug b/dashboard/views/dashboard/modules/overview.pug new file mode 100644 index 0000000..d30a6d7 --- /dev/null +++ b/dashboard/views/dashboard/modules/overview.pug @@ -0,0 +1,41 @@ +section#overviewModule.module.px-5 + .form-group.mt-5 + .row.text-center + .col-lg-3.col-md-6 + .border.rounded.m-3.p-3 + p.uppercase + i.fas.fa-user-ninja + strong.ml-1 Owner + p.mb-0 #{guild.owner.user.tag} + .col-lg-3.col-md-6 + .border.rounded.m-3.p-3 + p.uppercase + i.fas.fa-user-alt + strong.ml-1 Members + p.mb-0 #{guild.memberCount} + .col-lg-3.col-md-6 + .border.rounded.m-3.p-3 + p.uppercase + i.fas.fa-at + strong.ml-1 Roles + p.mb-0 #{guild.roles.cache.size} + .col-lg-3.col-md-6 + .border.rounded.m-3.p-3 + p.uppercase + i.fas.fa-hashtag + strong.ml-1 Channels + p.mb-0 #{guild.channels.cache.size} + + .charts.mt-5.px-4 + .card-dark.float-left + h3 Joins/Leaves + .joins-chart + .card-dark.float-left + h3 Punishments + .punishments-chart + .card-dark.float-left + h3 Messages + .messages-chart + .card-dark.float-left + h3 Commands + .commands-chart \ No newline at end of file diff --git a/dashboard/views/dashboard/show.pug b/dashboard/views/dashboard/show.pug new file mode 100644 index 0000000..5c07a04 --- /dev/null +++ b/dashboard/views/dashboard/show.pug @@ -0,0 +1,61 @@ +doctype +html(lang='en') + head + include ../includes/header.pug + include ../includes/mixins.pug + + script(defer). + const commandsLog = [!{savedLog.commands}]; + const messagesLog = [!{savedLog.messages}]; + const punishmentsLog = [!{savedLog.punishments}]; + const joinsLog = [!{savedLog.joins}]; + const leavesLog = [!{savedLog.leaves}]; + const guildId = '#{savedGuild.id}'; + const key = '#{key}'; + + script(src='https://cdn.jsdelivr.net/chartist.js/latest/chartist.min.js', defer) + script(src='https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.3.2/highlight.min.js', defer) + script(src='/js/charts.js', defer) + script(src='/js/sidebar.js', defer) + script(src='/js/guild.js', defer) + + script(src='/js/music/html-music-wrapper.js', defer) + script(src='/js/music/music-wrapper.js', defer) + script(src='/js/music/music.js', defer) + + link(rel='stylesheet', href='https://cdn.jsdelivr.net/chartist.js/latest/chartist.min.css') + link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.3.2/styles/ascetic.min.css') + link(rel='stylesheet', href='/css/sidebar.css') + link(rel='stylesheet', href='/css/charts.css') + link(rel='stylesheet', href='/css/music.css') + body + include ../includes/sidebar.pug + + #sidebarExtension + header.text-center.pt-4 + .large-icon.bg-white.round + if guild.icon + img.round(src=guild.iconURL({ dynamic: true, size: 128 }), alt=guild.name) + else + p.abbr #{guild.nameAcronym} + h4#overview.pt-2 #{guild.name} + .tabs.navbar-nav + .category Modules + a#autoMod.cursor-pointer #[i.fas.fa-gavel.pr-1.text-muted] Auto-mod + a#economy.cursor-pointer #[i.fas.fa-coins.pr-1.text-muted] Economy + a#general.cursor-pointer #[i.fas.fa-star.pr-1.text-muted] General + a#music.cursor-pointer #[i.fas.fa-music.pr-1.text-muted] Music + + .category Other + a#auditLog.cursor-pointer #[i.fas.fa-book.pr-1.text-muted] Audit Log + a.cursor-pointer(href='/leaderboard/' + guild.id) #[i.fas.fa-trophy.pr-1.text-muted] Leaderboard + + include ../includes/navbar.pug + + include modules/overview + include modules/auto-mod + include modules/economy + include modules/general + include modules/music + + include extensions/audit-log \ No newline at end of file diff --git a/dashboard/views/errors/400.pug b/dashboard/views/errors/400.pug new file mode 100644 index 0000000..46e7940 --- /dev/null +++ b/dashboard/views/errors/400.pug @@ -0,0 +1,12 @@ +doctype +html(lang='en') + head + include ../includes/header.pug + + link(rel='stylesheet', href='/css/index.css') + body + .jumbotron.text-center.bg-transparent + h1.display-3 400 + p.lead This planet denied your request. + i.fas.fa-times.pl-2 + a.btn.btn-dark(href='/') Return Home \ No newline at end of file diff --git a/dashboard/views/errors/401.pug b/dashboard/views/errors/401.pug new file mode 100644 index 0000000..66ac1ca --- /dev/null +++ b/dashboard/views/errors/401.pug @@ -0,0 +1,12 @@ +doctype +html(lang='en') + head + include ../includes/header.pug + + link(rel='stylesheet', href='/css/index.css') + body + .jumbotron.text-center.bg-transparent + h1.display-3 401 + p.lead This planet is heavily guarded. + i.fas.fa-exclamation.pl-2 + a.btn.btn-dark(href='/') Return Home \ No newline at end of file diff --git a/dashboard/views/errors/404.pug b/dashboard/views/errors/404.pug new file mode 100644 index 0000000..5d24a1d --- /dev/null +++ b/dashboard/views/errors/404.pug @@ -0,0 +1,12 @@ +doctype +html(lang='en') + head + include ../includes/header.pug + + link(rel='stylesheet', href='/css/index.css') + body + .jumbotron.text-center.bg-transparent + h1.display-3 404 + p.lead You are lost on the wrong planet. + i.fas.fa-question.pl-2 + a.btn.btn-dark(href='/') Return Home \ No newline at end of file diff --git a/dashboard/views/includes/header.pug b/dashboard/views/includes/header.pug new file mode 100644 index 0000000..083d9c4 --- /dev/null +++ b/dashboard/views/includes/header.pug @@ -0,0 +1,17 @@ +meta(charset="UTF-8") +meta(name="viewport", content="width=device-width, initial-scale=1.0") +title 1PG - #{subtitle || 'Best Discord Bot'} + +script(src='https://code.jquery.com/jquery-3.5.1.slim.min.js', integrity='sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj', crossorigin='anonymous', defer) +script(src='https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js', integrity='sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN', crossorigin='anonymous', defer) +script(src='https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js', integrity='sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV', crossorigin='anonymous', defer) + +script(src='/js/theme.js', defer) +script(src='/js/main.js', defer) + +link(rel='stylesheet', href='https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css', integrity='sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z', crossorigin='anonymous') +link(rel='stylesheet', href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css') + +link(rel='stylesheet', href='/css/theme.css') +link(rel='stylesheet', href='/css/main.css') +link(rel='stylesheet', href='/css/utils.css') \ No newline at end of file diff --git a/dashboard/views/includes/mixins.pug b/dashboard/views/includes/mixins.pug new file mode 100644 index 0000000..26ed85f --- /dev/null +++ b/dashboard/views/includes/mixins.pug @@ -0,0 +1,10 @@ +mixin category(category) + li.list-group-item(id=category.name) #[i(class=category.icon)] #{category.name} + +mixin command(command) + li.list-group-item(id=command.name + 'Command', class=command.category) #{command.name} + +mixin user(user) + span(title=user.id) + img.user-avatar.round.mr-2(src=user.displayAvatarURL({ dynamic: true })) + h5.d-inline #{user.username} \ No newline at end of file diff --git a/dashboard/views/includes/navbar.pug b/dashboard/views/includes/navbar.pug new file mode 100644 index 0000000..ee989f6 --- /dev/null +++ b/dashboard/views/includes/navbar.pug @@ -0,0 +1,23 @@ +nav.navbar.navbar-expand-lg + a.navbar-brand(href='/') 1PG + button.navbar-toggler(type='button', data-toggle='collapse', data-target='#navbar', aria-controls='navbar', aria-expanded='false', aria-label='Toggle navigation') + span.navbar-toggler-icon + #navbar.collapse.navbar-collapse + .navbar-nav + a.nav-link(href='/commands') + i.fas.fa-code + span.pl-1 Commands + .navbar-nav.ml-auto + if user + .dropdown + .dropdown-toggle(type='button', data-toggle='dropdown', aria-haspopup='true', aria-expanded='false') + img.user-avatar.round(src=user.avatarUrl(32)) + span.pl-2 #{user.username} + .dropdown-menu.dropdown-menu-right + a.dropdown-item(href='/dashboard') #[i.fas.fa-cogs.text-muted] Dashboard + hr + a.dropdown-item(href='/logout') #[i.fas.fa-user-slash.text-muted] Logout + else + a.nav-link(href='/login') + i.fas.fa-sign-in-alt + span.pl-1 Login \ No newline at end of file diff --git a/dashboard/views/includes/sidebar.pug b/dashboard/views/includes/sidebar.pug new file mode 100644 index 0000000..32129ae --- /dev/null +++ b/dashboard/views/includes/sidebar.pug @@ -0,0 +1,18 @@ +#sidebar.float-left + a(href='/dashboard') + .icon.round.shadow + img.round(alt=user.username, src=user.avatarUrl(64)) + hr + each guild in guilds + a(href='/servers/' + guild.id) + .icon.round.shadow.my-2(data-toggle='tooltip', data-placement='right', title=guild.name) + if guild.icon + img.round(alt=guild.name, src=guild.iconURL({ dynamic: true, size: 64 })) + else + p.text-center.abbr #{guild.nameAcronym} + a(href='/invite') + .icon + p.abbr.text-success.text-center + i.fas.fa-plus + .hamburger.px-1 + i.fas.fa-bars \ No newline at end of file diff --git a/dashboard/views/index.pug b/dashboard/views/index.pug new file mode 100644 index 0000000..0696840 --- /dev/null +++ b/dashboard/views/index.pug @@ -0,0 +1,13 @@ +doctype +html(lang='en') + head + include includes/header.pug + + link(rel='stylesheet', href='/css/index.css') + body + include includes/navbar.pug + .jumbotron.text-center.bg-transparent + h1.display-3 1PG + p.lead The best bot in the known universe and milky way. + i.fas.fa-star.pl-2 + a.btn.btn-dark(href='/invite') Add \ No newline at end of file