├── src ├── bot │ ├── bwd │ │ ├── users_Schema.json │ │ ├── provider │ │ │ └── json │ │ │ │ └── clientStorage │ │ │ │ └── 813855428106649660.json │ │ └── guilds_Schema.json │ ├── events │ │ ├── log.js │ │ ├── userUpdate.js │ │ ├── guildMemberAdd.js │ │ └── guildMemberRemove.js │ ├── inhibitors │ │ ├── nsfw.js │ │ ├── disabled.js │ │ ├── requireMusic.js │ │ ├── properPermission.js │ │ └── requiredProviders.js │ ├── arguments │ │ ├── monitor.js │ │ ├── roleSearch.js │ │ ├── integerCheck.js │ │ ├── userSearch.js │ │ ├── rolename.js │ │ ├── channelname.js │ │ ├── membername.js │ │ └── username.js │ ├── index.js │ ├── commands │ │ ├── Developer │ │ │ ├── exec.js │ │ │ ├── prefix.js │ │ │ ├── blacklist.js │ │ │ └── reload.js │ │ ├── Bot List │ │ │ ├── count.js │ │ │ ├── queue.js │ │ │ ├── marknsfw.js │ │ │ ├── bots.js │ │ │ ├── update.js │ │ │ ├── approve.js │ │ │ ├── botinfo.js │ │ │ ├── certify.js │ │ │ └── uncertify.js │ │ └── help.js │ └── tasks │ │ ├── reminder.js │ │ └── unmute.js ├── public │ ├── dmca-validation.html │ └── assets │ │ ├── img │ │ ├── logo.png │ │ ├── admin.png │ │ ├── banner.png │ │ ├── particles.png │ │ ├── home-banner.gif │ │ ├── default-avatar.png │ │ ├── normal-card-bg.png │ │ ├── verified_badge.png │ │ ├── certified-card-bg.png │ │ └── searching-card-bg.png │ │ ├── js │ │ ├── me.js │ │ ├── edit.js │ │ ├── like.js │ │ ├── admin.js │ │ ├── danloves.js │ │ ├── main.js │ │ ├── confetti.min.js │ │ ├── certify.js │ │ └── cards.js │ │ ├── css │ │ ├── bootstrap-multiselect.css │ │ ├── emojis.css │ │ ├── bootstrap-toaster.css │ │ └── table.css │ │ └── toadloader.js ├── assets │ └── img │ │ └── emoji │ │ ├── dnd.png │ │ ├── idle.png │ │ ├── info.png │ │ ├── list.png │ │ ├── affirm.png │ │ ├── offline.png │ │ ├── online.png │ │ ├── reject.png │ │ ├── arrowLeft.png │ │ ├── arrowRight.png │ │ ├── botBadge.png │ │ ├── partnered.png │ │ ├── verified.png │ │ ├── arrowToLeft.png │ │ └── arrowToRight.png ├── app.js ├── lib │ ├── structures │ │ ├── schemas │ │ │ ├── defaultUserSchema.js │ │ │ ├── defaultClientSchema.js │ │ │ └── permissionLevels.js │ │ └── ModEmbed.js │ ├── RTByteClient.js │ ├── SentryClient.js │ └── util │ │ ├── constants.js │ │ └── Util.js ├── routes │ ├── about.js │ ├── privacy.js │ ├── success.js │ ├── api │ │ ├── admin │ │ │ ├── mark_nsfw.js │ │ │ ├── approve.js │ │ │ └── deny.js │ │ ├── bots │ │ │ ├── list.js │ │ │ ├── search.js │ │ │ ├── delete.js │ │ │ ├── modify.js │ │ │ └── submit.js │ │ ├── avatar.js │ │ ├── theme.js │ │ ├── admin.js │ │ ├── auth.js │ │ ├── bots.js │ │ ├── auth │ │ │ ├── liked.js │ │ │ ├── reset.js │ │ │ ├── index.js │ │ │ └── stats.js │ │ ├── callback.js │ │ ├── cert │ │ │ └── index.js │ │ ├── index.js │ │ ├── like │ │ │ └── index.js │ │ └── embed.js │ ├── join.js │ ├── login.js │ ├── theme.js │ ├── add.js │ ├── certify.js │ ├── admin.js │ ├── bots │ │ ├── resubmit.js │ │ ├── like.js │ │ ├── search.js │ │ └── edit.js │ ├── user.js │ ├── tag │ │ └── index.js │ ├── index.js │ ├── logout.js │ └── me.js ├── dynamic │ ├── theme │ │ ├── dark.css │ │ └── light.css │ ├── includes │ │ ├── head.pug │ │ └── footer.pug │ ├── 403.pug │ └── 404.pug ├── utils │ ├── createAuth.js │ ├── fileWalk.js │ ├── getList.js │ ├── passport.js │ ├── authApi.js │ └── discordApi.js ├── extendables │ ├── GuildMember │ │ ├── fn_GuildMember#canMod.js │ │ └── fn_GuildMember#highestRole.js │ └── KlasaMessage │ │ ├── fn_KlasaMessage#info.js │ │ ├── fn_KlasaMessage#list.js │ │ ├── fn_KlasaMessage#affirm.js │ │ ├── fn_KlasaMessage#reject.js │ │ ├── fn_KlasaMessage#arrowLeft.js │ │ ├── fn_KlasaMessage#arrowRight.js │ │ ├── fn_KlasaMessage#arrowToLeft.js │ │ └── fn_KlasaMessage#arrowToRight.js ├── models │ ├── users.js │ └── bots.js ├── index.js ├── tasks │ ├── reminder.js │ └── unmute.js ├── arguments │ ├── rolename.js │ ├── channelname.js │ ├── username.js │ └── membername.js ├── structures │ └── app.js └── finalizers │ └── devCommandLog.js ├── .replit ├── start.bat ├── SECURITY.md ├── package.json ├── README.md ├── example-config.json ├── English Setup └── README.md └── Turkish Setup └── README.md /src/bot/bwd/users_Schema.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.replit: -------------------------------------------------------------------------------- 1 | language = "nodejs" 2 | run = "npm start" -------------------------------------------------------------------------------- /src/public/dmca-validation.html: -------------------------------------------------------------------------------- 1 | ajVXaFVBaThIbXIwV0xSdXVBa0NEZz090 -------------------------------------------------------------------------------- /src/assets/img/emoji/dnd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/dnd.png -------------------------------------------------------------------------------- /src/assets/img/emoji/idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/idle.png -------------------------------------------------------------------------------- /src/assets/img/emoji/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/info.png -------------------------------------------------------------------------------- /src/assets/img/emoji/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/list.png -------------------------------------------------------------------------------- /src/public/assets/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/public/assets/img/logo.png -------------------------------------------------------------------------------- /src/assets/img/emoji/affirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/affirm.png -------------------------------------------------------------------------------- /src/assets/img/emoji/offline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/offline.png -------------------------------------------------------------------------------- /src/assets/img/emoji/online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/online.png -------------------------------------------------------------------------------- /src/assets/img/emoji/reject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/reject.png -------------------------------------------------------------------------------- /src/bot/bwd/provider/json/clientStorage/813855428106649660.json: -------------------------------------------------------------------------------- 1 | {"id":"813855428106649660","guildBlacklist":[],"userBlacklist":[]} 2 | -------------------------------------------------------------------------------- /src/public/assets/img/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/public/assets/img/admin.png -------------------------------------------------------------------------------- /src/public/assets/img/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/public/assets/img/banner.png -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | color c 3 | title Welcome to Astra Development 4 | node index.js 5 | explorer "https://discord.gg/sQQFSnQhdt" 6 | -------------------------------------------------------------------------------- /src/assets/img/emoji/arrowLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/arrowLeft.png -------------------------------------------------------------------------------- /src/assets/img/emoji/arrowRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/arrowRight.png -------------------------------------------------------------------------------- /src/assets/img/emoji/botBadge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/botBadge.png -------------------------------------------------------------------------------- /src/assets/img/emoji/partnered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/partnered.png -------------------------------------------------------------------------------- /src/assets/img/emoji/verified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/verified.png -------------------------------------------------------------------------------- /src/public/assets/img/particles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/public/assets/img/particles.png -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | const { Client } = require('./index'); 2 | const { config, token } = require('./config'); 3 | 4 | new Client(config).login(token); 5 | -------------------------------------------------------------------------------- /src/assets/img/emoji/arrowToLeft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/arrowToLeft.png -------------------------------------------------------------------------------- /src/assets/img/emoji/arrowToRight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/assets/img/emoji/arrowToRight.png -------------------------------------------------------------------------------- /src/bot/events/log.js: -------------------------------------------------------------------------------- 1 | const { Event } = require('klasa'); 2 | 3 | module.exports = class extends Event { 4 | run() {} 5 | init() {} 6 | }; -------------------------------------------------------------------------------- /src/public/assets/img/home-banner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/public/assets/img/home-banner.gif -------------------------------------------------------------------------------- /src/lib/structures/schemas/defaultUserSchema.js: -------------------------------------------------------------------------------- 1 | const { KlasaClient } = require('klasa'); 2 | 3 | module.exports = KlasaClient.defaultUserSchema; 4 | -------------------------------------------------------------------------------- /src/public/assets/img/default-avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/public/assets/img/default-avatar.png -------------------------------------------------------------------------------- /src/public/assets/img/normal-card-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/public/assets/img/normal-card-bg.png -------------------------------------------------------------------------------- /src/public/assets/img/verified_badge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/public/assets/img/verified_badge.png -------------------------------------------------------------------------------- /src/public/assets/img/certified-card-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/public/assets/img/certified-card-bg.png -------------------------------------------------------------------------------- /src/public/assets/img/searching-card-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astra-Development/Discord-Bot-List-v1/HEAD/src/public/assets/img/searching-card-bg.png -------------------------------------------------------------------------------- /src/routes/about.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | 3 | const route = Router(); 4 | 5 | route.get("/", async (req, res) => { 6 | res.render("about", {req}) 7 | }); 8 | 9 | module.exports = route; -------------------------------------------------------------------------------- /src/routes/privacy.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | 3 | const route = Router(); 4 | 5 | route.get("/", async (req, res) => { 6 | res.render("privacy", {req}) 7 | }); 8 | 9 | module.exports = route; -------------------------------------------------------------------------------- /src/routes/success.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | 3 | const route = Router(); 4 | 5 | route.get("/", async (req, res) => { 6 | res.render("success", {req}) 7 | }); 8 | 9 | module.exports = route; -------------------------------------------------------------------------------- /src/routes/api/admin/mark_nsfw.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | 3 | const route = Router(); 4 | 5 | route.get("/", async (req, res) => { 6 | res.sendStatus(200) 7 | }); 8 | 9 | module.exports = route; 10 | -------------------------------------------------------------------------------- /src/dynamic/theme/dark.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background-color: #292e3d; 3 | --background-2: #161921; 4 | --color: rgb(255, 255, 255); 5 | --primary: #00c3ff; 6 | --secondary: #616161; 7 | --text-secondary: #00c3ff; 8 | } 9 | -------------------------------------------------------------------------------- /src/dynamic/theme/light.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --background-color: #292e3d; 3 | --background-2: #161921; 4 | --color: rgb(255, 255, 255); 5 | --primary: #00c3ff; 6 | --secondary: rgb(0, 0, 0); 7 | --text-secondary: #00c3ff; 8 | } 9 | -------------------------------------------------------------------------------- /src/routes/join.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { server: {invite} } = require("@root/config.json"); 3 | 4 | const route = Router(); 5 | 6 | route.get("/", async (_, res) => { 7 | res.redirect(invite) 8 | }); 9 | 10 | module.exports = route; 11 | -------------------------------------------------------------------------------- /src/routes/login.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const passport = require('passport'); 3 | const route = Router(); 4 | 5 | route.get("/", passport.authenticate('discord', { scope: ['identify'], prompt: "consent" }), function(req, res) {}); 6 | 7 | module.exports = route; 8 | -------------------------------------------------------------------------------- /src/utils/createAuth.js: -------------------------------------------------------------------------------- 1 | module.exports = (len=20) => { 2 | var text = ""; 3 | var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; 4 | for (var i = 0; i < len; i++) text += possible.charAt(Math.floor(Math.random() * possible.length)); 5 | return text; 6 | } -------------------------------------------------------------------------------- /src/routes/api/bots/list.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const getList = require('@utils/getList.js') 3 | 4 | const route = Router(); 5 | 6 | route.get("/", async (req, res) => { 7 | res.json(await getList()) //will return un-verified bots 8 | }); 9 | 10 | module.exports = route; 11 | -------------------------------------------------------------------------------- /src/bot/events/userUpdate.js: -------------------------------------------------------------------------------- 1 | const { Event } = require('klasa'); 2 | 3 | module.exports = class extends Event { 4 | run(oldUser, newUser) { 5 | if (oldUser.bot && oldUser.username !== newUser.username && newUser.username) 6 | Bots.updateOne({ botid: newUser.id }, {$set: {username: newUser.username}}); 7 | } 8 | }; -------------------------------------------------------------------------------- /src/routes/api/avatar.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | 3 | const route = Router(); 4 | 5 | route.get("/", async (req, res) => { 6 | let a = req.query.avatar; 7 | let got = await fetch(a); 8 | got = await got.buffer(); 9 | res.writeHead(200, { 'Content-Type': 'image/png' }); 10 | res.end(got, 'binary'); 11 | }); 12 | 13 | module.exports = route; 14 | -------------------------------------------------------------------------------- /src/routes/api/theme.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | 3 | const route = Router(); 4 | 5 | route.get("/", async (req, res) => { 6 | let theme = req.cookies["theme"] 7 | if (theme === "dark") 8 | res.cookie("theme", "light") 9 | else 10 | res.cookie("theme", "dark"); 11 | res.redirect(req.header('Referer') || '/'); 12 | }); 13 | 14 | module.exports = route; 15 | -------------------------------------------------------------------------------- /src/routes/theme.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const path = require('path'); 3 | 4 | 5 | const route = Router(); 6 | 7 | route.get("/", async (req, res) => { 8 | res.set('Cache-Control', 'no-store'); 9 | let theme = req.cookies["theme"] || "light"; 10 | 11 | res.sendFile(path.join(__dirname, `../dynamic/theme/${theme}.css`)) 12 | }); 13 | 14 | module.exports = route; 15 | -------------------------------------------------------------------------------- /src/routes/api/admin.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | 3 | const approve = require("@routes/api/admin/approve"); 4 | const deny = require("@routes/api/admin/deny"); 5 | const mark_nsfw = require("@routes/api/admin/mark_nsfw"); 6 | 7 | const route = Router(); 8 | 9 | route.use("/approve", approve); 10 | route.use("/deny", deny); 11 | route.use("/mark_nsfw", mark_nsfw); 12 | 13 | module.exports = route; -------------------------------------------------------------------------------- /src/utils/fileWalk.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | module.exports = (filepath) => { 4 | if (!filepath) throw new Error("No file path provided!"); 5 | 6 | if (!fs.existsSync(filepath)) fs.mkdirSync(filepath); 7 | 8 | const files = fs.readdirSync(filepath, { withFileTypes: true }) 9 | .filter((entry) => !entry.isDirectory()) 10 | .map((entry) => entry.name); 11 | 12 | return files; 13 | }; 14 | -------------------------------------------------------------------------------- /src/bot/events/guildMemberAdd.js: -------------------------------------------------------------------------------- 1 | const { Event } = require('klasa'); 2 | 3 | const { server: {role_ids} } = require("@root/config.json"); 4 | 5 | module.exports = class extends Event { 6 | run(member) { 7 | if (member.user.bot) { 8 | member.roles.add(member.guild.roles.cache.get(role_ids.bot)); 9 | member.roles.add(member.guild.roles.cache.get(role_ids.unverified)); 10 | } 11 | } 12 | }; -------------------------------------------------------------------------------- /src/bot/inhibitors/nsfw.js: -------------------------------------------------------------------------------- 1 | const { Inhibitor } = require("klasa"); 2 | 3 | module.exports = class extends Inhibitor { 4 | constructor(...args) { 5 | super(...args, { 6 | name: "nsfw", 7 | enabled: true 8 | }); 9 | } 10 | 11 | run(msg, command) { 12 | if (command.nsfw && !msg.channel.nsfw) { 13 | return msg.language.get("INHIBITOR_NSFW"); 14 | } 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /src/routes/api/auth.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | 3 | const auth = require("@routes/api/auth/index"); 4 | const reset = require("@routes/api/auth/reset"); 5 | const stats = require("@routes/api/auth/stats"); 6 | const liked = require("@routes/api/auth/liked"); 7 | 8 | const route = Router(); 9 | 10 | route.use("/", auth); 11 | route.use("/reset", reset); 12 | route.use("/stats", stats); 13 | route.use("/liked", liked); 14 | 15 | module.exports = route; 16 | -------------------------------------------------------------------------------- /src/routes/add.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { auth } = require('@utils/discordApi'); 3 | 4 | const { web: { recaptcha_v2: { site_key } }, bot_options: { bot_tags, max_summary_length } } = require("@root/config.json"); 5 | 6 | const route = Router(); 7 | 8 | route.get("/", auth, async (req, res) => { 9 | res.render("add", { 10 | bot_tags, 11 | max_summary_length, 12 | site_key, 13 | req 14 | }) 15 | }); 16 | 17 | module.exports = route; 18 | -------------------------------------------------------------------------------- /src/utils/getList.js: -------------------------------------------------------------------------------- 1 | const Bots = require("@models/bots"); 2 | 3 | module.exports = async () => { 4 | let bots = await Bots.find({"state": {$ne: "deleted"}}, { _id: false, auth: false, __v: false, addedAt: false }) 5 | bots.sort((a, b) => b.likes - a.likes); 6 | return bots.map(x => { 7 | if (x.servers && x.servers[x.servers.length - 1]) 8 | x.servers = x.servers[x.servers.length - 1].count; 9 | else x.servers = null 10 | return x 11 | }) 12 | }; 13 | -------------------------------------------------------------------------------- /src/extendables/GuildMember/fn_GuildMember#canMod.js: -------------------------------------------------------------------------------- 1 | const { Extendable } = require('klasa'); 2 | const { GuildMember } = require('discord.js'); 3 | 4 | module.exports = class extends Extendable { 5 | 6 | constructor(...args) { 7 | super(...args, { appliesTo: [GuildMember] }); 8 | } 9 | 10 | async canMod(user) { 11 | const member = await this.guild.members.fetch(user).catch(() => null); 12 | return member ? member.highestRole.position < this.highestRole.position : false; 13 | } 14 | 15 | }; 16 | -------------------------------------------------------------------------------- /src/extendables/GuildMember/fn_GuildMember#highestRole.js: -------------------------------------------------------------------------------- 1 | const { Extendable } = require('klasa'); 2 | const { GuildMember } = require('discord.js'); 3 | 4 | module.exports = class extends Extendable { 5 | 6 | constructor(...args) { 7 | super(...args, { appliesTo: [GuildMember] }); 8 | } 9 | 10 | get highestRole() { 11 | return this.roles.highest ? this.roles.highest : this.guild.id; 12 | } 13 | 14 | }; 15 | 16 | // Because freaking GuildMemberRoleStore#highest returns undefined if it's @everyone 17 | -------------------------------------------------------------------------------- /src/routes/certify.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { auth } = require('@utils/discordApi') 3 | const Bots = require("@models/bots"); 4 | const { web: {recaptcha_v2: {site_key}}} = require("@root/config.json"); 5 | 6 | const route = Router(); 7 | 8 | route.get("/:id",auth, async (req, res) => { 9 | let bots = await Bots.findOne({botid: req.params.id}) 10 | res.render("certify", { 11 | bots, 12 | req, 13 | site_key 14 | }) 15 | }); 16 | 17 | module.exports = route; -------------------------------------------------------------------------------- /src/routes/api/bots.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | 3 | const submit = require("@routes/api/bots/submit"); 4 | const list = require("@routes/api/bots/list"); 5 | const modify = require("@routes/api/bots/modify"); 6 | const search = require("@routes/api/bots/search"); 7 | const del = require("@routes/api/bots/delete"); 8 | 9 | const route = Router(); 10 | 11 | route.use("/", submit); 12 | route.use("/", modify); 13 | route.use("/", del); 14 | 15 | route.use("/list", list); 16 | route.use("/search", search); 17 | 18 | module.exports = route; 19 | -------------------------------------------------------------------------------- /src/bot/inhibitors/disabled.js: -------------------------------------------------------------------------------- 1 | const { Inhibitor } = require("klasa"); 2 | 3 | module.exports = class extends Inhibitor { 4 | constructor(...args) { 5 | super(...args, { 6 | name: "disabled", 7 | enabled: true 8 | }); 9 | } 10 | 11 | run(msg, command) { 12 | if (!command.enabled) { return msg.language.get("INHIBITOR_DISABLED_GLOBAL"); } 13 | 14 | if (msg.guildSettings.disabledCommands.includes(command.name)) { 15 | return msg.language.get("INHIBITOR_DISABLED_GUILD"); 16 | } 17 | } 18 | 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /src/bot/arguments/monitor.js: -------------------------------------------------------------------------------- 1 | const { Argument } = require("klasa"); 2 | 3 | module.exports = class extends Argument { 4 | run(arg, possible, msg) { 5 | let x = 0; 6 | const monitorIterator = this.client.monitors.entries(); 7 | while (x < this.client.monitors.size) { 8 | let currMonitor = monitorIterator.next().value; 9 | if (currMonitor[0].toLowerCase() === arg.toLowerCase()) { 10 | return currMonitor[1]; 11 | } 12 | } 13 | 14 | throw msg.language.get("RESOLVER_INVALID_PIECE", possible.name, "monitor"); 15 | } 16 | }; -------------------------------------------------------------------------------- /src/extendables/KlasaMessage/fn_KlasaMessage#info.js: -------------------------------------------------------------------------------- 1 | const { Extendable, KlasaMessage } = require('klasa'); 2 | const { Emojis } = require('../../lib/util/constants'); 3 | 4 | module.exports = class extends Extendable { 5 | 6 | constructor(...args) { 7 | super(...args, { appliesTo: [KlasaMessage] }); 8 | } 9 | 10 | async info(message = null, messageOptions = {}) { 11 | const infoEmoji = await this.client.emojis.cache.get(Emojis.info); 12 | await this.react(infoEmoji); 13 | return message ? this.sendMessage(`${this.author}\n${infoEmoji} ${message}`, messageOptions) : this; 14 | } 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /src/extendables/KlasaMessage/fn_KlasaMessage#list.js: -------------------------------------------------------------------------------- 1 | const { Extendable, KlasaMessage } = require('klasa'); 2 | const { Emojis } = require('../../lib/util/constants'); 3 | 4 | module.exports = class extends Extendable { 5 | 6 | constructor(...args) { 7 | super(...args, { appliesTo: [KlasaMessage] }); 8 | } 9 | 10 | async list(message = null, messageOptions = {}) { 11 | const listEmoji = await this.client.emojis.cache.get(Emojis.list); 12 | await this.react(listEmoji); 13 | return message ? this.sendMessage(`${this.author}\n${listEmoji} ${message}`, messageOptions) : this; 14 | } 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /src/models/users.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const usersSchema = new mongoose.Schema({ 4 | userid: { 5 | type: String, 6 | required: true, 7 | unique: true 8 | }, 9 | botliked: { 10 | type: String, 11 | required: true 12 | }, 13 | bio: { 14 | type: String, 15 | required: false, 16 | default: "This user has no bio" 17 | }, 18 | time: { 19 | type: Date, 20 | default: () => Date.now() 21 | }, 22 | certdev: { 23 | type: Boolean, 24 | default: false 25 | } 26 | }); 27 | 28 | module.exports = mongoose.model("users", usersSchema); 29 | -------------------------------------------------------------------------------- /src/extendables/KlasaMessage/fn_KlasaMessage#affirm.js: -------------------------------------------------------------------------------- 1 | const { Extendable, KlasaMessage } = require('klasa'); 2 | const { Emojis } = require('../../lib/util/constants'); 3 | 4 | module.exports = class extends Extendable { 5 | 6 | constructor(...args) { 7 | super(...args, { appliesTo: [KlasaMessage] }); 8 | } 9 | 10 | async affirm(message = null, messageOptions = {}) { 11 | const affirmEmoji = await this.client.emojis.cache.get(Emojis.affirm); 12 | await this.react(affirmEmoji); 13 | return message ? this.sendMessage(`${this.author}\n${affirmEmoji} ${message}`, messageOptions) : this; 14 | } 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /src/extendables/KlasaMessage/fn_KlasaMessage#reject.js: -------------------------------------------------------------------------------- 1 | const { Extendable, KlasaMessage } = require('klasa'); 2 | const { Emojis } = require('../../lib/util/constants'); 3 | 4 | module.exports = class extends Extendable { 5 | 6 | constructor(...args) { 7 | super(...args, { appliesTo: [KlasaMessage] }); 8 | } 9 | 10 | async reject(message = null, messageOptions = {}) { 11 | const rejectEmoji = await this.client.emojis.cache.get(Emojis.reject); 12 | await this.react(rejectEmoji); 13 | return message ? this.sendMessage(`${this.author}\n${rejectEmoji} ${message}`, messageOptions) : this; 14 | } 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /src/routes/admin.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { auth } = require('@utils/discordApi') 3 | const Bots = require("@models/bots"); 4 | 5 | const { server: { id } } = require("@root/config.json") 6 | 7 | const route = Router(); 8 | 9 | route.get("/", auth, async (req, res) => { 10 | if (!req.user.staff) return res.render("403", {req}); 11 | let bots = await Bots.find({ state: "unverified" }, { _id: false }) 12 | if (bots == '') bots = null; 13 | 14 | res.render("admin", { 15 | bots, 16 | id, 17 | req 18 | }); 19 | }); 20 | 21 | module.exports = route; 22 | -------------------------------------------------------------------------------- /src/extendables/KlasaMessage/fn_KlasaMessage#arrowLeft.js: -------------------------------------------------------------------------------- 1 | const { Extendable, KlasaMessage } = require('klasa'); 2 | const { Emojis } = require('../../lib/util/constants'); 3 | 4 | module.exports = class extends Extendable { 5 | 6 | constructor(...args) { 7 | super(...args, { appliesTo: [KlasaMessage] }); 8 | } 9 | 10 | async arrowLeft(message = null, messageOptions = {}) { 11 | const arrowLeftEmoji = await this.client.emojis.cache.get(Emojis.arrowLeft); 12 | await this.react(arrowLeftEmoji); 13 | return message ? this.sendMessage(`${this.author}\n${arrowLeftEmoji} ${message}`, messageOptions) : this; 14 | } 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /src/extendables/KlasaMessage/fn_KlasaMessage#arrowRight.js: -------------------------------------------------------------------------------- 1 | const { Extendable, KlasaMessage } = require('klasa'); 2 | const { Emojis } = require('../../lib/util/constants'); 3 | 4 | module.exports = class extends Extendable { 5 | 6 | constructor(...args) { 7 | super(...args, { appliesTo: [KlasaMessage] }); 8 | } 9 | 10 | async arrowRight(message = null, messageOptions = {}) { 11 | const arrowRightEmoji = await this.client.emojis.cache.get(Emojis.arrowRight); 12 | await this.react(arrowRightEmoji); 13 | return message ? this.sendMessage(`${this.author}\n${arrowRightEmoji} ${message}`, messageOptions) : this; 14 | } 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /src/extendables/KlasaMessage/fn_KlasaMessage#arrowToLeft.js: -------------------------------------------------------------------------------- 1 | const { Extendable, KlasaMessage } = require('klasa'); 2 | const { Emojis } = require('../../lib/util/constants'); 3 | 4 | module.exports = class extends Extendable { 5 | 6 | constructor(...args) { 7 | super(...args, { appliesTo: [KlasaMessage] }); 8 | } 9 | 10 | async arrowToLeft(message = null, messageOptions = {}) { 11 | const arrowToLeftEmoji = await this.client.emojis.cache.get(Emojis.arrowToLeft); 12 | await this.react(arrowToLeftEmoji); 13 | return message ? this.sendMessage(`${this.author}\n${arrowToLeftEmoji} ${message}`, messageOptions) : this; 14 | } 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /src/extendables/KlasaMessage/fn_KlasaMessage#arrowToRight.js: -------------------------------------------------------------------------------- 1 | const { Extendable, KlasaMessage } = require('klasa'); 2 | const { Emojis } = require('../../lib/util/constants'); 3 | 4 | module.exports = class extends Extendable { 5 | 6 | constructor(...args) { 7 | super(...args, { appliesTo: [KlasaMessage] }); 8 | } 9 | 10 | async arrowToRight(message = null, messageOptions = {}) { 11 | const arrowToRightEmoji = await this.client.emojis.cache.get(Emojis.arrowToRight); 12 | await this.react(arrowToRightEmoji); 13 | return message ? this.sendMessage(`${this.author}\n${arrowToRightEmoji} ${message}`, messageOptions) : this; 14 | } 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /src/routes/api/bots/search.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const Bots = require("@models/bots"); 3 | 4 | const route = Router(); 5 | 6 | route.get("/", async (req, res) => { 7 | res.setHeader('Access-Control-Allow-Origin', '*'); 8 | res.setHeader('Access-Control-Allow-Methods', 'GET'); 9 | res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); 10 | res.setHeader('Access-Control-Allow-Credentials', true); 11 | 12 | const bots = await Bots.find({ state: "verified" }, { _id: false }) 13 | res.json(bots.filter(x => Object.values(x).join("").includes(req.query.q.toLowerCase()))); 14 | }); 15 | 16 | module.exports = route; 17 | -------------------------------------------------------------------------------- /src/lib/RTByteClient.js: -------------------------------------------------------------------------------- 1 | const { KlasaClient } = require('klasa'); 2 | 3 | KlasaClient.use(require('klasa-dashboard-hooks')); 4 | 5 | const permissionLevels = require('./structures/schemas/permissionLevels'); 6 | const defaultGuildSchema = require('./structures/schemas/defaultGuildSchema'); 7 | const defaultClientSchema = require('./structures/schemas/defaultClientSchema'); 8 | const defaultUserSchema = require('./structures/schemas/defaultUserSchema'); 9 | 10 | class RTByteClient extends KlasaClient { 11 | 12 | constructor(options) { 13 | super({ ...options, permissionLevels, defaultGuildSchema, defaultClientSchema, defaultUserSchema }); 14 | } 15 | 16 | } 17 | 18 | module.exports = RTByteClient; 19 | -------------------------------------------------------------------------------- /src/utils/passport.js: -------------------------------------------------------------------------------- 1 | const { Strategy } = require('passport-discord'); 2 | const passport = require('passport'); 3 | 4 | const { web: {domain_with_protocol}, discord_client: {id, secret} } = require("@root/config.json"); 5 | 6 | var scopes = ['identify']; 7 | 8 | passport.use(new Strategy({ 9 | clientID: id, 10 | clientSecret: secret, 11 | callbackURL: `${domain_with_protocol}/api/callback`, 12 | scope: scopes 13 | }, 14 | function(accessToken, refreshToken, profile, cb) { 15 | return cb("", profile); 16 | })) 17 | 18 | passport.serializeUser(function(user, done) { 19 | done(null, user); 20 | }); 21 | passport.deserializeUser(function(obj, done) { 22 | done(null, obj); 23 | }); -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /src/bot/inhibitors/requireMusic.js: -------------------------------------------------------------------------------- 1 | const { Inhibitor } = require('klasa'); 2 | 3 | module.exports = class extends Inhibitor { 4 | 5 | constructor(...args) { 6 | super(...args, { spamProtection: true }); 7 | } 8 | 9 | async run(msg, cmd) { 10 | if (cmd.requireMusic !== true) return; 11 | 12 | if (msg.channel.type !== 'text') throw 'This command may be only executed in a server.'; 13 | 14 | if (!msg.member.voice.channel) throw 'You are not connected in a voice channel.'; 15 | if (!msg.guild.me.voice.channel) throw 'I am not connected in a voice channel.'; 16 | if (msg.member.voice.channel !== msg.guild.me.voice.channel) throw 'You must be in the same voice channel as me.'; 17 | } 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /src/routes/api/auth/liked.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const bodyParser = require("body-parser"); 3 | const Users = require("@models/users"); 4 | 5 | const authApi = require("@utils/authApi"); 6 | 7 | const route = Router(); 8 | route.use(bodyParser.json({ limit: '50mb' })); 9 | 10 | route.get('/:id', authApi, async (req, res) => { 11 | let unfiltered = await Users.find({botliked: req.params.id}, { _id: false, __v: false, botliked: false }); 12 | let users = [] 13 | for (let user of unfiltered) { 14 | if (user && (Date.now() - user.time) < 43200000) 15 | users.push(user) 16 | } 17 | return res.json({ success: "true", users }) 18 | }); 19 | 20 | module.exports = route; -------------------------------------------------------------------------------- /src/lib/SentryClient.js: -------------------------------------------------------------------------------- 1 | const { version: klasaVersion } = require('klasa'); 2 | const { version: discordVersion } = require('discord.js'); 3 | const { sentryIngestURL } = require('../config'); 4 | const Sentry = require('@sentry/node'); 5 | const os = require('os'); 6 | const chalk = require('chalk'); 7 | 8 | Sentry.init({ 9 | dsn: sentryIngestURL, 10 | // eslint-disable-next-line no-process-env 11 | release: `rtbyte@${process.env.npm_package_version}` 12 | }); 13 | 14 | Sentry.configureScope(scope => { 15 | scope.setTags({ 16 | host: `${os.hostname}`, 17 | 'klasa-version': klasaVersion, 18 | 'd.js-version': discordVersion 19 | }); 20 | }); 21 | 22 | if (sentryIngestURL) console.log(chalk.black.bgWhite('Sentry error reporting is active.\n')); 23 | -------------------------------------------------------------------------------- /src/lib/structures/schemas/defaultClientSchema.js: -------------------------------------------------------------------------------- 1 | const { KlasaClient } = require('klasa'); 2 | 3 | module.exports = KlasaClient.defaultClientSchema 4 | .add('guilds', folder => folder.add('controlGuild', 'guild')) 5 | .add('channels', folder => folder.add('globalLog', 'textchannel')) 6 | .add('logs', folder => folder 7 | .add('botReady', 'boolean', { default: true }) 8 | .add('commandRun', 'boolean', { default: true }) 9 | .add('guildCreate', 'boolean', { default: true }) 10 | .add('guildDelete', 'boolean', { default: true }) 11 | .add('guildUpdate', 'boolean', { default: true }) 12 | .add('guildUnavailable', 'boolean', { default: true })) 13 | .add('moderation', folder => folder 14 | .add('cases', 'any', { array: true })) 15 | .add('twitchOauthBearer', 'string'); 16 | -------------------------------------------------------------------------------- /src/routes/bots/resubmit.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { auth } = require('@utils/discordApi') 3 | const Bots = require("@models/bots"); 4 | 5 | const { web: {recaptcha_v2: {site_key}}, bot_options: {bot_tags, max_summary_length}} = require("@root/config.json"); 6 | 7 | const route = Router(); 8 | 9 | route.get("/:id", auth, async (req, res) => { 10 | let bot = await Bots.findOne({botid: req.params.id}, { _id: false }) 11 | 12 | if (!bot) return res.render("404", {req}); 13 | if (bot.state !== "deleted") return res.render("404", {req}); 14 | 15 | res.render("resubmit", { 16 | bot, 17 | user: req.user, 18 | bot_tags, 19 | max_summary_length, 20 | site_key, 21 | req 22 | }); 23 | }); 24 | 25 | module.exports = route; 26 | -------------------------------------------------------------------------------- /src/lib/util/constants.js: -------------------------------------------------------------------------------- 1 | exports.Colors = { 2 | white: '#FEFEFE', 3 | red: '#FF4B4B', 4 | green: '#4BFF4B', 5 | yellow: '#FFFF4B', 6 | blue: '#4B4BFF', 7 | pink: '#F47FFF', 8 | gold: '#DAA520', 9 | purple: '#9046FF' 10 | }; 11 | 12 | exports.Emojis = { 13 | affirm: '557276640124600350', 14 | reject: '557276639923404810', 15 | arrowLeft: '557276639726141451', 16 | arrowToLeft: '557276641819230259', 17 | arrowRight: '557276639399116820', 18 | arrowToRight: '557276640141508608', 19 | info: '557276638837080076', 20 | list: '557276640657408017', 21 | online: '548280346370637825', 22 | idle: '548280349310976001', 23 | dnd: '548280353278656528', 24 | offline: '548280355438985216', 25 | botBadge: '703369019981561877', 26 | discordPartner: '726810056577777754', 27 | discordVerified: '726810056087044189' 28 | }; 29 | -------------------------------------------------------------------------------- /src/routes/bots/like.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { auth } = require('@utils/discordApi') 3 | const Bots = require("@models/bots"); 4 | const Users = require("@models/users"); 5 | 6 | const { web: { recaptcha_v2: { site_key } } } = require("@root/config.json"); 7 | 8 | const route = Router(); 9 | 10 | route.get("/:id", auth, async (req, res) => { 11 | let bot = await Bots.findOne({ botid: req.params.id }, { _id: false, auth: false }) 12 | let users = await Users.findOne({ userid: req.user.id }, { _id: false, auth: false }); 13 | if (!bot) return res.render("404"), {req}; 14 | 15 | res.render("like", { 16 | bot, 17 | user: req.user, 18 | site_key, 19 | users, 20 | req 21 | }); 22 | }); 23 | 24 | module.exports = route; 25 | -------------------------------------------------------------------------------- /src/bot/index.js: -------------------------------------------------------------------------------- 1 | const { Client, Schema } = require('klasa'); 2 | const {server: {role_ids: {bot_verifier}}, discord_client: {prefix}} = require("@root/config.json"); 3 | 4 | Client.defaultPermissionLevels 5 | .add(8, ({ guild, member }) => member.roles.cache.has(bot_verifier)); 6 | 7 | const client = new Client({ 8 | commandEditing: true, 9 | prefix: prefix, 10 | production: true, 11 | consoleEvents: { 12 | log: true, 13 | error: false, 14 | warn: false 15 | }, 16 | disabledCorePieces: ["commands"] 17 | }); 18 | 19 | //Bot Status 20 | 21 | client.once('ready', () => { 22 | client.user.setActivity(`astrabots.xyz`, { type: "WATCHING" }); 23 | }); 24 | 25 | module.exports.init = async (token) => { 26 | client.userBaseDirectory = __dirname; 27 | await client.login(token); 28 | return client; 29 | } 30 | -------------------------------------------------------------------------------- /src/bot/events/guildMemberRemove.js: -------------------------------------------------------------------------------- 1 | const { Event } = require('klasa'); 2 | const Bots = require("@models/bots"); 3 | const { server: {id, mod_log_id} } = require("@root/config.json"); 4 | 5 | module.exports = class extends Event { 6 | async run(member) { 7 | let bots = await Bots.find({"owners.primary": member.user.id , state: { $ne: "deleted" } }, { _id: false }); 8 | for (let bot of bots) { 9 | await Bots.updateOne({ botid: bot.botid }, { $set: { state: "deleted" } }); 10 | try { 11 | let bot_member = await this.client.guilds.cache.get(id).members.fetch(bot.botid) 12 | bot_member.kick() 13 | this.channels.cache.get(mod_log_id).send(`<@${bot.botid}> has been deleted as <@${member.user.id}> (${member.user.tag}) has left.`); 14 | } catch(e) {} 15 | } 16 | } 17 | }; -------------------------------------------------------------------------------- /src/routes/api/auth/reset.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { auth } = require('@utils/discordApi'); 3 | const create = require('@utils/createAuth.js'); 4 | const Bots = require("@models/bots"); 5 | 6 | const { server: {admin_user_ids} } = require("@root/config.json"); 7 | 8 | const route = Router(); 9 | 10 | route.get("/:id", auth, async(req, res) => { 11 | const bot = await Bots.findOne({ botid: req.params.id }, { _id: false }) 12 | if (![bot.owners.primary].concat(bot.owners.additional).includes(req.user.id) && !admin_user_ids.includes(req.user.id)) return res.json({ "success": false, "error": "Bot owner is not user." }); 13 | 14 | let newAuthCode = create(20) 15 | await Bots.updateOne({ botid: req.params.id }, {$set: { auth: newAuthCode } }) 16 | 17 | res.json({ "success": true, "auth": newAuthCode }); 18 | }); 19 | 20 | module.exports = route; 21 | -------------------------------------------------------------------------------- /src/routes/user.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const Bots = require("@models/bots"); 3 | 4 | const { server: {admin_user_ids} } = require("@root/config.json") 5 | 6 | const route = Router(); 7 | 8 | route.get("/:id", async (req, res) => { 9 | let user = await req.app.get('client').users.fetch(req.params.id) 10 | .catch(_ => res.render("404", {req})); 11 | if (!user) return; 12 | 13 | let bots = await Bots.find({}, { _id: false }) 14 | bots = bots.filter(bot => [bot.owners.primary].concat(bot.owners.additional).includes(user.id)) 15 | 16 | if (bots.length === 0) return res.render("user/notfound", {user: req.user}) 17 | 18 | res.render("user/index", { 19 | userProfile: user, 20 | cards: bots, 21 | admin: admin_user_ids.includes(req.params.id), 22 | req 23 | }); 24 | }); 25 | 26 | module.exports = route; 27 | -------------------------------------------------------------------------------- /src/routes/tag/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const getList = require('@utils/getList'); 3 | 4 | const { bot_options: {bot_tags} } = require("@root/config.json"); 5 | 6 | const route = Router(); 7 | 8 | route.get("/:tag", async (req, res) => { 9 | let { tag } = req.params; 10 | if(bot_tags.includes(tag)) { 11 | let bots = await getList() 12 | bots = bots.filter(bot => { 13 | let tags = bot.tags 14 | if (!tags) 15 | tags = bot_tags.filter(t => t !== tag); 16 | return tags.includes(tag) 17 | }) 18 | if (bots == '') 19 | bots = null 20 | 21 | res.render('tag', { 22 | cards: bots, 23 | tag, 24 | req 25 | }); 26 | } else { 27 | res.render('404') 28 | } 29 | 30 | }); 31 | 32 | module.exports = route; 33 | -------------------------------------------------------------------------------- /src/bot/commands/Developer/exec.js: -------------------------------------------------------------------------------- 1 | const { Command, util: { exec, codeBlock } } = require('klasa'); 2 | 3 | module.exports = class extends Command { 4 | 5 | constructor(...args) { 6 | super(...args, { 7 | aliases: ['execute'], 8 | description: 'Execute commands in the terminal, use with EXTREME CAUTION.', 9 | guarded: true, 10 | permissionLevel: 10, 11 | usage: '' 12 | }); 13 | } 14 | 15 | async run(message, [input]) { 16 | const result = await exec(input, { timeout: 'timeout' in message.flags ? Number(message.flags.timeout) : 60000 }) 17 | .catch(error => ({ stdout: null, stderr: error })); 18 | const output = result.stdout ? `**\`OUTPUT\`**${codeBlock('prolog', result.stdout)}` : ''; 19 | const outerr = result.stderr ? `**\`ERROR\`**${codeBlock('prolog', result.stderr)}` : ''; 20 | 21 | return message.sendMessage([output, outerr].join('\n')); 22 | } 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /src/routes/bots/search.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const Bots = require("@models/bots"); 3 | const getList = require('@utils/getList.js') 4 | 5 | const route = Router(); 6 | 7 | route.get("/", async (req, res) => { 8 | let search = req.query.q; 9 | if (!search) search = ""; 10 | search = search.toLowerCase(); 11 | let bots = await getList(); 12 | let found = bots.filter(bot => { 13 | if (bot.state !== "verified") return false; 14 | if (bot.username.toLowerCase().includes(search)) return true; 15 | else if (bot.description.toLowerCase().includes(search)) return true; 16 | else return false; 17 | }); 18 | if (!found) return res.send({ error: "No bots found for this search" }); 19 | 20 | res.render("search", { 21 | cards: found, 22 | search, 23 | user: req.user, req 24 | }); 25 | }); 26 | 27 | module.exports = route; 28 | -------------------------------------------------------------------------------- /src/routes/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const Bots = require("@models/bots") 3 | const bots = require("@routes/bots/index"); 4 | const tag = require("@routes/tag/index"); 5 | const api = require("@routes/api/index"); 6 | const theme = require("@routes/theme"); 7 | 8 | const route = Router(); 9 | 10 | route.use("/bots", bots); 11 | route.use("/tag", tag); 12 | route.use("/api", api); 13 | route.use("/theme", theme); 14 | 15 | route.get('/', async (req, res) => { 16 | let certifiedbots = await Bots.find({certify: true}, { _id: false, auth: false, __v: false, addedAt: false }) 17 | certifiedbots.sort((a, b) => b.likes - a.likes); 18 | if (certifiedbots == '') { 19 | certifiedbots = null 20 | } 21 | if (!req.query.q) res.render('index', {req, bots: certifiedbots}); 22 | else res.redirect(`/bots/search?q=${encodeURIComponent(req.query.q)}`) 23 | }); 24 | 25 | module.exports = route; 26 | -------------------------------------------------------------------------------- /src/bot/arguments/roleSearch.js: -------------------------------------------------------------------------------- 1 | const { Argument } = require("klasa"); 2 | const IDRegex = /^((<@&)|())(\d{17,21})((>)|())$/; 3 | 4 | /* Possible role detection: 5 | * <@&662772689286594570> (Mention) 6 | * 662772689286594570 (Role ID) 7 | * Margarine (Role name) 8 | */ 9 | 10 | module.exports = class extends Argument { 11 | constructor(...args) { 12 | super(...args, { aliases: ["rolesearch"] }); 13 | } 14 | 15 | async run(arg, possible, msg) { 16 | if (arg === undefined || !msg.guild) { throw msg.language.get("ROLESEARCH_FAIL", msg); } 17 | if (IDRegex.test(arg)) { return msg.guild.roles.cache.get(/(\d{17,21})/.exec(arg)[0]); } 18 | 19 | var results = msg.guild.roles.cache.filter(m => m.name === arg || m.name.toLowerCase() === arg.toLowerCase()); 20 | if (results.size === 0) { throw msg.language.get("ROLESEARCH_FAIL", msg); } 21 | 22 | return msg.guild.roles.cache.get(results.keys().next().value); 23 | } 24 | }; -------------------------------------------------------------------------------- /src/routes/logout.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { MessageEmbed } = require("discord.js"); 3 | const { server } = require("@root/config.json"); 4 | const route = Router(); 5 | 6 | route.get("/", async (req, res) => { 7 | if(!server.website_logs){ 8 | req.logout(); 9 | res.redirect(`/`); 10 | }else{ 11 | let embed = new MessageEmbed() 12 | .setAuthor(`${req.user.username}#${req.user.discriminator}`, `https://cdn.discordapp.com/avatars/${req.user.id}/${req.user.avatar}.png?size=256`) 13 | .setColor('RED') 14 | .setThumbnail(`https://cdn.discordapp.com/avatars/${req.user.id}/${req.user.avatar}.png?size=256`) 15 | .setDescription(`<@${req.user.id}> (${req.user.id}), Logged out!`); 16 | req.app.get('client').channels.cache.get(server.website_logs).send(embed) 17 | req.logout(); 18 | res.redirect(`/`); 19 | } 20 | }); 21 | 22 | module.exports = route; 23 | -------------------------------------------------------------------------------- /src/utils/authApi.js: -------------------------------------------------------------------------------- 1 | const Bots = require("@models/bots"); 2 | 3 | const { web: { ratelimit } } = require("@root/config.json"); 4 | 5 | module.exports = async(req, res, next) => { 6 | let auth = req.headers.authorization; 7 | if (!auth) return res.json({ success: "false", error: "Authorization header not found." }); 8 | 9 | const bot = await Bots.findOne({ botid: req.params.id }, { _id: false }) 10 | if (!bot) return res.json({ "success": "false", "error": "Bot not found." }); 11 | 12 | if (!bot.auth) return res.json({ success: "false", error: "Create a bot authorization token." }); 13 | if (bot.auth !== auth) return res.json({ success: "false", error: "Incorrect authorization token." }); 14 | 15 | if (bot.ratelimit && Date.now() - bot.ratelimit < (ratelimit * 1000)) return res.json({ success: "false", error: "You are being ratelimited." }); 16 | 17 | Bots.updateOne({ botid: req.params.id }, { ratelimit: Date.now() }) 18 | return next(); 19 | } 20 | -------------------------------------------------------------------------------- /src/public/assets/js/me.js: -------------------------------------------------------------------------------- 1 | $(document).ready(() => { 2 | $(document).on("click",".delete", async function () { 3 | await Swal.fire({ 4 | title: `Deleting ${$(this).attr("data-name")}`, 5 | icon: 'warning', 6 | html: `Type ${$(this).attr("data-name")} to confirm`, 7 | showCancelButton: true, 8 | input: "text", 9 | confirmButtonText: `Delete`, 10 | preConfirm: async (name) => { 11 | if (name.toLowerCase() !== $(this).attr("data-name").toLowerCase()) { 12 | Swal.update({ 13 | title: "Cancelled", 14 | html: "" 15 | }); 16 | await wait(1) 17 | } else { 18 | await fetch(`/api/bots/${$(this).attr("data-id")}`, {method: "DELETE"}); 19 | location.href = "/me"; 20 | } 21 | } 22 | }) 23 | }) 24 | }) -------------------------------------------------------------------------------- /src/utils/discordApi.js: -------------------------------------------------------------------------------- 1 | const fetch = require('node-fetch'); 2 | 3 | const { server: { role_ids: { bot_verifier } }, server: { admin_user_ids, id } } = require("@root/config.json") 4 | 5 | module.exports.auth = async(req, res, next) => { 6 | if (!req.user) return res.redirect("/login"); 7 | 8 | req.user.staff = false; 9 | 10 | try { 11 | const member = await req.app.get('client').guilds.cache.get(id).members.fetch(req.user.id); 12 | if (admin_user_ids.includes(req.user.id) || member.roles.cache.has(bot_verifier)) 13 | req.user.staff = true 14 | } catch(_) {} 15 | 16 | return next(); 17 | } 18 | 19 | module.exports.getUser = async (user) => { 20 | let { accessToken } = user; 21 | 22 | user = await fetch(`https://discord.com/api/users/@me`, { 23 | headers: { 24 | Authorization: `Bearer ${accessToken}` 25 | } 26 | }); 27 | 28 | user = await user.json(); 29 | 30 | if (user.code === 0) return false; 31 | return user; 32 | }; -------------------------------------------------------------------------------- /src/bot/arguments/integerCheck.js: -------------------------------------------------------------------------------- 1 | const { Argument } = require("klasa"); 2 | const integerRegex = /^\d{1,}$/; 3 | 4 | /* Possible detection: 5 | * 13 [True] 6 | * 42.5 [False] 7 | * "Hello world" [False] 8 | */ 9 | 10 | module.exports = class extends Argument { 11 | constructor(...args) { 12 | super(...args, { aliases: ["integercheck", "intcheck"] }); 13 | } 14 | 15 | async run(arg, possible, msg) { 16 | const { min, max } = possible; 17 | var numArg = Number(arg); 18 | 19 | if (min > numArg) { throw msg.language.get("RESOLVER_MINMAX_MIN", possible.name, min); } 20 | if (max < numArg) { throw msg.language.get("RESOLVER_MINMAX_MAX", possible.name, max); } 21 | 22 | if (Number.isInteger(numArg)) { return numArg; } 23 | 24 | if (Number.isNaN(numArg)) { throw msg.language.get("INTEGERCHECK_NONUMBER"); } 25 | if (!integerRegex.test(numArg)) { throw msg.language.get("INTEGERCHECK_FLOAT"); } 26 | throw msg.language.get("INTEGERCHECK_FAIL"); 27 | } 28 | }; -------------------------------------------------------------------------------- /src/bot/commands/Bot List/count.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | const Bots = require("@models/bots"); 3 | const { MessageEmbed } = require('discord.js'); 4 | 5 | module.exports = class extends Command { 6 | constructor(...args) { 7 | super(...args, { 8 | name: 'count', 9 | runIn: ['text'], 10 | permLevel: 0, 11 | botPerms: ["SEND_MESSAGES"], 12 | requiredSettings: [], 13 | description: "Check how many bots there are in the list." 14 | }); 15 | } 16 | 17 | async run(message) { 18 | let bots = await Bots.find({}, { _id: false }) 19 | bots = bots.filter(bot => bot.state !== "deleted"); 20 | if (bots.length === 1) cont = "\```Ohh noo... Only [1] bot is in our botlist"; 21 | 22 | let embed = new MessageEmbed() 23 | .setAuthor(`${message.author.tag}`, message.author.displayAvatarURL({ format: "png", size: 256 })) 24 | .setColor('ORANGE') 25 | .setDescription(`**:arrow_right: Bot Count: ** \`${bots.length}\``) 26 | message.channel.send(embed) 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/lib/structures/schemas/permissionLevels.js: -------------------------------------------------------------------------------- 1 | const { KlasaClient } = require('klasa'); 2 | 3 | module.exports = KlasaClient.defaultPermissionLevels 4 | .add(0, () => true) 5 | .add(6, ({ guild, member }) => guild && member.roles.cache.has(guild.settings.get('roles.moderator')), { fetch: true }) 6 | .add(7, ({ guild, member }) => guild && member.roles.cache.has(guild.settings.get('roles.administrator')), { fetch: true }) 7 | .add(8, ({ guild, member }) => guild && member === guild.owner, { fetch: true }) 8 | // eslint-disable-next-line max-len 9 | .add(9, ({ client, guild, member }) => (guild.settings.get('developmentSettings.developersAreSuperUsers') || member.roles.cache.has(guild.settings.get('roles.administrator'))) && client.options.botOwners.includes(member.user.id), { break: true }) 10 | // eslint-disable-next-line max-len 11 | .add(10, ({ client, guild, member }) => (guild.settings.get('developmentSettings.developersAreSuperUsers') || member.roles.cache.has(guild.settings.get('roles.administrator'))) && client.options.botOwners.includes(member.user.id)); 12 | -------------------------------------------------------------------------------- /src/routes/bots/edit.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { auth } = require('@utils/discordApi') 3 | const Bots = require("@models/bots"); 4 | 5 | const { web: {recaptcha_v2: {site_key}}, bot_options: {bot_tags, max_summary_length}} = require("@root/config.json"); 6 | 7 | const route = Router(); 8 | 9 | route.get("/:id", auth, async (req, res) => { 10 | let bot = await Bots.findOne({botid: req.params.id}, { _id: false, auth: false }) 11 | 12 | if (!bot) return res.render("404", {req}); 13 | 14 | // Backward compaitibility 15 | let owners = [bot.owners.primary].concat(bot.owners.additional) 16 | if (String(bot.owners).startsWith("[")) 17 | owners = String(bot.owners).replace("[ '", "").replace("' ]", "").split("', '") 18 | 19 | if (!owners.includes(req.user.id) && !req.user.staff) return res.render("403", {req}); 20 | 21 | res.render("edit", { 22 | bot, 23 | bot_tags, 24 | max_summary_length, 25 | site_key, 26 | req 27 | }); 28 | }); 29 | 30 | module.exports = route; 31 | -------------------------------------------------------------------------------- /src/bot/arguments/userSearch.js: -------------------------------------------------------------------------------- 1 | const { Argument, util: { regExpEsc } } = require("klasa"); 2 | const IDRegex = /^((<@!)|())(\d{17,21})((>)|())$/; 3 | 4 | /* Possible user detection: 5 | * <@!303236614623068161> (Mention) 6 | * 303236614623068161 (User ID) 7 | * Butterstroke (Username) 8 | * Butter (Guild nickname) 9 | */ 10 | 11 | module.exports = class extends Argument { 12 | constructor(...args) { 13 | super(...args, { aliases: ["usersearch"] }); 14 | } 15 | 16 | async run(arg, possible, msg) { 17 | if (arg === undefined) { return msg.author; } 18 | if (IDRegex.test(arg)) { return this.client.users.cache.get(/(\d{17,21})/.exec(arg)[0]); } 19 | 20 | var results = []; 21 | 22 | if (msg.guild) { 23 | var regex = new RegExp(regExpEsc(arg), "i"); 24 | results = msg.guild.members.cache.filter(m => regex.test(m.user.username)); 25 | } 26 | 27 | if (results.size === 0) { throw msg.language.get("USERSEARCH_FAIL"); } 28 | 29 | return this.client.users.cache.get(results.keys().next().value); 30 | } 31 | }; -------------------------------------------------------------------------------- /src/routes/api/bots/delete.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const bodyParser = require("body-parser"); 3 | const { auth } = require("@utils/discordApi"); 4 | const Bots = require("@models/bots"); 5 | 6 | const { server } = require("@root/config.json"); 7 | 8 | const route = Router(); 9 | route.use(bodyParser.urlencoded({extended: true})); 10 | 11 | route.delete("/:id", auth, async (req, res) => { 12 | let {id} = req.params; 13 | 14 | const bot = await Bots.findOne({ botid: id }, { _id: false }) 15 | 16 | if (!bot) return res.sendStatus(404) 17 | if (bot.owners.primary !== req.user.id && !server.admin_user_ids.includes(req.user.id)) return res.sendStatus(403) 18 | 19 | await Bots.deleteOne({ botid: id }) 20 | 21 | req.app.get('client').channels.cache.get(server.mod_log_id).send(`<:db_delete:816717275431174144> <@${req.user.id}> has deleted <@${bot.botid}> - (${bot.botid})`); 22 | req.app.get('client').guilds.cache.get(server.id).members.fetch(id).then(bot => {bot.kick()}).catch(() => {}) 23 | res.sendStatus(200) 24 | }); 25 | 26 | module.exports = route; -------------------------------------------------------------------------------- /src/public/assets/css/bootstrap-multiselect.css: -------------------------------------------------------------------------------- 1 | span.multiselect-native-select{position:relative}span.multiselect-native-select select{border:0 !important;clip:rect(0 0 0 0) !important;height:1px !important;margin:-1px -1px -1px -3px !important;overflow:hidden !important;padding:0 !important;position:absolute !important;width:1px !important;left:50%;top:30px}.multiselect.dropdown-toggle:after{display:none}.multiselect-container{position:absolute;list-style-type:none;margin:0;padding:0}.multiselect-container .multiselect-reset .input-group{width:93%}.multiselect-container .multiselect-group.dropdown-item,.multiselect-container .multiselect-group.dropdown-toggle,.multiselect-container > .multiselect-option.dropdown-item,.multiselect-container > .multiselect-option.dropdown-toggle{cursor:pointer}.multiselect-container .multiselect-group > span,.multiselect-container > .multiselect-option > span{padding:3px 20px}.multiselect-container .multiselect-group > span > .form-check-label,.multiselect-container > .multiselect-option > span > .form-check-label{cursor:pointer}.form-inline .multiselect-container span.form-check{padding:3px 20px 3px 40px} -------------------------------------------------------------------------------- /src/routes/me.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { auth } = require('@utils/discordApi') 3 | const Bots = require("@models/bots"); 4 | 5 | const { server: {admin_user_ids} } = require("@root/config.json") 6 | 7 | const route = Router(); 8 | 9 | route.get("/", auth, async (req, res) => { 10 | let user = await req.app.get("client").users.fetch(req.user.id); 11 | if (!user) return res.render("user/notfound", {user: req.user}); 12 | 13 | 14 | let bots = await Bots.find({}, { _id: false }) 15 | bots = bots.filter(bot => { 16 | // Backward compaitibility 17 | let owners = [bot.owners.primary].concat(bot.owners.additional) 18 | if (String(bot.owners).startsWith("[")) 19 | owners = String(bot.owners).replace("[ '", "").replace("' ]", "").split("', '") 20 | return owners.includes(user.id) 21 | }) 22 | 23 | res.render("user/me", { 24 | userProfile: user, 25 | cards: bots, 26 | admin: admin_user_ids.includes(user.id), 27 | moderator: req.user.staff, 28 | req 29 | }); 30 | }); 31 | 32 | module.exports = route; -------------------------------------------------------------------------------- /src/routes/api/auth/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { auth } = require("@utils/discordApi"); 3 | const create = require('@utils/createAuth.js'); 4 | const Bots = require("@models/bots"); 5 | const { MessageEmbed } = require("discord.js"); 6 | const Users = require("@models/users"); 7 | 8 | const { server } = require("@root/config.json"); 9 | 10 | const route = Router(); 11 | 12 | route.get("/:id", auth, async (req, res) => { 13 | 14 | const bot = await Bots.findOne({ botid: req.params.id }, { _id: false }) 15 | if (!bot) return res.json({ "success": "false", "error": "Bot not found." }); 16 | if (![bot.owners.primary].concat(bot.owners.additional).includes(req.user.id) && !admin_user_ids.includes(req.user.id)) return res.json({ "success": false, "error": "Bot owner is not user." }); 17 | if (!bot.auth) { 18 | let newAuthCode = create(20); 19 | await Bots.updateOne({ botid: bot.botid }, { $set: { auth: newAuthCode } }) 20 | res.json({ "success": true, "auth": newAuthCode }); 21 | } else { 22 | res.json({ "success": true, "auth": bot.auth }); 23 | } 24 | }); 25 | 26 | 27 | module.exports = route; 28 | -------------------------------------------------------------------------------- /src/routes/api/auth/stats.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const bodyParser = require("body-parser"); 3 | const Bots = require("@models/bots"); 4 | 5 | const authApi = require("@utils/authApi"); 6 | 7 | const route = Router(); 8 | route.use(bodyParser.json({limit: '50mb'})); 9 | 10 | route.post('/:id', authApi, async (req, res) => { 11 | let count = req.body.count || req.body.server_count; 12 | 13 | if (!count) return res.json({ success: "false", error: "Count not found in body." }); 14 | count = parseInt(count); 15 | if (!count) return res.json({ success: "false", error: "Count not integer." }); 16 | 17 | let bot = await Bots.findOne({ botid: req.params.id }, { _id: false }) 18 | if (bot.servers.length > 0 && bot.servers[bot.servers.length-1] && Date.now() - bot.servers[bot.servers.length-1].time < ratelimit * 1000) return res.json({ success: "false", error: "You are being ratelimited." }); 19 | 20 | bot = await Bots.findOneAndUpdate({ botid: req.params.id }, {"$push":{"servers":{"$each": [{count}]}}}, { runValidators: true }) 21 | res.json({ success: true }); 22 | }); 23 | 24 | module.exports = route; 25 | -------------------------------------------------------------------------------- /src/routes/api/callback.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const passport = require('passport'); 3 | const { MessageEmbed } = require("discord.js"); 4 | const { server } = require("@root/config.json"); 5 | const perms1 = require("@root/config.json"); 6 | const { web: { domain_with_protocol } } = require("@root/config.json"); 7 | const route = Router(); 8 | 9 | route.get("/", passport.authenticate('discord', { 10 | failureRedirect: '/' 11 | }), function(req, res) { 12 | if(!server.website_logs){ 13 | res.redirect("/me"); 14 | }else{ 15 | res.redirect("/me"); 16 | let embed = new MessageEmbed() 17 | .setAuthor(`${req.user.username}#${req.user.discriminator}`, `https://cdn.discordapp.com/avatars/${req.user.id}/${req.user.avatar}`, `${domain_with_protocol}/user/${req.user.id}`) 18 | .setColor('#4d79ff') 19 | .setThumbnail(`https://cdn.discordapp.com/avatars/${req.user.id}/${req.user.avatar}`) 20 | .setDescription(`<@${req.user.id}> (${req.user.id}), Welcome to our Bot List!`) 21 | req.app.get('client').channels.cache.get(server.website_logs).send(embed) 22 | } 23 | }); 24 | 25 | module.exports = route; 26 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | require("module-alias/register"); 2 | const mongoose = require("mongoose"); 3 | const { MessageEmbed } = require('discord.js'); 4 | const bot = require('@bot/index'); 5 | const App = require('@structures/app.js'); 6 | const { web: {port}, discord_client: {token}, mongo_url } = require("@root/config.json"); 7 | 8 | 9 | 10 | (async () => { 11 | await mongoose.connect(`${mongo_url}`, { 12 | useCreateIndex: true, 13 | useNewUrlParser: true, 14 | useUnifiedTopology: true, 15 | useFindAndModify: false 16 | }); 17 | console.log("-----------------------------"); 18 | console.log(" I AM READY TO GO "); 19 | console.log("-----------------------------"); 20 | console.log(`Connected to the database on`,`\x1b[34m\x1b[4m${mongo_url}\x1b[0m`); 21 | console.log("-----------------------------"); 22 | let client = await bot.init(token); 23 | console.log(`Logged in as ` + `\x1b[34m\x1b[4m${client.user.tag}\x1b[0m`); 24 | await new App(client).listen(port || 8080); 25 | console.log(`Running on port ` + `\x1b[34m\x1b[4m${port || 80}\x1b[0m`); 26 | console.log("-----------------------------"); 27 | })() -------------------------------------------------------------------------------- /src/routes/api/cert/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { auth } = require("@utils/discordApi"); 3 | const { MessageEmbed } = require("discord.js"); 4 | const Bots = require("@models/bots"); 5 | const checkFields = require('@utils/checkFields'); 6 | const { server } = require("@root/config.json"); 7 | 8 | const route = Router(); 9 | 10 | route.patch("/:id", auth, async (req, res) => { 11 | let data = req.body; 12 | 13 | const bot = await Bots.findOne({ botid: req.params.id }, { _id: false }); 14 | let check = await checkFields(req, bot); 15 | if (!check.success) return res.json(check); 16 | let { certlong } = data; 17 | let embed = new MessageEmbed() 18 | .setTitle('New Certification Request') 19 | .setColor('BLUE') 20 | .addField('User', `<@${req.user.id}>`) 21 | .addField('Why Should we certify your bot ?', certlong) 22 | .addField('Bot', `<@${bot.botid}>`); 23 | await Bots.updateOne({ botid: req.params.id }, {$set: { certlong } }) 24 | req.app.get('client').channels.cache.get(server.mod_log_id).send(embed) 25 | return res.json({ success: true }) 26 | }); 27 | 28 | module.exports = route; 29 | -------------------------------------------------------------------------------- /src/bot/commands/Bot List/queue.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | const Bots = require("@models/bots"); 4 | 5 | const { server: { id } } = require("@root/config.json"); 6 | 7 | module.exports = class extends Command { 8 | constructor(...args) { 9 | super(...args, { 10 | aliases: ["q"], 11 | description: "Get the queue list of Astra's Bot List" 12 | }); 13 | } 14 | 15 | async run(message) { 16 | let cont = ""; 17 | let bots = await Bots.find({ state: "unverified" }, { _id: false }) 18 | 19 | bots.forEach(bot => { cont += `● **[Owner: <@${bot.owners.primary}> ]** - <@${bot.botid}> **[ [Invite to Test](https://discord.com/oauth2/authorize?client_id=${bot.botid}&scope=bot&guild_id=${id}&permissions=0) ]**\n` }) 20 | if (bots.length === 0) cont = "\```Oops! It seems like nobody applied a new bot!\```"; 21 | 22 | let embed = new MessageEmbed() 23 | .setAuthor(`${message.author.tag} here is the botlist queue`, message.author.displayAvatarURL({ format: "png", size: 256 })) 24 | .setColor('ORANGE') 25 | .setDescription(cont) 26 | message.channel.send(embed) 27 | } 28 | }; -------------------------------------------------------------------------------- /src/routes/api/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const bodyParser = require("body-parser"); 3 | 4 | const bots = require("@routes/api/bots"); 5 | const admin = require("@routes/api/admin"); 6 | const like = require("@routes/api/like"); 7 | const auth = require("@routes/api/auth"); 8 | const avatar = require("@routes/api/avatar"); 9 | const embed = require("@routes/api/embed"); 10 | const theme = require("@routes/api/theme"); 11 | const callback = require("@routes/api/callback"); 12 | 13 | const route = Router(); 14 | 15 | route.use(bodyParser.json({limit: '10mb'})); 16 | 17 | route.use(function (_, res, next) { 18 | res.setHeader('Access-Control-Allow-Origin', '*'); 19 | res.setHeader('Access-Control-Allow-Methods', 'GET'); 20 | res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With,content-type'); 21 | res.setHeader('Access-Control-Allow-Credentials', true); 22 | next(); 23 | }); 24 | 25 | route.use("/bots", bots); 26 | route.use("/admin", admin); 27 | route.use("/like", like); 28 | route.use("/auth", auth); 29 | route.use("/theme", theme); 30 | route.use("/avatar", avatar); 31 | route.use("/embed", embed); 32 | route.use("/callback", callback); 33 | 34 | module.exports = route; 35 | -------------------------------------------------------------------------------- /src/bot/commands/Developer/prefix.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: [], 9 | permissionLevel: 6, 10 | description: 'Change the prefix of the bot for your server.', 11 | extendedHelp: [ 12 | 'To change the prefix of your server:', 13 | '', 14 | 'prefix newPrefix', 15 | '', 16 | 'Reset your prefix to the default prefix', 17 | '', 18 | 'prefix', 19 | '', 20 | '**__Example Use__**', 21 | '.prefix !' 22 | ].join('\n'), 23 | usage: '[prefix:str{1,10}]' 24 | }); 25 | } 26 | 27 | async run(message, [prefix]) { 28 | if (!prefix) return this.reset(message); 29 | 30 | if (message.guild.settings.prefix === prefix) throw message.language.get('CONFIGURATION_EQUALS'); 31 | 32 | await message.guild.settings.update('prefix', prefix); 33 | return message.sendMessage(`The prefix for this guild has been set to ${prefix}`); 34 | } 35 | 36 | async reset(message) { 37 | await message.guild.settings.update('prefix', this.client.options.prefix); 38 | return message.sendMessage(`Switched back the guild's prefix back to \`${this.client.options.prefix}\`!`); 39 | } 40 | 41 | }; 42 | -------------------------------------------------------------------------------- /src/bot/commands/Bot List/marknsfw.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | const Bots = require("@models/bots"); 3 | const perms = require("@root/config.json"); 4 | module.exports = class extends Command { 5 | constructor(...args) { 6 | super(...args, { 7 | aliases: ["nsfw", "toggle-nsfw", "togglensfw"], 8 | usage: "[User:user]", 9 | description: "Mark bots as NSFW." 10 | }); 11 | } 12 | 13 | async run(message, [user]) { 14 | if (!perms.server.botreviewer.includes(message.author.id)) 15 | return message.channel.send({ 16 | embed: { 17 | color: 'RED', 18 | description: `> you do not have enough permissions to run this command.`, 19 | } 20 | }); 21 | if (!user || !user.bot) return message.channel.send(`You didn't ping a bot to mark as **NSFW**`); 22 | let bot = await Bots.findOne({ botid: user.id }); 23 | 24 | if (bot === null) 25 | return message.channel.send({ 26 | embed: { 27 | color: 'RED', 28 | description: `this bot is not on our botlist`, 29 | } 30 | }); 31 | await Bots.updateOne({ botid: user.id }, { $set: { nsfw: !bot.nsfw } }) 32 | message.channel.send(`<:db_verified:826375752840249365> \`${user.tag}\` Marked as an ${bot.nsfw ? "not " : ""}**NSFW**`) 33 | } 34 | }; -------------------------------------------------------------------------------- /src/bot/bwd/guilds_Schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "Folder", 3 | "administrator": { 4 | "type": "role", 5 | "array": false, 6 | "default": null, 7 | "min": null, 8 | "max": null, 9 | "sql": "TEXT", 10 | "configurable": true 11 | }, 12 | "disabledCommands": { 13 | "type": "command", 14 | "array": true, 15 | "default": [], 16 | "min": null, 17 | "max": null, 18 | "sql": "TEXT DEFAULT '[]'", 19 | "configurable": true 20 | }, 21 | "dj": { 22 | "type": "role", 23 | "array": false, 24 | "default": null, 25 | "min": null, 26 | "max": null, 27 | "sql": "TEXT", 28 | "configurable": true 29 | }, 30 | "language": { 31 | "type": "language", 32 | "array": false, 33 | "default": "en-US", 34 | "min": null, 35 | "max": null, 36 | "sql": "VARCHAR(5) NOT NULL DEFAULT 'en-US'", 37 | "configurable": true 38 | }, 39 | "prefix": { 40 | "type": "string", 41 | "array": false, 42 | "default": "m!", 43 | "min": 1, 44 | "max": 10, 45 | "sql": "VARCHAR(10) NOT NULL DEFAULT 'm!'", 46 | "configurable": true 47 | }, 48 | "management": { 49 | "type": "Folder", 50 | "embedReply": { 51 | "type": "boolean", 52 | "array": false, 53 | "default": false, 54 | "min": null, 55 | "max": null, 56 | "sql": "TEXT DEFAULT false", 57 | "configurable": true 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/bot/inhibitors/properPermission.js: -------------------------------------------------------------------------------- 1 | const { Inhibitor } = require("klasa"); 2 | 3 | module.exports = class extends Inhibitor { 4 | constructor(...args) { 5 | super(...args, { 6 | name: "properPermission", 7 | enabled: true 8 | }); 9 | } 10 | 11 | //Checks if higher permission level users can use this command. 12 | //IE: Bot primary and secondary owners. 13 | async run(msg, cmd) { 14 | //Inhibitor is not meant for DM use or commands lower than 5 (Moderator level). 15 | if (!msg.guild || cmd.permissionLevel < 5) { return; } 16 | 17 | // Owner and Secondary level commands should not be blocked by the bot. 18 | if (cmd.permissionLevel === 9 || cmd.permissionLevel === 10) { return; } 19 | 20 | //Guild leaders have access to all mid-range permission level commands. 21 | if (msg.guild.owner.id === msg.author.id) { return; } 22 | 23 | let user = msg.guild.members.cache.get(msg.author.id); 24 | 25 | if (cmd.permissionLevel === 6 && user.permissions.has("ADMINISTRATOR")) { return; } 26 | 27 | if (cmd.permissionLevel === 5) { 28 | if (user.permissions.has("ADMINISTRATOR") || user.roles.cache.has(msg.guild.settings.modRole)) { return; } 29 | } 30 | 31 | throw true; 32 | } 33 | }; -------------------------------------------------------------------------------- /src/public/assets/js/edit.js: -------------------------------------------------------------------------------- 1 | function wait(seconds) { 2 | return new Promise((resolve, _) => { 3 | setTimeout(() => { 4 | resolve(true) 5 | }, 1000*seconds) 6 | }) 7 | } 8 | 9 | $( document ).ready(async function() { 10 | $(".link").click((e) => { 11 | $(".section").hide(); 12 | $($(e.target).attr("data-target")).show() 13 | }) 14 | $(document).on("click",".delete", async function () { 15 | await Swal.fire({ 16 | title: `Deleting ${$(this).attr("data-name")}`, 17 | icon: 'warning', 18 | html: `Type ${$(this).attr("data-name")} to confirm`, 19 | showCancelButton: true, 20 | input: "text", 21 | confirmButtonText: `Delete`, 22 | preConfirm: async (name) => { 23 | if (name.toLowerCase() !== $(this).attr("data-name").toLowerCase()) { 24 | Swal.update({ 25 | title: "Cancelled", 26 | html: "" 27 | }); 28 | await wait(1) 29 | } else { 30 | await fetch(`/api/bots/${$(this).attr("data-id")}`, {method: "DELETE"}); 31 | location.href = "/me"; 32 | } 33 | } 34 | }) 35 | }) 36 | }) -------------------------------------------------------------------------------- /src/bot/commands/Bot List/bots.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | const Bots = require("@models/bots"); 4 | 5 | const { web: { domain_with_protocol } } = require("@root/config.json"); 6 | 7 | module.exports = class extends Command { 8 | constructor(...args) { 9 | super(...args, { 10 | usage: "[User:user]", 11 | description: "Check member's bots." 12 | }); 13 | } 14 | 15 | async run(message, [user]) { 16 | let person = user ? user : message.author; 17 | 18 | if (person.bot) return; 19 | 20 | let bots = await Bots.find({ $or: [{ "owners.primary": person.id }, { "owners.additional": person.id }], state: { $ne: "deleted" } }, { _id: false }); 21 | 22 | if (bots.length === 0) return message.channel.send(`\`${person.tag}\` has no bots.`) 23 | var cont = `` 24 | var un = false; 25 | for (let i = 0; i < bots.length; i++) { 26 | let bot = bots[i]; 27 | if (bot.state == "unverified") { 28 | un = true 29 | cont += `~~<@${bot.botid}>~~\n` 30 | } else cont += `● <@${bot.botid}> **[Website Link](${domain_with_protocol}/bots/${bot.botid})**\n` 31 | } 32 | let e = new MessageEmbed() 33 | .setAuthor(`${person.username}#${person.discriminator}`, `${person.displayAvatarURL({ dynamic: true })}`) 34 | .setTitle('Bots') 35 | .setDescription(cont) 36 | .setColor('BLUE') 37 | if (un) e.setFooter(`Bots with strikethrough are unverified on our botlist.`) 38 | message.channel.send(e) 39 | } 40 | 41 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "LuckyBotList", 3 | "version": "3.0.0", 4 | "description": "A discord bot list.", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "start": "node src/index.js", 8 | "devStart": "nodemon src/index.js" 9 | }, 10 | "dependencies": { 11 | "body-parser": "1.19.0", 12 | "canvas": "^2.8.0", 13 | "canvas-constructor": "^4.1.0", 14 | "client": "0.0.1", 15 | "cookie-parser": "^1.4.5", 16 | "date": "^1.0.2", 17 | "discord.js": "^12.5.3", 18 | "express": "^4.17.1", 19 | "express-session": "^1.17.1", 20 | "is-html": "^2.0.0", 21 | "is-url": "1.2.4", 22 | "klasa": "^0.5.0", 23 | "klasa-bot-plugin": "^1.0.18", 24 | "marked": "^4.0.10", 25 | "module-alias": "^2.2.2", 26 | "moment": "^2.29.2", 27 | "mongoose": "^5.12.7", 28 | "node-fetch": "^2.6.7", 29 | "passport": "^0.4.1", 30 | "passport-discord": "^0.1.4", 31 | "pug": "^3.0.2", 32 | "quick.db": "^7.1.3", 33 | "recaptcha2": "^1.3.3", 34 | "sanitize-html": "^2.3.3", 35 | "vcodes.js": "^1.0.1-renewed" 36 | }, 37 | "devDependencies": { 38 | "nodemon": "2.0.7" 39 | }, 40 | "engines": { 41 | "node": "12.x" 42 | }, 43 | "_moduleAliases": { 44 | "@root": ".", 45 | "@bot": "./src/bot", 46 | "@utils": "./src/utils", 47 | "@routes": "./src/routes", 48 | "@models": "./src/models", 49 | "@structures": "./src/structures" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/public/assets/js/like.js: -------------------------------------------------------------------------------- 1 | $(document).ready(async function () { 2 | $('#like').click(async () => { 3 | const swalWithBootstrapButtons = Swal.mixin({ 4 | customClass: { 5 | confirmButton: 'btn btn-success', 6 | cancelButton: 'btn btn-danger' 7 | }, 8 | buttonsStyling: true 9 | }) 10 | let { isConfirmed } = await swalWithBootstrapButtons.fire({ 11 | title: 'Are you sure you want to vote this bot ?', 12 | text: "You won't be able to vote for the next 12 hours.", 13 | icon: 'info', 14 | showCancelButton: true, 15 | confirmButtonText: 'Yes, Vote' 16 | }) 17 | if (!isConfirmed) return; 18 | let botid = location.href.split(location.host)[1].replace('/bots/like/', '').replace('/', ''); 19 | let req = await fetch(`/api/like/${botid}`, { 20 | method: "PATCH", 21 | headers: { 'Content-Type': 'application/json' } 22 | }) 23 | req = await req.json() 24 | if (req.success) { 25 | await swalWithBootstrapButtons.fire({ 26 | title: 'Success', 27 | text: `You have successfully voted ${bot.username}!`, 28 | icon: 'success' 29 | }) 30 | location.href = `/bots/${botid}` 31 | } else { 32 | let hours = 11 - Math.floor(req.time / 3600000); 33 | let minutes = 60 - Math.ceil((req.time / 60000) % 60); 34 | await swalWithBootstrapButtons.fire({ 35 | title: 'Error', 36 | text: `You can vote again after ${hours} hours and ${minutes} minutes`, 37 | icon: 'error' 38 | }) 39 | location.href = `/bots/${botid}` 40 | } 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /src/tasks/reminder.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 dirigeants 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | /* 27 | 28 | This is to be used with the remindme command located in 29 | /commands/Tools/remindme.js 30 | 31 | */ 32 | const { Task } = require('klasa'); 33 | 34 | module.exports = class extends Task { 35 | 36 | async run({ channel, user, text }) { 37 | const _channel = this.client.channels.get(channel); 38 | if (_channel) await _channel.send(`<@${user}> You wanted me to remind you: ${text}`); 39 | } 40 | 41 | }; 42 | -------------------------------------------------------------------------------- /src/bot/tasks/reminder.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 dirigeants 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | /* 27 | 28 | This is to be used with the remindme command located in 29 | /commands/Tools/remindme.js 30 | 31 | */ 32 | const { Task } = require('klasa'); 33 | 34 | module.exports = class extends Task { 35 | 36 | async run({ channel, user, text }) { 37 | const _channel = this.client.channels.get(channel); 38 | if (_channel) await _channel.send(`<@${user}> You wanted me to remind you: ${text}`); 39 | } 40 | 41 | }; 42 | -------------------------------------------------------------------------------- /src/bot/tasks/unmute.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 dirigeants 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | /* 27 | 28 | This is to be used with the mute command located in 29 | /commands/Moderation/mute.js 30 | 31 | */ 32 | const { Task } = require('klasa'); 33 | 34 | module.exports = class extends Task { 35 | 36 | async run({ guild, user }) { 37 | const _guild = this.client.guilds.get(guild); 38 | if (!_guild) return; 39 | const member = await _guild.members.fetch(user).catch(() => null); 40 | if (!member) return; 41 | await member.roles.remove(_guild.settings.roles.muted); 42 | } 43 | 44 | }; 45 | -------------------------------------------------------------------------------- /src/tasks/unmute.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 dirigeants 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | /* 27 | 28 | This is to be used with the mute command located in 29 | /commands/Moderation/mute.js 30 | 31 | */ 32 | const { Task } = require('klasa'); 33 | 34 | module.exports = class extends Task { 35 | 36 | async run({ guild, user }) { 37 | const _guild = this.client.guilds.get(guild); 38 | if (!_guild) return; 39 | const member = await _guild.members.fetch(user).catch(() => null); 40 | if (!member) return; 41 | await member.roles.remove(_guild.settings.roles.muted); 42 | } 43 | 44 | }; 45 | -------------------------------------------------------------------------------- /src/arguments/rolename.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 dirigeants. All rights reserved. MIT license. 2 | // Modified by the RTByte team for use in RTByte. 3 | const { Argument, util: { regExpEsc } } = require('klasa'); 4 | const { Role } = require('discord.js'); 5 | 6 | const ROLE_REGEXP = Argument.regex.role; 7 | 8 | function resolveRole(query, guild) { 9 | if (query instanceof Role) return guild.roles.cache.has(query.id) ? query : null; 10 | if (typeof query === 'string' && ROLE_REGEXP.test(query)) return guild.roles.cache.get(ROLE_REGEXP.exec(query)[1]); 11 | return null; 12 | } 13 | 14 | module.exports = class extends Argument { 15 | 16 | async run(arg, possible, msg) { 17 | if (!msg.guild) return this.store.get('role').run(arg, possible, msg); 18 | const resRole = resolveRole(arg, msg.guild); 19 | if (resRole) return resRole; 20 | 21 | const results = []; 22 | const reg = new RegExp(regExpEsc(arg), 'i'); 23 | for (const role of msg.guild.roles.cache.values()) { if (reg.test(role.name)) results.push(role); } 24 | 25 | let querySearch; 26 | if (results.length > 0) { 27 | const regWord = new RegExp(`\\b${regExpEsc(arg)}\\b`, 'i'); 28 | const filtered = results.filter(role => regWord.test(role.name)); 29 | querySearch = filtered.length > 0 ? filtered : results; 30 | } else { 31 | querySearch = results; 32 | } 33 | 34 | switch (querySearch.length) { 35 | case 0: throw msg.language.get('ARG_ROLENAME_INVALID', possible.name); 36 | case 1: return querySearch[0]; 37 | default: 38 | if (querySearch[0].name.toLowerCase() === arg.toLowerCase()) return querySearch[0]; 39 | throw msg.language.get('ARG_ROLENAME_MULTIPLE', querySearch); 40 | } 41 | } 42 | 43 | }; 44 | -------------------------------------------------------------------------------- /src/bot/inhibitors/requiredProviders.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 dirigeants 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | const { Inhibitor } = require('klasa'); 27 | 28 | module.exports = class extends Inhibitor { 29 | 30 | async run(message, cmd) { 31 | if (!cmd.requiredProviders || !cmd.requiredProviders.length) return false; 32 | const providers = cmd.requiredProviders.filter(provider => !this.client.providers.has(provider)); 33 | if (!providers.length) throw `The client is missing the **${providers.join(', ')}** provider${providers.length > 1 ? 's' : ''} and cannot run.`; 34 | return false; 35 | } 36 | 37 | }; 38 | -------------------------------------------------------------------------------- /src/public/assets/js/admin.js: -------------------------------------------------------------------------------- 1 | async function approve(id, username) { 2 | await Swal.fire({ 3 | title: `Approve ${username}`, 4 | html: `Are you sure you want to approve ${username}?`, 5 | showCancelButton: true, 6 | confirmButtonText: `Approve`, 7 | showLoaderOnConfirm: true, 8 | preConfirm: async () => { 9 | let body = await fetch(`/api/admin/approve/${id}`, { 10 | method: "POST", 11 | headers: { 'Content-Type': 'application/json' } 12 | }); 13 | body = await body.json(); 14 | if (body.success) location.reload() 15 | else Swal.showValidationMessage(body.message) 16 | } 17 | }) 18 | location.reload() 19 | } 20 | 21 | async function deny(id, username) { 22 | await Swal.fire({ 23 | title: `Deny ${username}`, 24 | html: `Enter a reason to deny ${username}`, 25 | showCancelButton: true, 26 | input: "text", 27 | confirmButtonText: `Deny`, 28 | showLoaderOnConfirm: true, 29 | preConfirm: async (reason) => { 30 | let body = await fetch(`/api/admin/deny/${id}`, { 31 | method: "POST", 32 | headers: { 'Content-Type': 'application/json' }, 33 | body: JSON.stringify({reason}) 34 | }); 35 | body = await body.json(); 36 | if (body.success) location.reload() 37 | else Swal.showValidationMessage(body.message) 38 | } 39 | }) 40 | location.reload() 41 | } 42 | 43 | async function note(note, username) { 44 | await Swal.fire({ 45 | title: `Note for ${username}`, 46 | text: note, 47 | confirmButtonText: `Ok` 48 | }) 49 | } -------------------------------------------------------------------------------- /src/arguments/channelname.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 dirigeants. All rights reserved. MIT license. 2 | // Modified by the RTByte team for use in RTByte. 3 | const { Argument, util: { regExpEsc } } = require('klasa'); 4 | const { Channel, Message } = require('discord.js'); 5 | 6 | const CHANNEL_REGEXP = Argument.regex.channel; 7 | 8 | function resolveChannel(query, guild) { 9 | if (query instanceof Channel) return guild.channels.cache.has(query.id) ? query : null; 10 | if (query instanceof Message) return query.guild.id === guild.id ? query.channel : null; 11 | if (typeof query === 'string' && CHANNEL_REGEXP.test(query)) return guild.channels.cache.get(CHANNEL_REGEXP.exec(query)[1]); 12 | return null; 13 | } 14 | 15 | module.exports = class extends Argument { 16 | 17 | async run(arg, possible, msg) { 18 | if (!msg.guild) return this.store.get('channel').run(arg, possible, msg); 19 | const resChannel = resolveChannel(arg, msg.guild); 20 | if (resChannel) return resChannel; 21 | 22 | const results = []; 23 | const reg = new RegExp(regExpEsc(arg), 'i'); 24 | for (const channel of msg.guild.channels.cache.values()) { 25 | if (reg.test(channel.name)) results.push(channel); 26 | } 27 | 28 | let querySearch; 29 | if (results.length > 0) { 30 | const regWord = new RegExp(`\\b${regExpEsc(arg)}\\b`, 'i'); 31 | const filtered = results.filter(channel => regWord.test(channel.name)); 32 | querySearch = filtered.length > 0 ? filtered : results; 33 | } else { 34 | querySearch = results; 35 | } 36 | 37 | switch (querySearch.length) { 38 | case 0: throw msg.language.get('ARG_CHANNELNAME_INVALID', possible.name); 39 | case 1: return querySearch[0]; 40 | default: throw msg.language.get('ARG_CHANNELNAME_MULTIPLE', querySearch); 41 | } 42 | } 43 | 44 | }; 45 | -------------------------------------------------------------------------------- /src/bot/commands/Developer/blacklist.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | const { User } = require('discord.js'); 3 | const Bots = require("@models/bots"); 4 | const Users = require("@models/users"); 5 | const { server: { mod_log_id, role_ids, admin_user_ids} } = require("@root/config.json"); 6 | var modLog; 7 | 8 | module.exports = class extends Command { 9 | constructor(...args) { 10 | super(...args, { 11 | permissionLevel: 10, 12 | description: language => language.get('COMMAND_BLACKLIST_DESCRIPTION'), 13 | usage: ' [...]', 14 | usageDelim: ' ', 15 | guarded: true 16 | }); 17 | 18 | this.terms = ['usersAdded', 'usersRemoved', 'guildsAdded', 'guildsRemoved']; 19 | } 20 | 21 | async run(message, usersAndGuilds) { 22 | const changes = [[], [], [], []]; 23 | const queries = [[], []]; 24 | 25 | for (const userOrGuild of new Set(usersAndGuilds)) { 26 | const type = userOrGuild instanceof User ? 'user' : 'guild'; 27 | if ( 28 | this.client.settings 29 | .get(`${type}Blacklist`) 30 | .includes(userOrGuild.id || userOrGuild) 31 | ) { 32 | changes[this.terms.indexOf(`${type}sRemoved`)].push( 33 | userOrGuild.name || userOrGuild.username || userOrGuild 34 | ); 35 | } else { 36 | changes[this.terms.indexOf(`${type}sAdded`)].push( 37 | userOrGuild.name || userOrGuild.username || userOrGuild 38 | ); 39 | } 40 | queries[Number(type === 'guild')].push(userOrGuild.id || userOrGuild); 41 | } 42 | 43 | const { errors } = await this.client.settings.update([ 44 | ['userBlacklist', queries[0]], 45 | ['guildBlacklist', queries[1]] 46 | ]); 47 | if (errors.length) throw String(errors[0]); 48 | 49 | return message.sendLocale('COMMAND_BLACKLIST_SUCCESS', changes); 50 | } 51 | }; 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/bot/commands/Bot List/update.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | const Bots = require("@models/bots"); 3 | const perms = require("@root/config.json"); 4 | module.exports = class extends Command { 5 | constructor(...args) { 6 | super(...args, { 7 | runIn: ['text'], 8 | botPerms: ["SEND_MESSAGES"], 9 | description: "Update the bots in the server.", 10 | }); 11 | } 12 | 13 | async run(message) { 14 | let m = await message.channel.send(`Updating bots.`); 15 | try { 16 | await this.update(message.client); 17 | } catch (e) { console.error(e) } 18 | m.edit(`${message.author}, All bots **Updated**`); 19 | } 20 | 21 | async update(client, message) { 22 | if (!perms.server.botreviewer.includes(message.author.id)) 23 | return message.channel.send({ 24 | embed: { 25 | color: 'RED', 26 | description: `${message.author}, You do not have enough permissions to run this command.`, 27 | } 28 | }); 29 | let bots = await Bots.find({}, { _id: false }) 30 | let updates = [] 31 | for (let bot of bots) { 32 | let botUser = client.users.cache.get(bot.id); 33 | if (!botUser) 34 | updates.push({ updateOne: { filter: { botid: bot.id }, update: { state: "deleted", owners: { primary: bot.owners.primary, additional: [] } } } }) 35 | if (bot.logo !== botUser.displayAvatarURL({ format: "png", size: 256 })) 36 | updates.push({ updateOne: { filter: { botid: bot.id }, update: { logo: botUser.displayAvatarURL({ format: "png", size: 256 }) } } }); 37 | if (bot.username !== botUser.username) 38 | updates.push({ updateOne: { filter: { botid: bot.id }, update: { username: botUser.username } } }) 39 | } 40 | await Bots.bulkWrite(updates) 41 | return true; 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/routes/api/like/index.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const fetch = require("node-fetch"); 3 | const { auth } = require("@utils/discordApi"); 4 | const Bots = require("@models/bots"); 5 | const Users = require("@models/users"); 6 | const { server } = require("@root/config.json"); 7 | const { web: {domain_with_protocol}, server: {id} } = require("@root/config.json"); 8 | 9 | const route = Router(); 10 | 11 | route.patch("/:id", auth, async (req, res) => { 12 | let user = await Users.findOne({ userid: req.user.id }) 13 | if (user && (Date.now() - user.time) < 43200000) 14 | return res.json({success: false, time: Date.now() - user.time}) 15 | 16 | let bot = await Bots.findOneAndUpdate({ botid: req.params.id }, { $inc: { likes: 1 } }) 17 | await Users.updateOne({ userid: req.user.id }, { $set: { time: Date.now(), botliked: req.params.id } }, { upsert: true }) 18 | 19 | let userProfile = await req.app.get('client').users.fetch(req.user.id); 20 | 21 | // Discord Webhook 22 | let channel = await req.app.get('client').channels.cache.get(server.like_log); 23 | let webhook = (await channel.fetchWebhooks()).first(); 24 | if (!webhook) 25 | webhook = await channel.createWebhook('Astra Vote') 26 | await webhook.send(`<:vote:839818624411500554> ${userProfile.tag} voted for <@${req.params.id}>\n<${domain_with_protocol}/bots/like/${req.params.id}>`); 27 | 28 | // Custom webhook 29 | if (bot.webhook) { 30 | fetch(bot.webhook, { 31 | method: "POST", 32 | body: JSON.stringify({ 33 | type: "like", 34 | bot: req.params.id, 35 | user: req.user.id, 36 | timestamp: new Date() 37 | }), 38 | headers: { 'Content-Type': 'application/json' } 39 | }) 40 | } 41 | 42 | return res.json({ success: true }) 43 | }); 44 | 45 | module.exports = route; 46 | -------------------------------------------------------------------------------- /src/arguments/username.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 dirigeants. All rights reserved. MIT license. 2 | // Modified by the RTByte team for use in RTByte. 3 | const { Argument, util: { regExpEsc } } = require('klasa'); 4 | const { GuildMember, User } = require('discord.js'); 5 | 6 | const USER_REGEXP = Argument.regex.userOrMember; 7 | 8 | function resolveUser(query, guild) { 9 | if (query instanceof GuildMember) return query.user; 10 | if (query instanceof User) return query; 11 | if (typeof query === 'string') { 12 | if (USER_REGEXP.test(query)) return guild.client.users.fetch(USER_REGEXP.exec(query)[1]).catch(() => null); 13 | if (/\w{1,32}#\d{4}/.test(query)) { 14 | const res = guild.members.cache.find(member => member.user.tag === query); 15 | return res ? res.user : null; 16 | } 17 | } 18 | return null; 19 | } 20 | 21 | module.exports = class extends Argument { 22 | 23 | async run(arg, possible, msg) { 24 | if (!msg.guild) return this.store.get('user').run(arg, possible, msg); 25 | const resUser = await resolveUser(arg, msg.guild); 26 | if (resUser) return resUser; 27 | 28 | const results = []; 29 | const reg = new RegExp(regExpEsc(arg), 'i'); 30 | for (const member of msg.guild.members.cache.values()) { 31 | if (reg.test(member.user.username)) results.push(member.user); 32 | } 33 | 34 | let querySearch; 35 | if (results.length > 0) { 36 | const regWord = new RegExp(`\\b${regExpEsc(arg)}\\b`, 'i'); 37 | const filtered = results.filter(user => regWord.test(user.username)); 38 | querySearch = filtered.length > 0 ? filtered : results; 39 | } else { 40 | querySearch = results; 41 | } 42 | 43 | switch (querySearch.length) { 44 | case 0: throw msg.language.get('ARG_USERNAME_INVALID', possible.name); 45 | case 1: return querySearch[0]; 46 | default: throw msg.language.get('ARG_USERNAME_MULTIPLE', querySearch); 47 | } 48 | } 49 | 50 | }; 51 | -------------------------------------------------------------------------------- /src/arguments/membername.js: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 dirigeants. All rights reserved. MIT license. 2 | // Modified by the RTByte team for use in RTByte. 3 | const { Argument, util: { regExpEsc } } = require('klasa'); 4 | const { GuildMember, User } = require('discord.js'); 5 | 6 | const USER_REGEXP = Argument.regex.userOrMember; 7 | 8 | function resolveMember(query, guild) { 9 | if (query instanceof GuildMember) return query; 10 | if (query instanceof User) return guild.members.fetch(query.id); 11 | if (typeof query === 'string') { 12 | if (USER_REGEXP.test(query)) return guild.members.fetch(USER_REGEXP.exec(query)[1]).catch(() => null); 13 | if (/\w{1,32}#\d{4}/.test(query)) { 14 | const res = guild.members.cache.find(member => member.user.tag.toLowerCase() === query.toLowerCase()); 15 | return res || null; 16 | } 17 | } 18 | return null; 19 | } 20 | 21 | module.exports = class extends Argument { 22 | 23 | async run(arg, possible, msg) { 24 | if (!msg.guild) throw msg.language.get('ARG_MEMBERNAME_NOT_GUILD'); 25 | const resUser = await resolveMember(arg, msg.guild); 26 | if (resUser) return resUser; 27 | 28 | const results = []; 29 | const reg = new RegExp(regExpEsc(arg), 'i'); 30 | for (const member of msg.guild.members.cache.values()) { 31 | if (reg.test(member.user.username)) results.push(member); 32 | } 33 | 34 | let querySearch; 35 | if (results.length > 0) { 36 | const regWord = new RegExp(`\\b${regExpEsc(arg)}\\b`, 'i'); 37 | const filtered = results.filter(member => regWord.test(member.user.username)); 38 | querySearch = filtered.length > 0 ? filtered : results; 39 | } else { 40 | querySearch = results; 41 | } 42 | 43 | switch (querySearch.length) { 44 | case 0: throw msg.language.get('ARG_MEMBERNAME_INVALID', possible.name); 45 | case 1: return querySearch[0]; 46 | default: throw msg.language.get('ARG_MEMBERNAME_MULTIPLE', querySearch); 47 | } 48 | } 49 | 50 | }; 51 | -------------------------------------------------------------------------------- /src/public/assets/js/danloves.js: -------------------------------------------------------------------------------- 1 | function notilert(text, cclass, autohide) { 2 | var ccclass = cclass.toUpperCase(); 3 | Toast.create(`Alert`, text, TOAST_STATUS.ccclass, 5000); 4 | } 5 | 6 | $(window).on("load", function() { 7 | $(".loader-wrapper").fadeOut(250); 8 | $(".mini-loader").fadeOut(400); 9 | }); 10 | 11 | $(window).on('beforeunload', function() { 12 | 13 | }); 14 | 15 | function gay() { 16 | Toast.create("Danger!", "Thats pretty gay", TOAST_STATUS.DANGER, 10000) 17 | } 18 | String.prototype.toProperCase = function(opt_lowerCaseTheRest) { 19 | return (opt_lowerCaseTheRest ? this.toLowerCase() : this) 20 | .replace(/(^|[\s\xA0])[^\s\xA0]/g, function(s) { return s.toUpperCase(); }); 21 | }; 22 | 23 | // function maintronPlusRandomBg() { 24 | // const bgurls = [ 25 | // 'http://discord.mx/cDVOj9BDiE.svg', 26 | // 'http://discord.mx/Z1LfxBWGKC.svg', 27 | // 'http://discord.mx/x6cwHvo3OC.svg', 28 | // 'http://discord.mx/KFn0rB6WM0.svg', 29 | // 'http://discord.mx/7j4fkWdwWA.svg', 30 | // 'http://discord.mx/Uf1ORFuAjL.svg', 31 | // 'http://discord.mx/g5iUH4qNiY.svg', 32 | // 'http://discord.mx/SNKxqDTXWw.svg', 33 | // 'http://discord.mx/d2P9KH0M3k.svg' 34 | // ]; 35 | // function randomURL(bgurls) { 36 | // return bgurls[Math.floor(Math.random() * bgurls.length)]; 37 | // } 38 | 39 | // $("head").append(` 40 | // `); 46 | // } 47 | // $(document).ready(function(){ 48 | // $(window).scroll(function() { 49 | // if ($(document).scrollTop() > 23) { 50 | // $(".mainnav").css("background", "#363536"); 51 | // } else { 52 | // $(".mainnav").css("background", "#2b2b2b"); 53 | // } 54 | // }); 55 | // }); -------------------------------------------------------------------------------- /src/lib/util/Util.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment-timezone'); 2 | 3 | exports.embedSplitter = async function embedSplitter(name, valueArray, embed) { 4 | const bigString = valueArray.join(' ') + ' '; // eslint-disable-line prefer-template 5 | if (bigString.length < 1024) return embed.addField(name, bigString); 6 | 7 | const substrings = []; 8 | let splitLength = 0; 9 | 10 | while (splitLength < bigString.length) { 11 | let validString = bigString.slice(splitLength, splitLength + 1024); 12 | validString = validString.slice(0, validString.lastIndexOf(' ') + 1); 13 | 14 | if (!validString.length) { 15 | splitLength = bigString.length; 16 | } else { 17 | substrings.push(validString); 18 | splitLength += validString.length; 19 | } 20 | } 21 | 22 | await substrings.forEach(substring => { 23 | if (substring.length) embed.addField(name, substring); 24 | }); 25 | 26 | return null; 27 | }; 28 | 29 | exports.capitalize = (str) => { 30 | if (typeof str !== 'string') return ''; 31 | return str.charAt(0).toUpperCase() + str.slice(1); 32 | }; 33 | 34 | exports.momentThreshold = () => { 35 | moment.relativeTimeThreshold('s', 60); 36 | moment.relativeTimeThreshold('ss', 0); 37 | moment.relativeTimeThreshold('m', 60); 38 | moment.relativeTimeThreshold('h', 24); 39 | moment.relativeTimeThreshold('d', 31); 40 | moment.relativeTimeThreshold('M', 12); 41 | }; 42 | 43 | exports.timezone = (prop, guild) => { 44 | const { capitalize } = require('./util'); 45 | return capitalize(moment.tz(prop, guild.settings.get('timezone')).fromNow()); 46 | }; 47 | exports.timezoneWithDate = (prop, guild) => { 48 | const { capitalize } = require('./util'); 49 | return `${capitalize(moment.tz(prop, guild.settings.get('timezone')).fromNow())} (${moment.tz(prop, guild.settings.get('timezone')).format('MMMM Do, YYYY')})`; 50 | }; 51 | 52 | exports.truncate = (str) => { 53 | if (typeof str !== 'string') return ''; 54 | if (str.length <= 1021) return str; 55 | 56 | return `${str.substr(0, str.lastIndexOf(' ', 1021))}...`; 57 | }; 58 | -------------------------------------------------------------------------------- /src/routes/api/embed.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { Canvas, resolveImage } = require("canvas-constructor"); 3 | const Bots = require("@models/bots"); 4 | 5 | const { web: {domain_with_protocol}, server: {id} } = require("@root/config.json"); 6 | 7 | const path = require("path"); 8 | 9 | const route = Router(); 10 | 11 | route.get("/:id", async (req, res) => { 12 | const bot = await Bots.findOne({ botid: req.params.id }, { _id: false }) 13 | if (!bot) return res.sendStatus(404); 14 | try { 15 | let owner = await req.app.get("client").guilds.cache.get(id).members.fetch(bot.owners.primary); 16 | let avatar = await resolveImage(bot.logo); 17 | let verified = await resolveImage(path.join(__dirname, "./verified_badge.png")); 18 | 19 | let discord_verified = (await (await req.app.get('client').users.fetch(req.params.id)).fetchFlags()).has("VERIFIED_BOT"); 20 | 21 | let img = new Canvas(500, 250) 22 | .setColor("#404E5C") 23 | .printRectangle(0, 0, 500, 250) 24 | .setColor("#DCE2F9") 25 | .setTextFont('bold 35px sans') 26 | .printText(bot.username, 120, 75) 27 | .printRoundedImage(avatar, 30, 30, 70, 70, 20) 28 | .setTextAlign("left") 29 | .setTextFont('bold 12px Verdana') 30 | if (bot.servers[bot.servers.length-1]) 31 | img.printText(`${bot.servers[bot.servers.length-1].count} servers | ${bot.likes} ❤️`, 30, 125); 32 | if (discord_verified) 33 | img.printImage(verified, 420, 55) 34 | img 35 | .printText(`Prefix: ${bot.prefix}`, 30, 145) 36 | .setTextFont('normal 15px Verdana') 37 | .printWrappedText(bot.description, 30, 175, 440, 15) 38 | 39 | .setTextFont('bold 12px sans-serif') 40 | .printText(owner.user.tag, 10, 245) 41 | .setTextAlign("right") 42 | .printText(domain_with_protocol, 490, 245); 43 | 44 | res.writeHead(200, { 45 | "Content-Type": "image/png" 46 | }); 47 | res.end(await img.toBuffer(), "binary"); 48 | } catch (e) { 49 | throw e 50 | res.sendStatus(500); 51 | } 52 | }); 53 | 54 | module.exports = route; 55 | -------------------------------------------------------------------------------- /src/bot/commands/Developer/reload.js: -------------------------------------------------------------------------------- 1 | const { Command, Store, Stopwatch } = require('klasa'); 2 | 3 | module.exports = class extends Command { 4 | 5 | constructor(...args) { 6 | super(...args, { 7 | aliases: ['r'], 8 | permissionLevel: 10, 9 | guarded: true, 10 | description: language => language.get('COMMAND_RELOAD_DESCRIPTION'), 11 | usage: '' 12 | }); 13 | } 14 | 15 | async run(message, [piece]) { 16 | if (piece === 'everything') return this.everything(message); 17 | if (piece instanceof Store) { 18 | const timer = new Stopwatch(); 19 | await piece.loadAll(); 20 | await piece.init(); 21 | if (this.client.shard) { 22 | await this.client.shard.broadcastEval(` 23 | if (String(this.options.shards) !== '${this.client.options.shards}') this.${piece.name}.loadAll().then(() => this.${piece.name}.init()); 24 | `); 25 | } 26 | return message.sendLocale('COMMAND_RELOAD_ALL', [piece, timer.stop()]); 27 | } 28 | 29 | try { 30 | const itm = await piece.reload(); 31 | const timer = new Stopwatch(); 32 | if (this.client.shard) { 33 | await this.client.shard.broadcastEval(` 34 | if (String(this.options.shards) !== '${this.client.options.shards}') this.${piece.store}.get('${piece.name}').reload(); 35 | `); 36 | } 37 | return message.sendLocale('COMMAND_RELOAD', [itm.type, itm.name, timer.stop()]); 38 | } catch (err) { 39 | piece.store.set(piece); 40 | return message.sendLocale('COMMAND_RELOAD_FAILED', [piece.type, piece.name]); 41 | } 42 | } 43 | 44 | async everything(message) { 45 | const timer = new Stopwatch(); 46 | await Promise.all(this.client.pieceStores.map(async (store) => { 47 | await store.loadAll(); 48 | await store.init(); 49 | })); 50 | if (this.client.shard) { 51 | await this.client.shard.broadcastEval(` 52 | if (String(this.options.shards) !== '${this.client.options.shards}') this.pieceStores.map(async (store) => { 53 | await store.loadAll(); 54 | await store.init(); 55 | }); 56 | `); 57 | } 58 | return message.sendLocale('COMMAND_RELOAD_EVERYTHING', [timer.stop()]); 59 | } 60 | 61 | }; -------------------------------------------------------------------------------- /src/routes/api/admin/approve.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { auth } = require("@utils/discordApi"); 3 | const Bots = require("@models/bots"); 4 | const { MessageEmbed } = require('discord.js'); 5 | const route = Router(); 6 | 7 | const { server: { id, role_ids, mod_log_id } } = require("@root/config.json") 8 | 9 | route.post("/:id", auth, async function(req, res) { 10 | if (!req.user.staff) return res.json({ success: false, message: 'Forbidden' }); 11 | 12 | // Check bot exists 13 | const bot = await Bots.findOne({ "state": "unverified", botid: req.params.id }, { _id: false }); 14 | if (!bot) return res.json({ success: false, message: 'Bot not found' }); 15 | 16 | // Update bot in database 17 | let botUser = await req.app.get('client').users.fetch(req.params.id); 18 | await Bots.updateOne({ botid: req.params.id }, { $set: { state: "verified", logo: botUser.displayAvatarURL({ format: "png", size: 256 }) } }); 19 | 20 | // Send messages 21 | let owners = [bot.owners.primary].concat(bot.owners.additional) 22 | let modLog = await req.app.get('client').channels.cache.get(mod_log_id); 23 | modLog.send( 24 | new MessageEmbed() 25 | .setTitle('Bot Approved') 26 | .addField(`Bot`, `<@${bot.botid}>`, true) 27 | .addField(`Owner(s)`, owners.map(x => x ? `<@${x}>` : ""), true) 28 | .addField("Reviewer", `<@${req.user.id}>`, true) 29 | .setThumbnail(botUser.displayAvatarURL({ format: "png", size: 256 })) 30 | .setColor('00ff00') 31 | ); 32 | modLog.send(owners.map(x => x ? `<@${x}>` : "")).then(m => { m.delete() }); 33 | 34 | // Update developer roles and send DM 35 | owners = await req.app.get('client').guilds.cache.get(id).members.fetch({ user: owners }) 36 | owners.forEach(o => { 37 | o.roles.add(req.app.get('client').guilds.cache.get(id).roles.cache.get(role_ids.bot_developer)); 38 | o.send(`Your bot <@${bot.botid}> has been approved! 🎉`) 39 | }) 40 | 41 | // Update bot roles 42 | req.app.get('client').guilds.cache.get(id).members.fetch(req.params.id).then(member => { 43 | member.roles.set([role_ids.bot, role_ids.verified]); 44 | }) 45 | 46 | return res.json({ success: true }) 47 | }) 48 | 49 | module.exports = route; -------------------------------------------------------------------------------- /src/routes/api/bots/modify.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const sanitizeHtml = require('sanitize-html'); 3 | const { auth } = require("@utils/discordApi"); 4 | const checkFields = require('@utils/checkFields'); 5 | const Bots = require("@models/bots"); 6 | const { web: {domain_with_protocol}, server: {id} } = require("@root/config.json"); 7 | const { server } = require("@root/config.json"); 8 | 9 | const opts = { 10 | allowedTags: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 11 | 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'hr', 'br', 12 | 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'img', 's', 'u'], 13 | disallowedTagsMode: 'discard', 14 | allowedAttributes: { 15 | a: [ 'href' ], 16 | img: [ 'src' ] 17 | }, 18 | allowedSchemes: [ 'https' ] 19 | } 20 | 21 | const route = Router(); 22 | 23 | route.patch("/:id", auth, async (req, res) => { 24 | let data = req.body; 25 | data.long = sanitizeHtml(data.long, opts); 26 | const bot = await Bots.findOne({ botid: req.params.id }, { _id: false }); 27 | 28 | // Backward compatability 29 | if (String(bot.owners).startsWith("[")) 30 | bot.owners = { 31 | primary: String(bot.owners).replace("[ '", "").replace("' ]", "").split("', '")[0], 32 | additional: String(bot.owners).replace("[ '", "").replace("' ]", "").split("', '").slice(1) 33 | } 34 | 35 | let check = await checkFields(req, bot); 36 | if (!check.success) return res.json(check); 37 | 38 | let { long, description, invite, prefix, support, website, github, banner, donation, tags, webhook } = data; 39 | 40 | await Bots.updateOne({ botid: req.params.id }, {$set: { long, description, invite, prefix, support, website, github, banner, donation, tags, webhook, owners: { primary: bot.owners.primary, additional: check.users } } }) 41 | req.app.get('client').channels.cache.get(server.mod_log_id).send(`<:db_edit:815581773039665163> <@${req.user.id}> has modified <@${bot.botid}>\n<${domain_with_protocol}/bots/${bot.botid}>`) 42 | return res.json({success: true, message: "Added bot", url: `/bots/${bot.botid}`}) 43 | }); 44 | 45 | module.exports = route; 46 | -------------------------------------------------------------------------------- /src/public/assets/css/emojis.css: -------------------------------------------------------------------------------- 1 | #emojis img { 2 | display: block; 3 | } 4 | 5 | .emoji-picker { 6 | background-color: #373737; 7 | width: 100%; 8 | height: 335px; 9 | display: flex; 10 | border-radius:0 0 6px 6px; 11 | } 12 | 13 | #emoji-info.active { 14 | display:block; 15 | flex-wrap:nowrap; 16 | } 17 | .emoji-info { 18 | color:#aaa; 19 | } 20 | .emoji-info.active, .emoji-info:hover { 21 | color:#fff; 22 | } 23 | 24 | .emoji-selectables { 25 | background-color: #2A2A2A; 26 | width: 45px; 27 | height: 100%; 28 | padding: 10px 15px; 29 | display: flex; 30 | flex-direction: column; 31 | align-items: center; 32 | border-radius:0 0 0 6px; 33 | } 34 | 35 | .emoji-selectables span { 36 | margin-bottom: 7px; 37 | cursor: pointer; 38 | } 39 | 40 | .emoji-selectables span.active img, .emoji-selectables span:hover img { 41 | filter: none; 42 | } 43 | 44 | .emoji-selectables span img { 45 | width: 25px; 46 | display: block; 47 | display: flex; 48 | align-items: center; 49 | filter: grayscale(100%) 50 | } 51 | 52 | .emoji-content div { 53 | width: 100%; 54 | flex-wrap: wrap; 55 | justify-content: left; 56 | padding: 5px; 57 | } 58 | 59 | .emoji-content span { 60 | display: block; 61 | padding: 5px; 62 | cursor: pointer; 63 | max-height:40px; 64 | } 65 | 66 | .emoji-content span:hover { 67 | transform: scale(1.1); 68 | background-color: #303030; 69 | border-radius: 5px; 70 | } 71 | 72 | .picker-emoji-content { 73 | display: none; 74 | 75 | } 76 | 77 | .picker-emoji-sel.face { 78 | color: aliceblue; 79 | font-size: 10px; 80 | } 81 | 82 | .picker-emoji-content.active { 83 | display: flex; 84 | max-height: 100%; 85 | overflow-y: scroll; 86 | } 87 | 88 | .emoji-content span img { 89 | width: 32px; 90 | height: 32px; 91 | } 92 | 93 | 94 | .picker-emoji-content::-webkit-scrollbar-thumb { 95 | height: 10px; 96 | background: linear-gradient(180deg, rgba(248,72,61,1) 0%, rgba(228,46,103,1) 100%); 97 | border-radius: 100px; 98 | } 99 | 100 | .picker-emoji-content::-webkit-scrollbar-track { 101 | margin-top:2px; 102 | background-color:transparent; 103 | } 104 | 105 | .picker-emoji-content::-webkit-scrollbar { 106 | width: 6px; 107 | } 108 | -------------------------------------------------------------------------------- /src/routes/api/admin/deny.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { auth } = require("@utils/discordApi"); 3 | const Bots = require("@models/bots"); 4 | const { MessageEmbed } = require('discord.js'); 5 | const route = Router(); 6 | 7 | const { server: { id, mod_log_id } } = require("@root/config.json") 8 | 9 | route.post("/:id", auth, async function (req, res) { 10 | if (!req.user.staff) return res.json({ success: false, message: 'Forbidden' }); 11 | 12 | // Check bot exists 13 | const bot = await Bots.findOne({ "state": "unverified", botid: req.params.id }, { _id: false }); 14 | if (!bot) return res.json({ success: false, message: 'Bot not found' }); 15 | 16 | let { reason } = req.body; 17 | 18 | // Update bot in database 19 | let botUser = await req.app.get('client').users.fetch(req.params.id); 20 | await Bots.updateOne({ botid: req.params.id }, { $set: { state: "deleted", likes: 0, servers: [], note: undefined, logo: botUser.displayAvatarURL({ format: "png", size: 256 }) } }); 21 | 22 | // Send messages 23 | let owners = [bot.owners.primary].concat(bot.owners.additional) 24 | let modLog = await req.app.get('client').channels.cache.get(mod_log_id); 25 | modLog.send( 26 | new MessageEmbed() 27 | .setTitle('Bot Declined') 28 | .addField(`Bot ID`, `${bot.botid}`, true) 29 | .addField(`Owner(s)`, owners.map(x => x ? `<@${x}>` : ""), true) 30 | .addField("Mod", `<@${req.user.id}>`, true) 31 | .addField("Reason", reason, true) 32 | .setThumbnail(botUser.displayAvatarURL({format: "png", size: 256})) 33 | .setColor('#ff0000') 34 | ); 35 | modLog.send(owners.map(x => x ? `<@${x}>` : "")).then(m => { m.delete() }); 36 | 37 | // Update developer roles and send DM 38 | owners = await req.app.get('client').guilds.cache.get(id).members.fetch({user:owners}) 39 | owners.forEach(o => { 40 | o.send(`Your bot <@${bot.botid}> (${bot.botid}) has been declined by reviewer <@${req.user.id}>.\nReason: ${reason}\nIf you would like to dispute your decline, please DM <@${req.user.id}> (User ID: ${req.user.id})`) 41 | }) 42 | 43 | // Kick bot 44 | req.app.get('client').guilds.cache.get(id).members.fetch(req.params.id) 45 | .then(member => {if (member) member.kick()}) 46 | .catch(_ => {}) 47 | 48 | return res.json({ success: true }) 49 | }) 50 | 51 | module.exports = route; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # New source code - v14 2 | [Latest source updated code](https://github.com/Astra-Development/Discord-Bot-List-v3) 3 | 4 | --- 5 | 6 | 7 |

8 | 9 |

10 | 11 |

12 | Welcome to Astra Bot List 13 |

14 | 15 |

16 |

17 | 18 | Discord 19 | 20 |

21 | 22 |

23 | Astra Bot List is an open-source!nt branch may have newer additions/features, but is also potentially more buggy or even insecure. Use at your own risk. If you have any issues, check the FAQs first please. 24 |

25 |

26 | 27 |

28 | 29 | ### Official Website Preview [Website Link](https://astrabots.xyz/) 30 | ### Do you need help? Join our [Discord Server](https://astrabots.xyz/join) 31 | 32 | # ScreenShots 33 |

34 | 35 |

36 |

37 | 38 |

39 |

40 | 41 |

42 |

43 | 44 |

45 |

46 | 47 |

48 | 49 | # 📝 Terms of use 50 | ![image](https://user-images.githubusercontent.com/39243722/120484161-7722c480-c3bb-11eb-8850-85a83bf3ab9d.png) 51 | > If i find your botlist without the footer you are X_X 52 | - You have permission to film and share videos, but you must include a mention of us, our server, in the video. 53 | - You are permitted to share in writing, but you must include a mention of us, our server, in the article. 54 | - You can't say things like "we did it." 55 | - You are unable to sell it. 56 | - Don't touch this section of the footer; 57 | -------------------------------------------------------------------------------- /src/models/bots.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const botsSchema = new mongoose.Schema({ 4 | addedAt: { 5 | default: () => new Date(), 6 | type: Date 7 | }, 8 | 9 | 10 | username: { 11 | type: String, 12 | required: true 13 | }, 14 | botid: { 15 | type: String, 16 | required: true, 17 | unique: true 18 | }, 19 | logo: { 20 | type: String, 21 | required: true, 22 | default: "https:discord.mx/hcvOiDRmYc.jpg" 23 | }, 24 | invite: { 25 | type: String 26 | }, 27 | botLibrary: { 28 | type: String, 29 | required: false 30 | }, 31 | description: { 32 | type: String, 33 | required: true 34 | }, 35 | inRecomendationQueue: { 36 | type: Boolean, 37 | default: false 38 | }, 39 | long: { 40 | type: String, 41 | required: true 42 | }, 43 | prefix: { 44 | type: String, 45 | required: true 46 | }, 47 | 48 | 49 | state: { 50 | type: String, 51 | required: true, 52 | default: "unverified" 53 | }, 54 | 55 | 56 | 57 | support: { 58 | type: String, 59 | required: false 60 | }, 61 | website: { 62 | type: String 63 | }, 64 | github: { 65 | type: String 66 | }, 67 | donation: { 68 | type: String 69 | }, 70 | banner: { 71 | type: String 72 | }, 73 | webhook: { 74 | type: String 75 | }, 76 | 77 | 78 | 79 | tags: { 80 | type: Array, 81 | required: false, 82 | default: [] 83 | }, 84 | 85 | 86 | 87 | owners: { 88 | primary: { 89 | type: String, 90 | required: true 91 | }, 92 | additional: { 93 | type: Array, 94 | default: [] 95 | } 96 | }, 97 | 98 | 99 | auth: { 100 | type: String 101 | }, 102 | 103 | 104 | servers: [ 105 | { 106 | time: { 107 | type: Date, 108 | default: () => Date.now() 109 | }, 110 | count: { 111 | type: Number, 112 | required: true 113 | } 114 | } 115 | ], 116 | 117 | 118 | nsfw: { 119 | type: Boolean, 120 | default: false 121 | }, 122 | likes: { 123 | type: Number, 124 | default: 0 125 | }, 126 | ratelimit: { 127 | time: { 128 | type: Date, 129 | default: () => Date.now() 130 | } 131 | }, 132 | note: { 133 | type: String, 134 | required: false 135 | }, 136 | certify: { 137 | type: Boolean, 138 | default: false 139 | } 140 | }); 141 | 142 | module.exports = mongoose.model("Bots", botsSchema); -------------------------------------------------------------------------------- /src/structures/app.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const express = require("express"); 3 | const cookieParser = require("cookie-parser"); 4 | const session = require("express-session"); 5 | const passport = require("passport"); 6 | 7 | const { discord_client: {secret} } = require("@root/config.json"); 8 | 9 | require("@utils/passport.js"); 10 | 11 | const getFilesSync = require("@utils/fileWalk"); 12 | 13 | class App { 14 | constructor(client, locals = {}) { 15 | this.express = express(); 16 | this.express.set("views", "src/dynamic"); 17 | this.express.set("view engine", "pug"); 18 | this.express.set("client", client); 19 | this.express.locals = locals; 20 | 21 | / * Middleware Functions */; 22 | this.express.use(cookieParser()); 23 | this.express.use(express.static(__dirname + "/../public")); 24 | this.express.use( 25 | session({ 26 | secret, 27 | resave: false, 28 | saveUninitialized: false, 29 | }) 30 | ); 31 | this.express.use(passport.initialize()); 32 | this.express.use(passport.session()); 33 | this.express.use((req, _, next) => { 34 | if (!["/theme", "/login"].includes(req.originalUrl) && !req.originalUrl.startsWith("/api")) 35 | req.session.url = req.originalUrl 36 | next() 37 | }) 38 | 39 | this.loadRoutes().loadErrorHandler(); 40 | } 41 | 42 | listen(port) { 43 | return new Promise((resolve) => this.express.listen(port, resolve)); 44 | } 45 | 46 | loadRoutes() { 47 | const routesPath = path.join(__dirname, "../routes"); 48 | const routes = getFilesSync(routesPath); 49 | 50 | if (!routes.length) return this; 51 | 52 | routes.forEach((filename) => { 53 | const route = require(path.join(routesPath, filename)); 54 | 55 | const routePath = 56 | filename === "index.js" ? "/" : `/${filename.slice(0, -3)}`; 57 | 58 | try { 59 | this.express.use(routePath, route); 60 | } catch (error) { 61 | console.error(`Error occured with the route "${filename}"\n\n${error}`); 62 | } 63 | }); 64 | 65 | return this; 66 | } 67 | 68 | loadErrorHandler() { 69 | this.express.use((req, res) => { 70 | res.status(404); 71 | 72 | if (req.accepts("html")) return res.render("404", {req}); 73 | 74 | if (req.accepts("json")) 75 | return res.send({ 76 | status: 404, 77 | error: "Not found", 78 | }); 79 | 80 | res.type("txt").send("404 - Not found"); 81 | }); 82 | 83 | return this; 84 | } 85 | } 86 | 87 | module.exports = App; 88 | -------------------------------------------------------------------------------- /src/bot/arguments/rolename.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 dirigeants 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | const { Argument, util: { regExpEsc } } = require('klasa'); 27 | const { Role } = require('discord.js'); 28 | 29 | const ROLE_REGEXP = Argument.regex.role; 30 | 31 | function resolveRole(query, guild) { 32 | if (query instanceof Role) return guild.roles.has(query.id) ? query : null; 33 | if (typeof query === 'string' && ROLE_REGEXP.test(query)) return guild.roles.get(ROLE_REGEXP.exec(query)[1]); 34 | return null; 35 | } 36 | 37 | module.exports = class extends Argument { 38 | 39 | async run(arg, possible, message) { 40 | if (!message.guild) return this.role(arg, possible, message); 41 | const resRole = resolveRole(arg, message.guild); 42 | if (resRole) return resRole; 43 | 44 | const results = []; 45 | const reg = new RegExp(regExpEsc(arg), 'i'); 46 | for (const role of message.guild.roles.values()) { if (reg.test(role.name)) results.push(role); } 47 | 48 | let querySearch; 49 | if (results.length > 0) { 50 | const regWord = new RegExp(`\\b${regExpEsc(arg)}\\b`, 'i'); 51 | const filtered = results.filter(role => regWord.test(role.name)); 52 | querySearch = filtered.length > 0 ? filtered : results; 53 | } else querySearch = results; 54 | 55 | 56 | switch (querySearch.length) { 57 | case 0: throw `${possible.name} Must be a valid name, id or role mention`; 58 | case 1: return querySearch[0]; 59 | default: throw `Found multiple matches: \`${querySearch.map(role => role.name).join('`, `')}\``; 60 | } 61 | } 62 | 63 | }; 64 | -------------------------------------------------------------------------------- /example-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "discord_client": { 3 | "id": "xxxxxxxxxx", 4 | "token": "xxxxxxxxxx", 5 | "secret": "xxxxxxxxxx", 6 | "prefix": "lb!" 7 | }, 8 | "web": { 9 | "domain_with_protocol": "xxxxxxxxxx", 10 | "port": 5000, 11 | "ratelimit": 100, 12 | "recaptcha_v2": { 13 | "site_key": "xxxxxxxxxx", 14 | "secret_key": "xxxxxxxxxx" 15 | } 16 | }, 17 | "mongo_url": "xxxxxxxxxx", 18 | "server": { 19 | "id": "xxxxxxxxxx", 20 | "invite": "https://discord.gg/sQQFSnQhdt", 21 | "like_log": "xxxxxxxxxx", 22 | "botOwners": "xxxxxxxxxx", 23 | "mod_log_id": "xxxxxxxxxx", 24 | "website_logs": "xxxxxxxxxx", 25 | "admin_user_ids": "xxxxxxxxxx", 26 | "role_ids": { 27 | "bot": "xxxxxxxxxx", 28 | "verified": "xxxxxxxxxx", 29 | "unverified": "xxxxxxxxxx", 30 | "bot_verifier": "xxxxxxxxxx", 31 | "bot_developer": "xxxxxxxxxx", 32 | "cert_user": "xxxxxxxxxx", 33 | "cert_bot": "xxxxxxxxxx" 34 | }, 35 | "botreviewer": [ 36 | "user-id-1", 37 | "user-id-2" 38 | ] 39 | }, 40 | "bot_options": { 41 | "bot_tags": [ 42 | "Music", 43 | "Moderation", 44 | "Fun", 45 | "Economy", 46 | "Anime", 47 | "Game", 48 | "Meme", 49 | "Social", 50 | "Leveling", 51 | "Roleplay", 52 | "Role Managment", 53 | "Logging", 54 | "Web Dashboard", 55 | "Utility", 56 | "Stream", 57 | "Games", 58 | "Youtube", 59 | "Twitch", 60 | "Reddit" 61 | ], 62 | "bot_libraries": [ 63 | "discljord", 64 | "aegis.cpp", 65 | "D++", 66 | "Sleepy Discord", 67 | "discordcr", 68 | "Discord.Net", 69 | "DSharpPlus", 70 | "dscord", 71 | "coxir", 72 | "Nostrum", 73 | "DiscordGo", 74 | "DisGord", 75 | "catnip", 76 | "Discord4J", 77 | "Javacord", 78 | "JDA", 79 | "discord.js", 80 | "Eris", 81 | "Discord.jl", 82 | "Discordia", 83 | "Dimscord", 84 | "discordnim", 85 | "DiscordPHP", 86 | "RestCord", 87 | "discord.py", 88 | "disco", 89 | "discordrb", 90 | "discord-rs", 91 | "Serenity", 92 | "Twilight", 93 | "AckCord", 94 | "Sword", 95 | "Detritus", 96 | "discordeno", 97 | "Harmony" 98 | ], 99 | "max_bot_tags": 6, 100 | "max_owners_count": 3, 101 | "max_summary_length": 140, 102 | "min_description_length": 300, 103 | "max_description_length": 10000 104 | } 105 | } -------------------------------------------------------------------------------- /src/finalizers/devCommandLog.js: -------------------------------------------------------------------------------- 1 | const { Finalizer } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | const { Colors } = require('../lib/util/constants'); 4 | 5 | module.exports = class extends Finalizer { 6 | 7 | constructor(...args) { 8 | super(...args, { enabled: true }); 9 | } 10 | 11 | async run(message, command, response, runTime, custom) { 12 | // Checks if message sent in guild, command log enabled, guild opted in to command analytics, NOT custom, then calls commandRunLog 13 | // eslint-disable-next-line max-len 14 | if (message.guild && this.client.settings.get('logs.commandRun') && message.guild.settings.get('developmentSettings.commandAnalytics') && !custom) await this.commandRunLog(message, runTime, this.client.settings.get('channels.globalLog')); 15 | // Checks if message NOT sent in guild, command log enabled, then calls dmCommandLog 16 | if (!message.guild && this.client.settings.get('logs.commandRun')) await this.dmCommandLog(message, runTime, this.client.settings.get('channels.globalLog')); 17 | // Checks if message sent in guild, guild command log enabled, then calls commandRunLog for guild 18 | if (message.guild && message.guild.settings.get('logs.events.commandRun')) await this.commandRunLog(message, runTime, message.guild.settings.get('channels.log')); 19 | 20 | return; 21 | } 22 | 23 | async commandRunLog(message, runTime, logChannel) { 24 | const embed = new MessageEmbed() 25 | .setAuthor(`${message.guild.name} (#${message.channel.name})`, message.guild.iconURL()) 26 | .setColor(Colors.white) 27 | .setTitle(message.guild.language.get('GLOBAL_LOG_COMMANDRUN')) 28 | .setDescription(`[${message.guild.language.get('CLICK_TO_VIEW')}](${message.url})`) 29 | .addField('Message', message.content) 30 | .addField('Runtime', runTime) 31 | .setTimestamp() 32 | .setFooter(message.author.tag, message.author.displayAvatarURL()); 33 | 34 | logChannel = await this.client.channels.cache.get(logChannel); 35 | if (logChannel) logChannel.send('', { embed: embed }); 36 | 37 | return; 38 | } 39 | 40 | async dmCommandLog(message, runTime, logChannel) { 41 | const embed = new MessageEmbed() 42 | .setAuthor(`${message.author.tag} (${message.author.id})`, message.author.displayAvatarURL()) 43 | .setColor(Colors.white) 44 | .setTitle(message.language.get('GLOBAL_LOG_COMMANDRUN_DM')) 45 | .addField('Message', message.content) 46 | .addField('Runtime', runTime) 47 | .setFooter(message.author.tag, message.author.displayAvatarURL()); 48 | 49 | logChannel = await this.client.channels.cache.get(logChannel); 50 | if (logChannel) logChannel.send('', { embed: embed }); 51 | 52 | return; 53 | } 54 | 55 | }; 56 | -------------------------------------------------------------------------------- /src/bot/arguments/channelname.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 dirigeants 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | const { Argument, util: { regExpEsc } } = require('klasa'); 27 | const { Channel, Message } = require('discord.js'); 28 | 29 | const CHANNEL_REGEXP = Argument.regex.channel; 30 | 31 | function resolveChannel(query, guild) { 32 | if (query instanceof Channel) return guild.channels.has(query.id) ? query : null; 33 | if (query instanceof Message) return query.guild.id === guild.id ? query.channel : null; 34 | if (typeof query === 'string' && CHANNEL_REGEXP.test(query)) return guild.channels.get(CHANNEL_REGEXP.exec(query)[1]); 35 | return null; 36 | } 37 | 38 | module.exports = class extends Argument { 39 | 40 | async run(arg, possible, message) { 41 | if (!message.guild) return this.channel(arg, possible, message); 42 | const resChannel = resolveChannel(arg, message.guild); 43 | if (resChannel) return resChannel; 44 | 45 | const results = []; 46 | const reg = new RegExp(regExpEsc(arg), 'i'); 47 | for (const channel of message.guild.channels.values()) { 48 | if (reg.test(channel.name)) results.push(channel); 49 | } 50 | 51 | let querySearch; 52 | if (results.length > 0) { 53 | const regWord = new RegExp(`\\b${regExpEsc(arg)}\\b`, 'i'); 54 | const filtered = results.filter(channel => regWord.test(channel.name)); 55 | querySearch = filtered.length > 0 ? filtered : results; 56 | } else { 57 | querySearch = results; 58 | } 59 | 60 | switch (querySearch.length) { 61 | case 0: throw `${possible.name} Must be a valid name, id or channel mention`; 62 | case 1: return querySearch[0]; 63 | default: throw `Found multiple matches: \`${querySearch.map(channel => channel.name).join('`, `')}\``; 64 | } 65 | } 66 | 67 | }; 68 | -------------------------------------------------------------------------------- /src/public/assets/js/main.js: -------------------------------------------------------------------------------- 1 | function c(name) { 2 | var match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)")); 3 | if (match) return match[2]; 4 | } 5 | 6 | String.prototype.replaceAll = function (search, replacement) { 7 | var target = this; 8 | return target.split(search).join(replacement); 9 | }; 10 | 11 | $(window).scroll(function () { 12 | var header = document.getElementById("navTop"); 13 | var elemRect = header.getBoundingClientRect(); 14 | if (elemRect.top <= 0) stickify(header); 15 | if ($(window).scrollTop() === 0) unstickify(header); 16 | }); 17 | 18 | function stickify(something) { 19 | something.style.width = "100%"; 20 | something.style.position = "fixed"; 21 | something.style.margin = "0"; 22 | something.style.boxShadow = "0 4px 4px -4px #888888"; 23 | 24 | if (document.getElementsByClassName("dd-content")[0]) 25 | document.getElementsByClassName( 26 | "dd-content" 27 | )[0].style.boxShadow = `0px 4px 4px -4px rgb(136, 136, 136), -4px 0 4px -4px rgb(136, 136, 136)`; 28 | } 29 | 30 | function unstickify(something) { 31 | something.style.width = "calc(100% - 100px)"; 32 | something.style.position = "absolute"; 33 | something.style.margin = "30px 50px"; 34 | something.style.boxShadow = "0 0 0 0 #888888"; 35 | 36 | if (document.body.offsetWidth < 768) { 37 | something.style.width = "calc(100% - 20px)"; 38 | something.style.margin = "30px 10px"; 39 | } 40 | 41 | document.getElementsByClassName( 42 | "dd-content" 43 | )[0].style.boxShadow = `0 0 0 0 #fff`; 44 | } 45 | 46 | $(document).ready(function () { 47 | let login = document.getElementById("login"); 48 | if (login.innerText == "Login") return; 49 | login.href = `/me`; 50 | 51 | let w = login.getBoundingClientRect().width; 52 | 53 | if (document.getElementById("search")) 54 | document.getElementById("search").style.right = `${w - 50}px`; 55 | if (document.getElementById("searchimg")) 56 | document.getElementById("searchimg").style.right = `${w - 50}px`; 57 | if (document.getElementsByClassName("dropdown")[0]) { 58 | document.getElementsByClassName("dropdown")[0].onmouseover = () => 59 | (document.getElementsByClassName("dd-content")[0].style.display = 60 | "block"); 61 | document.getElementsByClassName("dropdown")[0].onmouseout = () => 62 | (document.getElementsByClassName("dd-content")[0].style.display = "none"); 63 | } 64 | }); 65 | 66 | $('a[href^="#"]').on("click", function (event) { 67 | var target = $(this.getAttribute("href")); 68 | if (target.length) { 69 | event.preventDefault(); 70 | $("html, body") 71 | .stop() 72 | .animate({ 73 | scrollTop: target.offset().top 74 | }, 75 | 1500 76 | ); 77 | } 78 | }); -------------------------------------------------------------------------------- /src/bot/arguments/membername.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 dirigeants 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | const { Argument, util: { regExpEsc } } = require('klasa'); 27 | const { GuildMember, User } = require('discord.js'); 28 | 29 | const USER_REGEXP = Argument.regex.userOrMember; 30 | 31 | function resolveMember(query, guild) { 32 | if (query instanceof GuildMember) return query; 33 | if (query instanceof User) return guild.members.fetch(query); 34 | if (typeof query === 'string') { 35 | if (USER_REGEXP.test(query)) return guild.members.fetch(USER_REGEXP.exec(query)[1]).catch(() => null); 36 | if (/\w{1,32}#\d{4}/.test(query)) return guild.members.find(member => member.user.tag.toLowerCase() === query.toLowerCase()) || null; 37 | } 38 | return null; 39 | } 40 | 41 | module.exports = class extends Argument { 42 | 43 | async run(arg, possible, message) { 44 | if (!message.guild) throw 'This command can only be used inside a guild.'; 45 | const resUser = await resolveMember(arg, message.guild); 46 | console.log(resUser); 47 | if (resUser) return resUser; 48 | 49 | const results = []; 50 | const reg = new RegExp(regExpEsc(arg), 'i'); 51 | for (const member of message.guild.members.values()) { 52 | if (reg.test(member.user.username)) results.push(member); 53 | } 54 | 55 | let querySearch = results; 56 | 57 | if (results.length > 0) { 58 | const regWord = new RegExp(`\\b${regExpEsc(arg)}\\b`, 'i'); 59 | const filtered = results.filter(member => regWord.test(member.user.username)); 60 | querySearch = filtered.length > 0 ? filtered : results; 61 | } 62 | 63 | switch (querySearch.length) { 64 | case 0: throw `${possible.name} Must be a valid name, id or user mention`; 65 | case 1: return querySearch[0]; 66 | default: throw `Found multiple matches: \`${querySearch.map(user => user.tag).join('`, `')}\``; 67 | } 68 | } 69 | 70 | }; 71 | -------------------------------------------------------------------------------- /src/bot/arguments/username.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) 2017-2018 dirigeants 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | */ 25 | 26 | const { Argument, util: { regExpEsc } } = require('klasa'); 27 | const { GuildMember, User } = require('discord.js'); 28 | 29 | const USER_REGEXP = Argument.regex.userOrMember; 30 | 31 | function resolveUser(query, guild) { 32 | if (query instanceof GuildMember) return query.user; 33 | if (query instanceof User) return query; 34 | if (typeof query === 'string') { 35 | if (USER_REGEXP.test(query)) return guild.client.users.fetch(USER_REGEXP.exec(query)[1]).catch(() => null); 36 | if (/\w{1,32}#\d{4}/.test(query)) { 37 | const res = guild.members.find(member => member.user.tag === query); 38 | return res ? res.user : null; 39 | } 40 | } 41 | return null; 42 | } 43 | 44 | module.exports = class extends Argument { 45 | 46 | async run(arg, possible, message) { 47 | if (!message.guild) return this.store.get('user').run(arg, possible, message); 48 | const resUser = await resolveUser(arg, message.guild); 49 | if (resUser) return resUser; 50 | 51 | const results = []; 52 | const reg = new RegExp(regExpEsc(arg), 'i'); 53 | for (const member of message.guild.members.values()) { 54 | if (reg.test(member.user.username)) results.push(member.user); 55 | } 56 | 57 | let querySearch; 58 | if (results.length > 0) { 59 | const regWord = new RegExp(`\\b${regExpEsc(arg)}\\b`, 'i'); 60 | const filtered = results.filter(user => regWord.test(user.username)); 61 | querySearch = filtered.length > 0 ? filtered : results; 62 | } else { 63 | querySearch = results; 64 | } 65 | 66 | switch (querySearch.length) { 67 | case 0: throw `${possible.name} Must be a valid name, id or user mention`; 68 | case 1: return querySearch[0]; 69 | default: throw `Found multiple matches: \`${querySearch.map(user => user.tag).join('`, `')}\``; 70 | } 71 | } 72 | 73 | }; 74 | -------------------------------------------------------------------------------- /src/public/assets/css/bootstrap-toaster.css: -------------------------------------------------------------------------------- 1 | .status-icon { 2 | font-size: 1.1rem; 3 | } 4 | 5 | .toast { 6 | /* Ported back from Bootstrap 5's toast styling */ 7 | width: 350px; 8 | } 9 | 10 | #toastContainer { 11 | /* 12 | Variables dedicated to light and dark theme colors. The light theme ones are only used 13 | for JavaScript overrides, and are duplicates of Bootstrap's settings. 14 | */ 15 | --header-color-light: #6c757d; 16 | --text-color-light: #212529; 17 | --header-bg-color-light: rgba(255,255,255,.85); 18 | --body-bg-color-light: rgba(255,255,255,.85); 19 | --header-color-dark: #f8f9fa; 20 | --text-color-dark: #f8f9fa; 21 | --header-bg-color-dark: rgba(64, 64, 64, 1); 22 | --body-bg-color-dark: rgba(48, 48, 48, 1); 23 | max-height: 100vh; 24 | overflow-y: scroll; 25 | /* Hide scrollbar in Firefox */ 26 | scrollbar-width: none; 27 | /* Render in front of Bootstrap toasts */ 28 | z-index: 1060; 29 | } 30 | 31 | /* Hide scrollbar in all webkit-based browsers. */ 32 | #toastContainer::-webkit-scrollbar { 33 | display: none; 34 | } 35 | 36 | #toastContainer .toast { 37 | margin: 0.8rem; 38 | } 39 | 40 | /* Force toast container to top center on mobile. */ 41 | @media only screen and (max-width: 576px) { 42 | #toastContainer { 43 | width: 100%; 44 | top: 0 !important; 45 | bottom: auto !important; 46 | left: 0 !important; 47 | transform: none !important; 48 | } 49 | 50 | #toastContainer .toast { 51 | margin: 0.8rem auto; 52 | } 53 | } 54 | 55 | /* @media (prefers-color-scheme: dark){ */ 56 | .toast, .toast-header, .toast-header .close { 57 | color: var(--text-color-dark); 58 | } 59 | 60 | .toast { 61 | background-color: var(--body-bg-color-dark); 62 | } 63 | 64 | .toast-header { 65 | background-color: var(--header-bg-color-dark); 66 | padding-bottom:2px; 67 | } 68 | .toast .close { 69 | padding-top:2px; 70 | } 71 | /* } */ 72 | 73 | /* Styles borrowed from Bootstrap 5 for backport */ 74 | 75 | .top-0 { 76 | top: 0 !important; 77 | } 78 | 79 | .top-50 { 80 | top: 50% !important; 81 | } 82 | 83 | .bottom-0 { 84 | bottom: 0 !important; 85 | } 86 | 87 | .left-0 { 88 | left: 0 !important; 89 | } 90 | 91 | .left-50 { 92 | left: 50% !important; 93 | } 94 | 95 | .right-0 { 96 | right: 0 !important; 97 | } 98 | 99 | .translate-middle { 100 | transform: translate(-50%, -50%) !important; 101 | } 102 | 103 | .translate-middle-x { 104 | transform: translateX(-50%) !important; 105 | } 106 | 107 | .translate-middle-y { 108 | transform: translateY(-50%) !important; 109 | } 110 | .toast-timebar-holder { 111 | background:var(--header-bg-color-dark); 112 | } 113 | .toast-timebar { 114 | height:2px; 115 | background:#666666; 116 | border-radius:4px; 117 | } -------------------------------------------------------------------------------- /src/bot/commands/Bot List/approve.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | const Bots = require("@models/bots"); 4 | const perms = require("@root/config.json"); 5 | const { server: { mod_log_id, role_ids } } = require("@root/config.json"); 6 | 7 | var modLog; 8 | 9 | module.exports = class extends Command { 10 | constructor(...args) { 11 | super(...args, { 12 | usage: '[User:user]', 13 | description: "Approve a bot" 14 | }); 15 | } 16 | 17 | async run(message, [user]) { 18 | if (!perms.server.botreviewer.includes(message.author.id)) 19 | return message.channel.send({ 20 | embed: { 21 | color: 'RED', 22 | description: `${message.author}, You do not have enough permissions to run this command.`, 23 | } 24 | }); 25 | 26 | if (!user || !user.bot) return message.channel.send(`Ping a **bot**.`); 27 | let bot = await Bots.findOne({ botid: user.id }, { _id: false }); 28 | 29 | if (bot === null) 30 | return message.channel.send({ 31 | embed: { 32 | color: 'RED', 33 | description: `this bot is not on our botlist`, 34 | } 35 | }); 36 | 37 | if (bot.state === 'verified') 38 | return message.channel.send({ 39 | embed: { 40 | color: 'RED', 41 | description: `${message.author}, \`${bot.username}\` is already verified`, 42 | } 43 | }); 44 | 45 | const botUser = await this.client.users.fetch(user.id); 46 | if (bot.logo !== botUser.displayAvatarURL({ format: "png", size: 256 })) 47 | await Bots.updateOne({ botid: user.id }, { $set: { state: "verified", logo: botUser.displayAvatarURL({ format: "png", size: 256 }) } }); 48 | else 49 | await Bots.updateOne({ botid: user.id }, { $set: { state: "verified" } }) 50 | 51 | let owners = [bot.owners.primary].concat(bot.owners.additional) 52 | let e = new MessageEmbed() 53 | .setTitle('Bot Approved') 54 | .addField(`Bot`, `<@${bot.botid}>`, true) 55 | .addField(`Owner(s)`, owners.map(x => x ? `<@${x}>` : ""), true) 56 | .addField("Reviewer", message.author, true) 57 | .setThumbnail(botUser.displayAvatarURL({ format: "png", size: 256 })) 58 | .setColor(0x26ff00) 59 | modLog.send(e); 60 | modLog.send(owners.map(x => x ? `<@${x}>` : "")).then(m => { m.delete() }); 61 | 62 | owners = await message.guild.members.fetch({ user: owners }) 63 | owners.forEach(o => { 64 | o.roles.add(message.guild.roles.cache.get(role_ids.bot_developer)); 65 | o.send(`Your bot <@${bot.botid}> has been approved! :data:`) 66 | }) 67 | message.guild.members.fetch(message.client.users.cache.find(u => u.id === bot.botid)).then(bot => { 68 | bot.roles.set([role_ids.bot, role_ids.verified]); 69 | }) 70 | message.channel.send(`Approved \`${bot.username}\``); 71 | } 72 | 73 | async init() { 74 | modLog = await this.client.channels.fetch(mod_log_id); 75 | } 76 | }; -------------------------------------------------------------------------------- /src/bot/commands/Bot List/botinfo.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | const Bots = require("@models/bots"); 4 | const { web: { domain_with_protocol } } = require("@root/config.json"); 5 | 6 | module.exports = class extends Command { 7 | constructor(...args) { 8 | super(...args, { 9 | usage: '[User:user]', 10 | description: "Get bot information" 11 | }); 12 | } 13 | 14 | async run(message, [user]) { 15 | if (!user || !user.bot) return message.channel.send(`Please include a bot mention or ID`); 16 | if (user.id === message.client.user.id) return message.channel.send(`Bot not found.`); 17 | 18 | const bot = await Bots.findOne({ botid: user.id }, { _id: false }) 19 | if (!bot) return message.channel.send(`Bot not found.`); 20 | let servers; 21 | if (bot.servers[bot.servers.length - 1]) 22 | servers = bot.servers[bot.servers.length - 1].count; 23 | else servers = null; 24 | const botUser = await this.client.users.fetch(user.id); 25 | if (bot.logo !== botUser.displayAvatarURL({ format: "png", size: 256 })) 26 | await Bots.updateOne({ botid: user.id }, { $set: { logo: botUser.displayAvatarURL({ format: "png", size: 256 }) } }); 27 | let e = new MessageEmbed() 28 | 29 | e.setColor('ORANGE') 30 | e.setAuthor(bot.username, botUser.displayAvatarURL({ format: "png", size: 256 }), bot.invite) 31 | e.setThumbnail(botUser.displayAvatarURL({ format: "png", size: 256 })) 32 | 33 | e.addField(`Bot ID`, !bot.botid ? "Unknown" : bot.botid, true) 34 | e.addField(`Bot Username`, bot.username ? bot.username : "Unknown", true) 35 | e.addField(`Bot Owner`, `<@${bot.owners.primary}>`, true) 36 | e.addField(`Bot Joined BotList`, bot.addedAt, true) 37 | e.addField(`Bot Created`, `${user.createdAt}`, true) 38 | e.addField(`\u200b`, `\u200b`, true) 39 | 40 | e.addField(`[+] Visit on Astra Bots`, `[Click Here](${domain_with_protocol}/bots/${bot.botid})`, true) 41 | e.addField(`[+] Invite the Bot`, `[Click Here](${bot.invite})`, true) 42 | e.addField(`\u200b`, `\u200b`, true) 43 | 44 | e.addField(`Description`, bot.description, false) 45 | 46 | e.addField(`Prefix`, bot.prefix ? bot.prefix : "Unknown", true) 47 | e.addField(`Votes`, `${bot.likes || 0} Likes`, true) 48 | e.addField(`Status`, user.presence.status, true) 49 | e.addField(`Queue`, !bot.state ? "Not Verified" : 'Verified', true) 50 | e.addField(`Certified`, !bot.certify ? "False" : 'True', true) 51 | // e.addField(`Server Count`, `${servers || 0} Servers`, true) 52 | 53 | e.addField(`Github`, !bot.github ? "Not Set" : `[Click Here](${bot.github})`, true) 54 | e.addField(`Donation`, !bot.donation ? "Not Set" : `[Click Here](${bot.donation})`, true) 55 | e.addField(`Website`, !bot.website ? "Not Set" : `[Click Here](${bot.website})`, true) 56 | e.addField(`Support Server`, !bot.support ? "Not Set" : `[Click Here](${bot.support})`, true) 57 | 58 | // e.addField(`\u200b`, `\u200b`, true) 59 | message.channel.send(e); 60 | } 61 | }; -------------------------------------------------------------------------------- /src/bot/commands/Bot List/certify.js: -------------------------------------------------------------------------------- 1 | const { Command, Timestamp } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | const Bots = require("@models/bots"); 4 | const Users = require("@models/users"); 5 | const { server: { mod_log_id, role_ids, admin_user_ids } } = require("@root/config.json"); 6 | const perms = require("@root/config.json"); 7 | var modLog; 8 | 9 | module.exports = class extends Command { 10 | constructor(...args) { 11 | super(...args, { 12 | name: 'certify', 13 | runIn: ['text'], 14 | description: "Certify a bot", 15 | usage: '[User:user]' 16 | }); 17 | } 18 | 19 | async run(message, [user]) { 20 | if (!perms.server.botreviewer.includes(message.author.id)) 21 | return message.channel.send({ 22 | embed: { 23 | color: 'RED', 24 | description: `${message.author}, You do not have enough permissions to run this command.` 25 | } 26 | }); 27 | if (!user || !user.bot) return message.channel.send(`Ping a **bot**.`); 28 | let bot = await Bots.findOne({ botid: user.id }, { _id: false }); 29 | 30 | if (bot === null) 31 | return message.channel.send({ 32 | embed: { 33 | color: 'RED', 34 | thumpbnail: message.guild.iconURL, 35 | description: `This bot is not on our botlist`, 36 | } 37 | }); 38 | 39 | if (bot.certify === true) 40 | return message.channel.send({ 41 | embed: { 42 | color: 'RED', 43 | description: `${message.author}, \`${bot.username}\` is already certify`, 44 | } 45 | }); 46 | const botUser = await this.client.users.fetch(user.id); 47 | if (bot.logo !== botUser.displayAvatarURL({ format: "png", size: 256 })) 48 | await Bots.updateOne({ botid: user.id }, { $set: { certify: true, logo: botUser.displayAvatarURL({ format: "png", size: 256 }) } }); 49 | else 50 | await Bots.updateOne({ botid: user.id }, { $set: { certify: true } }) 51 | 52 | let owners = [bot.owners.primary].concat(bot.owners.additional) 53 | let e = new MessageEmbed() 54 | .setTitle('Bot Certified') 55 | .addField(`Bot`, `<@${bot.botid}>`, true) 56 | .addField(`Owner(s)`, owners.map(x => x ? `<@${x}>` : ""), true) 57 | .addField("Mod", message.author, true) 58 | .setThumbnail(botUser.displayAvatarURL({ format: "png", size: 256 })) 59 | .setColor(0x26ff00) 60 | modLog.send(e); 61 | modLog.send(owners.map(x => x ? `<@${x}>` : "")).then(m => { m.delete() }); 62 | owners = await message.guild.members.fetch({ user: owners }) 63 | owners.forEach(o => { 64 | Users.updateOne({ userid: o.id }, { $set: { certdev: true } }).then(); 65 | o.roles.add(message.guild.roles.cache.get(role_ids.cert_user)); 66 | o.send(`Your bot \`${bot.username}\` / <@${bot.botid}> has been certified! :tada:.`) 67 | }) 68 | message.guild.members.fetch(message.client.users.cache.find(u => u.id === bot.botid)).then(bot => { 69 | bot.roles.set([role_ids.cert_bot, role_ids.bot, role_ids.verified]); 70 | }) 71 | message.channel.send(`Certified \`${bot.username}\``); 72 | } 73 | async init() { 74 | modLog = this.client.channels.cache.get(mod_log_id); 75 | } 76 | }; -------------------------------------------------------------------------------- /src/dynamic/includes/head.pug: -------------------------------------------------------------------------------- 1 | head 2 | meta(property='og:title', content='Astra Bot List') 3 | meta(name='twitter:title', content='Astra Bot List') 4 | meta(name='viewport', content='width=device-width, initial-scale=1') 5 | meta(property='og:description', content='Find the best Discord bots for your server with our discord bot list. Browse information on each bot and vote for your favourites.') 6 | meta(name='description', content='Find the best Discord bots for your server with our discord bot list. Browse information on each bot and vote for your favourites.') 7 | meta(property='twitter:description', content='Find the best Discord bots for your server with our discord bot list. Browse information on each bot and vote for your favourites.') 8 | meta(name='og:image', content='/assets/img/banner.png') 9 | meta(name='og:site_name', content='Astra Bots') 10 | meta(name='keywords', content='bots, discord, discord bots, bot, discord bot, Astra, Astrabots, Astra Bots, the Astra, find, find bots') 11 | meta(name='twitter:image', content='/assets/img/banner.png') 12 | meta(name='twitter:card', content='summary_large_image') 13 | script(type='text/javascript', async='', src='https://www.gstatic.com/recaptcha/releases/dpzVjBAupwRfx3UzvXRnnAKb/recaptcha__en.js', crossorigin='anonymous', integrity='sha384-ZPSKvKG0WipeaRQyUewUQ0wMawOWTmw32n5nBKnR21FA4MBKHzMEW63JHSOUpp32') 14 | script(type='text/javascript', async='', src='https://www.google-analytics.com/analytics.js') 15 | meta(name='theme-color', content='#3366ff') 16 | meta(property='og:type', content='website') 17 | meta(name='keywords', content='bots, discord, discord bots, bot, discord bot, Astra, Astrabots, Astrabots, find, find bots') 18 | meta(name='language', content='EN') 19 | meta(name='distribution', content='global') 20 | meta(name='rating', content='general') 21 | meta(name='PreMiD_details', content='general.viewHome') 22 | meta(name='PreMiD_smallImageText', content='Page') 23 | meta(name='PreMiD_smallImageKey', content='img_icon_page') 24 | 25 | head 26 | 27 | //- Font from google fonts 28 | link(href='https://fonts.googleapis.com/icon?family=Markazi+Text' rel='stylesheet') 29 | 30 | //- CKEDITOR 4 for Description editing in HTML 31 | script(src='https://cdn.ckeditor.com/4.14.1/standard/ckeditor.js') 32 | 33 | //- jQuery 34 | script(src='https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.3.1.min.js') 35 | 36 | //- Noty 37 | script(src='https://cdnjs.cloudflare.com/ajax/libs/noty/3.1.4/noty.min.js') 38 | link(href='https://cdnjs.cloudflare.com/ajax/libs/noty/3.1.4/noty.css' rel='stylesheet') 39 | 40 | // Pixel Code for https://www.widgetsquad.com/ 41 | script(async='', src='https://www.widgetsquad.com/pixel/naqbklm9ikm2xbwokngvpbdnptmlb4gl') 42 | 43 | //- FontAwesome 44 | link(href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.14.0/css/all.min.css' rel='stylesheet') 45 | 46 | script(src='/assets/js/main.js') 47 | script(src='/assets/js/cards.js') 48 | 49 | //- Import SWAL 50 | script(src='https://cdn.jsdelivr.net/npm/sweetalert2@9') 51 | link(href='https://cdn.jsdelivr.net/npm/@sweetalert2/theme-dark@3/dark.css' rel='stylesheet') 52 | 53 | 54 | title Astra — Discord Bot List 55 | link(rel='icon', href='/assets/img/logo.png') 56 | -------------------------------------------------------------------------------- /src/public/assets/css/table.css: -------------------------------------------------------------------------------- 1 | #bots-list { 2 | margin: 0 100px; 3 | } 4 | 5 | table { 6 | border-collapse: collapse 7 | } 8 | 9 | th { 10 | text-align: inherit 11 | } 12 | 13 | button { 14 | border-radius: 0 15 | } 16 | 17 | button:focus { 18 | outline: 1px dotted; 19 | outline: 5px auto -webkit-focus-ring-color 20 | } 21 | 22 | button { 23 | margin: 0; 24 | font-family: inherit; 25 | font-size: inherit; 26 | line-height: inherit 27 | } 28 | 29 | button { 30 | overflow: visible 31 | } 32 | 33 | button { 34 | text-transform: none 35 | } 36 | 37 | button { 38 | -webkit-appearance: button 39 | } 40 | 41 | button:not(:disabled) { 42 | cursor: pointer 43 | } 44 | 45 | button::-moz-focus-inner { 46 | padding: 0; 47 | border-style: none 48 | } 49 | 50 | 51 | h5 { 52 | font-size: 1.5rem; 53 | color: var(--primary); 54 | } 55 | 56 | table { 57 | width: 100%; 58 | margin-bottom: 1rem; 59 | color: var(--primary) 60 | } 61 | 62 | th, td { 63 | padding: 0.75rem; 64 | vertical-align: middle; 65 | border-top: 1px solid var(--background-color) 66 | } 67 | 68 | tr { 69 | transition: 400ms ease all; 70 | } 71 | 72 | tbody tr:hover { 73 | color: var(--primary); 74 | background-color: var(--background-2) 75 | } 76 | 77 | .rounded-circle { 78 | user-select: none; 79 | vertical-align: middle; 80 | border-radius: 50px; 81 | border-style: none; 82 | max-width:32px; 83 | height:32px; 84 | } 85 | 86 | .btn { 87 | display: inline-block; 88 | font-weight: 400; 89 | color: #ffffff !important; 90 | text-align: center; 91 | vertical-align: middle; 92 | -webkit-user-select: none; 93 | -moz-user-select: none; 94 | -ms-user-select: none; 95 | user-select: none; 96 | background-color: transparent; 97 | border: 1px solid transparent; 98 | padding: 0.375rem 1rem; 99 | font-size: 0.875rem; 100 | line-height: 1.5; 101 | border-radius: 0.25rem; 102 | cursor: pointer; 103 | transition: 400ms ease all; 104 | } 105 | 106 | .btn:focus { 107 | outline: 0; 108 | -webkit-box-shadow: 0 0 0 0.2rem rgba(42, 159, 214, 0.25); 109 | box-shadow: 0 0 0 0.2rem rgba(42, 159, 214, 0.25) 110 | } 111 | 112 | .btn:focus, .btn:active:focus { 113 | -webkit-box-shadow: 0 0 0 0.2rem var(--background-2); 114 | box-shadow: 0 0 0 0.2rem var(--background-2) 115 | } 116 | 117 | .btn-primary { 118 | color: var(--background-color) !important; 119 | background-color: var(--primary); 120 | border-color: var(--primary) 121 | } 122 | 123 | .btn-secondary { 124 | background-color: var(--background-2); 125 | border-color: var(--background-2) 126 | } 127 | tr:hover td .btn-secondary { 128 | background-color: var(--background-color); 129 | border-color: var(--background-color) 130 | } 131 | 132 | .btn-success { 133 | background-color: var(--text-secondary); 134 | border-color: var(--text-secondary) 135 | } 136 | 137 | .btn-danger { 138 | background-color: var(--secondary); 139 | border-color: var(--secondary) 140 | } 141 | 142 | .pad-right { 143 | margin-right: 10px; 144 | } 145 | 146 | table { 147 | color: var(--color); 148 | } 149 | -------------------------------------------------------------------------------- /src/dynamic/includes/footer.pug: -------------------------------------------------------------------------------- 1 | footer.my-5.pt-5.text-muted.text-center.text-small(style='cursor:default;') 2 | .container 3 | hr 4 | p.mb-1(style='color:#DDD;') Copyright © Astra 2021 5 | p.mb-1 6 | | Proudly developed by 7 | a.text-muted(href='https://discord.gg/sQQFSnQhdt') Astra Development 8 | ul.list-inline 9 | li.list-inline-item 10 | a.prlink(href='https://discord.gg/sQQFSnQhdt') Support 11 | 12 | #google_translate_element 13 | script(type='text/javascript'). 14 | function googleTranslateElementInit() { 15 | new google.translate.TranslateElement({pageLanguage: 'en', layout: google.translate.TranslateElement.InlineLayout.SIMPLE}, 'google_translate_element'); 16 | } 17 | br 18 | br 19 | script(type='text/javascript', src='//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit') 20 | a(href='https://www.dmca.com/compliance/astrabots.xyz', title='DMCA Compliance information for astrabots.xyz') 21 | img(src='https://www.dmca.com/img/dmca-compliant-grayscale.png') 22 | 23 | 24 | a.dmca-badge(href='//www.dmca.com/Protection/Status.aspx?ID=19367a82-4c50-4473-86ca-5bd54fdf294c', title='DMCA.com Protection Status') 25 | img(src='https://images.dmca.com/Badges/dmca_protected_26_120.png?ID=19367a82-4c50-4473-86ca-5bd54fdf294c', alt='DMCA.com Protection Status') 26 | script(src='https://images.dmca.com/Badges/DMCABadgeHelper.min.js') 27 | 28 | style. 29 | #button { 30 | display: inline-block; 31 | background-color: grey; 32 | width: 50px; 33 | height: 50px; 34 | text-align: center; 35 | border-radius: 4px; 36 | position: fixed; 37 | bottom: 30px; 38 | right: 30px; 39 | transition: background-color .3s, 40 | opacity .5s, visibility .5s; 41 | opacity: 0; 42 | visibility: hidden; 43 | z-index: 1000; 44 | } 45 | #button::after { 46 | content: "\f077"; 47 | font-family: FontAwesome; 48 | font-weight: normal; 49 | font-style: normal; 50 | font-size: 2em; 51 | line-height: 50px; 52 | color: #fff; 53 | } 54 | #button:hover { 55 | cursor: pointer; 56 | background-color: #333; 57 | } 58 | #button:active { 59 | background-color: #555; 60 | } 61 | #button.show { 62 | opacity: 1; 63 | visibility: visible; 64 | } 65 | /* Styles for the content section */ 66 | .content { 67 | width: 77%; 68 | margin: 50px auto; 69 | font-family: 'Merriweather', serif; 70 | font-size: 17px; 71 | color: #6c767a; 72 | line-height: 1.9; 73 | } 74 | @media (min-width: 500px) { 75 | .content { 76 | width: 43%; 77 | } 78 | #button { 79 | margin: 30px; 80 | } 81 | } 82 | .content h1 { 83 | margin-bottom: -10px; 84 | color: #03a9f4; 85 | line-height: 1.5; 86 | } 87 | .content h3 { 88 | font-style: italic; 89 | color: #96a2a7; 90 | } 91 | a#button 92 | script. 93 | var btn = $('#button'); 94 | $(window).scroll(function() { 95 | if ($(window).scrollTop() > 300) { 96 | btn.addClass('show'); 97 | } else { 98 | btn.removeClass('show'); 99 | } 100 | }); 101 | btn.on('click', function(e) { 102 | e.preventDefault(); 103 | $('html, body').animate({scrollTop:0}, '300'); 104 | }); 105 | -------------------------------------------------------------------------------- /English Setup/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | Welcome to Astra Bot List 7 |

8 | 9 |

10 |

11 | 12 | Discord 13 | 14 |

15 | 16 |

17 | Astra Bot List is an open-source!nt branch may have newer additions/features, but is also potentially more buggy or even insecure. Use at your own risk. If you have any issues, check the FAQs first please. 18 |

19 | 20 | ### Website Preview [Website Link](https://astrabots.xyz/) 21 | ### Do you need help? Join our [Discord Server](https://astrabots.xyz/join) 22 | 23 | # Setup 24 | Rename `example.config.json` to `config.json` 25 | Fill All the **Values** 26 | Run `npm i` 27 | Start the bot-list `npm start` :) 28 | 29 | # Dashboard Setup 30 | **1.** Visit [Discord Developer Portal](https://discord.com/developers/applications) 31 | 32 | **2.** Click on your bot 33 | 34 | **3.** Go on `OAuth2` 35 | 36 | **4.** On `Redirects` add your website `domain/api/callback` [Eg. `https://astrabots.xyz/api/callback`] 37 | 38 | **5.** Click `Save` 39 | 40 | # All Image location 41 | `src/public/assets/img/` 42 | [Make sure to keep names as it is] 43 | 44 | # MongoURL Value 45 | **1.** Visit [MongoDB](https://www.mongodb.com/) 46 | 47 | **2.** Create Clutser 48 | 49 | **3.** Click connect 50 | 51 | **4.** Connect your application & copy the code 52 | 53 | **5.** On the left side of MongoDB click on `DataBase Access` 54 | 55 | **6.** Add **New database user** 56 | 57 | **7.** Add a custom username [Eg. `LuckyTarget-username`] and a password [Eg. `LuckyTarget-password`] 58 | 59 | **8.** Click the add user Button 60 | 61 | **9.** Visit `config.json` 62 | 63 | **10.** At `"mongo_url": ""` paste the code you copied from step **4** 64 | 65 | ### **Make sure on `` & `` to replace them with what you created on Step 6-8** 66 | 67 | # Recaptcha_V2 Value 68 | **1.** Visit [Google Recaptcha Website](https://www.google.com/recaptcha/admin/) 69 | 70 | **2.** Click on `Create` button 71 | 72 | **3.** On the `label` section you can put whatever you want 73 | 74 | **4.** Select `reCAPTCHA v2` 75 | 76 | **5.** On Domains, add the domain that you have for the **Bot List** [Eg. `astrabots.xyz`] > Click the **+** 77 | 78 | **6.** Click on `Submit` Button 79 | 80 | ![image](https://user-images.githubusercontent.com/39243722/118609705-ac2bf600-b7c3-11eb-9378-6770576dad25.png) 81 | **7.** After `Submit` button you will be navigate to a page similar like this 82 | ![image](https://user-images.githubusercontent.com/39243722/118610249-3c6a3b00-b7c4-11eb-8eb6-15733fdeb656.png) 83 | **8.** Copy `Site Key` & `Secret Key` and paste them on `config.json` file 84 | 85 | ![image](https://user-images.githubusercontent.com/39243722/118610668-aedb1b00-b7c4-11eb-9eb7-a3fe931afb89.png) 86 | 87 | 88 | 89 | # 📝 Do not change the footer of the website! 90 | ![image](https://user-images.githubusercontent.com/39243722/120484161-7722c480-c3bb-11eb-8850-85a83bf3ab9d.png) 91 | > If i find your botlist without the footer you are X_X 92 | 93 | # License Notice 94 | ![License Notice](https://i.ibb.co/Q8vQDTs/image.png) 95 | -------------------------------------------------------------------------------- /src/public/assets/js/confetti.min.js: -------------------------------------------------------------------------------- 1 | var confetti = { maxCount: 150, speed: 2, frameInterval: 15, alpha: 1, gradient: !1, start: null, stop: null, toggle: null, pause: null, resume: null, togglePause: null, remove: null, isPaused: null, isRunning: null }; !function() { confetti.start = s, confetti.stop = w, confetti.toggle = function() { e ? w() : s() }, confetti.pause = u, confetti.resume = m, confetti.togglePause = function() { i ? m() : u() }, confetti.isPaused = function() { return i }, confetti.remove = function() { stop(), i = !1, a = [] }, confetti.isRunning = function() { return e }; var t = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame, n = ["rgba(30,144,255,", "rgba(107,142,35,", "rgba(255,215,0,", "rgba(255,192,203,", "rgba(106,90,205,", "rgba(173,216,230,", "rgba(238,130,238,", "rgba(152,251,152,", "rgba(70,130,180,", "rgba(244,164,96,", "rgba(210,105,30,", "rgba(220,20,60,"], e = !1, i = !1, o = Date.now(), a = [], r = 0, l = null; function d(t, e, i) { return t.color = n[Math.random() * n.length | 0] + (confetti.alpha + ")"), t.color2 = n[Math.random() * n.length | 0] + (confetti.alpha + ")"), t.x = Math.random() * e, t.y = Math.random() * i - i, t.diameter = 10 * Math.random() + 5, t.tilt = 10 * Math.random() - 10, t.tiltAngleIncrement = .07 * Math.random() + .05, t.tiltAngle = Math.random() * Math.PI, t } function u() { i = !0 } function m() { i = !1, c() } function c() { if (!i) if (0 === a.length) l.clearRect(0, 0, window.innerWidth, window.innerHeight), null; else { var n = Date.now(), u = n - o; (!t || u > confetti.frameInterval) && (l.clearRect(0, 0, window.innerWidth, window.innerHeight), function() { var t, n = window.innerWidth, i = window.innerHeight; r += .01; for (var o = 0; o < a.length; o++)t = a[o], !e && t.y < -15 ? t.y = i + 100 : (t.tiltAngle += t.tiltAngleIncrement, t.x += Math.sin(r) - .5, t.y += .5 * (Math.cos(r) + t.diameter + confetti.speed), t.tilt = 15 * Math.sin(t.tiltAngle)), (t.x > n + 20 || t.x < -20 || t.y > i) && (e && a.length <= confetti.maxCount ? d(t, n, i) : (a.splice(o, 1), o--)) }(), function(t) { for (var n, e, i, o, r = 0; r < a.length; r++) { if (n = a[r], t.beginPath(), t.lineWidth = n.diameter, i = n.x + n.tilt, e = i + n.diameter / 2, o = n.y + n.tilt + n.diameter / 2, confetti.gradient) { var l = t.createLinearGradient(e, n.y, i, o); l.addColorStop("0", n.color), l.addColorStop("1.0", n.color2), t.strokeStyle = l } else t.strokeStyle = n.color; t.moveTo(e, n.y), t.lineTo(i, o), t.stroke() } }(l), o = n - u % confetti.frameInterval), requestAnimationFrame(c) } } function s(t, n, o) { var r = window.innerWidth, u = window.innerHeight; window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(t) { return window.setTimeout(t, confetti.frameInterval) }; var m = document.getElementById("confetti-canvas"); null === m ? ((m = document.createElement("canvas")).setAttribute("id", "confetti-canvas"), m.setAttribute("style", "display:block;z-index:999999;pointer-events:none;position:fixed;top:0"), document.body.prepend(m), m.width = r, m.height = u, window.addEventListener("resize", function() { m.width = window.innerWidth, m.height = window.innerHeight }, !0), l = m.getContext("2d")) : null === l && (l = m.getContext("2d")); var s = confetti.maxCount; if (n) if (o) if (n == o) s = a.length + o; else { if (n > o) { var f = n; n = o, o = f } s = a.length + (Math.random() * (o - n) + n | 0) } else s = a.length + n; else o && (s = a.length + o); for (; a.length < s;)a.push(d({}, r, u)); e = !0, i = !1, c(), t && window.setTimeout(w, t) } function w() { e = !1 } }(); -------------------------------------------------------------------------------- /src/public/assets/js/certify.js: -------------------------------------------------------------------------------- 1 | var recaptcha_token = null; 2 | 3 | function update_token(token) { 4 | recaptcha_token = token; 5 | } 6 | 7 | function flash(element_id) { 8 | let element = document.getElementById(element_id); 9 | 10 | const yOffset = -100; 11 | const y = element.getBoundingClientRect().top + window.pageYOffset + yOffset; 12 | 13 | window.scrollTo({ top: y, behavior: 'smooth' }); 14 | 15 | element.style.border = "2px solid #ff0000"; 16 | element.style.backgroundColor = "rgba(255, 0, 0, 0.5)"; 17 | setTimeout(() => { 18 | element.style.backgroundColor = "rgba(0, 0, 0, 0)"; 19 | element.style.border = "1px solid #888"; 20 | }, 600) 21 | } 22 | 23 | function submit(edit = false) { 24 | var $select = $('#api').selectize({ 25 | delimiter: ',', 26 | persist: false, 27 | }); 28 | var $select2 = $('#tos').selectize({ 29 | delimiter: ',', 30 | persist: false, 31 | }); 32 | var $select3 = $('#bot').selectize({ 33 | delimiter: ',', 34 | persist: false, 35 | }); 36 | var selectizeControl = $select[0].selectize 37 | var selectizeControl2 = $select2[0].selectize 38 | var selectizeControl3 = $select3[0].selectize 39 | let form_items = ["bot", "longdesc"] 40 | let data = {} 41 | for (let form_item of form_items) { 42 | data[form_item] = $(`#${form_item}`).val() 43 | } 44 | if (selectizeControl.getValue().includes('No') || selectizeControl.getValue().includes('certify')) return new Noty({ 45 | type: "error", 46 | text: 'You aren\'t using our API', 47 | theme: "sunset", 48 | timeout: 3500 49 | }).show() 50 | if (selectizeControl2.getValue().includes('No') || selectizeControl2.getValue().includes('certify')) return new Noty({ 51 | type: "error", 52 | text: 'You bot isn\'t following Discord ToS or Discord Bot Directory Rules', 53 | theme: "sunset", 54 | timeout: 3500 55 | }).show() 56 | data["id"] = selectizeControl3.getValue()[0]; 57 | data["long"] = data["longdesc"]; 58 | data["prefix"] = "certify" 59 | data["certlong"] = data["longdesc"]; 60 | data["recaptcha_token"] = recaptcha_token; 61 | data["description"] = "certify"; 62 | let method = "PATCH"; 63 | console.log(data.id) 64 | fetch(`/api/cert/${data.id}`, { 65 | method, 66 | headers: { 67 | 'Content-Type': 'application/json' 68 | }, 69 | body: JSON.stringify(data) 70 | }).then(body => body.json()).then(body => { 71 | if (!body.success) { 72 | recaptcha_token = null; 73 | grecaptcha.reset(); 74 | let opts = { 75 | type: "error", 76 | text: body.message, 77 | theme: "sunset", 78 | timeout: 3500 79 | } 80 | if (body.button) { 81 | opts.buttons = [ 82 | Noty.button(body.button.text, 'btn btn-success', function () { 83 | location.href = body.button.url 84 | }), 85 | ] 86 | } 87 | new Noty(opts).show(); 88 | } else { 89 | location.href = "/success" 90 | } 91 | }) 92 | } 93 | $(document).ready(async function () { 94 | var $select = $('#api').selectize({ 95 | persist: false, 96 | maxItems: 1, 97 | placeholder: 'Select your option' 98 | }); 99 | var $select2 = $('#tos').selectize({ 100 | persist: false, 101 | maxItems: 1, 102 | placeholder: 'Select your option' 103 | }); 104 | var $select2 = $('#bot').selectize({ 105 | persist: false, 106 | maxItems: 1, 107 | placeholder: 'Select your option' 108 | }); 109 | }) -------------------------------------------------------------------------------- /Turkish Setup/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | Astra Bot List'e Hoşgeldiniz 7 |

8 | 9 |

10 |

11 | 12 | Discord 13 | 14 |

15 | 16 |

17 | Astra Botlist açık bir kaynaktır. NT dalı yeni eklemeler/özelliklere sahip olabilir, ama aynı zamanda hataları daha fazla hatta güvensiz. Kendin bu riski göz önünde bulundurarak kullan. Eğer soruların varsa, sıkça sorulan sorular kısmına bak lütfen.. 18 |

19 | 20 | ### Siteye Göz At [Sitemiz](https://astrabots.xyz/) 21 | ### Yardıma mı ihtiyacın var? Discord Sunucumuza Katıl [Sunucumuz](https://astrabots.xyz/join) 22 | 23 | # Kurulum 24 | `example.config.json` 'u `config.json` şeklinde yeniden adlandır 25 | Tüm **değerleri** doldur 26 | Ve `npm i` 'i çalıştır. 27 | Botlist'i başlatmak için `npm start` bu komutu kullan :) 28 | 29 | # Panel Kurulum 30 | **1.** Developer Portala gir [Discord Developer Portal](https://discord.com/developers/applications) 31 | 32 | **2.** Botuna tıkla 33 | 34 | **3.** `OAuth2` bölümüne gir 35 | 36 | **4.** `Redirects`/`Yönlendirmeler` kısmına siteni ekle `domain/api/callback` [Örn. `https://astrabots.xyz/api/callback`] 37 | 38 | **5.** `Kaydet` butonuna tıkla 39 | 40 | # Tüm Görüntülerin Konumu 41 | `src/public/assets/img/` 42 | [İsimleri olduğu gibi tuttuğunuzdan emin olun] 43 | 44 | # MongoURL Değeri 45 | **1.** MongoDB sitesine git [MongoDB](https://www.mongodb.com/) 46 | 47 | **2.** `Clutser`/ `Küme` oluştur 48 | 49 | **3.** `Connect`/ `Bağlan` 'e tıkla 50 | 51 | **4.** Uygulamanızı bağlayın ve kodu kopyalayın 52 | 53 | **5.** MongoDB'nin sol tarafında `DataBase Access` üzerine tıklayın 54 | 55 | **6.** **Yeni veritabanı kullanıcısı** ekleyin 56 | 57 | **7.** Özel bir kullanıcı adı ekleyin [Örn. `LuckyTarget-username`] ve bir şifre [Örn. 'LuckyTarget-şifre'] 58 | 59 | **8.** Kullanıcı Ekle Düğmesine tıklayın 60 | 61 | **9.** `config.json` sayfasına geri gelin 62 | 63 | **10.** `"mongo_url": "URL"` URL kısmına **4.** adımdan kopyaladığınız kodu yapıştırın 64 | 65 | ### **6. ve 8. adımdaki oluşturduğunuz kullanıcı adı ve şifreyi username / password kısımlarına girin, kullanıcı adı veya şifre yanlış ise çalışmaz.** 66 | 67 | # Recaptcha_V2 Değeri 68 | **1.** Google Recaptcha sitesine girin [Google Recaptcha Website](https://www.google.com/recaptcha/admin/) 69 | 70 | **2.** `Create`/`Oluştur` butonuna tıklayın 71 | 72 | **3.** `Label`/`Etiket` bölümüne ne istersen koyabilirsin 73 | 74 | **4.** `reCAPTCHA v2` 'yi seç!! 75 | 76 | 77 | 78 | **5.** Domains/Alan Adları kısmında, **BotList** için sahip olduğunuz alan adınızı ekleyin [Örn. `astrabots.xyz`] > **+** simgesine tıklayın 79 | 80 | **6.** `Submit`/`Gönder` butonuna tıklayın 81 | 82 | ![image](https://user-images.githubusercontent.com/85226977/120470241-fe683c00-c3ab-11eb-813b-e3003492acd9.png) 83 | 84 | **7.** `Submit`/`Gönder` butonuna tıkladıktan sonra alttaki resim gibi bir sayfaya gideceksiniz 85 | 86 | ![image](https://user-images.githubusercontent.com/85226977/120470173-ebee0280-c3ab-11eb-9fe6-681302353692.png) 87 | 88 | **8.** `Site Key` & `Secret Key` 'i kopyalayın ve `config.json` içerisinde bu bölümlere yapıştırın 89 | 90 | ![image](https://user-images.githubusercontent.com/39243722/118610668-aedb1b00-b7c4-11eb-9eb7-a3fe931afb89.png) 91 | 92 | 93 | 94 | # 📝 Web sitesinin alt bilgisini/footer kısmını değiştirmeyin! 95 | ![image](https://user-images.githubusercontent.com/39243722/120484241-8c97ee80-c3bb-11eb-9555-a9f7db4aa6ed.png) 96 | > Botlistinizi altbilgi/footer olmadan bulursam, haklarımızı savunmaktan çekinmeyeceğim. 97 | 98 | # Lisans Bildirimi 99 | ![License Bildirimi](https://i.ibb.co/Q8vQDTs/image.png) 100 | -------------------------------------------------------------------------------- /src/routes/api/bots/submit.js: -------------------------------------------------------------------------------- 1 | const { Router } = require("express"); 2 | const { auth } = require('@utils/discordApi'); 3 | const checkFields = require('@utils/checkFields'); 4 | const sanitizeHtml = require('sanitize-html'); 5 | const Bots = require("@models/bots"); 6 | const { web: {domain_with_protocol}, server: {id} } = require("@root/config.json"); 7 | const { server } = require("@root/config.json"); 8 | 9 | const opts = { 10 | allowedTags: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 11 | 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'hr', 'br', 12 | 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'img', 's', 'u' ], 13 | disallowedTagsMode: 'discard', 14 | allowedAttributes: { 15 | a: [ 'href' ], 16 | img: [ 'src' ] 17 | }, 18 | allowedSchemes: [ 'https' ] 19 | } 20 | 21 | const route = Router(); 22 | 23 | Array.prototype.remove = function() { 24 | var what, a = arguments, L = a.length, ax; 25 | while (L && this.length) { 26 | what = a[--L]; 27 | while ((ax = this.indexOf(what)) !== -1) { 28 | this.splice(ax, 1); 29 | } 30 | } 31 | return this; 32 | }; 33 | 34 | route.post("/:id", auth, async (req, res) => { 35 | let resubmit = false; 36 | let check; 37 | 38 | try { 39 | check = await checkFields(req); 40 | if (!check.success) return res.json(check); 41 | } catch (e) { 42 | return res.json({ success: false, message: "Unknown error" }) 43 | } 44 | let { bot, users } = check; 45 | let data = req.body; 46 | data.long = sanitizeHtml(data.long, opts) 47 | let owners = { 48 | primary: req.user.id, 49 | additional: users 50 | }; 51 | 52 | let original = await Bots.findOne({ botid: req.params.id }); 53 | if (original && original.state !== "deleted") 54 | return res.json({ success: false, message: "Your bot already exists on the list.", button: { text: "Edit", url: `/bots/edit/${bot.id}` } }); 55 | else if (original && original.state == "deleted") resubmit = true; 56 | 57 | if (resubmit) { 58 | await Bots.updateOne({ botid: req.params.id }, { 59 | username: bot.username, 60 | invite: data.invite, 61 | description: data.description, 62 | long: data.long, 63 | prefix: data.prefix, 64 | state: "unverified", 65 | support: data.support, 66 | website: data.website, 67 | banner: data.banner, 68 | donation: data.donation, 69 | github: data.github, 70 | tags: data.tags, 71 | note: data.note, 72 | owners 73 | }); 74 | } else { 75 | new Bots({ 76 | username: bot.username, 77 | botid: req.params.id, 78 | logo: `https://cdn.discordapp.com/avatars/${req.params.id}/${bot.avatar}.png?size=256`, 79 | invite: data.invite, 80 | description: data.description, 81 | long: data.long, 82 | prefix: data.prefix, 83 | state: "unverified", 84 | support: data.support, 85 | website: data.website, 86 | banner: data.banner, 87 | donation: data.donation, 88 | github: data.github, 89 | tags: data.tags, 90 | note: data.note, 91 | owners 92 | }).save(); 93 | } 94 | try { 95 | await req.app.get('client').channels.cache.find(c => c.id === server.mod_log_id).send(`<:db_add:816717375331631124> <@${req.user.id}> ${resubmit ? "re-" : ""}added unverified bot <@${req.params.id}>: <@&${server.role_ids.bot_verifier}>\n<${domain_with_protocol}/bots/${req.params.id}>`); 96 | return res.json({ success: true, message: "Your bot has been added" }) 97 | } catch (e) { 98 | return res.json({ success: true, message: "Your bot has been added" }) 99 | } 100 | }); 101 | 102 | module.exports = route; 103 | -------------------------------------------------------------------------------- /src/bot/commands/Bot List/uncertify.js: -------------------------------------------------------------------------------- 1 | const { Command } = require('klasa'); 2 | const { MessageEmbed } = require('discord.js'); 3 | const Bots = require("@models/bots"); 4 | const Users = require("@models/users"); 5 | const { server: { mod_log_id, role_ids, admin_user_ids } } = require("@root/config.json"); 6 | const perms = require("@root/config.json"); 7 | const reasons = { 8 | "1": `Your bot went offline during testing.`, 9 | "2": `Your bot seems to be an unmodified. We don't allow unmodified clones of other bots.`, 10 | "3": `Your bot was **offline** when we tried to review it. For that reason, we are unable to test it. Please get your bot online and re-apply.`, 11 | "4": `The majority of your commands listed on your bot's page, or help command do not provide a response, or do not seem to function/work.`, 12 | "5": `Your bot doesn't have any/enough working commands. (Minimum: 7)`, 13 | "6": `The long description on your bot's page is filled out with spam/junk to reach the 300 character minimum requirement. Please rewrite your description to include more useful information about your bot.`, 14 | "7": `Your bot doesn't have a (working) help command or obvious point of entry. Please make sure your bot has a help command or has an explanation in the bot description.`, 15 | "8": `Your bot's commands have emojis or gifs that could cause epileptic seizures due to its flashy and flickering nature. Please remove all content of such nature in your commands.` 16 | } 17 | var modLog; 18 | 19 | module.exports = class extends Command { 20 | constructor(...args) { 21 | super(...args, { 22 | name: 'uncertify', 23 | runIn: ['text'], 24 | aliases: ["uncert"], 25 | description: "Uncertify's a bot", 26 | usage: '[User:user]' 27 | }); 28 | } 29 | 30 | async run(message, [user]) { 31 | if (!perms.server.botreviewer.includes(message.author.id)) 32 | return message.channel.send({ 33 | embed: { 34 | color: 'RED', 35 | description: `${message.author}, You do not have enough permissions to run this command.`, 36 | } 37 | }); 38 | 39 | //if (!admin_user_ids.includes(message.author.id)) return 40 | 41 | if (!user || !user.bot) return message.channel.send(`Ping a **bot**.`); 42 | 43 | let bot = await Bots.findOne({ botid: user.id }, { _id: false }); 44 | if (bot === null) 45 | return message.channel.send({ 46 | embed: { 47 | color: 'RED', 48 | description: `${message.author} This bot is not on our botlist`, 49 | } 50 | }); 51 | 52 | if (bot.certify === false) 53 | return message.channel.send({ 54 | embed: { 55 | color: 'RED', 56 | description: `${message.author}, \`${bot.username}\` is already un-certify`, 57 | } 58 | }); 59 | 60 | const botUser = await this.client.users.fetch(user.id); 61 | if (bot.logo !== botUser.displayAvatarURL({ format: "png", size: 256 })) 62 | await Bots.updateOne({ botid: user.id }, { $set: { certify: true, logo: botUser.displayAvatarURL({ format: "png", size: 256 }) } }); 63 | else 64 | await Bots.updateOne({ botid: user.id }, { $set: { certify: false } }) 65 | 66 | let owners = [bot.owners.primary].concat(bot.owners.additional) 67 | let e = new MessageEmbed() 68 | .setTitle('Bot Un-Certified') 69 | .addField(`Bot ID`, `${bot.botid}`, true) 70 | .addField(`Bot Owner`, owners.map(x => x ? `<@${x}>` : ""), true) 71 | .addField("Reviewer", message.author, true) 72 | .setThumbnail(botUser.displayAvatarURL({ format: "png", size: 256 })) 73 | .setColor("RED") 74 | modLog.send(e); 75 | modLog.send(owners.map(x => x ? `<@${x}>` : "")).then(m => { m.delete() }); 76 | 77 | owners = await message.guild.members.fetch({ user: owners }) 78 | owners.forEach(o => { 79 | Users.updateOne({ userid: o.id }, { $set: { certdev: 0 } }).then(); 80 | o.roles.add(message.guild.roles.cache.get(role_ids.cert_user)); 81 | o.send(`Your bot \`${bot.username}\` / <@${bot.botid}> has been Un-certified.`) 82 | }) 83 | message.guild.members.fetch(message.client.users.cache.find(u => u.id === bot.botid)).then(bot => { 84 | bot.roles.set([role_ids.bot, role_ids.verified]); 85 | }) 86 | message.channel.send(`Uncertified \`${bot.username}\``); 87 | } 88 | 89 | async init() { 90 | modLog = this.client.channels.cache.get(mod_log_id); 91 | } 92 | }; -------------------------------------------------------------------------------- /src/public/assets/toadloader.js: -------------------------------------------------------------------------------- 1 | ;(function (win, doc) { 2 | 3 | if (!('requestAnimationFrame' in win)) { 4 | win.requestAnimationFrame = (function () { 5 | return win.webkitRequestAnimationFrame 6 | || win.mozRequestAnimationFrame 7 | || win.oRequestAnimationFrame 8 | || win.msRequestAnimationFrame 9 | || function (callback) { return win.setTimeout(callback, 1000/60) }; 10 | })(); 11 | } 12 | 13 | if (!('cancelAnimationFrame' in win)) { 14 | win.cancelAnimationFrame = (function () { 15 | return win.webkitCancelAnimationFrame 16 | || win.mozCancelAnimationFrame 17 | || win.oCancelAnimationFrame 18 | || win.msCancelAnimationFrame 19 | || function (id) { return win.cancelTimeout(id) }; 20 | })(); 21 | } 22 | 23 | win.toad = { 24 | startListening: start 25 | }; 26 | 27 | function addEventHandler (ev, h) { 28 | win.addEventListener ? 29 | win.addEventListener(ev, h, !1) : 30 | win.attachEvent ? 31 | win.attachEvent('on' + ev, h) : 32 | win['on' + ev] = h; 33 | } 34 | 35 | function removeEventHandler (ev, h) { 36 | win.removeEventListener ? 37 | win.removeEventListener(ev, h, !1) : 38 | win.detachEvent ? 39 | win.detachEvent('on' + ev, h) : 40 | win['on' + ev] = null; 41 | } 42 | 43 | function isInViewport (r) { 44 | return r.left >= 0 && r.top <= win.innerHeight; // r.top >= 0 && r.left >= 0 && r.top <= win.innerHeight 45 | } 46 | 47 | function rebounce (f) { 48 | var scheduled; 49 | var context; 50 | var args; 51 | var len; 52 | var i; 53 | return function () { 54 | context = this; 55 | args = []; 56 | len = arguments.length; 57 | i = 0; 58 | 59 | for (;i < len; ++i) { 60 | args[i] = arguments[i]; 61 | } 62 | 63 | if (!!scheduled) { 64 | win.cancelAnimationFrame(scheduled); 65 | } 66 | 67 | scheduled = win.requestAnimationFrame(function () { 68 | f.apply(context, args); 69 | scheduled = null; 70 | }); 71 | } 72 | } 73 | 74 | function toad () { 75 | images(); 76 | botbackgrounds(); 77 | } 78 | 79 | function images() { 80 | var elements = doc.querySelectorAll('[data-src]') || []; 81 | var len = elements.length; 82 | var j = 0; 83 | var this_el; 84 | 85 | for (; j < len; ++j) { 86 | this_el = elements[j]; 87 | if (!this_el.getAttribute('data-src') || !isInViewport(this_el.getBoundingClientRect())) { 88 | return; 89 | } 90 | 91 | if (!this_el.getAttribute('data-src') || !isInViewport(this_el.getBoundingClientRect())) { 92 | return; 93 | } 94 | 95 | if (!!this_el.getAttribute('data-src') && isInViewport(this_el.getBoundingClientRect())) { 96 | if ('img' === this_el.tagName.toLowerCase()) { 97 | this_el.src = this_el.getAttribute('data-src'); 98 | this_el.removeAttribute('data-src'); 99 | 100 | } else { 101 | this_el.style.backgroundImage = 'url(' + this_el.getAttribute('data-src') + ')'; 102 | this_el.removeAttribute('data-src'); 103 | } 104 | } 105 | } 106 | } 107 | 108 | function botbackgrounds() { 109 | 110 | var elements = doc.querySelectorAll('[data-bot-src]') || []; 111 | var len = elements.length; 112 | var j = 0; 113 | var this_el; 114 | 115 | for (; j < len; ++j) { 116 | this_el = elements[j]; 117 | if (!this_el.getAttribute('data-bot-src') || !isInViewport(this_el.getBoundingClientRect())) { 118 | return; 119 | } 120 | 121 | if (!!this_el.getAttribute('data-bot-src') && isInViewport(this_el.getBoundingClientRect())) { 122 | this_el.style = this_el.getAttribute('data-bot-src'); 123 | this_el.removeAttribute('data-bot-src'); 124 | } 125 | } 126 | 127 | } 128 | 129 | function start() { 130 | // console.log('Started Toad Loading') // Debug 131 | // addEventHandler('DOMContentLoaded', rebounce(toad)); 132 | rebounce(toad) 133 | addEventHandler('load', rebounce(toad)); 134 | addEventHandler('scroll', rebounce(toad)); 135 | addEventHandler('resize', rebounce(toad)); 136 | } 137 | 138 | 139 | })(window, window.document); -------------------------------------------------------------------------------- /src/bot/commands/help.js: -------------------------------------------------------------------------------- 1 | const { Command, RichDisplay, util: { isFunction } } = require('klasa'); 2 | const { MessageEmbed, Permissions } = require('discord.js'); 3 | 4 | const PERMISSIONS_RICHDISPLAY = new Permissions([Permissions.FLAGS.MANAGE_MESSAGES, Permissions.FLAGS.ADD_REACTIONS]); 5 | const time = 1000 * 60 * 3; 6 | 7 | module.exports = class extends Command { 8 | 9 | constructor(...args) { 10 | super(...args, { 11 | aliases: ['commands', 'cmd', 'cmds'], 12 | guarded: true, 13 | description: (language) => language.get('COMMAND_HELP_DESCRIPTION'), 14 | usage: '(Command:command)' 15 | }); 16 | 17 | this.createCustomResolver('command', (arg, possible, message) => { 18 | if (!arg || arg === '') return undefined; 19 | return this.client.arguments.get('command').run(arg, possible, message); 20 | }); 21 | 22 | // Cache the handlers 23 | this.handlers = new Map(); 24 | } 25 | 26 | async run(message, [command]) { 27 | if (command) { 28 | return message.sendMessage([ 29 | `= ${command.name} = `, 30 | isFunction(command.description) ? command.description(message.language) : command.description, 31 | message.language.get('COMMAND_HELP_USAGE', command.usage.fullUsage(message)), 32 | message.language.get('COMMAND_HELP_EXTENDED'), 33 | isFunction(command.extendedHelp) ? command.extendedHelp(message.language) : command.extendedHelp 34 | ], { code: 'asciidoc' }); 35 | } 36 | 37 | if (!('all' in message.flags) && message.guild && message.channel.permissionsFor(this.client.user).has(PERMISSIONS_RICHDISPLAY)) { 38 | // Finish the previous handler 39 | const previousHandler = this.handlers.get(message.author.id); 40 | if (previousHandler) previousHandler.stop(); 41 | 42 | const handler = await (await this.buildDisplay(message)).run(await message.send('Loading Commands...'), { 43 | filter: (reaction, user) => user.id === message.author.id, 44 | time 45 | }); 46 | handler.on('end', () => this.handlers.delete(message.author.id)); 47 | this.handlers.set(message.author.id, handler); 48 | return handler; 49 | } 50 | 51 | return message.author.send(await this.buildHelp(message), { split: { char: '\n' } }) 52 | .then(() => { if (message.channel.type !== 'dm') message.sendMessage(message.language.get('COMMAND_HELP_DM')); }) 53 | .catch(() => { if (message.channel.type !== 'dm') message.sendMessage(message.language.get('COMMAND_HELP_NODM')); }); 54 | } 55 | 56 | async buildHelp(message) { 57 | const commands = await this._fetchCommands(message); 58 | const { prefix } = message.guildSettings; 59 | 60 | const helpMessage = []; 61 | for (const [category, list] of commands) { 62 | helpMessage.push(`🍥 **${category} Commands**:\n`, list.map(this.formatCommand.bind(this, message, prefix, false)).join('\n'), ''); 63 | } 64 | 65 | return helpMessage.join('\n'); 66 | } 67 | 68 | async buildDisplay(message) { 69 | const commands = await this._fetchCommands(message); 70 | const { prefix } = message.guildSettings; 71 | const display = new RichDisplay(); 72 | const color = message.member.displayColor; 73 | for (const [category, list] of commands) { 74 | display.addPage(new MessageEmbed() 75 | .setAuthor(`${category} Commands`, "https://media.discordapp.net/attachments/846824301676068874/847413435129135124/logo.png") 76 | .setColor(color) 77 | .setDescription(list.map(this.formatCommand.bind(this, message, prefix, true)).join('\n')) 78 | .setThumbnail("https://media.discordapp.net/attachments/846824301676068874/847413435129135124/logo.png") 79 | .setImage("https://media.discordapp.net/attachments/846824301676068874/847409881707315220/standard.gif") 80 | ); 81 | } 82 | 83 | return display; 84 | } 85 | 86 | formatCommand(message, prefix, richDisplay, command) { 87 | const description = isFunction(command.description) ? command.description(message.language) : command.description; 88 | return richDisplay ? `\`${prefix}${command.name}\` - ${description}` : `• **${prefix}${command.name}** → ${description}`; 89 | } 90 | 91 | async _fetchCommands(message) { 92 | const run = this.client.inhibitors.run.bind(this.client.inhibitors, message); 93 | const commands = new Map(); 94 | await Promise.all(this.client.commands.map((command) => run(command, true) 95 | .then(() => { 96 | const category = commands.get(command.category); 97 | if (category) category.push(command); 98 | else commands.set(command.category, [command]); 99 | }).catch(() => { 100 | // noop 101 | }) 102 | )); 103 | 104 | return commands; 105 | } 106 | 107 | }; -------------------------------------------------------------------------------- /src/lib/structures/ModEmbed.js: -------------------------------------------------------------------------------- 1 | const { MessageEmbed } = require('discord.js'); 2 | const { Colors } = require('../util/constants'); 3 | const { embedSplitter } = require('../util/util'); 4 | 5 | class ModEmbed extends MessageEmbed { 6 | 7 | constructor(modCase) { 8 | super(); 9 | this.modCase = modCase; 10 | this.client = modCase.client; 11 | } 12 | 13 | // eslint-disable-next-line complexity 14 | async build() { 15 | this.setAuthor(this.modCase.guild.language.get(`MODERATION_LOG_${this.modCase.type.toUpperCase()}`)); 16 | if (this.modCase.user && this.modCase.user.id !== this.client.user.id) this.setTitle(`${this.modCase.user.tag} (${this.modCase.user.id})`); 17 | if (!this.modCase.user) this.setTitle(this.modCase.guild.language.get('UNFETCHABLE_USER')); 18 | this.setDescription(this.modCase.guild.language.get('MODERATION_LOG_CASEID', this.modCase.id)); 19 | if (this.modCase.user) this.setThumbnail(this.modCase.user.displayAvatarURL()); 20 | /* eslint-disable indent */ 21 | this.setColor( 22 | this.modCase.type === 'ban' ? Colors.red : 23 | this.modCase.type === 'unban' ? Colors.yellow : 24 | this.modCase.type === 'kick' ? Colors.red : 25 | this.modCase.type === 'mute' ? Colors.red : 26 | this.modCase.type === 'unmute' ? Colors.yellow : 27 | this.modCase.type === 'purge' ? Colors.red : 28 | this.modCase.type === 'softban' ? Colors.red : 29 | this.modCase.type === 'vcban' ? Colors.red : 30 | this.modCase.type === 'vcunban' ? Colors.yellow : 31 | this.modCase.type === 'vckick' ? Colors.red : 32 | this.modCase.type === 'antiInvite' ? Colors.red : 33 | this.modCase.type === 'mentionSpam' ? Colors.red : 34 | this.modCase.type === 'blacklistedWord' ? Colors.red : 35 | this.modCase.type === 'blacklistedNickname' ? Colors.red : 36 | this.modCase.type === 'warn' ? Colors.yellow : 37 | Colors.blue); 38 | /* eslint-enable indent */ 39 | this.setTimestamp(this.modCase.timestamp); 40 | this.setFooter(this.modCase.guild.language.get('MODERATION_LOG_EVENTLOGGED'), this.modCase.client.user.displayAvatarURL()); 41 | 42 | // Fields for all 43 | this.addField(this.modCase.guild.language.get('MODERATION_LOG_MODERATOR'), this.modCase.moderator ? this.modCase.moderator : this.modCase.guild.language.get('MODERATION_LOG_UNSPECIFIED'), true); 44 | this.addField(this.modCase.guild.language.get('REASON'), this.modCase.reason ? this.modCase.reason : this.modCase.guild.language.get('MODERATION_LOG_UNSPECIFIED'), true); 45 | 46 | // Type-specific fields 47 | if (this.modCase.channel) this.addField(this.modCase.guild.language.get('CHANNEL'), this.modCase.channel); 48 | if (this.modCase.duration) this.addField(this.modCase.guild.language.get('MODERATION_LOG_DURATION'), this.modCase.guild.language.get('MODERATION_LOG_DURATIONEND', this.modCase.duration), true); 49 | if (this.modCase.deletedMessageCount) this.addField(this.modCase.guild.language.get('MODERATION_LOG_DELETEDMESSAGECOUNT'), this.modCase.deletedMessageCount, true); 50 | if (this.modCase.messageContent) await embedSplitter(this.modCase.guild.language.get('MODERATION_LOG_DELETEDMESSAGECONTENT'), this.modCase.messageContent.split(' '), this); 51 | if (this.modCase.badNickname) this.addField(this.modCase.guild.language.get('MODERATION_LOG_BADNICKNAME'), this.modCase.badNickname, true); 52 | 53 | // Optional (for all) fields 54 | if (this.modCase.link) this.addField(this.modCase.guild.language.get('MODERATION_LOG_LINK', this.modCase.link), this.modCase.guild.language.get('MODERATION_LOG_LINK', this.modCase.link), true); 55 | if (this.modCase.silent) this.addField(this.modCase.guild.language.get('MODERATION_LOG_SILENT'), this.modCase.silent ? 'true' : 'false', true); 56 | 57 | return this; 58 | } 59 | 60 | async send() { 61 | if (this.modCase.guild.settings.get(`logs.moderation.${[this.modCase.type]}`)) { 62 | const logChannel = this.modCase.guild.channels.cache.get(this.modCase.guild.settings.get('channels.log')); 63 | if (logChannel) await logChannel.send('', { embed: this }); 64 | } 65 | 66 | if (!this.modCase.guild.settings.get('moderation.notifyUser')) return null; 67 | if (this.modCase.type === 'unban') return null; 68 | if (this.modCase.silent) return null; 69 | if (!this.client.users.cache.has(this.modCase.user.id)) return null; 70 | if (this.modCase.user.id === this.client.user.id) return null; 71 | if (this.modCase.user.bot || this.modCase.user.system) return null; 72 | this.modCase.user.createDM().then((dm) => dm.send(this.modCase.moderator ? this.modCase.moderator.id === this.client.user.id ? 73 | this.modCase.guild.language.get('MODERATION_LOG_BOILERPLATE_AUTO', this.modCase.guild) : 74 | this.modCase.guild.language.get('MODERATION_LOG_BOILERPLATE', this.modCase.guild) : '', { embed: this }).catch((err) => err)); 75 | 76 | return true; 77 | } 78 | 79 | } 80 | 81 | module.exports = ModEmbed; 82 | -------------------------------------------------------------------------------- /src/dynamic/403.pug: -------------------------------------------------------------------------------- 1 | head 2 | include includes/nav.pug 3 | meta(property='og:title', content='Error 403') 4 | meta(name='twitter:title', content='Error 403') 5 | meta(property='og:description', content='The page you were looking for either does not exist or has been removed!') 6 | meta(name='description', content='The page you were looking for either does not exist or has been removed!') 7 | meta(property='twitter:description', content='The page you were looking for either does not exist or has been removed!') 8 | script(type='text/javascript', async='', src='https://www.gstatic.com/recaptcha/releases/dpzVjBAupwRfx3UzvXRnnAKb/recaptcha__en.js', crossorigin='anonymous', integrity='sha384-ZPSKvKG0WipeaRQyUewUQ0wMawOWTmw32n5nBKnR21FA4MBKHzMEW63JHSOUpp32') 9 | script(type='text/javascript', async='', src='https://www.google-analytics.com/analytics.js') 10 | link(type='image/png', rel='shortcut icon', href='https://images.cloudflareapps.com/veNvlGxHRvP7/rick%20%2810%29.png') 11 | meta(name='theme-color', content='#CF4141') 12 | meta(property='og:type', content='website') 13 | meta(name='revisit-after', content='3 days') 14 | meta(name='language', content='EN') 15 | meta(name='distribution', content='global') 16 | meta(name='rating', content='general') 17 | meta(name='PreMiD_details', content='Error 403') 18 | meta(name='PreMiD_smallImageText', content='Page') 19 | meta(name='PreMiD_smallImageKey', content='img_icon_page') 20 | style. 21 | ::-webkit-scrollbar { 22 | display: none; 23 | } 24 | .mainfoot { 25 | margin-top:0!important; 26 | } 27 | .errorpage__link { 28 | color:#aaa; 29 | cursor:pointer; 30 | } 31 | .errorpage__link:hover { 32 | color:#BF59FF; 33 | text-decoration:none; 34 | } 35 | title Astra Bots - Error 403 36 | link(rel='icon', href='https://media.discordapp.net/attachments/817464926066835486/828880449689878528/eb26e4b4bcf61bab6b26b9ab6689a22c.webp') 37 | script(async='', src='https://www.googletagmanager.com/gtag/js?id=UA-109410618-4') 38 | script. 39 | window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());gtag('config','UA-109410618-4'); 40 | meta(name='viewport', content='width=device-width, initial-scale=1, shrink-to-fit=no') 41 | link(rel='shortcut icon', type='image/png', href='/assets/img/logo.png') 42 | link(rel='stylesheet', href='/assets/css/main.css') 43 | link(rel='stylesheet', href='/assets/css/secondary.css') 44 | link(rel='stylesheet', href='/assets/css/bootstrap-multiselect.css') 45 | link(rel='stylesheet', href='https://pro.fontawesome.com/releases/v5.14.0/css/all.css', crossorigin='anonymous') 46 | script(src='https://www.google.com/recaptcha/api.js') 47 | link(rel='stylesheet', href='/assets/css/bootstrap-toaster.css') 48 | .flexcen(style='height:50vh;') 49 | .container 50 | .d-flex.justify-content-center 51 | .d-inline-block(style='max-width:600px;') 52 | span.text-muted.text-bold(style='line-height:4px;padding-left:1px;') Error 403 53 | h2.text-boldest.w-auto.mb-1(style='font-size:2.6rem;') Page Not Found 54 | p.text-damp 55 | | The page you were looking for either does not exist or has been removed! 56 | a.btn.btn-outline-tag(href='javascript:window.history.back()') 57 | i.fas.fa-arrow-left.text-damp.mr-2 58 | | Back 59 | a.btn.btn-outline-tag(href='/') 60 | i.fas.fa-home.text-damp.mr-2 61 | | Go Home 62 | 63 | include includes/footer.pug 64 | 65 | script(src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js') 66 | script(src='https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js') 67 | script(src='https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.bundle.min.js') 68 | script(defer='', src='https://cdn.jsdelivr.net/gh/Wruczek/Bootstrap-Cookie-Alert@gh-pages/cookiealert.min.js') 69 | script(defer='', src='https://cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/clipboard.min.js') 70 | script(src='/assets/js/bootstrap-multiselect.js') 71 | script(src='/assets/js/bootstrap-toaster.js') 72 | #toastContainer.position-fixed.top-0.right-0(aria-live='polite') 73 | script(src='/assets/js/danloves.js') 74 | script(src='/assets/toadloader.js', type='text/javascript') 75 | script(defer='', src='/assets/js/confetti.min.js') 76 | script. 77 | // $("[data-toggle=popover]").popover(); 78 | $("[data-toggle=tooltip]").tooltip(); 79 | $('[data-toggle="popover"]').popover({ 80 | placement : 'top', 81 | trigger : 'hover' 82 | }); 83 | $(window).on("load", function () { 84 | window.toad.startListening(); 85 | setTimeout(function(){window.dispatchEvent(new Event('scroll'));}, 200); 86 | }) 87 | script. 88 | let hhistory = history.length; 89 | if(hhistory == 0) hhistory = 1; 90 | if(hhistory > 1) {}else{ 91 | $('[href="javascript:window.history.back()"]').hide(); 92 | } 93 | cloudflare-app 94 | style. 95 | .reset-3c756112--trademark-a8da4b94 {display:none !important;} 96 | -------------------------------------------------------------------------------- /src/dynamic/404.pug: -------------------------------------------------------------------------------- 1 | head 2 | include includes/nav.pug 3 | meta(property='og:title', content='Error 404') 4 | meta(name='twitter:title', content='Error 404') 5 | meta(property='og:description', content='The page you were looking for either does not exist or has been removed!') 6 | meta(name='description', content='The page you were looking for either does not exist or has been removed!') 7 | meta(property='twitter:description', content='The page you were looking for either does not exist or has been removed!') 8 | script(type='text/javascript', async='', src='https://www.gstatic.com/recaptcha/releases/dpzVjBAupwRfx3UzvXRnnAKb/recaptcha__en.js', crossorigin='anonymous', integrity='sha384-ZPSKvKG0WipeaRQyUewUQ0wMawOWTmw32n5nBKnR21FA4MBKHzMEW63JHSOUpp32') 9 | script(type='text/javascript', async='', src='https://www.google-analytics.com/analytics.js') 10 | link(type='image/png', rel='shortcut icon', href='https://media.discordapp.net/attachments/817464926066835486/828880449689878528/eb26e4b4bcf61bab6b26b9ab6689a22c.webp') 11 | 12 | meta(name='theme-color', content='#CF4141') 13 | meta(property='og:type', content='website') 14 | meta(name='revisit-after', content='3 days') 15 | meta(name='language', content='EN') 16 | meta(name='distribution', content='global') 17 | meta(name='rating', content='general') 18 | meta(name='PreMiD_details', content='Error 404') 19 | meta(name='PreMiD_smallImageText', content='Page') 20 | meta(name='PreMiD_smallImageKey', content='img_icon_page') 21 | style. 22 | ::-webkit-scrollbar { 23 | display: none; 24 | } 25 | .mainfoot { 26 | margin-top:0!important; 27 | } 28 | .errorpage__link { 29 | color:#aaa; 30 | cursor:pointer; 31 | } 32 | .errorpage__link:hover { 33 | color:#BF59FF; 34 | text-decoration:none; 35 | } 36 | title Astra Bots - Error 404 37 | script(async='', src='https://www.googletagmanager.com/gtag/js?id=UA-109410618-4') 38 | script. 39 | window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());gtag('config','UA-109410618-4'); 40 | meta(name='viewport', content='width=device-width, initial-scale=1, shrink-to-fit=no') 41 | link(rel='shortcut icon', type='image/png', href='/assets/img/logo.png') 42 | link(rel='stylesheet', href='/assets/css/main.css') 43 | link(rel='stylesheet', href='/assets/css/secondary.css') 44 | link(rel='stylesheet', href='/assets/css/bootstrap-multiselect.css') 45 | link(rel='stylesheet', href='https://pro.fontawesome.com/releases/v5.14.0/css/all.css', crossorigin='anonymous') 46 | script(src='https://www.google.com/recaptcha/api.js') 47 | link(rel='stylesheet', href='https://cdn.jsdelivr.net/gh/Wruczek/Bootstrap-Cookie-Alert@gh-pages/cookiealert.css') 48 | link(rel='stylesheet', href='/assets/css/bootstrap-toaster.css') 49 | .flexcen(style='height:50vh;') 50 | .container 51 | .d-flex.justify-content-center 52 | .d-inline-block(style='max-width:600px;') 53 | span.text-muted.text-bold(style='line-height:4px;padding-left:1px;') Error 404 54 | h2.text-boldest.w-auto.mb-1(style='font-size:2.6rem;') Page Not Found 55 | p.text-damp 56 | | The page you were looking for either does not exist or has been removed! 57 | a.btn.btn-outline-tag(href='javascript:window.history.back()') 58 | i.fas.fa-arrow-left.text-damp.mr-2 59 | | Back 60 | a.btn.btn-outline-tag(href='/') 61 | i.fas.fa-home.text-damp.mr-2 62 | | Go Home 63 | 64 | 65 | include includes/footer.pug 66 | 67 | script(src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js') 68 | script(src='https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js') 69 | script(src='https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.bundle.min.js') 70 | script(defer='', src='https://cdn.jsdelivr.net/gh/Wruczek/Bootstrap-Cookie-Alert@gh-pages/cookiealert.min.js') 71 | script(defer='', src='https://cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/clipboard.min.js') 72 | script(src='/assets/js/bootstrap-multiselect.js') 73 | script(src='/assets/js/bootstrap-toaster.js') 74 | #toastContainer.position-fixed.top-0.right-0(aria-live='polite') 75 | script(src='/assets/js/danloves.js') 76 | script(src='/assets/toadloader.js', type='text/javascript') 77 | script(defer='', src='/assets/js/confetti.min.js') 78 | script. 79 | // $("[data-toggle=popover]").popover(); 80 | $("[data-toggle=tooltip]").tooltip(); 81 | $('[data-toggle="popover"]').popover({ 82 | placement : 'top', 83 | trigger : 'hover' 84 | }); 85 | $(window).on("load", function () { 86 | window.toad.startListening(); 87 | setTimeout(function(){window.dispatchEvent(new Event('scroll'));}, 200); 88 | }) 89 | script. 90 | let hhistory = history.length; 91 | if(hhistory == 0) hhistory = 1; 92 | if(hhistory > 1) {}else{ 93 | $('[href="javascript:window.history.back()"]').hide(); 94 | } 95 | cloudflare-app 96 | style. 97 | .reset-3c756112--trademark-a8da4b94 {display:none !important;} 98 | -------------------------------------------------------------------------------- /src/public/assets/js/cards.js: -------------------------------------------------------------------------------- 1 | Array.prototype.shuffle = function() { 2 | let a = this; 3 | for (let i = a.length - 1; i > 0; i--) { 4 | const j = Math.floor(Math.random() * (i + 1)); 5 | [a[i], a[j]] = [a[j], a[i]]; 6 | } 7 | return a; 8 | } 9 | 10 | async function load() { 11 | var n = 0; 12 | let BotList = await fetch(`/api/bots/list`); 13 | BotList = await BotList.json() 14 | BotList = BotList.sort((a, b) => b.likes - a.likes);; 15 | 16 | $('#loading').css("display", "none"); 17 | 18 | let selection = BotList.slice(n, n + 10); 19 | loadMore(selection); 20 | 21 | $(window).scroll(function() { 22 | if ($(window).scrollTop() + $(window).height() > $(document).height() - 200) { 23 | n += 10; 24 | loadMore(BotList.slice(n, n + 10)); 25 | } 26 | }); 27 | }; 28 | 29 | function loadMore(res) { 30 | res.forEach(function(bot) { 31 | if (bot.state == "unverified") return; 32 | 33 | let html = ` 34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 |
42 | Bot Avatar 43 |
44 |
45 |
46 |
47 | 48 | 49 | ${bot.likes || 0} 50 |
51 |
52 |
53 |
54 |
55 |

${bot.username} 56 | 57 | 58 | 59 | 60 | 61 |

62 |

${bot.description}

63 |
64 |
65 |
66 |
67 | 68 | 69 |
70 |
` 71 | 72 | document.getElementById('cards').insertAdjacentHTML("beforeend", html) 73 | }) 74 | } 75 | 76 | function search() { 77 | if (document.getElementById('search').contentEditable === "false") return; 78 | let s = String(document.getElementById('search').innerHTML.toLowerCase()).replaceAll('
', ""); 79 | let cards = document.getElementById('cards'); 80 | cards.style.display = "none"; 81 | if (document.getElementById('loading')) document.getElementById('loading').display = "block"; 82 | if (cards) { 83 | let totalCards = 0; 84 | let cardsVisible = 0; 85 | 86 | let list = cards.children; 87 | for (var i = 1; i < list.length; i++) { 88 | totalCards++ 89 | let card = list[i]; 90 | let title = card.children[1].innerHTML.toLowerCase(); 91 | let desc = card.children[2].innerHTML.toLowerCase(); 92 | if (!title.includes(s) && !desc.includes(s)) card.style.display = "none"; 93 | else { 94 | card.style.display = "inline-block"; 95 | cardsVisible++; 96 | } 97 | } 98 | 99 | if (cardsVisible === 0) { 100 | document.getElementById('searchMore').innerHTML = `No bots found. Would you like to search all bots?`; 101 | document.getElementById('searchMore').style.display = "block"; 102 | } else { 103 | document.getElementById('searchMore').innerHTML = `Would you like to search all bots` 104 | document.getElementById('searchMore').style.display = "block"; 105 | } 106 | 107 | if (document.getElementById('search').innerHTML === "") { 108 | document.getElementById('searchMore').style.display = "none"; 109 | for (var i = 1; i < list.length; i++) { 110 | let card = list[i]; 111 | card.style.display = "inline-block"; 112 | } 113 | } 114 | } 115 | if (document.getElementById('loading')) document.getElementById('loading').display = "none"; 116 | cards.style.display = "block"; 117 | } --------------------------------------------------------------------------------