├── .gitignore ├── public ├── join.png ├── logo.png ├── new.png ├── index.html ├── client.js └── style.css ├── .env ├── commands ├── Guilds │ ├── reset.js │ ├── download.js │ ├── backup.js │ ├── install.js │ └── setup.js └── Utility │ ├── invite.js │ ├── ping.js │ ├── prefix.js │ └── help.js ├── package.json ├── events └── guildCreate.js ├── README.md ├── providers └── mongodb.js ├── tasks ├── backup.js └── install.js ├── views └── guild │ └── index.ejs └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /public/join.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sank6/Backup/HEAD/public/join.png -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sank6/Backup/HEAD/public/logo.png -------------------------------------------------------------------------------- /public/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sank6/Backup/HEAD/public/new.png -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # Your mongodb url 2 | MongoDB= 3 | 4 | # Your discord developer application config 5 | DISCORD_TOKEN= 6 | CLIENT_ID= 7 | CLIENT_SECRET= 8 | 9 | # Your bot prefix 10 | PREFIX= 11 | 12 | # Your discord server invite link 13 | INVITE= 14 | 15 | # Your Domain including http/https 16 | DOMAIN= 17 | -------------------------------------------------------------------------------- /commands/Guilds/reset.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | 3 | module.exports = class extends Command { 4 | constructor(...args) { 5 | super(...args, { 6 | runIn: ["text"], 7 | description: "Reset the backups", 8 | permissionLevel: 10 9 | }); 10 | } 11 | async run(message) { 12 | message.client.settings.reset('backups') 13 | message.channel.send(`✅ Reset the backups.`) 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /commands/Utility/invite.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | 3 | module.exports = class extends Command { 4 | 5 | constructor(...args) { 6 | super(...args, { 7 | runIn: ['text'], 8 | aliases: ["inv"], 9 | description: language => language.get('COMMAND_INVITE_DESCRIPTION') 10 | }); 11 | } 12 | 13 | async run(message) { 14 | return message.channel.send(`Add me to your server: <${this.client.invite}>`) 15 | } 16 | 17 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "backup-bot", 3 | "version": "2.0.0", 4 | "main": "server.js", 5 | "scripts": { 6 | "start": "node server.js" 7 | }, 8 | "dependencies": { 9 | "btoa": "*", 10 | "discord.js": "github:discordjs/discord.js", 11 | "dotenv": "*", 12 | "express": "*", 13 | "klasa": "github:dirigeants/klasa", 14 | "mongodb": "*", 15 | "ejs": "*", 16 | "node-fetch": "*" 17 | }, 18 | "engines": { 19 | "node": "12.x" 20 | }, 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /commands/Utility/ping.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | 3 | module.exports = class extends Command { 4 | constructor(...args) { 5 | super(...args, { 6 | aliases: ["pong", "latency"], 7 | botPerms: ["SEND_MESSAGES"], 8 | description: "Check the bot's latency", 9 | }); 10 | } 11 | 12 | async run(message, [...params]) { 13 | let then = Date.now() 14 | let m = await message.channel.send(`Pinging...`); 15 | m.edit(`Pong! \`${Date.now() - then}\`ms`) 16 | } 17 | 18 | }; -------------------------------------------------------------------------------- /events/guildCreate.js: -------------------------------------------------------------------------------- 1 | const { Event } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | const { PREFIX, DOMAIN } = process.env; 4 | 5 | module.exports = class extends Event { 6 | 7 | run(guild) { 8 | let m = new MessageEmbed() 9 | .setColor(0xff0050) 10 | .setDescription(`Welcome to BackupBot. To setup verification, use the \`${PREFIX}setup\` command. For a list of commands, use \`${PREFIX}help\`. You can also use the [website](${DOMAIN}) to manage your server.`) 11 | let channel = guild.systemChannel; 12 | channel.send(m) 13 | } 14 | 15 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archived 2 | This project will no longer be maintained 3 | 4 | 5 | ## Discord Backup Bot 6 | 7 | Backup discord servers and reinstall them from the database. 8 | 9 | ### Features 10 | - Easy-to-use web panel with discord authetication 11 | - Discord bot commands for backup and installation 12 | - Restricted access for admins only 13 | 14 | 15 | ### How to run 16 | 17 | 1. Install [NodeJS and NPM](https://nodejs.org/en/download/). 18 | 2. Install [Git](https://www.atlassian.com/git/tutorials/install-git) 19 | 2. Install the required dependencies: `npm install`. 20 | 3. Configure the environment variables in `.env`. ([MongoDB](https://mongodb.com/atlas) | [Discord Token](https://discordapp.com/developers/applications)) 21 | 4. Run the bot: `npm start`. 22 | -------------------------------------------------------------------------------- /commands/Utility/prefix.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | 3 | module.exports = class extends Command { 4 | 5 | constructor(...args) { 6 | super(...args, { 7 | aliases: ['setPrefix'], 8 | cooldown: 5, 9 | description: 'Change the command prefix the bot uses in your server.', 10 | permissionLevel: 6, 11 | runIn: ['text'], 12 | usage: '[reset|prefix:str{1,10}]' 13 | }); 14 | } 15 | 16 | async run(msg, [prefix]) { 17 | if (prefix === 'reset') return this.reset(msg); 18 | if (msg.guild.settings.prefix === prefix) throw msg.language.get('CONFIGURATION_EQUALS'); 19 | await msg.guild.settings.update('prefix', prefix); 20 | return msg.sendMessage(`The prefix for this guild has been set to \`${prefix}\``); 21 | } 22 | 23 | async reset(msg) { 24 | await msg.guild.settings.update('prefix', this.client.options.prefix); 25 | return msg.sendMessage(`Switched back the guild's prefix back to \`${this.client.options.prefix}\``); 26 | } 27 | 28 | }; -------------------------------------------------------------------------------- /commands/Guilds/download.js: -------------------------------------------------------------------------------- 1 | const { Command, RichDisplay } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | 4 | module.exports = class extends Command { 5 | constructor(...args) { 6 | super(...args, { 7 | description: "Download a backup using its ID", 8 | permissionLevel: 6, 9 | usage: "[ID:string]" 10 | }); 11 | } 12 | async run(message, [ID]) { 13 | let b = JSON.parse(message.client.settings.backups); 14 | let chosen = b.find(b => b.backupID == ID); 15 | if (!chosen) return message.channel.send(`❌ Invalid backup ID`); 16 | else message.delete(); 17 | 18 | let buf = Buffer.from(JSON.stringify(chosen), 'utf8'); 19 | 20 | if (message.guild) { 21 | message.author.send({files: [{attachment: buf, name: `backup-${ID}.json`}]}).then(() => 22 | message.channel.send(`I have sent you a PM with the backup.`) 23 | ).catch(e => 24 | message.channel.send(`I could not send you a PM. Check you have PMs enabled in settings.`) 25 | ) 26 | } else { 27 | message.channel.send(`Here's your backup`, {files: [{attachment: buf, name: `backup-${ID}.json`}]}) 28 | } 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /commands/Guilds/backup.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | 4 | module.exports = class extends Command { 5 | constructor(...args) { 6 | super(...args, { 7 | runIn: ["text"], 8 | description: "Backup the server" 9 | }); 10 | } 11 | async run(message) { 12 | if (!message.member.hasPermission('MANAGE_GUILD')) 13 | return message.channel.send(`You do not have enough permissions to run a backup. You need \`Manage Guild\`.`) 14 | let res = await this.prompt(message) 15 | if (res) { 16 | message.client.tasks.first().run(message.guild, message.author); 17 | message.channel.send(`✅ Backed up the server. Check your DMs for backup key`) 18 | } 19 | } 20 | 21 | async prompt(message) { 22 | let e = new MessageEmbed() 23 | .setColor(0xff0050) 24 | .setDescription(`Are you sure you want to run a backup? It will override all previous backups of this server by all other admins.\nReply with \`confirm\` to continue`) 25 | .setFooter(`Cancelling in 30s`) 26 | message.channel.send(e); 27 | const filter = m => m.author.id === message.author.id; 28 | let msg = await message.channel.awaitMessages(filter, { max: 1, time: 30000, errors: ['time'] }); 29 | msg = msg.first().content.toLowerCase(); 30 | if (msg !== "confirm") message.channel.send('Cancelled') 31 | else return true; 32 | return false; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /commands/Guilds/install.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | 4 | module.exports = class extends Command { 5 | constructor(...args) { 6 | super(...args, { 7 | description: "Install a backups on the server", 8 | permissionLevel: 6, 9 | requiredPermissions: 8, 10 | runIn: ["text"], 11 | usage: "[ID:string]" 12 | }); 13 | } 14 | async run(message, [ID]) { 15 | let b = JSON.parse(message.client.settings.backups); 16 | let chosen = b.find(b => b.backupID == ID); 17 | if (!chosen) return message.channel.send(`❌ Invalid backup ID`); 18 | else message.delete(); 19 | 20 | 21 | if ( 22 | (message.guild.me.roles.cache.find(x => x.managed).rawPosition && 23 | ((message.guild.roles.size - 1) - message.guild.me.roles.cache.find(x => x.managed).rawPosition !== 0)) 24 | ) 25 | return message.channel.send(`❌ I do not have enough permissions to install a backup. Please make sure the role called \`${message.guild.me.roles.find(x => x.managed).name}\` is above all the others in \`Server Settings > Roles\` and try this command again.`) 26 | else if ( 27 | !message.guild.me.roles.cache.find(x => x.managed).rawPosition && 28 | message.guild.me.roles.size > 0 && 29 | ((message.guild.roles.size - 1) - message.guild.me.roles.highest !== 0) 30 | ) 31 | return message.channel.send(`❌ I do not have enough permissions to install a backup. Please make sure my highest role, (\`${message.guild.me.roles.highest.name}\` is above all the others in \`Server Settings > Roles\` and try this command again.`) 32 | let res = await this.prompt(message) 33 | if (!res) return; 34 | 35 | this.client.tasks.find(x => x.name == "install").run(message.guild, ID) 36 | } 37 | 38 | async prompt(message) { 39 | let e = new MessageEmbed() 40 | .setColor(0xff0050) 41 | .setDescription(`Are you sure you want to install a backup. It will override the current server.\nReply with \`confirm\` to continue`) 42 | .setFooter(`Cancelling in 30s`) 43 | message.channel.send(e); 44 | const filter = m => m.author.id === message.author.id; 45 | let msg = await message.channel.awaitMessages(filter, { max: 1, time: 30000, errors: ['time'] }); 46 | msg = msg.first().content.toLowerCase(); 47 | if (msg !== "confirm") message.channel.send('Cancelled') 48 | else return true; 49 | return false; 50 | } 51 | }; 52 | -------------------------------------------------------------------------------- /providers/mongodb.js: -------------------------------------------------------------------------------- 1 | const { Provider, util: { mergeDefault, mergeObjects, isObject } } = require('klasa'); 2 | 3 | const { MongoClient: Mongo } = require('mongodb'); 4 | 5 | module.exports = class extends Provider { 6 | 7 | constructor(...args) { 8 | super(...args, { description: 'Allows use of MongoDB functionality throughout Klasa' }); 9 | this.db = null; 10 | } 11 | 12 | async init() { 13 | const mongoClient = await Mongo.connect(process.env.MongoDB, { useNewUrlParser: true }); 14 | this.db = mongoClient.db(this.client.options.providers.db); 15 | } 16 | 17 | /* Table methods */ 18 | 19 | get exec() { 20 | return this.db; 21 | } 22 | 23 | hasTable(table) { 24 | return this.db.listCollections().toArray().then(collections => collections.some(col => col.name === table)); 25 | } 26 | 27 | createTable(table) { 28 | return this.db.createCollection(table); 29 | } 30 | 31 | deleteTable(table) { 32 | return this.db.dropCollection(table); 33 | } 34 | 35 | /* Document methods */ 36 | 37 | getAll(table, filter = []) { 38 | if (filter.length) return this.db.collection(table).find({ id: { $in: filter } }, { _id: 0 }).toArray(); 39 | return this.db.collection(table).find({}, { _id: 0 }).toArray(); 40 | } 41 | 42 | getKeys(table) { 43 | return this.db.collection(table).find({}, { id: 1, _id: 0 }).toArray(); 44 | } 45 | 46 | get(table, id) { 47 | return this.db.collection(table).findOne(resolveQuery(id)); 48 | } 49 | 50 | has(table, id) { 51 | return this.get(table, id).then(Boolean); 52 | } 53 | 54 | getRandom(table) { 55 | return this.db.collection(table).aggregate({ $sample: { size: 1 } }); 56 | } 57 | 58 | create(table, id, doc = {}) { 59 | return this.db.collection(table).insertOne(mergeObjects(this.parseUpdateInput(doc), resolveQuery(id))); 60 | } 61 | 62 | delete(table, id) { 63 | return this.db.collection(table).deleteOne(resolveQuery(id)); 64 | } 65 | 66 | update(table, id, doc) { 67 | return this.db.collection(table).updateOne(resolveQuery(id), { $set: isObject(doc) ? flatten(doc) : parseEngineInput(doc) }); 68 | } 69 | 70 | replace(table, id, doc) { 71 | return this.db.collection(table).replaceOne(resolveQuery(id), this.parseUpdateInput(doc)); 72 | } 73 | 74 | }; 75 | 76 | const resolveQuery = query => isObject(query) ? query : { id: query }; 77 | 78 | function flatten(obj, path = '') { 79 | let output = {}; 80 | for (const [key, value] of Object.entries(obj)) { 81 | if (isObject(value)) output = Object.assign(output, flatten(value, path ? `${path}.${key}` : key)); 82 | else output[path ? `${path}.${key}` : key] = value; 83 | } 84 | return output; 85 | } 86 | 87 | function parseEngineInput(updated) { 88 | return Object.assign({}, ...updated.map(entry => ({ [entry.data[0]]: entry.data[1] }))); 89 | } 90 | -------------------------------------------------------------------------------- /commands/Guilds/setup.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | 4 | const { DOMAIN } = process.env; 5 | 6 | module.exports = class extends Command { 7 | constructor(...args) { 8 | super(...args, { 9 | runIn: ["text"], 10 | description: "Setup verification for the server", 11 | permissionLevel: 6 12 | }); 13 | } 14 | async run(message) { 15 | if (!message.guild.me.hasPermission(268435472)) 16 | return message.channel.send(`I do not have enough permissions. Make sure I have \`Admin\` settings.`); 17 | 18 | let p = await this.prompt(message); 19 | if (p) { 20 | message.guild.roles.everyone.setPermissions(0) 21 | let role = await message.guild.roles.create({ 22 | data: { 23 | name: "Verified", 24 | position: 0, 25 | permissions: 3072 26 | } 27 | }) 28 | let channel = await message.guild.channels.create("verification", { 29 | type: "text", 30 | topic: "To join the server, you have to follow the steps outlined below.", 31 | position: 0, 32 | permissionOverwrites: [ 33 | { 34 | id: message.guild.id, 35 | allow: 1024, 36 | deny: 2048, 37 | type: "role" 38 | }, 39 | { 40 | id: role.id, 41 | deny: 3072, 42 | type: "role" 43 | } 44 | ] 45 | }); 46 | let e = new MessageEmbed() 47 | .setColor(0xff0050) 48 | .setDescription(`To access the server, you'll need to sign in at ${DOMAIN}/verify/${message.guild.id}`) 49 | channel.send(e) 50 | } 51 | } 52 | 53 | async prompt(message) { 54 | let e = new MessageEmbed() 55 | .setColor(0xff0050) 56 | .setDescription(`Are you sure you want to setup verification? 57 | This will create a verification channel and a verified role. 58 | Users will need to confirm by signing in with discord on the web interface. 59 | Reply with \`confirm\` to continue`) 60 | .setFooter(`Cancelling in 30s`) 61 | message.channel.send(e); 62 | const filter = m => m.author.id === message.author.id; 63 | let msg = await message.channel.awaitMessages(filter, { max: 1, time: 30000, errors: ['time'] }); 64 | msg = msg.first().content.toLowerCase(); 65 | if (msg !== "confirm") message.channel.send('Cancelled') 66 | else return true; 67 | return false; 68 | } 69 | }; 70 | -------------------------------------------------------------------------------- /tasks/backup.js: -------------------------------------------------------------------------------- 1 | const { Task } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | 4 | var generateID = () => { 5 | let text = ""; 6 | let digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 7 | for (var i = 0; i < 15; i++) 8 | text += digits.charAt(Math.floor(Math.random() * digits.length)); 9 | return text; 10 | } 11 | 12 | module.exports = class extends Task { 13 | constructor(...args) { 14 | super(...args, { name: 'backup', enabled: true }); 15 | } 16 | 17 | async run(g2=null, owner=null) { 18 | let old = JSON.parse(this.client.settings.backups) 19 | if (g2 !== null) { 20 | let g = g2.toJSON(); 21 | // Add some detail 22 | g.channels = []; 23 | for (let ch of g2.channels.array()) { 24 | let n = ch.toJSON(); 25 | if (ch.parent) n.parent = ch.parent.name; 26 | n.permissionOverwrites = ch.permissionOverwrites.map(x => { 27 | let y = x.toJSON(); 28 | if (y.type == "role") y.role = g2.roles.get(y.id).name; 29 | return y; 30 | }); 31 | g.channels.push(n) 32 | } 33 | g.emojis = g2.emojis.toJSON(); 34 | g.roles = g2.roles.toJSON(); 35 | g.members = []; 36 | let mem = await g2.members.fetch(); 37 | for (let member of mem.array()) { 38 | let n = {}; 39 | n.roles = member.roles.array() 40 | n.nickname = member.nickname; 41 | n.user = {id: member.user.id} 42 | g.members.push(n) 43 | } 44 | 45 | delete g.client; 46 | delete g.settings; 47 | 48 | var found = false; 49 | var id; 50 | for (let x = 0; x < old.length; x++) { 51 | if (old[x].id === g.id) { 52 | g.backupID = old[x].backupID 53 | old[x] = g; 54 | 55 | // Send Message 56 | let e = new MessageEmbed() 57 | .setColor(0xff0050) 58 | .setDescription(`Here's your backup ID: \`${g.backupID}\`.\nKeep this in a safe place in case you lose access to this chat.`) 59 | if (owner && owner.presence.clientStatus.mobile !== undefined) { 60 | e.setDescription(`📱Here's your backup ID.\nKeep this in a safe place in case you lose access to this chat. To copy it, press and hold the backup code and click \`Copy Message\`.`) 61 | await owner.send(e); 62 | owner.send(g.backupID); 63 | } else if (owner) owner.send(e); 64 | else id = g.backupID; 65 | found = true 66 | } 67 | } 68 | if (!found) { 69 | g.backupID = generateID(); 70 | 71 | // Send Message 72 | let e = new MessageEmbed() 73 | .setColor(0xff0050) 74 | .setDescription(`Here's your backup ID: \`${g.backupID}\`.\nKeep this in a safe place in case you lose access to this chat.`) 75 | if (owner && owner.presence.clientStatus.mobile !== undefined) { 76 | e.setDescription(`📱Here's your backup ID.\nKeep this in a safe place in case you lose access to this chat. To copy it, press and hold the backup code and click \`Copy Message\`.`) 77 | await owner.send(e); 78 | owner.send(g.backupID); 79 | } else if (owner) owner.send(e); 80 | else id = g.backupID; 81 | old.push(g); 82 | } 83 | 84 | if (id) { 85 | this.client.settings.update("backups", JSON.stringify(old)); 86 | return id; 87 | } 88 | } else { 89 | for(let guild of this.client.guilds.array()) { 90 | console.log(guild.name) 91 | let g = guild.toJSON(); 92 | 93 | // Add some detail 94 | g.channels = []; 95 | for (let ch of guild.channels.array()) { 96 | let n = ch.toJSON(); 97 | if (ch.parent) n.parent = ch.parent.name; 98 | n.permissionOverwrites = ch.permissionOverwrites.map(x => { 99 | let y = x.toJSON(); 100 | if (y.type == "role") y.role = guild.roles.get(y.id).name; 101 | return y; 102 | }); 103 | console.log(n.permissionOverwrites) 104 | g.channels.push(n) 105 | } 106 | g.emojis = guild.emojis.toJSON(); 107 | g.roles = guild.roles.toJSON(); 108 | g.members = guild.members.array(); 109 | delete g.client; 110 | var found = false; 111 | for (let oldg of old) { 112 | if (oldg.id === g.id) { 113 | oldg = g; 114 | found = true 115 | } 116 | } 117 | if (!found) old.push(g); 118 | } 119 | } 120 | return this.client.settings.update("backups", JSON.stringify(old)); 121 | } 122 | }; 123 | -------------------------------------------------------------------------------- /tasks/install.js: -------------------------------------------------------------------------------- 1 | const { Task } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | 4 | wait = (ms) => { 5 | return new Promise((resolve, reject) => { 6 | setTimeout(() => { 7 | resolve(true) 8 | }, ms) 9 | }) 10 | } 11 | 12 | module.exports = class extends Task { 13 | constructor(...args) { 14 | super(...args, { name: 'install', enabled: true }); 15 | } 16 | 17 | async run(guild, id) { 18 | let b = JSON.parse(this.client.settings.backups); 19 | let chosen = b.find(b => b.backupID == id); 20 | if (!chosen) return `Invalid backup ID` 21 | 22 | // Emojis 23 | guild.emojis.each(x => x.delete()) 24 | 25 | 26 | // Roles 27 | for (let role of guild.roles.array()) { 28 | try { 29 | if (role.id !== role.guild.id && ! role.managed) { 30 | await role.delete(); 31 | await wait(100); 32 | } 33 | } catch(e) {console.log(role);console.log(e);} 34 | }; 35 | for (let role of chosen.roles) { 36 | if (role.id !== role.guild && ! role.managed) { 37 | try { 38 | await guild.roles.create({ 39 | data: { 40 | name: role.name, 41 | color: role.color, 42 | hoist: role.hoist, 43 | position: role.rawPosition, 44 | permissions: role.permissions, 45 | mentionable: role.mentionable 46 | } 47 | }); 48 | } catch(e) {} 49 | } 50 | } 51 | 52 | 53 | // Channels 54 | let old = guild.channels.array() 55 | for (let ch of old) { 56 | await ch.delete(); 57 | await wait(100); 58 | } 59 | 60 | let categories = chosen.channels.filter(x => x.type == "category"); 61 | let notcategories = chosen.channels.filter(x => x.type !== "category"); 62 | for (let ch of categories) { 63 | for (let ow in ch.permissionOverwrites) { 64 | if (ch.permissionOverwrites[ow].role) 65 | ch.permissionOverwrites[ow].id = guild.roles.find(x => x.name == ch.permissionOverwrites[ow].role).id 66 | } 67 | await guild.channels.create(ch.name, { 68 | type: ch.type, 69 | topic: ch.topic ? ch.topic : "", 70 | nsfw: ch.nsfw, 71 | position: ch.rawPosition, 72 | rateLimitPerUser: ch.rateLimitPerUser, 73 | bitrate: ch.bitrate, 74 | userLimit: ch.userLimit, 75 | permissionOverwrites: ch.permissionOverwrites 76 | }) 77 | } 78 | for (let ch of notcategories) { 79 | for (let ow in ch.permissionOverwrites) { 80 | if (ch.permissionOverwrites[ow].role) 81 | ch.permissionOverwrites[ow].id = guild.roles.find(x => x.name == ch.permissionOverwrites[ow].role).id 82 | } 83 | let cat = guild.channels.array().find(x => x.name == ch.parent && x.type == "category"); 84 | if (ch.parent && cat) { 85 | await guild.channels.create(ch.name, { 86 | type: ch.type, 87 | topic: ch.topic ? ch.topic : "", 88 | nsfw: ch.nsfw, 89 | position: ch.rawPosition, 90 | rateLimitPerUser: ch.rateLimitPerUser, 91 | bitrate: ch.bitrate, 92 | userLimit: ch.userLimit, 93 | parent: cat, 94 | permissionOverwrites: ch.permissionOverwrites 95 | }) 96 | } else 97 | await guild.channels.create(ch.name, { 98 | type: ch.type, 99 | topic: ch.topic ? ch.topic : "", 100 | nsfw: ch.nsfw, 101 | position: ch.rawPosition, 102 | rateLimitPerUser: ch.rateLimitPerUser, 103 | bitrate: ch.bitrate, 104 | userLimit: ch.userLimit, 105 | permissionOverwrites: ch.permissionOverwrites 106 | }) 107 | } 108 | 109 | 110 | // emojis 111 | for (let emoji of chosen.emojis) { 112 | guild.emojis.create(emoji.url, emoji.name) 113 | } 114 | 115 | // Members 116 | let m = await guild.members.fetch() 117 | for (let member of m.array()) { 118 | let equivalent = chosen.members.find(x => x.user.id === member.user.id); 119 | if (equivalent) { 120 | for (let role of equivalent.roles) { 121 | if (role && !role.managed && role.rawPosition !== 0) { 122 | let r = guild.roles.find(x => x.name === role.name); 123 | await member.roles.add(r.id) 124 | } 125 | } 126 | } 127 | } 128 | 129 | 130 | // Management 131 | guild.setName(chosen.name); 132 | guild.setIcon(`${chosen.iconURL.replace('.webp', '.png')}?size=2048`); 133 | guild.setRegion(chosen.region); 134 | guild.setAFKChannel(guild.channels.get(chosen.afkChannelID)); 135 | guild.setAFKTimeout(chosen.afkTimeout); 136 | guild.setVerificationLevel(chosen.verificationLevel); 137 | guild.setExplicitContentFilter(chosen.explicitContentFilter); 138 | guild.setDefaultMessageNotifications(chosen.defaultMessageNotifications); 139 | guild.setSystemChannel(guild.channels.get(chosen.systemChannelID)); 140 | return `Success` 141 | } 142 | }; 143 | -------------------------------------------------------------------------------- /commands/Utility/help.js: -------------------------------------------------------------------------------- 1 | const { Command, RichDisplay, util: { isFunction } } = require('klasa'); 2 | const { MessageEmbed, Permissions } = require('discord.js'); 3 | const PERMISSIONS_RICHDISPLAY = new Permissions([Permissions.FLAGS.MANAGE_MESSAGES, Permissions.FLAGS.ADD_REACTIONS]); 4 | const time = 1000 * 60 * 3; 5 | 6 | String.prototype.capitalize = function() { 7 | return this.charAt(0).toUpperCase() + this.slice(1); 8 | } 9 | 10 | module.exports = class extends Command { 11 | constructor(...args) { 12 | super(...args, { 13 | aliases: ['commands', 'cmd', 'cmds'], 14 | guarded: true, 15 | description: (language) => language.get('COMMAND_HELP_DESCRIPTION'), 16 | usage: '(Command:command)' 17 | }); 18 | this.createCustomResolver('command', (arg, possible, message) => { 19 | if (!arg || arg === '') return undefined; 20 | return this.client.arguments.get('command').run(arg, possible, message); 21 | }); 22 | 23 | // Cache the handlers 24 | this.handlers = new Map(); 25 | } 26 | 27 | async run(message, [command]) { 28 | if (command) { 29 | let desc = isFunction(command.description) ? command.description(message.language) : command.description; 30 | desc += "\n" + message.language.get('COMMAND_HELP_EXTENDED').replace(`Extended Help ::`, ``).replace(`No extended help provided.`, ``); 31 | let e = new MessageEmbed() 32 | .setTitle(`${command.name.capitalize()}`) 33 | .setDescription(desc) 34 | .setTimestamp() 35 | .setColor(0xff0050) 36 | .setFooter(`Requested by ${message.author.tag}`, message.author.displayAvatarURL()) 37 | return message.channel.send({ embed: e }) 38 | } else { 39 | if ((!('all' in message.flags) && message.guild && message.channel.permissionsFor(this.client.user).has(PERMISSIONS_RICHDISPLAY))) { 40 | // Finish the previous handler 41 | const previousHandler = this.handlers.get(message.author.id); 42 | if (previousHandler) previousHandler.stop(); 43 | 44 | const handler = await (await this.buildDisplay(message)).run(await message.send('Loading Commands...'), { 45 | filter: (reaction, user) => user.id === message.author.id, 46 | time 47 | }); 48 | handler.on('end', () => this.handlers.delete(message.author.id)); 49 | this.handlers.set(message.author.id, handler); 50 | return handler; 51 | } 52 | return message.author.send(await this.buildHelp(message), { split: { char: '\n' } }) 53 | .then(() => { if (message.channel.type !== 'dm') message.sendMessage(message.language.get('COMMAND_HELP_DM')); }) 54 | .catch(() => { if (message.channel.type !== 'dm') message.sendMessage(message.language.get('COMMAND_HELP_NODM')); }); 55 | } 56 | } 57 | 58 | async buildDisplay(message) { 59 | const commands = await this._fetchCommands(message); 60 | 61 | const { PREFIX } = message.guildSettings; 62 | const display = new RichDisplay(); 63 | display.addPage(new MessageEmbed() 64 | .setTitle(`Help`) 65 | .setColor(0xff0050) 66 | .setDescription(`Welcome to \`Backup\`. Use [our web interface](${process.env.domain}) to manage your discord servers from anywhere.\nTo check out the commands, scroll along.\nFor more help or support, join our support server: https://discord.gg/AnMenxv`) 67 | ) 68 | for (const [category, list] of commands) { 69 | display.addPage(new MessageEmbed() 70 | .setTitle(`${category} Commands`) 71 | .setColor(0xff0050) 72 | .setDescription(list.map(this.formatCommand.bind(this, message, PREFIX, true)).join('\n')) 73 | ); 74 | } 75 | return display; 76 | } 77 | async buildHelp(message) { 78 | const commands = await this._fetchCommands(message); 79 | const { PREFIX } = message.guildSettings; 80 | 81 | const helpMessage = []; 82 | for (const [category, list] of commands) { 83 | helpMessage.push(`**${category} Commands**:\n`, list.map(this.formatCommand.bind(this, message, PREFIX, false)).join('\n'), ''); 84 | } 85 | return helpMessage.join('\n'); 86 | } 87 | 88 | async _fetchCommands(message) { 89 | const run = this.client.inhibitors.run.bind(this.client.inhibitors, message); 90 | const commands = new Map(); 91 | await Promise.all(this.client.commands.map((command) => { 92 | const category = commands.get(command.category); 93 | if (command.category !== "Admin" && command.category !== "General" && category) category.push(command); 94 | else if (command.category !== "Admin" && command.category !== "General") commands.set(command.category, [command]); 95 | })); 96 | 97 | return commands; 98 | } 99 | 100 | formatCommand(message, PREFIX, richDisplay, command) { 101 | const description = isFunction(command.description) ? command.description(message.language) : command.description; 102 | return richDisplay ? `■ \`${PREFIX}${command.name}\` : ${description}` : `■ **\`${PREFIX}${command.name}\`** : ${description}`; 103 | } 104 | }; -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Backup 16 | 17 | 18 | 19 | 20 | 28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Logout 40 | 41 | 42 |
43 | 44 |
45 |
46 |

Featured

47 |
48 |
49 | 50 |
51 |
52 |

Announcements

53 | 54 | 55 |
56 |

Coming Soon

57 |

Features we'll be adding soon: 58 |

    59 |
  • Add members to installed server automatically
  • 60 |
  • Backup and install messages
  • 61 |
  • Backup and install bans
  • 62 |
63 |

64 |
65 | 66 |
67 |

Welcome

68 |
69 | 70 |
71 |
72 |
73 |
74 | 75 | 76 | 77 |
78 | Login with 79 |
80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /views/guild/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Backup 16 | 17 | 18 | 19 | 20 | 28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Logout 40 | 41 | 42 |
43 | 44 | 45 | 46 | 47 |
48 |
49 |

Settings

50 |

51 | 52 |

53 |

Backup

54 | 55 |
56 |
57 |
58 |
59 |

Your last backup

60 | 61 |
62 |
63 |
64 |
65 | 66 | 67 |
68 | Login with 69 |
70 | 71 |
72 | Invite the bot 73 |
74 |
75 |
76 | 77 | 78 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | require("dotenv").config(); 2 | 3 | const express = require("express"); 4 | const btoa = require("btoa"); 5 | const fetch = require("node-fetch"); 6 | const Discord = require("discord.js"); 7 | const { Client } = require("klasa"); 8 | 9 | const { CLIENT_ID, CLIENT_SECRET, PREFIX, DOMAIN, INVITE } = process.env; 10 | 11 | // Express config 12 | const app = express(); 13 | app.set("view engine", "ejs"); 14 | app.use(express.static("public")); 15 | app.listen("80"); 16 | 17 | // Express endpoints 18 | app.get("/discord", (req, res) => { 19 | res.redirect(INVITE); 20 | }); 21 | 22 | app.get("/api/login", (req, res) => { 23 | res.redirect( 24 | `https://discordapp.com/api/oauth2/authorize?client_id=${CLIENT_ID}&redirect_url=${req.headers.host}/api/callback&response_type=code&scope=identify+guilds+guilds.join` 25 | ); 26 | }); 27 | 28 | app.get("/api/callback", async (req, res) => { 29 | let { code } = req.query; 30 | if (!code) throw new Error("NoCodeProvided"); 31 | let params = new URLSearchParams(); 32 | params.set('grant_type', 'authorization_code'); 33 | params.set('code', code); 34 | let opts = { 35 | method: "POST", 36 | headers: { 37 | Authorization: `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`, 38 | "Content-Type": "application/x-www-form-urlencoded" 39 | }, 40 | body: params.toString() 41 | }; 42 | let response = await fetch( 43 | `https://discordapp.com/api/oauth2/token`, 44 | opts 45 | ); 46 | let json = await response.json(); 47 | res.redirect( 48 | `/api/store?tokens=${encodeURIComponent(JSON.stringify(json))}&getcode=true` 49 | ); 50 | }); 51 | 52 | app.get("/api/code", async (req, res) => { 53 | if (!req.query.tokens) res.redirect("/"); 54 | let c = JSON.parse(req.query.tokens).refresh_token; 55 | const params = new URLSearchParams(); 56 | params.set('grant_type', 'refresh_token'); 57 | params.set('refresh_token', c); 58 | let response = await fetch( 59 | `https://discordapp.com/api/oauth2/token`, 60 | { 61 | method: "POST", 62 | headers: { 63 | Authorization: `Basic ${btoa(`${CLIENT_ID}:${CLIENT_SECRET}`)}`, 64 | "Content-Type": "application/x-www-form-urlencoded" 65 | }, 66 | body: params.toString() 67 | } 68 | ); 69 | let json = await response.json(); 70 | response = await fetch("https://discordapp.com/api/users/@me", { 71 | headers: { 72 | Authorization: `Bearer ${json.access_token}` 73 | } 74 | }); 75 | let user = await response.json(); 76 | res.redirect( 77 | `/api/store?tokens=${encodeURIComponent( 78 | JSON.stringify(json) 79 | )}&user=${encodeURIComponent(JSON.stringify(user))}` 80 | ); 81 | }); 82 | 83 | app.get("/api/join", async (req, res) => { 84 | let g = req.query.guild; 85 | let u = req.query.user; 86 | let token = req.query.token; 87 | let x = await client.guilds.cache.get(g).addMember(u, { 88 | accessToken: token 89 | }); 90 | res.send(x); 91 | }); 92 | 93 | app.get("/api/info", (req, res) => { 94 | if (!req.query.tokens) 95 | return res.json({ 96 | error: "Invalid Token" 97 | }); 98 | fetch("https://discordapp.com/api/users/@me/guilds", { 99 | headers: { 100 | Authorization: `Bearer ${JSON.parse(req.query.tokens).access_token}` 101 | } 102 | }) 103 | .then(res => res.json()) 104 | .then(user => { 105 | if (!user || user.error) 106 | return res.json({ 107 | error: "Invaid bearer token" 108 | }); 109 | res.json(user); 110 | }); 111 | }); 112 | 113 | app.get("/api/store", (req, res) => { 114 | res.send( 115 | `` 122 | ); 123 | }); 124 | 125 | app.get("/api/logout", (req, res) => { 126 | res.send( 127 | `` 128 | ); 129 | }); 130 | 131 | app.get("/api/invite", (req, res) => { 132 | if (req.query.id) 133 | return res.redirect( 134 | `https://discordapp.com/oauth2/authorize?client_id=${CLIENT_ID}&permissions=3080&scope=bot&guild_id=${req.query.id}` 135 | ); 136 | else 137 | return res.redirect( 138 | `https://discordapp.com/oauth2/authorize?client_id=${CLIENT_ID}&permissions=3080&scope=bot` 139 | ); 140 | }); 141 | 142 | app.get("/api/guild/:id", (req, res) => { 143 | if (client.guilds.cache.get(req.params.id) == undefined) 144 | return res.json({ 145 | result: false 146 | }); 147 | else 148 | return res.json({ 149 | result: true 150 | }); 151 | }); 152 | 153 | app.get("/api/install/:guildid", async (req, res) => { 154 | let backupid = req.query.id; 155 | if (!req.query.tokens || !req.query.id || !req.params.guildid) 156 | return res.json({ 157 | error: "Invalid Request" 158 | }); 159 | 160 | let guilds = await fetch( 161 | `${DOMAIN}/api/info?tokens=${encodeURIComponent(req.query.tokens)}` 162 | ); 163 | guilds = await guilds.json(); 164 | let find = guilds.find(x => x.id === req.params.guildid); 165 | if (!find) 166 | return res.json({ 167 | error: "You are not in this guild." 168 | }); 169 | let perms = new Discord.Permissions(find.permissions).has("MANAGE_GUILD"); 170 | if (!perms) 171 | return res.json({ 172 | error: "You don't have enough perms to install backups." 173 | }); 174 | 175 | let guild = client.guilds.cache.get(req.params.guildid); 176 | if ( 177 | guild.roles.size - 1 - guild.me.roles.find(x => x.managed).rawPosition !== 178 | 0 179 | ) 180 | return res.json( 181 | `Error: Please make sure the role called ${ 182 | message.guild.me.roles.find(x => x.managed).name 183 | } is above all the others in Server Settings > Roles and try again.` 184 | ); 185 | let r = await client.tasks 186 | .find(x => x.name === "install") 187 | .run(guild, backupid); 188 | res.json({ 189 | result: r 190 | }); 191 | }); 192 | 193 | app.get("/api/backup/:guildid", async (req, res) => { 194 | if (!req.query.tokens || !req.params.guildid) 195 | return res.json({ 196 | error: "Invalid Request" 197 | }); 198 | 199 | let guilds = await fetch( 200 | `${DOMAIN}/api/info?tokens=${encodeURIComponent(req.query.tokens)}` 201 | ); 202 | guilds = await guilds.json(); 203 | let find = guilds.find(x => x.id === req.params.guildid); 204 | if (!find) 205 | return res.json({ 206 | error: "You are not in this guild." 207 | }); 208 | let perms = new Discord.Permissions(find.permissions).has("MANAGE_GUILD"); 209 | if (!perms) 210 | return res.json({ 211 | error: "You don't have enough perms to backup this guild." 212 | }); 213 | 214 | let guild = client.guilds.cache.get(req.params.guildid); 215 | let r = await client.tasks.find(x => x.name === "backup").run(guild); 216 | res.json({ 217 | result: `Successfully backed up guild. Your backup id is: ${r}` 218 | }); 219 | }); 220 | 221 | app.get("/api/guildinfo/:id", async (req, res) => { 222 | if (!req.query.tokens) 223 | return res.json({ 224 | error: "No token provided" 225 | }); 226 | 227 | let found = JSON.parse(client.settings.backups).find( 228 | guild => guild.id == req.params.id 229 | ); 230 | if (found === undefined) 231 | found = { 232 | error: "No backups found." 233 | }; 234 | 235 | setTimeout(async () => { 236 | let guilds = await fetch( 237 | `${DOMAIN}/api/info?tokens=${encodeURIComponent(req.query.tokens)}` 238 | ); 239 | guilds = await guilds.json(); 240 | if (!Array.isArray(guilds)) 241 | return res.json({ 242 | error: "You are being ratelimited. Reload the page after a few seconds." 243 | }); 244 | let find = guilds.find(x => x.id === req.params.id); 245 | if (!find) 246 | return res.json({ 247 | error: "You are not in this guild." 248 | }); 249 | let perms = new Discord.Permissions(find.permissions).has("MANAGE_GUILD"); 250 | if (!perms) 251 | return res.json({ 252 | error: "You don't have enough perms to view backups." 253 | }); 254 | 255 | return res.json(found); 256 | }, 2000); 257 | }); 258 | 259 | app.get("/info/:id", (req, res) => { 260 | res.render("guild/index", {}); 261 | }); 262 | 263 | app.get("/new", (req, res) => { 264 | res.redirect( 265 | `https://discordapp.com/oauth2/authorize?client_id=${CLIENT_ID}&permissions=3080&scope=bot` 266 | ); 267 | }); 268 | 269 | // Discord bot config 270 | const client = new Client({ 271 | commandEditing: true, 272 | prefix: PREFIX, 273 | disabledCorePieces: ["commands"], 274 | providers: { 275 | default: "mongodb" 276 | }, 277 | gateways: { 278 | clientStorage: { 279 | schema: Client.defaultClientSchema.add("backups", "string", { 280 | default: "[]" 281 | }) 282 | } 283 | } 284 | }); 285 | 286 | client.on("klasaReady", () => { 287 | client.schedule.create("backup", "0 0 * * 0"); 288 | client.user.setPresence({ 289 | activity: { 290 | name: `for ${PREFIX}help`, 291 | type: "WATCHING" 292 | } 293 | }); 294 | }); 295 | 296 | client.login(); 297 | -------------------------------------------------------------------------------- /public/client.js: -------------------------------------------------------------------------------- 1 | var HttpClient = function() { 2 | this.get = function(aUrl, aCallback) { 3 | var anHttpRequest = new XMLHttpRequest(); 4 | anHttpRequest.onreadystatechange = function() { 5 | if (anHttpRequest.readyState == 4 && anHttpRequest.status == 200) 6 | aCallback(anHttpRequest.responseText); 7 | } 8 | 9 | anHttpRequest.open( "GET", aUrl, true ); 10 | anHttpRequest.send( null ); 11 | } 12 | } 13 | 14 | function offset(el) { 15 | var rect = el.getBoundingClientRect(), 16 | scrollLeft = window.pageXOffset || document.documentElement.scrollLeft, 17 | scrollTop = window.pageYOffset || document.documentElement.scrollTop; 18 | return { top: rect.top + scrollTop, left: rect.left + scrollLeft } 19 | } 20 | 21 | function install() { 22 | let backupid = document.getElementById('backupid').value; 23 | let guildid = location.href.split('/info/')[1]; 24 | let x = confirm('Are you sure you want to clear the current guild data?'); 25 | if (!x) return; 26 | var client = new HttpClient(); 27 | client.get(`/api/install/${encodeURIComponent(guildid)}?id=${encodeURIComponent(backupid)}&tokens=${encodeURIComponent(localStorage.tokens)}`, function(response) { 28 | response = JSON.parse(response); 29 | console.log(response); 30 | if (response.error) alert(response.error) 31 | else alert(response.result); 32 | }); 33 | } 34 | 35 | 36 | function backup() { 37 | let guildid = location.href.split('/info/')[1]; 38 | let x = confirm('Are you sure you want to clear overwrite any existing backups?'); 39 | if (!x) return; 40 | var client = new HttpClient(); 41 | client.get(`/api/backup/${encodeURIComponent(guildid)}?tokens=${encodeURIComponent(localStorage.tokens)}`, function(response) { 42 | response = JSON.parse(response); 43 | console.log(response); 44 | if (response.error) alert(response.error) 45 | else alert(response.result); 46 | }); 47 | } 48 | 49 | var justChanged = false; 50 | 51 | var style = (function (style) { 52 | var sheet = document.head.appendChild(style).sheet; 53 | return function (selector, css) { 54 | var propText = typeof css === "string" ? css : Object.keys(css).map(function (p) { 55 | return p + ":" + (p === "content" ? "'" + css[p] + "'" : css[p]); 56 | }).join(";"); 57 | sheet.insertRule(selector + "{" + propText + "}", sheet.cssRules.length); 58 | }; 59 | })(document.createElement("style")); 60 | 61 | 62 | document.body.onload = async function() { 63 | 64 | let user = localStorage.getItem('user'); 65 | if (!user) { 66 | document.getElementById('main').style.display = "none"; 67 | document.getElementById('login').style.display = "block"; 68 | } 69 | if (location.href.includes('info')) { 70 | document.getElementById('content').style.display = "none"; 71 | let guildid = location.href.split('/info/')[1]; 72 | var client = new HttpClient(); 73 | client.get(`/api/guild/${encodeURIComponent(guildid)}`, function(response) { 74 | response = JSON.parse(response); 75 | if (response.result === false) { 76 | document.getElementById('content').style.display = "block"; 77 | document.getElementById('splits').style.display = "none"; 78 | document.getElementById('invite-btn').href = `/api/invite?id=${guildid}` 79 | document.getElementById('invite').style.display = "block"; 80 | } 81 | }) 82 | 83 | 84 | var client2 = new HttpClient(); 85 | client2.get(`/api/guildinfo/${encodeURIComponent(guildid)}?tokens=${encodeURIComponent(localStorage.tokens)}`, function(response) { 86 | response = JSON.parse(response); 87 | document.getElementById('content').style.display = "block"; 88 | let main = document.getElementById('previous'); 89 | main.innerHTML = ""; 90 | if (response.error) { 91 | if (response.error === "You don't have enough perms to view backups.") document.getElementById('form').innerHTML = `

Error

You need Manager Server permissions to edit this server.

` 92 | return main.innerHTML = response.error 93 | } 94 | 95 | if (response.iconURL) { 96 | let img = document.createElement('img'); 97 | img.src = response.iconURL; 98 | img.classList.add('servericon'); 99 | main.appendChild(img); 100 | } 101 | 102 | let h3 = document.createElement('h3'); 103 | h3.innerHTML = response.name; 104 | main.appendChild(h3); 105 | 106 | let channels = document.createElement('div'); 107 | main.appendChild(channels) 108 | 109 | let dropdown = document.createElement('span'); 110 | dropdown.innerHTML = 'Channels '; 111 | dropdown.classList.add('ch-dropdown'); 112 | channels.appendChild(dropdown) 113 | dropdown.onclick = function() { 114 | let first = true 115 | for (let child of channels.children) { 116 | if (first) first = false; 117 | else { 118 | child.style.display = child.style.display === "none" ? "block" : "none"; 119 | } 120 | } 121 | } 122 | for (let channel of response.channels.filter(x => x.type == "category")) { 123 | let category = document.createElement('div'); 124 | category.classList.add('category', "channel"); 125 | category.innerHTML = channel.name; 126 | for (let minic of response.channels.filter(x => x.parentID !== null && x.parentID == channel.id)) { 127 | let ch = document.createElement('div'); 128 | ch.innerHTML = `# ${minic.name}`; 129 | ch.classList.add('mini', "channel"); 130 | category.appendChild(ch); 131 | } 132 | channels.appendChild(category); 133 | } 134 | for (let minic of response.channels.filter(x => x.parentID === null && x.type !== "category")) { 135 | let ch = document.createElement('div'); 136 | ch.classList.add('independant', "channel"); 137 | ch.innerHTML = `#${minic.name}`; 138 | channels.appendChild(ch); 139 | } 140 | dropdown.click(); 141 | 142 | 143 | let roles = document.createElement('div'); 144 | main.appendChild(roles); 145 | dropdown = document.createElement('span'); 146 | dropdown.innerHTML = 'Roles '; 147 | dropdown.classList.add('ch-dropdown'); 148 | roles.appendChild(dropdown) 149 | dropdown.onclick = function() { 150 | let first = true 151 | for (let child of roles.children) { 152 | if (first) first = false; 153 | else { 154 | child.style.display = child.style.display === "none" ? "block" : "none"; 155 | } 156 | } 157 | } 158 | for (let role of response.roles) { 159 | let r = document.createElement('div'); 160 | r.classList.add('role'); 161 | r.style.color = `#${role.color.toString(16)}`; 162 | if (role.name.startsWith('@')) r.innerHTML = role.name; 163 | else r.innerHTML = `@${role.name}`; 164 | roles.appendChild(r); 165 | } 166 | dropdown.click(); 167 | 168 | 169 | 170 | let emojis = document.createElement('div'); 171 | main.appendChild(emojis); 172 | dropdown = document.createElement('span'); 173 | dropdown.innerHTML = 'Emojis '; 174 | dropdown.classList.add('ch-dropdown'); 175 | emojis.appendChild(dropdown) 176 | dropdown.onclick = function() { 177 | let first = true 178 | for (let child of emojis.children) { 179 | if (first) first = false; 180 | else { 181 | child.style.display = child.style.display === "none" ? "block" : "none"; 182 | } 183 | } 184 | } 185 | for (let em of response.emojis) { 186 | let e = document.createElement('div'); 187 | e.classList.add('emoji'); 188 | 189 | let i = document.createElement('img'); 190 | i.classList.add('emoji-img'); 191 | i.src = em.url; 192 | e.appendChild(i); 193 | 194 | let sp = document.createElement('span'); 195 | sp.innerHTML = `:${em.name}:`; 196 | e.appendChild(sp) 197 | 198 | emojis.appendChild(e); 199 | } 200 | dropdown.click(); 201 | }); 202 | }; 203 | 204 | user = JSON.parse(user); 205 | 206 | if (user) { 207 | document.getElementById('username').innerHTML = user.username; 208 | document.getElementById('discrim').innerHTML = "#" + user.discriminator; 209 | document.getElementById('avatar').src = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=128`; 210 | } 211 | 212 | document.getElementById('login-btn').onmouseover = function() { 213 | document.getElementById('login-img').src = "https://discordapp.com/assets/28174a34e77bb5e5310ced9f95cb480b.png"; 214 | } 215 | document.getElementById('login-btn').onmouseout = function() { 216 | document.getElementById('login-img').src = "https://cdn.discordapp.com/attachments/489141650619367434/508286547854884865/discord_greyple.png"; 217 | } 218 | if (document.getElementById('invite-btn')) { 219 | document.getElementById('invite-btn').onmouseover = function() { 220 | document.getElementById('invite-img').src = "https://discordapp.com/assets/28174a34e77bb5e5310ced9f95cb480b.png"; 221 | } 222 | document.getElementById('invite-btn').onmouseout = function() { 223 | document.getElementById('invite-img').src = "https://cdn.discordapp.com/attachments/489141650619367434/508286547854884865/discord_greyple.png"; 224 | } 225 | } 226 | 227 | 228 | if (document.getElementById('settings')) { 229 | document.body.onclick = function() { 230 | let o = document.getElementById('logout') 231 | if (o.classList.contains('see') && !justChanged) { 232 | o.classList.remove('see'); 233 | } 234 | } 235 | document.getElementById('settings').onclick = function() { 236 | let o = document.getElementById('logout'); 237 | if (o.classList.contains('see')) o.classList.remove('see'); 238 | else { 239 | o.classList.add('see'); 240 | justChanged = true; 241 | setTimeout(() => { 242 | justChanged = false 243 | }, 5) 244 | } 245 | } 246 | }; 247 | 248 | //Sidebar 249 | 250 | let span = document.getElementById('my-ones'); 251 | 252 | var client = new HttpClient(); 253 | client.get(`/api/info?tokens=${encodeURIComponent(localStorage.tokens)}`, function(response) { 254 | response = JSON.parse(response); 255 | console.log(response); 256 | if (response.message && response.message === "You are being rate limited.") { 257 | return setTimeout(() => { 258 | location.reload() 259 | }, 2000) 260 | } 261 | if (response.message && response.message === "401: Unauthorized") return location.href = `/api/code?tokens=${encodeURIComponent(localStorage.tokens)}` 262 | if (response.error) location.href = `/api/code?tokens=${encodeURIComponent(localStorage.getItem('tokens'))}` 263 | response.forEach(req => { 264 | let n = document.createElement('li'); 265 | let a = document.createElement('a'); 266 | a.href = `/info/${req.id}`; 267 | 268 | if (!req.icon) { 269 | let rgx = /([ .,\/#!$%\^&\*;:{}=\-_`~()])/g 270 | let initials = req.name.split(rgx).map(x => x.charAt(0)).join(''); 271 | n.innerHTML = initials; 272 | } 273 | 274 | n.style.backgroundImage = `url('${req.icon ? `https://cdn.discordapp.com/icons/${req.id}/${req.icon}.png` : ""}')`; 275 | n.style.backgroundSize = "contain" 276 | a.appendChild(n); 277 | span.appendChild(a); 278 | 279 | let span2 = document.createElement('span'); 280 | span2.classList.add('servername'); 281 | span2.innerHTML = req.name; 282 | span2.style.top = offset(n).top + 17 + "px"; 283 | span2.style.left = offset(n).left + 70 + "px"; 284 | n.appendChild(span2); 285 | 286 | if (location.href.includes(req.id)) { 287 | a.classList.add('activer'); 288 | style('.activer:before', { 289 | top: `${n.getBoundingClientRect().y}px`, 290 | left: `${n.getBoundingClientRect().x - 57}px` 291 | }) 292 | } 293 | 294 | n.onmouseover = function() { 295 | span2.style.top = n.getBoundingClientRect().y + 10 + "px"; 296 | span2.style.left = n.getBoundingClientRect().x + 70 + "px"; 297 | } 298 | }) 299 | }); 300 | 301 | } -------------------------------------------------------------------------------- /public/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: 'Roboto', sans-serif; 3 | background-color: #353A3E; 4 | color: #ffffff; 5 | margin: 0; 6 | user-select: none; 7 | -moz-user-select: none; 8 | -khtml-user-select: none; 9 | -webkit-user-select: none; 10 | -o-user-select: none; 11 | } 12 | 13 | code { 14 | background-color: white; 15 | color: #353A3E; 16 | padding: 2px 5px; 17 | border-radius: 2px; 18 | } 19 | 20 | a { 21 | color: #7289DA; 22 | } 23 | 24 | #main, #login { 25 | margin-left: 80px; 26 | } 27 | 28 | #login { 29 | display: none; 30 | } 31 | 32 | @media screen and (max-width: 700px) { 33 | #main, #login { 34 | margin-left: 10px; 35 | } 36 | } 37 | /* 38 | @media screen and (max-width: 400px) { 39 | #main, #login { 40 | margin-left: 10px; 41 | } 42 | } 43 | */ 44 | #sidebar hr { 45 | margin: 0 10px 0 10px; 46 | background-color: #353A3E; 47 | color: #353A3E; 48 | border: 1px solid #353A3E; 49 | } 50 | 51 | #sidebar ul { 52 | padding: 0; 53 | margin: 0; 54 | } 55 | 56 | #sidebar { 57 | margin: 0; 58 | padding: 0; 59 | width: 70px; 60 | background-color: #25262B; 61 | position: fixed; 62 | height: 100%; 63 | overflow: auto; 64 | z-index: 8; 65 | } 66 | 67 | #sidebar ul a { 68 | padding: 4px 8px; 69 | color: white; 70 | text-decoration: none; 71 | display: table; 72 | } 73 | 74 | #sidebar ul li:not(.hr) { 75 | border-radius: 50%; 76 | background-color: #303136; 77 | padding: 8px; 78 | text-decoration: none; 79 | width: 38px; 80 | height: 38px; 81 | 82 | background-size: contain; 83 | position: relative; 84 | text-align: center; 85 | vertical-align: middle; 86 | display: table-cell; 87 | 88 | transition: 250ms ease all; 89 | } 90 | 91 | #sidebar ul li.hr { 92 | display: block; 93 | color: black; 94 | padding: 8px; 95 | text-decoration: none; 96 | } 97 | 98 | #sidebar ul a:hover:not(.hr) > li { 99 | border-radius: 15px; 100 | color: white; 101 | cursor: pointer; 102 | } 103 | 104 | div.content { 105 | margin-left: 200px; 106 | padding: 1px 16px; 107 | height: 1000px; 108 | } 109 | 110 | @media screen and (max-width: 700px) { 111 | #sidebar { 112 | width: 100%; 113 | height: 71px; 114 | position: relative; 115 | } 116 | #sidebar ul li {float: left;} 117 | #sidebar ul li.hr {display: none !important;} 118 | div.content {margin-left: 0;} 119 | } 120 | 121 | /* 122 | @media screen and (max-width: 400px) { 123 | #sidebar ul li { 124 | text-align: center; 125 | float: none; 126 | display: block !important; 127 | margin: 10px auto !important; 128 | } 129 | } 130 | */ 131 | 132 | /* Sidebar Navigation Done */ 133 | 134 | #profile { 135 | font-size: 0.9em; 136 | width: 270px; 137 | height: 60px; 138 | background-color: #2A292E; 139 | position: fixed; 140 | bottom: 20px; 141 | right: 30px; 142 | box-shadow: 0px 0px 5px 1px #1a1d1e; 143 | transition: box-shadow 0.5s; 144 | z-index: 10; 145 | } 146 | 147 | #profile:hover { 148 | box-shadow: 0px 0px 10px 2px #1a1d1e; 149 | } 150 | 151 | #avatar { 152 | margin: 10px; 153 | border-radius: 50%; 154 | background-color: #303136; 155 | } 156 | 157 | #username { 158 | position: absolute; 159 | margin-top: 15px; 160 | } 161 | 162 | #discrim { 163 | font-size: 0.85em; 164 | position: absolute; 165 | color: #99AAB5; 166 | bottom: 15px; 167 | } 168 | 169 | #settings, #join { 170 | position: absolute; 171 | right: 0; 172 | float: right; 173 | margin: 12px 30px 12px 30px; 174 | padding: 8px; 175 | height: 18px; 176 | border-radius: 4px; 177 | } 178 | 179 | #join { 180 | margin: 12px 80px 12px 0; 181 | } 182 | 183 | #settings:hover, #join:hover { 184 | cursor: pointer; 185 | background-color: #23272A; 186 | } 187 | 188 | #logout { 189 | visibility: hidden; 190 | width: 120px; 191 | background-color: black; 192 | color: #fff; 193 | text-align: center; 194 | border-radius: 6px; 195 | padding: 5px 0; 196 | position: absolute; 197 | z-index: 1; 198 | bottom: 110%; 199 | left: 50%; 200 | margin-left: -60px; 201 | transition: 400ms ease all; 202 | } 203 | 204 | #logout:after { 205 | content: ""; 206 | position: absolute; 207 | top: 100%; 208 | left: 50%; 209 | margin-left: -5px; 210 | border-width: 5px; 211 | border-style: solid; 212 | border-color: black transparent transparent transparent; 213 | } 214 | 215 | #settings:hover #logout, .see { 216 | visibility: visible !important; 217 | } 218 | 219 | @media screen and (max-width: 400px) { 220 | #profile { 221 | right: auto; 222 | } 223 | } 224 | 225 | /* Profile box done */ 226 | 227 | #login, #invite, #invite { 228 | width: calc(100% - 80px); 229 | } 230 | 231 | .btn { 232 | display: block; 233 | text-decoration: none; 234 | font-size: 3em; 235 | padding: 20px 40px; 236 | text-align: center; 237 | border: 3px solid #99AAB5; 238 | color: #99AAB5; 239 | 240 | position: absolute; 241 | left: 50%; 242 | top: 50%; 243 | transform: translate(-50%, -50%); 244 | transition: background-color 0.5s; 245 | } 246 | 247 | .btn:hover { 248 | background-color: #99AAB5; 249 | color: #ffffff; 250 | } 251 | 252 | 253 | @media screen and (max-width: 400px) { 254 | .btn { 255 | position: relative; 256 | left: 0; 257 | top: 0; 258 | transform: translate(0, 0); 259 | margin: 20px; 260 | } 261 | } 262 | 263 | /* Login Button Done */ 264 | 265 | .centered { 266 | position: absolute; 267 | left: 50%; 268 | text-align: center; 269 | transform: translate(-55%, 0); 270 | } 271 | 272 | .very-centered { 273 | top: 50%; 274 | transform: translate(-50%, -50%); 275 | } 276 | 277 | .split { 278 | height: 100%; 279 | width: 50%; 280 | position: fixed; 281 | z-index: 1; 282 | top: 0; 283 | overflow-x: hidden; 284 | padding-top: 20px; 285 | } 286 | 287 | .left { 288 | background-color: #303136; 289 | left: 70px; 290 | } 291 | 292 | .right { 293 | background-color: #353A3E; 294 | left: calc(50% + 70px); 295 | } 296 | 297 | @media screen and (max-width: 700px) { 298 | .split { 299 | position: absolute; 300 | width: 100%; 301 | top: 70px; 302 | } 303 | .left { 304 | left: 0; 305 | } 306 | .right { 307 | top: 100%; 308 | left: 0; 309 | } 310 | .announcement { 311 | width: 80vw !important; 312 | transform: translate(0, 20px); 313 | } 314 | .centered { 315 | transform: translate(-50%, 0); 316 | } 317 | } 318 | 319 | /* Split Screen - Half Done */ 320 | 321 | .announcement { 322 | background-color: #303136; 323 | border-radius: 10px; 324 | padding: 30px; 325 | margin: 20px; 326 | width: calc(50vw - 40px - 70px - 100px); 327 | text-align: left; 328 | } 329 | 330 | .announcement-title { 331 | text-align: center !important; 332 | margin-top: 0; 333 | } 334 | 335 | .end { 336 | height: 100px; 337 | } 338 | 339 | 340 | 341 | 342 | #form { 343 | padding: 50px; 344 | background-color: #303136; 345 | border-radius: 40px; 346 | } 347 | 348 | input[type="text"], .drpdown, input[type="number"] { 349 | box-sizing: border-box; 350 | width: 100%; 351 | background-color: #99AAB5; 352 | border: 0px; 353 | padding: 10px; 354 | transition: background-color 0.5s; 355 | border-radius: 4px; 356 | } 357 | 358 | input[type="text"]:focus, .drpdown:focus, input[type="number"]:focus, textarea:focus { 359 | background-color: #c8ccce !important; 360 | outline: none; 361 | } 362 | 363 | textarea { 364 | background-color: #99AAB5; 365 | box-sizing: border-box; 366 | width: 100%; 367 | min-height: 100px; 368 | padding: 10px; 369 | transition: background-color 0.5s; 370 | border-radius: 4px; 371 | } 372 | 373 | 374 | #submit { 375 | display: block; 376 | text-decoration: none; 377 | font-size: 1em; 378 | padding: 10px 20px; 379 | text-align: center; 380 | border: 3px solid #99AAB5; 381 | color: #99AAB5; 382 | margin-bottom: -10px; 383 | background-color: #303136; 384 | 385 | position: relative; 386 | left: 50%; 387 | transform: translate(-50%, 0); 388 | transition: background-color 0.5s; 389 | } 390 | 391 | #submit:hover { 392 | background-color: #99AAB5; 393 | color: #ffffff; 394 | cursor: pointer; 395 | } 396 | 397 | 398 | @media screen and (max-width: 700px) { 399 | #form { 400 | margin-top: 150px; 401 | max-width: calc(100vw - 130px) !important; 402 | min-width: calc(100vw - 130px) !important; 403 | width: calc(100vw - 130px) !important; 404 | } 405 | } 406 | 407 | .price { 408 | position: fixed; 409 | top: 20px; 410 | } 411 | 412 | .circular { 413 | margin-right: 20px; 414 | box-shadow: 0 0 5px 0 #000000; 415 | padding: 20px; 416 | border-radius: 50%; 417 | background-color: #303136; 418 | } 419 | 420 | .buttons { 421 | float: right; 422 | position: relative; 423 | right: 0; 424 | } 425 | 426 | .button:hover { 427 | cursor: pointer; 428 | } 429 | 430 | .real-button { 431 | border-radius: 5px; 432 | margin: 10px; 433 | background-color: #43b581; 434 | padding: 2px 10px; 435 | transition: 0.2s background-color; 436 | } 437 | 438 | .real-button:hover { 439 | background-color: #04E880; 440 | } 441 | 442 | .no-button:hover { 443 | text-decoration: underline; 444 | } 445 | 446 | li:hover .servername { 447 | display: block; 448 | } 449 | 450 | .servername { 451 | display: none; 452 | position: fixed; 453 | color: white; 454 | background-color: #000; 455 | font-size: 0.7em; 456 | padding: 10px; 457 | border-radius: 7px; 458 | } 459 | 460 | .servername:after { 461 | content: " "; 462 | position: absolute; 463 | top: 50%; 464 | right: 100%; 465 | margin-top: -5px; 466 | border-width: 5px; 467 | border-style: solid; 468 | border-color: transparent black transparent transparent; 469 | } 470 | 471 | ::-webkit-scrollbar { 472 | width: 0; 473 | display: none; 474 | } 475 | 476 | .activer:before { 477 | content: ""; 478 | border-radius: 10px; 479 | background-color: white; 480 | padding: 27px; 481 | position: absolute; 482 | } 483 | 484 | .activer li { 485 | border-radius: 15px !important; 486 | } 487 | 488 | @media screen and (max-width: 700px) { 489 | .activer, .activer:before { 490 | display: none; 491 | } 492 | } 493 | 494 | .switch { 495 | position: relative; 496 | display: inline-block; 497 | width: 42px; 498 | height: 24px; 499 | } 500 | 501 | .switch input { 502 | opacity: 0; 503 | width: 0; 504 | height: 0; 505 | } 506 | 507 | .slider { 508 | position: absolute; 509 | cursor: pointer; 510 | top: 0; 511 | left: 0; 512 | right: 0; 513 | bottom: 0; 514 | background-color: #72767d; 515 | -webkit-transition: 200ms; 516 | transition: 200ms; 517 | } 518 | 519 | .slider:before { 520 | position: absolute; 521 | content: ""; 522 | height: 15px; 523 | width: 15px; 524 | left: 4px; 525 | bottom: 5px; 526 | background-color: white; 527 | -webkit-transition: 200ms; 528 | transition: 200ms; 529 | } 530 | 531 | input:checked + .slider { 532 | background-color: #7289DA; 533 | } 534 | 535 | input:focus + .slider { 536 | box-shadow: 0 0 1px #7289DA; 537 | } 538 | 539 | input:checked + .slider:before { 540 | -webkit-transform: translateX(20px); 541 | -ms-transform: translateX(20px); 542 | transform: translateX(20px); 543 | } 544 | 545 | .slider.round { 546 | border-radius: 34px; 547 | } 548 | 549 | .slider.round:before { 550 | border-radius: 50%; 551 | } 552 | 553 | .new { 554 | background-color: rgba(0, 0, 0, 0) !important; 555 | border: 1px dashed #303136; 556 | } 557 | 558 | .new:hover { 559 | border-color: #fff; 560 | } 561 | 562 | #previous { 563 | padding: 20px; 564 | background-color: #303136; 565 | } 566 | 567 | .channel { 568 | text-align: left; 569 | padding: 10px; 570 | } 571 | 572 | .mini, .independant { 573 | border-radius: 5px; 574 | margin-left: 10px; 575 | } 576 | 577 | .channel:not(.category):hover, .role:hover { 578 | background-color: #353A3E; 579 | } 580 | 581 | .servericon { 582 | width: 50px; 583 | height: 50px; 584 | border-radius: 50%; 585 | } 586 | 587 | .ch-dropdown { 588 | width: calc(100% + 20px); 589 | left: -20px; 590 | padding: 10px; 591 | display: inline-block; 592 | position: relative; 593 | background-color: #303136; 594 | transition: 400ms ease all; 595 | } 596 | 597 | .ch-dropdown:hover { 598 | cursor: pointer; 599 | background-color: #25262B; 600 | } 601 | 602 | .role { 603 | text-align: left; 604 | padding: 3px 5px; 605 | } 606 | 607 | .emoji-img { 608 | width: 20px; 609 | height: 20px; 610 | text-align: left; 611 | padding: 0 10px; 612 | position: relative; 613 | top: 4px; 614 | } --------------------------------------------------------------------------------