Initial gitea commit

This commit is contained in:
Kaydax 2023-09-17 01:29:56 -04:00
commit d647273993
Signed by: Kaydax
GPG Key ID: 6D32EED87DE7F090
61 changed files with 7696 additions and 0 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
node_modules
npm-debug.log
Dockerfile
.dockerignore

9
.gitignore vendored Normal file
View File

@ -0,0 +1,9 @@
node_modules
backups
secret.js
lavalink/logs
lavalink/application.yml
lavalink/Lavalink.jar.old
npm-debug.log
config.json
utils/Utils.js.old

165
App.js Normal file
View File

@ -0,0 +1,165 @@
module.exports = class App
{
constructor()
{
var secret = require("./secret.js");
this.bot = new Eris(secret.discord, { maxShards: 'auto' });
this.commands = new Commands(this);
this.version = require('./package.json').version;
this.config = JSON.parse(fs.readFileSync("./config.json"));
if(this.config.disableDBL == false)
{
setInterval(() => {
DLA.postGuilds(this.bot, secret.tokens);
}, 1800000);
}
this.db = new Database(this);
this.bot.on("ready", this.onReady.bind(this));
this.bot.on("guildCreate", this.onJoin.bind(this));
this.bot.on("guildMemberAdd", this.onMemberJoined.bind(this));
this.bot.on("messageCreate", this.onMessage.bind(this));
this.bot.on("error", this.onDisconnect.bind(this));
this.bot.connect();
}
onReady()
{
console.log("Vertbot is now online.");
this.bot.editStatus("online", {name: "v-help for info", type: 0})
var secret = require("./secret.js");
if(this.config.disableDBL == false) { DLA.postGuilds(this.bot, secret.tokens); } //Post the guild stats to bot list apon launch
//TODO: init function?
this.lavalink = new Lavalink(this);
//this.settings = new Settings(this);
}
onPosted()
{
console.log("Server count posted!");
}
onDBLError()
{
console.error("Server count could not be posted. This may be because of the fact there is no token");
}
onDisconnect(err, id)
{
//this.bot.disconnect();
//console.log("Lost connection to discord, trying to reconnect...");
//this.bot.connect();
console.log(err);
}
onJoin(guild)
{
var channel = U.getLogicalChannel(this, guild);
if(channel != null)
{
this.bot.createMessage(channel.id, U.createWelcomeEmbed(this));
}
}
onMemberJoined(guild, member)
{
if(member.username.toLowerCase().includes("h0nd"))
{
member.ban(7, "Shitty fucking spammer");
}
}
async onMessage(msg)
{
try
{
if(!msg.channel.type == 1)
{
//var prefix = (await this.db.getGuildSettings(msg.channel.guild.id)).prefix || this.config.prefix;
var prefix = (this.bot.user.id == "430118450795380736") ? "[=" : (await this.db.getGuildSettings(msg.channel.guild.id)).prefix || this.config.prefix;
var mention = msg.content.startsWith(this.bot.user.mention) ? "<@" + this.bot.user.id + ">" : "<@!" + this.bot.user.id + ">"
if((msg.content.startsWith(prefix) || msg.content.startsWith(mention)) && !msg.author.bot)
{
//the command
var text = msg.content.startsWith(prefix) ? msg.content.slice(prefix.length).trim() : msg.content.slice((mention).length).trim(); //TODO: trim?
this.commands.doCommand(msg, this, text); //Command system
}
if(msg.author.bot && msg.author.id == "817790099823525909")
{
//Scramble!
if(msg.embeds.length > 0)
{
if(msg.embeds[0].title == "Scramble!")
{
var word = msg.embeds[0].description.split("\n")[1];
console.log(word);
var words = ["bell","jail","heart","Mickey Mouse","bus","butterfly","elephant","flower","turtle","snowflake","candy","key","sun","starfish","button","leg","table","ghost","orange","house","nail","apple","football","love","ants","beak","feet","river","lemon","daisy","jar","clock","flag","snail","smile","island","inchworm","mitten","king","bench","dog","knee","horse","music","square","hair","doll","sea turtle","book","whale","arm","seashell","shirt","purse","stairs","oval","camera","truck","motorcycle","moon","fly","pillow","coat","helicopter","bowl","mouth","chicken","light","hippo","woman","chimney","grapes","jacket","float","rock","snowman","bread","snake","corn","cup","baseball","pizza","beach","bug","sunglasses","rain","robot","cherry","bunny","lamp","popsicle","ears","lollipop","socks","ladybug","triangle","zebra","broom","mouse","caterpillar","dragon","bone","door","desk","grass","car","curl","cat","ring","worm","banana","mountain","water","ball","roly poly/pill bug/doodle bug","legs","sheep","rocket","man","coin","ant","night","crayon","ocean","swimming pool","bumblebee","swing","person","blanket","bark","slide","boy","hat","star","mountains","diamond","tree","bow","computer","zigzag","dinosaur","balloon","cow","boat","bunk bed","giraffe","eyes","bird","chair","comb","circle","nose","hook","feather","bear","bee","ear","leaf","pig","drum","pie","milk","suitcase","fire","window","wheel","glasses","kite","box","cupcake","zoo","girl","pants","bed","rainbow","hamburger","jellyfish","bracelet","baby","octopus","ship","eye","lips","alive","face","dream","spider web","angel","train","egg","tail","alligator","finger","pen","bike","fish","cheese","bounce","blocks","kitten","bathroom","crack","skateboard","fork","bridge","plant","owl","branch","ice cream cone","monkey","spoon","shoe","duck","crab","lizard","lion","backpack","sea","hand","head","frog","carrot","cloud","candle","line","bat","neck","cube","family","airplane","Earth","spider","basketball","monster","pencil","cookie","rabbit"];
var scram = await unscramble(word, words);
console.log(scram);
this.bot.createMessage(msg.channel.id, U.createQuickEmbed("Word Unscrambled!", "Try: `" + scram.join(", ") + "`"));
}
//Translate!
if(msg.embeds[0].title == "Translate!")
{
var word = msg.embeds[0].description.split("\n")[1];
console.log(word);
const res = await fetch("https://libretranslate.de/translate", {
method: "POST",
body: JSON.stringify({
q: word,
source: "es",
target: "en",
format: "text",
api_key: ""
}),
headers: { "Content-Type": "application/json" }
});
var json = await res.json();
var tran = json.translatedText;
console.log(json);
console.log(tran);
this.bot.createMessage(msg.channel.id, U.createQuickEmbed("Word Translated!", "The word is: `" + tran + "`"));
}
}
}
} else {
if(!msg.author.bot && !msg.author.id == "817790099823525909")
{
this.bot.createMessage(msg.channel.id, U.createErrorEmbed("This is a DM chat", "I can't do anything in dms. If you need a gf that bad go outside"));
}
}
}
catch(e)
{
console.log(e);
//this.bot.createMessage(msg.channel.id, "" + (typeof e));
}
}
}
//var YouTube = require('youtube-node');
var Eris = require('eris');
var assert = require("assert");
//var unscramble = require('unscramble');
const unscramble = require('word-unscrambler');
//const { translate } = require('free-translate');
const fetch = require('node-fetch');
var U = require('./utils/Utils.js');
var DLA = require('./utils/Discord-List-api.js');
var Commands = require("./commands/Commands.js");
var Lavalink = require("./utils/Lavalink.js");
var Database = require("./utils/Database.js");
var Settings = require("./utils/Settings.js");
//var RestartHandler = require("./utils/RestartHandler.js");
var fs = require("fs");

16
Dockerfile Normal file
View File

@ -0,0 +1,16 @@
FROM node:19-alpine
# Create app directory
WORKDIR /app
#copy package(lock).json
COPY package*.json ./
#install git (needed in alpine for git repos)
#RUN apk add git
#install packages
RUN npm ci --only=production
# Bundle app source
COPY . .
#ports and env
#EXPOSE 80
CMD [ "node", "main.js" ]

26
LICENSE Normal file
View File

@ -0,0 +1,26 @@
# DON'T BE A DICK PUBLIC LICENSE
> Version 1.1, December 2016
> Copyright (C) 2019 Kaydax & tehZevo
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document.
> DON'T BE A DICK PUBLIC LICENSE
> TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
1. Do whatever you like with the original work, just don't be a dick.
Being a dick includes - but is not limited to - the following instances:
1a. Outright copyright infringement - Don't just copy this and change the name.
1b. Selling the unmodified original with no work done what-so-ever, that's REALLY being a dick.
1c. Modifying the original work to contain hidden harmful content. That would make you a PROPER dick.
2. If you become rich through modifications, related works/services, or supporting the original work,
share the love. Only a dick would make loads off this work and not buy the original work's
creator(s) a pint.
3. Code is provided with no warranty. Using somebody else's code and bitching when it goes wrong makes
you a DONKEY dick. Fix the problem yourself. A non-dick would submit the fix back.

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# Vertbot
Vertbot - A lightweight lag free music bot

212
commands/Command.js Normal file
View File

@ -0,0 +1,212 @@
module.exports = class Command
{
constructor()
{
this.name = "";
this.aliases = [];
this.usage = "";
this.description = "";
this.commandFiles = [];
this.commands = [];
//required permissions (must have all; except for hierarchy)
this.permissions = [];
this.parent = null;
//TODO: guild/dm commands?
}
setParent(parent)
{
this.parent = parent;
}
/** returns the root of the command tree */
getRoot()
{
var parent = this.parent;
while(parent.parent != null)
{
parent = parent.parent;
}
return parent;
}
init()
{
//TODO: other init stuff
this.reload();
}
reload()
{
this.commands = this.commandFiles.map((e) =>
{
//console.log("loading " + e + " in " + this.constructor.name);
var sub = new (require(e))();
sub.setParent(this);
return sub;
});
}
async doCommand(message, app, text)
{
text = text || message.content;
//Get the playlist settings for dj mode when checking permissions
var pl = await app.db.getPlaylist(message.channel.guild.id);
//grab permissions for user that sent the message
var perms = await P.getPerms(app, message);
var ret = this.findCommand(text);
if(ret != null && ret.sub != this)
{
if(perms.includes("banned") && !perms.includes("dev"))
{
app.bot.createMessage(message.channel.id, U.createErrorEmbed("You seem to be banned", "You seem to be banned from using the bot by a developer, sorry about that..."));
return;
}
if(!U.canUseCommand(perms, ret.sub, pl))
{
//app.bot.createMessage(message.channel.id, U.wrapCode("ur perms here: " + Array.from(perms).join(" ")));
app.bot.createMessage(message.channel.id, U.createErrorEmbed("You don't have permissions", "You seem to not have `" + ret.sub.permissions.join(", ") + "`"));
return;
}
ret.sub.doCommand(message, app, ret.text);
return;
}
//TODO: display help, but not on root level
//TODO: we can use this != root check for that
if(this.parent != null)
{
app.bot.createMessage(message.channel.id, U.wrapMention(message, U.wrapCode(this.getHelp())));
}
}
/** pass a command FILENAME not an object */
addCommand(commandFile)
{
if(this.commandFiles.includes(commandFile))
{
console.log("REEEEEEE YOU ALREADY ADDED THIS COMMAND");
return;
}
this.commandFiles.push(commandFile);
this.reload();
}
findCommand(text)
{
var ret = {};
ret.text = null;
ret.sub = null;
text = text.trim(); //TODO: hmm
var token = text.split(/\s+/)[0]; //grab the first token in text
token = token.toLowerCase() //just to be safe
var matches = this.findMatchingCommands(token);
//no matches found
if(matches.length == 0)
{
return null;
}
//TODO: filter out commands user cant access?
//matches = matches.filter((e) => U.canUseCommand(perms, e));
return {sub: matches[0], text: text.slice(token.length).trim()}; //TODO: trim?
}
/** pass it a single command token, if perms is passed, performs permission checks, returns {cmd, canUse} */
findMatchingCommands(token)
{
token = token.toLowerCase(); //just to be safe
var commands = [];
for(var i = 0; i < this.commands.length; i++)
{
var sub = this.commands[i];
if(sub.name.toLowerCase().startsWith(token) || sub.aliases.some((e) => e.toLowerCase().startsWith(token)))
{
//var canUse = perms == null ? true : U.canUseCommand(perms, e);
//TODO: command visibility based on "hidden" and permissions (recursively?)
commands.push(sub);
}
//grab subcommand's matches too
commands = commands.concat(sub.findMatchingCommands(token));
}
return commands;
}
/** padLength = how much to pad name+usage by */
formatHelp(name, usage, desc, padLength)
{
var nameUsage = name + " " + usage;
padLength = padLength == null ? nameUsage.length - 1 : padLength;
nameUsage = pad(Array(padLength + 2).join(" "), nameUsage);
return nameUsage + " | " + desc; //TODO: OwO
}
/** if self, then print command's own help line, otherwise, print help for all subcommands */
getHelp(self)
{
if(self)
{
return this.formatHelp(this.name, this.usage, this.description);
}
var str = "";
var nameUsageLen = 0;
var data = [];
//iterate over each subcommand, gather data, get max length of name + usage
this.commands.forEach((e) =>
{
nameUsageLen = Math.max(nameUsageLen, e.name.length + e.usage.length);
data.push([e.name, e.usage, e.description]);
});
//create help return string (pad name + usage to max length)
data.forEach((e) =>
{
str += this.formatHelp(e[0], e[1], e[2], nameUsageLen);
str += "\n";
});
return str;
}
}
var U = require.main.require("./utils/Utils.js");
var P = require.main.require("./utils/Permissions.js");
function pad(pad, str, padLeft)
{
if (typeof str === 'undefined')
return pad;
if (padLeft)
{
return (pad + str).slice(-pad.length);
}
else
{
return (str + pad).substring(0, pad.length);
}
}

20
commands/Commands.js Normal file
View File

@ -0,0 +1,20 @@
var Command = require("./Command.js");
module.exports = class Commands extends Command
{
constructor()
{
super();
this.addCommand("./HelpCommand.js");
this.addCommand("./PingCommand.js");
this.addCommand("./DonateCommand.js");
this.addCommand("./SettingsCommand.js");
this.addCommand("./InfoCommand.js");
this.addCommand("./MusicCommand.js");
this.addCommand("./FunCommand.js");
this.addCommand("./DevCommand.js");
this.init();
}
}

21
commands/DevCommand.js Normal file
View File

@ -0,0 +1,21 @@
var Command = require("./Command.js");
module.exports = class DevCommand extends Command
{
//TODO: shuffle, loop, silent, volume (auto?), autoremove (queue mode?)
constructor()
{
super();
this.name = "dev";
this.description = "Commands for only my developer";
this.usage = "<subcommand>";
this.permissions = ["dev"];
this.addCommand("./dev/PermCommand.js");
this.addCommand("./dev/PermsCommand.js");
this.addCommand("./dev/AvatarCommand.js");
this.addCommand("./dev/SayCommand.js");
this.addCommand("./dev/TestCommand.js");
}
}

19
commands/DonateCommand.js Normal file
View File

@ -0,0 +1,19 @@
var Command = require("./Command.js");
module.exports = class DonateCommand extends Command
{
constructor()
{
super();
this.name = "donate";
this.description = "Help me run this bot!";
}
async doCommand(msg, app, text)
{
app.bot.createMessage(msg.channel.id, U.createDonateEmbed());
}
}
var U = require.main.require("./utils/Utils.js");

18
commands/FunCommand.js Normal file
View File

@ -0,0 +1,18 @@
var Command = require("./Command.js");
module.exports = class FunCommand extends Command
{
//TODO: shuffle, loop, silent, volume (auto?), autoremove (queue mode?)
constructor()
{
super();
this.name = "fun";
this.description = "Commands for some random fun things";
this.usage = "<subcommand>";
this.addCommand("./fun/AsciiCommand.js");
this.addCommand("./fun/BooruCommand.js");
this.addCommand("./fun/VideoCommand.js");
}
}

36
commands/HelpCommand.js Normal file
View File

@ -0,0 +1,36 @@
var Command = require("./Command.js");
module.exports = class HelpCommand extends Command
{
constructor()
{
super();
this.name = "help";
this.description = "shows you the help menu";
}
async doCommand(msg, app, text)
{
var command;
var self = false;
//if empty, use root
if(text.trim() == "")
{
command = this.getRoot();
}
else
{
//grab subcommand
command = this.getRoot().findCommand(text);
//if not found, use root
command = command == null ? this.getRoot() : command.sub
//Check if the command has sub commands or not, and if none are found then display just that command
self = command.commands.length == 0 ? true : false
}
U.sendHelp(app, msg, command, self);
}
}
var U = require.main.require("./utils/Utils.js");

20
commands/InfoCommand.js Normal file
View File

@ -0,0 +1,20 @@
var Command = require("./Command.js");
module.exports = class InfoCommand extends Command
{
constructor()
{
super();
this.name = "info";
this.description = "Gives you important info about me";
}
async doCommand(msg, app, text)
{
var prefix = (await app.db.getGuildSettings(msg.channel.guild.id)).prefix || app.config.prefix;
app.bot.createMessage(msg.channel.id, U.createInfoEmbed(app, msg.channel.guild.id, prefix, msg));
}
}
var U = require.main.require("./utils/Utils.js");

26
commands/MusicCommand.js Normal file
View File

@ -0,0 +1,26 @@
var Command = require("./Command.js");
module.exports = class MusicCommand extends Command
{
//TODO: shuffle, loop, silent, volume (auto?), autoremove (queue mode?)
constructor()
{
super();
this.name = "music";
this.description = "Commands for music related things";
this.usage = "<subcommand>";
this.addCommand("./music/PlayCommand.js");
this.addCommand("./music/AddCommand.js");
this.addCommand("./music/StopCommand.js");
this.addCommand("./music/ClearCommand.js");
this.addCommand("./music/RemoveCommand.js");
this.addCommand("./music/SkipCommand.js");
this.addCommand("./music/GotoCommand.js");
this.addCommand("./music/VolumeCommand.js");
this.addCommand("./music/BassCommand.js");
this.addCommand("./music/PlaylistCommand.js");
this.addCommand("./music/ControlCommand.js");
}
}

17
commands/PingCommand.js Normal file
View File

@ -0,0 +1,17 @@
var Command = require("./Command.js");
module.exports = class PingCommand extends Command
{
constructor()
{
super();
this.name = "ping";
this.description = "pong";
}
async doCommand(msg, app, text)
{
app.bot.createMessage(msg.channel.id, 'Here is my current shard latency: ' + msg.channel.guild.shard.latency + "ms");
}
}

View File

@ -0,0 +1,16 @@
var Command = require("./Command.js");
module.exports = class SettingsCommand extends Command
{
//TODO: shuffle, loop, silent, volume (auto?), autoremove (queue mode?)
constructor()
{
super();
this.name = "settings";
this.description = "Commands for bot settings";
this.usage = "<subcommand>";
this.addCommand("./settings/PrefixCommand.js");
}
}

View File

@ -0,0 +1,24 @@
var Command = require("../Command.js");
module.exports = class AvatarCommand extends Command
{
constructor()
{
super();
this.name = "avatar";
this.usage = "<url>";
this.description = "Sets the avatar to what ever url you put in";
this.permissions = ["dev"];
}
async doCommand(msg, app, text)
{
var url = text.trim();
const res = await require('snekfetch').get(url);
app.bot.editSelf({ avatar: `data:image/jpg;base64,${res.body.toString('base64')}` });
app.bot.createMessage(msg.channel.id, "Avatar Set!")
}
}
var U = require.main.require("./utils/Utils.js");

View File

@ -0,0 +1,75 @@
var Command = require("../Command.js");
module.exports = class PermCommand extends Command
{
constructor()
{
super();
this.name = "perm";
this.usage = "<user> <add/remove> <permission>";
this.description = "Add and remove permissions from users";
this.permissions = ["dev"];
}
async doCommand(msg, app, text)
{
var tokens = text.toLowerCase().split(/\s+/);
//not enough tokens
if(tokens.length < 3)
{
U.sendHelp(app, msg, this, true);
return;
}
var mention = tokens[0];
//grab id
var id = U.str2id(mention);
//fail, no user id provided
if(id == null)
{
U.sendHelp(app, msg, this, true);
return;
}
//grab user settings
var us = await app.db.getUserSettings(id);
var command = tokens[1];
var perm = tokens[2];
if(command == "add")
{
if(us.hasPermission(perm))
{
U.reply(app, msg, mention + " already has '" + perm + "'");
}
else
{
await us.addPermission(perm);
U.reply(app, msg, "Gave '" + perm + "' to " + mention);
}
}
else if(command == "remove")
{
if(!us.hasPermission(perm))
{
U.reply(app, msg, mention + " doesn't have '" + perm + "'");
}
else
{
await us.removePermission(perm);
U.reply(app, msg, "Removed '" + perm + "' from " + mention);
}
}
else if(command == "get")
{
var perms = us.getPermissions().join(", ");
U.reply(app, msg, mention + " has: " + perms);
}
}
}
var U = require.main.require("./utils/Utils.js");

View File

@ -0,0 +1,63 @@
var Command = require("../Command.js");
module.exports = class PermsCommand extends Command
{
constructor()
{
super();
this.name = "perms";
this.usage = "<user>";
this.description = "get the perms of a user";
this.permissions = ["dev"];
}
async doCommand(msg, app, text)
{
text = text.trim();
if(text == "")
{
//TODO: help?
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("No user mentioned", "Please mention a user."))
return;
}
var id = U.str2id(text);
var member = U.getMemberById(msg.channel.guild, id);
if(member == null)
{
//TODO: help member not found
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("Invalid user mentioned", "Please mention a valid user."))
return;
}
var dbp = Array.from(await P.getDBPerms(app, member));
var gp = Array.from(await P.getGuildPerms(app, member));
var rp = Array.from(await P.getRolePerms(app, member));
var cp = Array.from(await P.getConfigPerms(app, member));
var str = "";
if(dbp.length > 0)
{
str += "Database: " + dbp.join(", ") + "\n";
}
if(gp.length > 0)
{
str += "Guild: " + gp.join(", ") + "\n";
}
if(rp.length > 0)
{
str += "Role: " + rp.join(", ") + "\n";
}
if(cp.length > 0)
{
str += "Config: " + cp.join(", ") + "\n";
}
U.reply(app, msg, "Permissions for: " + text + U.wrapCode(str));
}
}
var U = require.main.require("./utils/Utils.js");
var P = require.main.require("./utils/Permissions.js");

View File

@ -0,0 +1,20 @@
var Command = require("../Command.js");
module.exports = class SayCommand extends Command
{
constructor()
{
super();
this.name = "dsay";
this.usage = "<words>";
this.description = "Makes me say something";
this.permissions = ["dev"];
}
async doCommand(msg, app, text)
{
msg.delete("Vertbot tts message (dsay)");
app.bot.createMessage(msg.channel.id, text);
}
}

View File

@ -0,0 +1,27 @@
var Command = require("../Command.js");
module.exports = class TestCommand extends Command
{
constructor()
{
super();
this.name = "test";
this.usage = "";
this.description = "A command that is used for anything I need it for";
this.permissions = ["dev"];
}
async doCommand(msg, app, text)
{
app.bot.createMessage(U.getLogicalChannel(app, msg.channel.guild).id, U.createWelcomeEmbed(app));
}
}
/*
NOTE:
This command is nothing important, as it has no use at all. It just exist for the purpose of debugging
the bot itself during development and should be ignored.
*/
var U = require.main.require("./utils/Utils.js");

View File

@ -0,0 +1,34 @@
var Command = require("../Command.js");
module.exports = class AsciiCommand extends Command
{
constructor()
{
super();
this.name = "ascii";
this.description = "Make some fun ascii text art!";
this.usage = "<text>"
}
async doCommand(msg, app, text)
{
if(text == "")
{
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("No text", "You put no text in, so I can't create the ascii art..."));
return;
}
figlet(text, function(err, data) {
if (err) {
console.log('Something went wrong...');
console.dir(err);
return;
}
app.bot.createMessage(msg.channel.id, U.wrapCode(data));
});
}
}
var U = require.main.require("./utils/Utils.js");
var figlet = require("figlet");

View File

@ -0,0 +1,48 @@
var Command = require("../Command.js");
module.exports = class BooruCommand extends Command
{
constructor()
{
super();
this.name = "booru";
this.description = "NSFW commands are the best";
this.usage = "<site> <tags>"
}
async doCommand(msg, app, text)
{
var sites = ["danbooru","konachan","konachannet","yandere","gelbooru","rule34","safebooru","tbib","xbooru","youhateus"]
if(text <= 0)
{
app.bot.createMessage(msg.channel.id, U.createQuickEmbed("Here are the sites that are supported:","```" + sites.join("\n") + "```"));
} else if(text.split(' ')[0] == "help") {
app.bot.createMessage(msg.channel.id, U.createQuickEmbed("Here are the sites that are supported:","```" + sites.join("\n") + "```"));
} else {
var array = text.split(' ');
var site = array[0];
var tag = array.splice(1, text.length);
if(msg.channel.nsfw == true)
{
if(sites.indexOf(site) > -1)
{
const images = kaori.search(site, { tags: tag, random: true }).then((images) =>
{
images.map(image => {
console.log();
app.bot.createMessage(msg.channel.id, U.createBooruEmbed(msg.author.username, image.fileURL, image.tags.join(" "), image.fileURL))
});
}).catch(err => app.bot.createMessage(msg.channel.id, U.createErrorEmbed("Something went wrong", "```" + err + "```")));
} else {
app.bot.createMessage(msg.channel.id, U.createQuickEmbed("Here are the sites that are supported:","```" + sites.join("\n") + "```"));
}
} else {
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("This channel is not NSFW", "This command can only be used in channels that are marked as NSFW"))
}
}
}
}
var U = require.main.require("./utils/Utils.js");
var kaori = require('kaori');

View File

@ -0,0 +1,24 @@
var Command = require("../Command.js");
module.exports = class VideoCommand extends Command {
constructor()
{
super();
this.name = "videochat";
this.description = "Creates a link to allow video chat in your current voice channel";
}
async doCommand(msg, app, text)
{
var vc = U.msg2vc(msg);
if(vc != undefined || vc != null)
{
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Created video chat link", "[Click Here](https://discordapp.com/channels/" + msg.channel.guild.id + "/" + vc.id + ")"));
} else {
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("No Voice Channel", "You seem to not be connected to any voice channel"));
}
}
}
var U = require.main.require("./utils/Utils.js");

View File

@ -0,0 +1,32 @@
var Command = require("../Command.js");
module.exports = class AddCommand extends Command
{
constructor()
{
super();
this.name = "add";
this.description = "add music to playlist";
this.usage = "<url / query>";
}
async doCommand(msg, app, text)
{
var pl = await app.db.getPlaylist(msg.channel.guild.id);
var perms = await P.getPerms(app, msg);
//if query doesnt begin with http(s)://, prepend ytsearch:
if(text.match(/^https?:\/\//i) == null)
{
text = "ytsearch:" + text;
}
//app.bot.createMessage(msg.channel.id, text);
await app.lavalink.add(msg, text, false);
}
}
var U = require.main.require("./utils/Utils.js");
var P = require.main.require("./utils/Permissions.js");

View File

@ -0,0 +1,75 @@
var Command = require("../Command.js");
module.exports = class BassCommand extends Command
{
constructor()
{
super();
this.name = "bassboost";
this.aliases = ["boost"];
this.description = "crank that bass up to 11 (well actually 100)";
this.usage = "<#>";
}
async bassBoost(msg, app, cmd, pl, perms)
{
var vc = U.msg2vc(msg);
var hasNumber = /\d/;
var player = await app.lavalink.getPlayer(vc, true);
var boost = U.getVal(msg.content, 0, 100) / 100;
if(hasNumber.test(cmd))
{
if(cmd != "")
{
//Bands: 0-15, Gain: -0.25-1.0
pl.setBoost(boost);
if(player != null)
{
var bands = [{"band": 0,"gain": boost},{"band": 1,"gain": boost},{"band": 2,"gain": boost}]
player.setEQ(bands);
}
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Bass boost has been changed", "The bass boost has been set to " + (pl.boost * 100) + "%"));
}
} else {
if(cmd != "")
{
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("Could not change bass boost", "You seem to have not put any numbers or something broke..."));
} else {
app.bot.createMessage(msg.channel.id, U.createQuickEmbed("Bass boost:", "Currently " + (pl.boost * 100) + "%"));
}
}
}
async doCommand(msg, app, text)
{
var cmd = text.trim().toLowerCase();
var hasNumber = /\d/;
var pl = await app.db.getPlaylist(msg.channel.guild.id);
var perms = await P.getPerms(app, msg);
//Getto permission check to have less code and to make it so the command can still be used in some ways
if(pl.djmode == true && U.canUseCommand(perms, {permissions: ["dj"]}, pl))
{
await this.bassBoost(msg, app, cmd, pl, perms);
} else {
if(pl.djmode == false || pl.djmode == undefined) { await this.bassBoost(msg, app, cmd, pl, perms); }
if(pl.djmode == true)
{
if(hasNumber.test(cmd))
{
//The person failed the getto permission check and we display the fact they don't have the role we want
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("You don't have permissions", "You seem to not have `dj`"));
} else {
//Cool the person has the role we want
app.bot.createMessage(msg.channel.id, U.createQuickEmbed("Bass boost:", "Currently " + (pl.boost * 100) + "%"));
}
}
}
}
}
var U = require.main.require("./utils/Utils.js");
var P = require.main.require("./utils/Permissions.js");

View File

@ -0,0 +1,26 @@
var Command = require("../Command.js");
module.exports = class ClearCommand extends Command
{
constructor()
{
super();
this.name = "clear";
this.description = "clear the playlist";
this.permissions = ["dj"];
}
async doCommand(msg, app, text)
{
var pl = await app.db.getPlaylist(msg.channel.guild.id);
pl.clear();
if(U.currentVC(app, msg.channel.guild.id) != null)
{
app.lavalink.stop(msg, app);
}
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Playlist cleared", "The playlist has been completely cleared"));
}
}
var U = require.main.require("./utils/Utils.js");

View File

@ -0,0 +1,19 @@
var Command = require("../Command.js");
module.exports = class ControlCommand extends Command
{
//TODO: shuffle, loop, silent, volume (auto?), autoremove (queue mode?)
constructor()
{
super();
this.name = "control";
this.description = "Commands for music control related things";
this.usage = "<subcommand>";
this.addCommand("./music/control/RepeatCommand.js");
this.addCommand("./music/control/SilentCommand.js");
this.addCommand("./music/control/ShuffleCommand.js");
this.addCommand("./music/control/DJModeCommand.js");
}
}

View File

@ -0,0 +1,56 @@
var Command = require("../Command.js");
module.exports = class GotoCommand extends Command
{
constructor()
{
super();
this.name = "goto";
this.description = "Goto a song in the playlist";
this.usage = "<# for video>"
this.permission = ["dj"]
}
async findSong(songNum, pl)
{
//This is prob the best way to make this command work with shuffle turned on... but this may change
return pl.indexes.indexOf(songNum, 0);
}
async doCommand(msg, app, text)
{
var vc = U.msg2vc(msg);
var pl = await app.db.getPlaylist(msg.channel.guild.id);
var gid = U.msg2gid(msg);
var hasNumber = /\d/;
var num = parseInt(text) - 1;
if(text.match(/^\d+$/) && (text.match(/^\d+$/) != 0))
{
if(pl.tracks[num] == undefined)
{
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("Invalid number", "Please put a valid number"));
return;
} else {
await pl.setPosition(pl.shuffle ? await this.findSong(num, pl) : num); //advance playlist (wait for save confirmation to prevent glitch)
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Set new track", "Set track to: `" + pl.tracks[num].info.title + "`"));
if(U.currentVC(app, gid) != null)
{
app.lavalink.play(vc, msg, app);
}
}
} else {
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("Invalid number", "Please put a valid number"));
return;
}
}
}
/*
[=add https://www.youtube.com/watch?v=dJaVI7jzc8s
[=add https://www.youtube.com/watch?v=WNxZiBexDM8
[=add https://www.youtube.com/watch?v=IUun0CWrvT0
*/
var U = require.main.require("./utils/Utils.js");

View File

@ -0,0 +1,41 @@
var Command = require("../Command.js");
module.exports = class PlayCommand extends Command
{
constructor()
{
super();
this.name = "play";
this.aliases = ["join"];
this.description = "play music";
this.usage = "<url / query>";
}
async doCommand(msg, app, text)
{
var vc = U.msg2vc(msg);
var pl = await app.db.getPlaylist(msg.channel.guild.id);
var perms = await P.getPerms(app, msg);
if(msg.member.voiceState.channelID != null)
{
if(text.length > 0)
{
if(text.match(/^https?:\/\//i) == null)
{
text = "ytsearch:" + text;
}
//app.bot.createMessage(msg.channel.id, text);
await app.lavalink.add(msg, text, true, vc, app);
} else {
app.lavalink.play(vc, msg, app, true);
}
} else {
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("No Voice Channel", "You seem to not be connected to any voice channel"));
}
}
}
var U = require.main.require("./utils/Utils.js");
var P = require.main.require("./utils/Permissions.js");

View File

@ -0,0 +1,24 @@
var Command = require("../Command.js");
module.exports = class PlaylistCommand extends Command
{
constructor()
{
super();
this.name = "list";
this.aliases = ["playlist", "np", "nowplaying"];
this.description = "show the playlist";
}
async doCommand(msg, app, text)
{
var pl = await app.db.getPlaylist(msg.channel.guild.id);
var vc = U.currentVC(app, msg.channel.guild.id);
var player = app.lavalink.getPlayer(vc, true); //jeebus
app.bot.createMessage(msg.channel.id, U.createPlaylistEmbed(pl, player, vc));
}
}
var U = require.main.require("./utils/Utils.js");

View File

@ -0,0 +1,42 @@
var Command = require("../Command.js");
module.exports = class ClearCommand extends Command
{
constructor()
{
super();
this.name = "remove";
this.description = "remove an item from the playlist";
this.usage = "<# for video>";
this.permissions = ["dj"];
}
async doCommand(msg, app, text)
{
var cmd = text.trim().toLowerCase();
var pl = await app.db.getPlaylist(msg.channel.guild.id);
var hasNumber = /\d/;
var number = U.getVal(cmd, 1, pl.tracks.length) - 1;
if(pl.tracks.length == 0)
{
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("The playlist is empty", "You can't remove something that isn't there ya know..."));
return;
}
if(hasNumber.test(cmd))
{
if(cmd != "")
{
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Removed track", "`" + (number + 1 + ": ") + pl.tracks[number].info.title + "` has been removed from the playlist"));
await pl.removeTrack(number);
}
} else {
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("Could not remove track", "You seem to have not put any numbers or something broke..."));
return;
}
}
}
var U = require.main.require("./utils/Utils.js");

View File

@ -0,0 +1,81 @@
var Command = require("../Command.js");
module.exports = class SkipCommand extends Command
{
constructor()
{
super();
this.name = "skip";
this.description = "skip the current playlist item";
this.votes = [];
}
async voteSkip(msg, app, vc)
{
var user = msg.author.id;
if(this.votes.includes(user))
{
return;
} else {
this.votes.push(user);
}
return;
}
async skip(msg, app, text, pl, vc)
{
//glitch likely caused by lavalink grabbing its own (slightly outdated) copy of the playlist
await pl.next(); //advance playlist (wait for save confirmation to prevent glitch)
app.lavalink.play(vc, msg, app, true);
return;
}
async doCommand(msg, app, text)
{
var pl = await app.db.getPlaylist(msg.channel.guild.id);
var vc = U.msg2vc(msg);
var beforeVotes = this.votes;
var perms = await P.getPerms(app, msg);
var gid = U.msg2gid(msg);
//Getto permission check to have less code and to make it so the command can still be used in some ways
if(pl.djmode == true && U.canUseCommand(perms, {permissions: ["dj"]}, pl))
{
this.skip(msg, app, text, pl, vc)
} else {
if(pl.djmode == false || pl.djmode == undefined) { await this.skip(msg, app, text, pl, vc); }
if(pl.djmode == true)
{
await this.voteSkip(msg, app, vc);
var currentVotes = this.votes.length;
var quota = U.getUsersInVc(msg, vc) / 2;
var voteString = currentVotes + "/" + Math.round(quota);
console.log(voteString);
if(currentVotes >= quota)
{
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Vote Passed", voteString + " voted"));
await this.skip(msg, app, text, pl, vc);
this.votes = [];
} else {
app.bot.createMessage(msg.channel.id, U.createQuickEmbed("Vote Skip:", voteString + " voted"));
}
}
}
}
}
/*
[=add https://www.youtube.com/watch?v=dJaVI7jzc8s
[=add https://www.youtube.com/watch?v=WNxZiBexDM8
[=add https://www.youtube.com/watch?v=IUun0CWrvT0
*/
var U = require.main.require("./utils/Utils.js");
var P = require.main.require("./utils/Permissions.js");

View File

@ -0,0 +1,21 @@
var Command = require("../Command.js");
module.exports = class StopCommand extends Command
{
constructor()
{
super();
this.name = "stop";
this.aliases = ["leave"];
this.description = "stop playing music";
this.permissions = ["dj"];
}
async doCommand(msg, app, text)
{
app.lavalink.stop(msg, app);
}
}
var U = require.main.require("./utils/Utils.js");

View File

@ -0,0 +1,81 @@
var Command = require("../Command.js");
module.exports = class VolumeCommand extends Command
{
constructor()
{
super();
this.name = "volume";
this.usage = "<#>"
this.description = "Sets the volume of the playback";
}
async volume(msg, app, cmd, pl, perms)
{
var vc = U.msg2vc(msg);
var hasNumber = /\d/;
var player = await app.lavalink.getPlayer(vc, true);
var volume = U.canUseCommand(perms, {permissions: ["admin"]}, pl) ? U.getVal(cmd, 0, 1000) : U.getVal(cmd, 0, 100);
if(hasNumber.test(cmd))
{
if(cmd != "")
{
pl.setVolume(volume);
if(player != null)
{
player.setVolume(volume);
}
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Volume has been changed", "The volume has been set to " + pl.volume + "%"));
}
} else {
if(cmd != "")
{
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("Could not change volume", "You seem to have not put any numbers or something broke..."));
return;
} else {
app.bot.createMessage(msg.channel.id, U.createQuickEmbed("Volume:", "Currently " + pl.volume + "%"));
}
}
return;
}
async doCommand(msg, app, text)
{
var cmd = text.trim().toLowerCase();
var hasNumber = /\d/;
var pl = await app.db.getPlaylist(msg.channel.guild.id);
var perms = await P.getPerms(app, msg);
//Getto permission check to have less code and to make it so the command can still be used in some ways
if(pl.djmode == true && U.canUseCommand(perms, {permissions: ["dj"]}, pl))
{
await this.volume(msg, app, cmd, pl, perms);
} else {
if(pl.djmode == false || pl.djmode == undefined) { await this.volume(msg, app, cmd, pl, perms); }
if(pl.djmode == true)
{
if(hasNumber.test(cmd))
{
//The person failed the getto permission check and we display the fact they don't have the role we want
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("You don't have permissions", "You seem to not have `dj`"));
} else {
//Cool the person has the role we want
app.bot.createMessage(msg.channel.id, U.createQuickEmbed("Volume:", "Currently " + pl.volume + "%"));
}
}
}
}
}
/*
[=add https://www.youtube.com/watch?v=dJaVI7jzc8s
[=add https://www.youtube.com/watch?v=WNxZiBexDM8
[=add https://www.youtube.com/watch?v=IUun0CWrvT0
*/
var U = require.main.require("./utils/Utils.js");
var P = require.main.require("./utils/Permissions.js");

View File

@ -0,0 +1,41 @@
var Command = require("../../Command.js");
module.exports = class DJModeCommand extends Command
{
constructor()
{
super();
this.name = "djmode";
this.description = "Turn on or off the requirement for the dj role";
this.usage = "<on/off>"
this.permissions = ["admin"];
}
async doCommand(msg, app, text)
{
var cmd = text.trim().toLowerCase();
var pl = await app.db.getPlaylist(msg.channel.guild.id);
if(cmd == "on")
{
await pl.setDJMode(true);
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("DJ Mode is now on", "DJ Mode is now turned on"));
}
else if(cmd == "off")
{
await pl.setDJMode(false);
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("DJ Mode is now off", "DJ Mode is now turned off"));
}
else if(cmd == "")
{
app.bot.createMessage(msg.channel.id, U.createQuickEmbed("DJ Mode is currently:", (pl.djmode ? "on" : "off")));
}
else
{
U.sendHelp(app, msg, this, true);
}
}
}
var U = require.main.require("./utils/Utils.js");

View File

@ -0,0 +1,41 @@
var Command = require("../../Command.js");
module.exports = class RepeatCommand extends Command
{
constructor()
{
super();
this.name = "repeat";
this.description = "Toggle Repeat for the Playlist";
this.usage = "<on/off>"
this.permissions = ["dj"];
}
async doCommand(msg, app, text)
{
var cmd = text.trim().toLowerCase();
var pl = await app.db.getPlaylist(msg.channel.guild.id);
if(cmd == "on")
{
await pl.setRepeat(true);
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Repeat is now on", "Repeat is now turned on"));
}
else if(cmd == "off")
{
await pl.setRepeat(false);
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Repeat is now off", "Repeat is now turned off"));
}
else if(cmd == "")
{
app.bot.createMessage(msg.channel.id, U.createQuickEmbed("Repeat is currently:", (pl.repeat ? "on" : "off")));
}
else
{
U.sendHelp(app, msg, this, true);
}
}
}
var U = require.main.require("./utils/Utils.js");

View File

@ -0,0 +1,42 @@
var Command = require("../../Command.js");
module.exports = class ShuffleCommand extends Command
{
constructor()
{
super();
this.name = "shuffle";
this.description = "toggle shuffle";
this.usage = "<on/off>"
this.permissions = ["dj"];
}
async doCommand(msg, app, text)
{
var cmd = text.trim().toLowerCase();
var pl = await app.db.getPlaylist(msg.channel.guild.id);
if(cmd == "on")
{
await pl.setShuffle(true);
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Shuffle is now on", "Shuffle is now turned on"));
}
else if(cmd == "off")
{
await pl.setShuffle(false);
await pl.setPosition(pl.indexes[pl.position]); //This makes sure that when shuffle is turned off, it will stay on the song in the normal playlist so it will continue on
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Shuffle is now off", "Shuffle is now turned off"));
}
else if(cmd == "")
{
app.bot.createMessage(msg.channel.id, U.createQuickEmbed("Shuffle is currently:", (pl.shuffle ? "on" : "off")));
}
else
{
U.sendHelp(app, msg, this, true);
}
}
}
var U = require.main.require("./utils/Utils.js");

View File

@ -0,0 +1,41 @@
var Command = require("../../Command.js");
module.exports = class SilentCommand extends Command
{
constructor()
{
super();
this.name = "silent";
this.description = "toggle silent";
this.usage = "<on/off>"
this.permissions = ["admin"];
}
async doCommand(msg, app, text)
{
var cmd = text.trim().toLowerCase();
var pl = await app.db.getPlaylist(msg.channel.guild.id);
if(cmd == "on")
{
await pl.setSilent(true);
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Silent is now on", "Silent is now turned on"));
}
else if(cmd == "off")
{
await pl.setSilent(false);
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Silent is now off", "Silent is now turned off"));
}
else if(cmd == "")
{
app.bot.createMessage(msg.channel.id, U.createQuickEmbed("Silent is currently:", (pl.silent ? "on" : "off")));
}
else
{
U.sendHelp(app, msg, this, true);
}
}
}
var U = require.main.require("./utils/Utils.js");

View File

@ -0,0 +1,42 @@
var Command = require("../Command.js");
module.exports = class PrefixCommand extends Command
{
constructor()
{
super();
this.name = "prefix";
this.description = "Sets my prefix";
this.permissions = ["admin"];
}
async doCommand(msg, app, text)
{
/*var prefix = (await app.db.getGuildSettings(msg.channel.guild.id)).prefix || this.config.prefix;
var newPrefix = msg.content.slice(prefix.length).trim().split(/\s+/)[1];
//await this.settings.set(msg.channel.guild.id, "prefix", newPrefix);
(await app.db.getGuildSettings(msg.channel.guild.id)).setPrefix(newPrefix);
app.bot.createMessage(msg.channel.id, "set prefix to " + newPrefix);
return;*/
var prefix = (await app.db.getGuildSettings(msg.channel.guild.id)).prefix || app.config.prefix;
text = text.trim();
if(text.length != 0)
{
app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Prefix has been set", "The prefix has been set to: `" + text + "`"));
(await app.db.getGuildSettings(msg.channel.guild.id)).setPrefix(text);
return;
} else
{
app.bot.createMessage(msg.channel.id, U.createQuickEmbed("The prefix is currently:", "`" + prefix + "`"));
return;
}
}
}
var U = require.main.require("./utils/Utils.js");

14
config-demo.json Normal file
View File

@ -0,0 +1,14 @@
{
"dbUrl": "mongodb://127.0.0.1:27017",
"dbName": "vertbot",
"autoSave": true,
"perms": {
"<your id>": ["dev"]
},
"autoSaveInterval": 60000,
"disableDBL": true,
"prefix": "v-",
"nodes": [
{ "host": "localhost", "port": 2333, "restport": 2333, "region": "us", "password": "youshallnotpass" }
]
}

18
docker-compose.yml Normal file
View File

@ -0,0 +1,18 @@
services:
vertbot:
image: "kaydax/vertbot"
depends_on:
- lavalink
- mongo
restart: "unless-stopped"
lavalink:
image: "kaydax/lavalink"
restart: "unless-stopped"
#ports:
# - 2333:2333
mongo:
image: mongo
restart: always
environment:
MONGO_INITDB_ROOT_USERNAME: vertbot
MONGO_INITDB_ROOT_PASSWORD: ZYc34mqE

6
lavalink/Dockerfile Normal file
View File

@ -0,0 +1,6 @@
FROM eclipse-temurin:17
WORKDIR /usr/app
COPY . .
#RUN java Lavalink.java
#CMD ["java", "Main"]
CMD ["java", "-Xmx1G", "-jar", "Lavalink.jar"]

BIN
lavalink/Lavalink.jar Normal file

Binary file not shown.

View File

@ -0,0 +1,42 @@
server: # REST server
port: 2333
address: 0.0.0.0
spring:
main:
banner-mode: log
lavalink:
server:
password: "youshallnotpass"
sources:
youtube: true
bandcamp: true
soundcloud: true
twitch: true
vimeo: true
mixer: true
http: true
local: false
bufferDurationMs: 400
youtubePlaylistLoadLimit: 2147483647
gc-warnings: true
metrics:
prometheus:
enabled: false
endpoint: /metrics
sentry:
dsn: ""
# tags:
# some_key: some_value
# another_key: another_value
logging:
file:
max-history: 30
max-size: 1GB
path: ./logs/
level:
root: INFO
lavalink: INFO

Binary file not shown.

Binary file not shown.

1
lavalink/start.bat Normal file
View File

@ -0,0 +1 @@
java -jar Lavalink.jar

2
lavalink/start.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
screen -S Lavalink java -jar Lavalink.jar

1
log.txt Normal file
View File

@ -0,0 +1 @@
2023/01/10 00:32:13 Micro started

3
main.js Normal file
View File

@ -0,0 +1,3 @@
var App = require("./App.js");
new App();

4606
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

52
package.json Normal file
View File

@ -0,0 +1,52 @@
{
"name": "vertbot",
"version": "2.5.3",
"description": "Vertbot - A lightweight multi purpose, lag free music bot",
"main": "main.js",
"scripts": {
"test": "node main.js"
},
"repository": {
"type": "git",
"url": "git@gitlab.com:Kaydax/Vertbot.git"
},
"keywords": [
"Discord",
"Bot",
"Music"
],
"author": "Kaydax",
"license": "MIT",
"bugs": {
"url": "https://github.com/Kaydax/Vertbot/issues"
},
"homepage": "https://github.com/Kaydax/Vertbot#readme",
"dependencies": {
"assert": "^2.0.0",
"dblapi.js": "^2.3.0",
"deepl-scraper": "^1.0.8",
"deepl-translator": "^1.2.1",
"eris": "^0.15.1",
"extend": "^3.0.2",
"figlet": "^1.2.4",
"format-duration": "^1.3.1",
"free-translate": "^0.1.1",
"install": "^0.13.0",
"jsonpath": "^1.1.1",
"kaori": "^2.0.0",
"mongodb": "^3.3.3",
"mongoose": "^5.12.13",
"node-fetch": "2.0",
"number-format.js": "^2.0.9",
"request": "^2.88.0",
"request-promise": "^4.2.4",
"shuffle-array": "^1.0.1",
"snekfetch": "^4.0.4",
"split2": "^3.1.1",
"spotify-web-api-node": "git+https://github.com/thelinmichael/spotify-web-api-node.git",
"unscramble": "^0.0.1-v",
"vertbot-eris-lavalink": "^1.0.1",
"word-unscrambler": "^1.1.1",
"ws": "^7.4.6"
}
}

8
secret-demo.js Normal file
View File

@ -0,0 +1,8 @@
//Put your bot token and youtube api key in here, and rename the file to secret.js
module.exports = {
discord: "<bot token>",
youtube: "<youtube api token>",
spotify: "<spotify access token>",
dbl: "<Your discord bot list token>",
dbots: "<Your discord bots auth token>"
}

60
utils/Database.js Normal file
View File

@ -0,0 +1,60 @@
/** mongoose database io stuff */
module.exports = class Database
{
constructor(app)
{
this.app = app;
this.dbUrl = this.app.config.dbUrl;
this.dbName = this.app.config.dbName; //"vertbot"; //TODO: putin config
this.db = mongoose.createConnection(this.dbUrl + "/" + this.dbName, { useUnifiedTopology: true });
this.GuildSetting = this.db.model("guildSetting", schema.guildSetting, "settings");
this.Playlist = this.db.model("playlist", schema.playlist, "settings");
this.User = this.db.model("user", schema.user, "settings");
//TODO: create other settings likewise
//TODO: autosave?
}
//TODO: inter-shard communication
async getGuildSettings(gid)
{
var gs = await this.GuildSetting.findOne({id: gid, type: "guild"}).exec();
if(gs == null)
{
return new this.GuildSetting({id: gid});
}
return gs;
}
async getPlaylist(gid)
{
var pl = await this.Playlist.findOne({id: gid, type: "playlist"}).exec();
if(pl == null)
{
return new this.Playlist({id: gid});
}
return pl;
}
async getUserSettings(uid)
{
var user = await this.User.findOne({id: uid, type: "user"}).exec();
if(user == null)
{
return new this.User({id: uid});
}
return user;
}
}
var mongoose = require("mongoose");
var schema = require("./Schema.js");

31
utils/Discord-List-api.js Normal file
View File

@ -0,0 +1,31 @@
//Basically discord-bots-api on npm but for only posting the guild count
var DLA = {};
module.exports = DLA;
DLA.postGuilds = async function(bot, tokens)
{
var url = "https://botblock.org/api/count";
var options =
{
method: 'POST',
uri: url,
body: {
"server_count": bot.guilds.size,
"bot_id": bot.user.id,
"shard_count": bot.shards.size,
"bots.ondiscord.xyz": tokens.bod,
"discord.bots.gg": tokens.dbots,
"discordapps.dev": tokens.dapps,
"top.gg": tokens.topgg
},
json: true
};
rp(options).catch(function (err) {
console.log(err);
});
console.log("[DLA] Server Count Posted!");
}
var rp = require('request-promise');

336
utils/Lavalink.js Normal file
View File

@ -0,0 +1,336 @@
module.exports = class Lavalink
{
//TODO: store voice channel or something because uhhhhh if the user moves or d/cs what happens to msg2vc
//TODO: playlist control with left/right + 0-9
//TODO: add checks for user (so they cant stop other peoples music)
constructor(app)
{
this.app = app;
//NOTE: As of 3.1.X and up, the WS and Rest Ports are the same
this.nodes = this.app.config.nodes //[{ host: 'localhost', port: 80, region: 'us', password: 'youshallnotpass' }];
this.regions = {
asia: ['hongkong', 'singapore', 'sydney'],
eu: ['eu', 'amsterdam', 'frankfurt', 'russia'],
us: ['us', 'brazil'],
};
this.shardCount = app.bot.shards.size; //lol size instead of length
this.userId = app.bot.user.id;
//TODO: currently assuming single lavalink node...
this.node = this.nodes[0];
//console.log(this.nodes[0]);
this.playerManager = new PlayerManager(this.app.bot, this.nodes,
{
numShards: this.shardCount, // number of shards
userId: this.userId, // the user id of the bot
regions: this.regions,
client_name: "Vertbot",
defaultRegion: 'us',
});
this.app.bot.voiceConnections = this.playerManager;
}
/** pass voice channel and the message that caused the command, if play is called with an OOB position, position will be reset to 0 */
async play(vc, msg, app, doMessage)
{
//get next track to play
var pl = await this.app.db.getPlaylist(vc.guild.id);
if(pl.tracks.length == 0)
{
this.app.bot.createMessage(msg.channel.id, U.createErrorEmbed("No tracks in playlist", "There seems to be no tracks in the playlist, please add some and try again"));
return;
}
if(pl.position < 0 || pl.position >= pl.tracks.length)
{
await pl.restart();
}
var track = pl.currentTrack();
if(track == null)
{
if(msg != null)
{
this.app.bot.createMessage(msg.channel.id, U.createErrorEmbed("Track is null", "The track really shouldn't be null here..."));
}
return;
}
if(!vc.permissionsOf(this.app.bot.user.id).has("voiceConnect"))
{
if(msg != null)
{
this.app.bot.createMessage(msg.channel.id, U.createErrorEmbed("Invalid permissions", "It seems that I don't have permission to join that voice channel"));
}
return;
}
//grab player
var player = this.getPlayer(vc);
//if its null, join voice channel
if(player == null)
{
player = await this.join(vc);
this.registerPlayerEvents(player, msg);
}
player.setVolume(pl.volume);
var bands = [{"band": 0,"gain": pl.boost},{"band": 1,"gain": pl.boost},{"band": 2,"gain": pl.boost}];
player.setEQ(bands);
player.play(track.track); // track is the base64 track we get from Lavalink
if(!pl.silent || doMessage)
{
var position = pl.shuffle ? pl.indexes[pl.position] + 1 : pl.position + 1;
if(msg.channel != null)
{
this.app.bot.createMessage(msg.channel.id, U.createNowPlayingEmbed(position + " / " + pl.tracks.length, "**" + track.info.title + "**"));
}
}
}
async stop(msg, app)
{
var vc = U.currentVC(this.app, msg.channel.guild.id);
if(vc == null)
{
//throw "Bot is not in a vc";
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("No Voice Channel", "You seem to not be connected to any voice channel"));
}
var player = await this.getPlayer(vc);
if(player == null)
{
this.app.bot.leaveVoiceChannel(vc.id);
app.bot.createMessage(msg.channel.id, U.createErrorEmbed("Player stuck in vc?", "I seem to have encountered an error, I will try to disconnect from the voice channel to avoid any more issues"));
}
player.stop();
this.unregisterPlayerEvents(player);
this.app.bot.leaveVoiceChannel(vc.id);
}
async add(msg, search, play, vc, app)
{
var tracks = await this.resolveTracks(this.node, search);
if(tracks.tracks.length == 0)
{
this.app.bot.createMessage(msg.channel.id, U.createErrorEmbed("No tracks to add", "There seems to be nothing found that I can add. Try to make your search more clear"));
return;
}
//Show the video info from text search
if(search.startsWith("ytsearch:"))
{
var info = tracks.tracks[0].info;
var title = info.title;
var id = info.identifier;
var author = info.author;
//This used to be using the youtube api until I figured out how the thumbnail's worked
this.app.bot.createMessage(msg.channel.id, U.createSearchEmbed(title, id, author));
}
var pl = await this.app.db.getPlaylist(msg.channel.guild.id);
//pl.addAll(tracks);
//pl.add(tracks[0]);
//Lavalink 3 uses tracks.tracks[0] and not tracks[0] to add playlist info support
if(msg.content.match(/https?:/) != null)
{
await pl.addAll(tracks.tracks)
} else {
await pl.add(tracks.tracks[0]);
}
//this.app.bot.createMessage(msg.channel.id, "Adding " + tracks.length + " tracks");
if(tracks.loadType == "PLAYLIST_LOADED")
{
this.app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Added new playlist", "Added all of `" + tracks.playlistInfo.name + "` to the playlist"))
} else {
this.app.bot.createMessage(msg.channel.id, U.createSuccessEmbed("Added new track", "Added `" + tracks.tracks[0].info.title + "` to the playlist"));
}
await pl.createShuffleArray(); //Creates an index array used to randomize playback when shuffle is enabled
if(play)
{
//if(!pl.shuffle) { await pl.setPosition(pl.tracks.length - 1); }; //Outdated code, don't use. Allows for bypass of vote skip when dj mode is enabled
//this.play(vc, msg, app, true);
if(pl.djmode == false || pl.djmode == undefined)
{
if(!pl.shuffle) { await pl.setPosition(pl.tracks.length - 1); };
this.play(vc, msg, app, true);
}
//TODO: Add support for people with DJ perms to do a bypass skip with the play command.
//If DJMode is enabled just do nothing as it will just add the track like normal.
//Too lazy to actually implement a DJ perm bypass to this as it will slow down the add command more then it needs to be
}
//This is for debuging track info:
//console.log(tracks);
//var res = await U.youtube(this.app, tracks[0].info.identifier);
//console.log(JSON.stringify(res.items[0].snippet.thumbnails.maxres.url, null, 2));
}
async onDisconnect(err)
{
if(err)
{
throw "player disconnected: " + err;
}
console.log("player disconnected");
}
async onError(err)
{
throw "player error: " + err;
}
async onStuck(info)
{
console.log("player stuck player stuck please i beg you: " + info);
}
async onEnd(data, msg)
{
// REPLACED reason is emitted when playing without stopping, I ignore these to prevent skip loops
if(data.reason && data.reason === 'REPLACED')
{
return;
}
//advance playlist
var pl = await this.app.db.getPlaylist(msg.channel.guild.id);
await pl.next();
var vc = U.currentVC(this.app, msg.channel.guild.id);
//playlist ended
if(pl.position >= pl.tracks.length)
{
if(pl.repeat)
{
//do repeat
await pl.restart();
} else {
setTimeout(() => this.stop(msg), 1000);
this.app.bot.createMessage(msg.channel.id, U.createQuickEmbed("Finished playing", "Playlist over"));
return;
}
}
//play next song
this.play(vc, msg);
}
unregisterPlayerEvents(player)
{
player.removeAllListeners("disconnect");
player.removeAllListeners("error");
player.removeAllListeners("stuck");
player.removeAllListeners("end");
}
registerPlayerEvents(player, msg)
{
//remove listeners to prevent memory leaks
this.unregisterPlayerEvents(player);
player.on("disconnect", (e) => this.onDisconnect(e, msg));
player.on("error", (e) => this.onError(e, msg));
player.on("stuck", (e) => this.onStuck(e, msg));
player.on("end", (e) => this.onEnd(e, msg));
}
getPlayer(channel, unsafe)
{
if(!channel || !channel.guild)
{
if(unsafe)
{
return null;
}
throw "channel is null or not a guild channel";
}
return this.playerManager.get(channel.guild.id);
}
join(channel)
{
if(!channel || !channel.guild)
{
return Promise.reject('Not a guild channel.');
}
return this.app.bot.joinVoiceChannel(channel.id);
}
getPlayerOld(channel)
{
if(!channel || !channel.guild)
{
return Promise.reject('Not a guild channel.');
}
//let player = this.app.bot.voiceConnections.get(channel.guild.id);
let player = this.playerManager.get(channel.guild.id);
if(player)
{
return Promise.resolve(player);
}
let options = {}; //create a variable called "options", and store an empty object in it.
if(channel.guild.region)
{
options.region = channel.guild.region; //put "channel.guild.region"'s info in options.region
}
//return this.app.bot.voiceConnections.join(channel.guild.id, channel.id, options);
//return this.playerManager.join(channel.guild.id, channel.id, options);
//return this.app.bot.voiceConnections.join(channel.guild.id, channel.id, null);
var conn = this.app.bot.joinVoiceChannel(channel.id);
return conn;
}
async resolveTracks(node, search)
{
try
{
var result = await superagent.get(`http://${node.host}:${node.restport}/loadtracks?identifier=${search}`)
.set('Authorization', node.password)
.set('Accept', 'application/json');
} catch (err) {
throw err;
}
if(!result)
{
throw 'Unable play that video.';
}
return result.body; // array of tracks resolved from lavalink
}
}
var {PlayerManager} = require("vertbot-eris-lavalink");
var superagent = require("superagent");
//var SpotifyWebApi = require('spotify-web-api-node');
//var spotifyApi = new SpotifyWebApi();
var U = require.main.require("./utils/Utils.js");

89
utils/Permissions.js Normal file
View File

@ -0,0 +1,89 @@
var Eris = require("eris");
var P = {}
module.exports = P;
//dev
//donor
//owner, admin
//dev > owner > admin > donor
/** returns all permissions of user that sent the given message */
P.getPerms = async function(app, msg)
{
var member = msg.member;
var dbp = await P.getDBPerms(app, member);
var gp = await P.getGuildPerms(app, member);
var rp = await P.getRolePerms(app, member);
var cp = await P.getConfigPerms(app, member);
return Array.from(new Set([...dbp,...gp, ...cp,...rp]));
}
P.getDBPerms = async function(app, member)
{
//TODO: get from database (donor?)
var uid = member.id;
var us = await app.db.getUserSettings(uid);
var perms = us.getPermissions();
return new Set(perms);
}
P.getGuildPerms = async function(app, member)
{
var perms = new Set();
var userPerms = member.permission
if(userPerms.has("administrator"))
{
perms.add("admin");
}
if(member.guild.ownerID == member.id)
{
perms.add("owner");
}
//guild.ownerID
return perms;
}
P.getRolePerms = async function(app, member)
{
//role name: permission name
var mappings = {
"dj": "dj"
};
var perms = new Set();
for(var key in mappings)
{
if(U.hasRoleWithName(member, key))
{
perms.add(mappings[key]);
}
}
return perms;
}
P.getConfigPerms = async function(app, member)
{
//dev, etc
var uid = member.id;
var perms = (app.config.perms || {})[uid];
if(perms == null)
{
return new Set();
}
return new Set(perms);
}
var U = require.main.require("./utils/Utils.js");

192
utils/Schema.js Normal file
View File

@ -0,0 +1,192 @@
var mongoose = require("mongoose");
var shuffle = require('shuffle-array');
var Schema = mongoose.Schema;
//oh boy here we go...
var S = {};
module.exports = S;
/*----------Guild Data----------*/
var guildSetting = Schema({
id: String, //guild id
type: {type: String, default: "guild"},
prefix: String,
});
S.guildSetting = guildSetting;
guildSetting.methods.setPrefix = function(prefix)
{
this.prefix = prefix;
//console.log("new prefix: " + prefix)
this.save();
}
/*----------Playlist Data----------*/
var playlist = Schema({
id: String, //guild id
type: {type: String, default: "playlist"},
tracks: [],
indexes: [],
position: {type: Number, default: 0},
volume: {type: Number, default: 100},
boost: {type: Number, default: 0},
shuffle: Boolean,
repeat: Boolean,
autoRemove: Boolean,
silent: Boolean,
djmode: Boolean
});
S.playlist = playlist;
playlist.methods.currentTrack = function()
{
return this.shuffle ? this.tracks[this.indexes[this.position || 0]] : this.tracks[this.position || 0];
}
playlist.methods.clear = async function()
{
//clear
this.tracks.splice(0, this.tracks.length);
this.indexes.splice(0, this.tracks.length);
this.position = 0;
return await this.save();
}
playlist.methods.removeTrack = async function(index)
{
//remove
this.tracks.splice(index, 1);
return await this.save();
}
playlist.methods.next = async function()
{
//increment, null safe
this.position = this.position == null ? 0 : this.position;
this.position = this.position + 1;
return await this.save();
}
playlist.methods.restart = async function()
{
this.position = 0;
return await this.save();
}
//TODO: make add/addall cleaner?
playlist.methods.add = async function(track)
{
this.tracks.push(track);
return await this.save();
}
playlist.methods.addAll = async function(tracks)
{
for(var track of tracks)
{
await this.add(track);
}
}
playlist.methods.createShuffleArray = async function()
{
var indexes = this.tracks.map((e, i) => i);
this.indexes = shuffle(indexes);
return await this.save();
}
playlist.methods.setPosition = async function(pos)
{
this.position = pos
return await this.save();
}
playlist.methods.setRepeat = async function(repeat)
{
this.repeat = repeat;
return await this.save();
}
playlist.methods.setShuffle = async function(shuffle)
{
this.shuffle = shuffle;
return await this.save();
}
playlist.methods.setSilent = async function(silent)
{
this.silent = silent;
return await this.save();
}
playlist.methods.setDJMode = async function(djmode)
{
this.djmode = djmode;
return await this.save();
}
playlist.methods.setVolume = async function(volume)
{
this.volume = volume;
return await this.save();
}
playlist.methods.setBoost = async function(boost)
{
this.boost = boost;
return await this.save();
}
/*----------User Data----------*/
var user = Schema({
id: String, //user id
type: {type: String, default: "user"},
permissions: [String], //this should be a set but whatever
});
S.user = user;
user.methods.addPermission = async function(perm)
{
perm = perm.trim().toLowerCase();
if(this.permissions.includes(perm))
{
//already has perm
return;
}
this.permissions.push(perm);
return await this.save();
}
user.methods.removePermission = async function(perm)
{
perm = perm.trim().toLowerCase();
this.permissions = this.permissions.filter((e) => e != perm);
return await this.save();
}
user.methods.hasPermission = function(perm)
{
return this.permissions.includes(perm);
}
user.methods.getPermissions = function(perm)
{
return this.permissions;
}

107
utils/Settings.js Normal file
View File

@ -0,0 +1,107 @@
/** read from database on load, periodically save back to database */
module.exports = class Settings
{
constructor(app)
{
this.app = app;
//global settings
//per-guild settings (maps ids to mongodb documents)
this.guildSettings = {};
//per-channel settings?
//per-user settings?
//map of guild ids to "dirty" (upload to mongo) flags
this.guildDirtyFlags = new Set();
this.load();
setTimeout(this.doAutoSave.bind(this), this.app.config.autoSaveInterval);
}
/** uses jsonpath */
guildGet(gid, query)
{
console.log(query);
var gs = this.getGuildSettings(gid, true);
return jp.value(gs, query);
}
guildSet(gid, query, value)
{
console.log(query);
var gs = this.getGuildSettings(gid, true);
jp.value(gs, query, value);
this.markGuildDirty();
}
doAutoSave()
{
if(this.app.config.autoSave)
{
this.save();
}
setTimeout(this.doAutoSave.bind(this), this.app.config.autoSaveInterval);
}
markGuildDirty(gid)
{
this.guildDirtyFlags.add(gid);
}
async load()
{
//load all guild settings for this shard
var ids = Array.from(this.app.bot.guilds).map(([k, v]) => v.id);
//grab guild settings from database
var gs = await this.app.db.getGuildSettings(ids);
//store in local cache (mapped by id)
gs.forEach((e) => this.guildSettings[e.id] = e);
gs.forEach((e) => console.log(e.id + ": " + e.prefix));
}
/** creates object if create is true, not null-safe */
getGuildSettings(gid, create)
{
if(!this.guildSettings[gid] && create)
{
this.guildSettings[gid] = this.createNewGS(gid);
this.setDirty(gid);
}
return this.guildSettings[gid];
}
createNewGS(gid)
{
var gs = {};
gs.type = "guild";
gs.id = gid;
return gs;
}
save()
{
//for each guild with dirty flag set
this.guildDirtyFlags.forEach(async (id, flag) =>
{
if(flag)
{
//grab guild settings
var gs = this.getGuildSettings(id);
//replace in mongodb
var res = await this.app.db.updateGuildSettings(gs);
//console.log("updated settings for guild " + id);
}
});
console.log("...Saved Settings..."); //ocd
this.guildDirtyFlags.clear();
}
}

481
utils/Utils.js Normal file
View File

@ -0,0 +1,481 @@
var U = {};
var fd = require("format-duration");
var format = require("number-format.js");
module.exports = U;
U.getUsersInVc = function(msg, vc)
{
var members = Array.from(vc.voiceMembers.keys());
var users = 0;
var guild = msg.channel.guild;
for(var i = 0; i < members.length; i++)
{
if(!U.getMemberById(guild, members[i]).bot)
{
users++
}
}
return users;
}
U.hasRoleWithName = function(member, role)
{
role = role.toLowerCase();
//get roles from guild
var rolesWithName = member.guild.roles;
//filter roles that have the same name as "role"
rolesWithName = rolesWithName.filter((e) => e.name.toLowerCase() == role);
//map to ids
rolesWithName = rolesWithName.map((e) => parseInt(e.id));
//console.log("role we want: " + rolesWithName)
//get roles of member
var userRoles = member.roles;
for(var i = 0; i < userRoles.length; i++)
{
var id = parseInt(userRoles[i]);
if(rolesWithName.includes(id))
{
return true;
}
}
return false;
}
U.getMemberById = function(guild, id)
{
return guild.members.filter((e) => e.id == id)[0];
}
/** finds the voice channel of the user that sent the given message */
U.msg2vc = function(msg)
{
var vcid = msg.member.voiceState.channelID;
var vc = msg.channel.guild.channels.get(vcid);
return vc;
}
U.msg2gid = function(msg)
{
var gid = msg.channel.guild.id;
return gid;
}
U.currentVC = function(app, gid)
{
var guild = app.bot.guilds.get(gid);
var member = guild.members.get(app.bot.user.id);
var vcid = member.voiceState.channelID;
var vc = guild.channels.get(vcid);
return vc;
}
/*U.sharedVc = function(app, msg, gid)
{
if()
{
} else {
}
}*/
U.onOff = function(onOff)
{
return onOff ? "on" : "off";
}
U.wrapMention = function(msg, text)
{
return msg.author.mention + " " + text;
}
U.wrapCode = function(text)
{
return "```\n" + text + "```";
}
/** convert milliseconds to string */
//TODO: optional minutes/hours based on a second input (total time)
U.ms2str = function(ms, total)
{
return fd(ms); //:^)
}
U.sendHelp = function(app, msg, command, self)
{
//console.log("extra large penis", command, command.constructor.name, command.getHelp)
U.reply(app, msg, U.wrapCode(command.getHelp(self)));
}
U.reply = function(app, msg, text)
{
app.bot.createMessage(msg.channel.id, U.wrapMention(msg, text));
}
U.getVal = function(v, min, max)
{
var val = v.replace( /^\D+/g, '');
return (val > min) ? ((val < max) ? val : max) : min;
}
U.str2id = function(str)
{
str = str.trim();
var id = str.match(/<@!?(\d+)>|(\d+)/);
if(id == null)
{
return null;
}
//id = id[0];
id = id[1];
return id;
}
/** returns true if the permission set has access to the command */
U.canUseCommand = function(userPerms, command, pl)
{
if(command.permissions.length == 0)
{
return true;
}
//if we have all permissions
//REEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEturn
return command.permissions.every((reqPerm) => {
if(userPerms.includes("dev"))
{
return true;
}
//if require permission is in user perms
if(userPerms.includes(reqPerm))
{
return true;
}
//override owner -> admin
if(reqPerm == "admin" && ["owner"].some((e) => userPerms.includes(e)))
{
return true;
}
//override owner & admin -> dj
if(reqPerm == "dj" && ["owner", "admin"].some((e) => userPerms.includes(e)))
{
return true;
}
//override the dj permission check if dj mode is turned on
if((reqPerm == "dj" && pl.djmode == false) || (reqPerm == "dj" && pl.djmode == undefined))
{
return true;
}
return false; //User does not have the permissions to use the command
});
}
U.hexToColour = function(hex)
{
return parseInt(hex, 16);
}
U.getCurrentShard = function(app, gid)
{
return (app.bot.guilds.get(gid).shard.id + 1) + " / " + ((app.bot.shards.size != undefined || app.bot.shards.size != null) ? app.bot.shards.size : "1");
}
U.getLogicalChannel = function(app, guild)
{
var chans = guild.channels.filter(c => !c.type);
var channels = Array.from(chans);
channels.sort((a,b) => a.position - b.position);
//Bot channel check
for(var i = 0; i < channels.length; i++)
{
if(channels[i].name.match(/(-bot)|(bot-)|^bot$/gi))
{
return channels[i];
}
}
//General Channel Check
for(var i = 0; i < channels.length; i++)
{
if(channels[i].name.match(/(-general)|(general-)|^general$/gi))
{
return channels[i];
}
}
//If the general and bot channel cannot be found, use the first channel the bot can speak in
for(var i = 0; i < channels.length; i++)
{
if(U.canSpeak(app, channels[i]))
{
return channels[i];
}
}
//If all else fails return null
return null;
}
U.canSpeak = function(app, chan)
{
var id = app.bot.user.id;
return chan.permissionsOf(id).has("sendMessages");
}
//------------Embeds------------
U.embedField = function(name, value, inline)
{
return {
name: name,
value: value,
inline: inline
}
}
U.thumbnail = function(url, width, height)
{
return {
url: url,
proxy_url: url,
width: width,
height: height
}
}
U.author = function(name, url, icon)
{
return {
name: name,
url: url,
icon_url: icon,
proxy_icon_url: icon
}
}
U.createSearchEmbed = function(title, id, author)
{
var url = "https://youtube.com/watch?v=" + id; //This is the link back to the video... you should understand how this works already
var thumbnail = "https://i.ytimg.com/vi/" + id + "/hqdefault.jpg"; //This is pretty much how the api gets the thumbnail, but without the api
return {
embed:
{
color: U.hexToColour("00C4B1"),
description: "[" + title + "](" + url + ")",
author:
{
name: author
},
image:
{
url: thumbnail
}
}
}
}
U.createPlaylistEmbed = function(pl, player, vc)
{
//TODO: fill these in
var curTrack = pl.shuffle ? pl.tracks[pl.indexes[pl.position]] : pl.tracks[pl.position];
var position = pl.shuffle ? pl.indexes[pl.position] : pl.position;
var nowPlaying = vc == undefined ? "Nothing" : curTrack.info.title;
//TODO: player null check
var time = player == null || curTrack == null ? "0:00 / 0:00" : U.ms2str(player.state.position) + " / " + U.ms2str(curTrack.info.length);
var modes = U.wrapCode("Repeat: " + U.onOff(pl.repeat) + ", Shuffle: " + U.onOff(pl.shuffle) + ", Silent: " + U.onOff(pl.silent) + ", DJ Mode: " + U.onOff(pl.djmode));
//var trackNames = pl.tracks.map((e) => e.info.title);
//find start, end centered around current position (pl.position)
var beforeAfter = 7;
var start = position - beforeAfter;
var end = position + beforeAfter;
var str = "```markdown\n";
for(var i = start; i < end; i++)
{
if(i >= 0 && i < pl.tracks.length)
{
var t = pl.tracks[i];
str += i == position ? "> " : " ";
str += position <= 992 ? (i + 1 + ": ").padEnd(5, " ") : (i + 1 + ": ").padEnd(6, " ");
str += position <= 992 ? (t.info.title.length > 50 ? (t.info.title.substring(0, 50) + "...") : t.info.title) : (t.info.title.length > 48 ? (t.info.title.substring(0, 48) + "...") : t.info.title);
str += "\n";
}
}
str += "\n{" + pl.tracks.length + " videos in total}```\n"
return {
embed:
{
color: U.hexToColour("00C4B1"),
fields:
[
U.embedField("Playlist:", str, false),
U.embedField("Now Playing:", nowPlaying, false),
U.embedField("Time:", time, false),
U.embedField("Modes:", modes, false)
]
}
};
}
U.createInfoEmbed = function(app, gid, prefix, msg)
{
return {
embed:
{
author: U.author("Vertbot", "https://kaydax.xyz/", app.bot.user.avatarURL),
description: "I'm Vertbot, a Discord bot built with Eris and Lavalink. My main purpose is to be one of the only Discord bots that give people the power to play music with little lag and very few limitations.",
color: U.hexToColour("00C4B1"),
fields:
[
U.embedField("Current prefix:", prefix, true),
U.embedField("Current Build:", app.version, true),
U.embedField("Uptime:", U.ms2str(app.bot.uptime), true),
U.embedField("Servers:", format("#,###.", app.bot.guilds.size), true),
U.embedField("Users:", format("#,###.", app.bot.users.size), true),
U.embedField("Shard:", U.getCurrentShard(app, gid), true),
U.embedField("My Discord:", "[Join Now](https://discord.gg/WPUU2dF)", true),
U.embedField("Servers using music:", app.bot.voiceConnections.size, true),
U.embedField("Ping:", msg.channel.guild.shard.latency, true)
],
footer:
{
text: "Developed by: Kaydax#0001 & tehZevo#0321."
}
}
} //U.embedField("", "", true)
}
U.createWelcomeEmbed = function(app)
{
return {
embed:
{
author: U.author("Vertbot", "https://kaydax.xyz/", app.bot.user.avatarURL),
description: "Hello, I'm Vertbot! Thank you for adding me to your server. To get started just do `v-help` and look through the list of commands. If you don't understand how to use the commands, join my support server.",
color: U.hexToColour("00C4B1"),
fields:
[
U.embedField("My Support Server:", "[Join Now](https://discord.gg/WPUU2dF)", true),
U.embedField("Vote For Me:", "[Please Vote](https://discordbots.org/bot/316520238835433482/vote)", true),
U.embedField("Donate Now:", "Please help me continue to run by [clicking here](https://donatebot.io/checkout/317570975908495361) else do `v-donate`", true),
],
footer:
{
text: "Developed by: Kaydax#0001 & tehZevo#0321"
}
}
}
}
U.createDonateEmbed = function()
{
return {
embed:
{
title: "Help me continue to run by donating!",
description: "If you want to help me continue to run, just donate to either [Kaydax](https://donatebot.io/checkout/317570975908495361) or [tehZevo](https://zevo.me/)",
color: U.hexToColour("00C4B1"),
fields:
[
U.embedField("I also support crypto:", "```\nBitcoin: 12gzEmGsNtqPpaJQG4dKGAFcgvu7aP6q47\nEthereum: 0xCC4050FF70008D3D9875CeEFbDCbCe27F5bc61bd\nXRP: r4TQWdYrpiibCwmXWZhajbnUcwHZvKRebn\nBAT: 0xCC4050FF70008D3D9875CeEFbDCbCe27F5bc61bd\nDogecoin: DKvXdxNAGLPAob2yEDoZPNr8cp31oSjKWD\nStellar: GBCVXJJQDIT2WJYRVN23DBC635VPZDRGCBG3A3ILMGJWZOCWK4GSKAEF\nBitcoin Cash: qpytmmpm8an6cfds3t8mqyxnn4pcmqderscv6dgtar\nBinance Coin: bnb1pf4mwyh76g5y4gqjh9khlsagqxy8trjhnel0mq\nBitcoin SV: 1DXqBUUXUwiEjSJy4zFg7Hsn455iCxBwCi\nDash: XxB815XHAMGdv5PECfWtnx6oyHmP4FW2pv\nLitecoin: LdCHqHD4ggWdRQF84TSBU5BahJAn7LvF2q\nNEO: AR1piF14vZfesSmERGbsyJ1b5cct6SYqUL\nTron: TULZsQj4539DHi9mA9Df7RFBmjAeQeSkoV\nTUSD: 0xCC4050FF70008D3D9875CeEFbDCbCe27F5bc61bd\nUSDC: 0xCC4050FF70008D3D9875CeEFbDCbCe27F5bc61bd\nZcash: t1f951Yv8UddswqNreAgiojwcGBBMVzd7wE```", false),
]
}
}
}
U.createBooruEmbed = function(user, image, tags, url)
{
return {
embed:
{
title: "Booru command requested by " + user,
color: U.hexToColour("00C4B1"),
image:
{
url: image,
proxy_url: image
},
fields:
[
U.embedField("Tags:", "```" + tags + "```", false),
U.embedField("Post URL:", url, false)
]
}
}
}
U.createSuccessEmbed = function(reason, desc)
{
return {
embed:
{
title: "Success: " + reason,
description: desc,
color: U.hexToColour("009b19")
}
}
}
U.createNowPlayingEmbed = function(pos, desc)
{
return {
embed:
{
title: "Now Playing " + pos + ":",
description: desc,
color: U.hexToColour("00C4B1")
}
}
}
U.createErrorEmbed = function(reason, desc)
{
return {
embed:
{
title: "Error: " + reason,
description: desc,
color: U.hexToColour("c04040")
}
}
}
U.createQuickEmbed = function(title, desc)
{
return {
embed:
{
title: title,
description: desc,
color: U.hexToColour("00C4B1")
}
}
}
//------------------------------