├── .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 |
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 |
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 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
Settings
50 |
51 |
52 |
53 | Backup
54 |
55 |
56 |
57 |
58 |
59 |
Your last backup
60 |
Loading...
61 |
62 |
63 |
64 |
65 |
66 |
67 |
70 |
71 |
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 | }
--------------------------------------------------------------------------------