Formatting Preview Alpha

This commit is contained in:
WatDuhHekBro 2020-12-14 19:44:28 -06:00
parent 98e47e3796
commit 39f89a9f63
47 changed files with 4714 additions and 4581 deletions

View File

@ -1,4 +1,4 @@
name: 'CodeQL Config'
name: "CodeQL Config"
queries:
- uses: security-and-quality

View File

@ -1,4 +1,4 @@
name: 'CodeQL'
name: "CodeQL"
on:
push:
@ -6,7 +6,7 @@ on:
pull_request:
branches: [typescript]
schedule:
- cron: '0 5 * * 1'
- cron: "0 5 * * 1"
jobs:
analyze:
@ -25,7 +25,7 @@ jobs:
- name: Setup Node.JS
uses: actions/setup-node@v2-beta
with:
node-version: '12'
node-version: "12"
- run: npm ci
- name: Build codebase

View File

@ -1,3 +1,10 @@
# Specific to prettier (so it doesn't throw a bunch of errors when running "npm run format")
.dockerignore
.gitignore
.prettierignore
Dockerfile
LICENSE
# Specific to this repository
dist/
data/

View File

@ -17,3 +17,4 @@
- ...update the [changelog](CHANGELOG.md) and any other necessary docs.
- ...update the version numbers in `package.json` and `package-lock.json`.
- ...make sure the test suite passes by running `npm test`.
- ...format the code by running `npm run format`.

View File

@ -1,14 +1,14 @@
module.exports = {
printWidth: 80,
tabWidth: 2,
tabWidth: 4,
useTabs: false,
semi: true,
singleQuote: true,
quoteProps: 'as-needed',
singleQuote: false,
quoteProps: "as-needed",
jsxSingleQuote: false,
trailingComma: 'all',
bracketSpacing: true,
jsxBracketSameLine: true,
arrowParens: 'always',
endOfLine: 'auto',
trailingComma: "none",
bracketSpacing: false,
jsxBracketSameLine: false,
arrowParens: "always",
endOfLine: "lf"
};

View File

@ -1,23 +1,23 @@
import Command from '../core/command';
import { CommonLibrary, logs, botHasPermission, clean } from '../core/lib';
import { Config, Storage } from '../core/structures';
import { PermissionNames, getPermissionLevel } from '../core/permissions';
import { Permissions } from 'discord.js';
import * as discord from 'discord.js';
import Command from "../core/command";
import {CommonLibrary, logs, botHasPermission, clean} from "../core/lib";
import {Config, Storage} from "../core/structures";
import {PermissionNames, getPermissionLevel} from "../core/permissions";
import {Permissions} from "discord.js";
import * as discord from "discord.js";
function getLogBuffer(type: string) {
return {
files: [
{
attachment: Buffer.alloc(logs[type].length, logs[type]),
name: `${Date.now()}.${type}.log`,
},
],
name: `${Date.now()}.${type}.log`
}
]
};
}
const activities = ['playing', 'listening', 'streaming', 'watching'];
const statuses = ['online', 'idle', 'dnd', 'invisible'];
const activities = ["playing", "listening", "streaming", "watching"];
const statuses = ["online", "idle", "dnd", "invisible"];
export default new Command({
description:
@ -25,54 +25,56 @@ export default new Command({
async run($: CommonLibrary): Promise<any> {
if (!$.member)
return $.channel.send(
"Couldn't find a member object for you! Did you make sure you used this in a server?",
"Couldn't find a member object for you! Did you make sure you used this in a server?"
);
const permLevel = getPermissionLevel($.member);
$.channel.send(
`${$.author.toString()}, your permission level is \`${
PermissionNames[permLevel]
}\` (${permLevel}).`,
}\` (${permLevel}).`
);
},
subcommands: {
set: new Command({
description: 'Set different per-guild settings for the bot.',
run: 'You have to specify the option you want to set.',
description: "Set different per-guild settings for the bot.",
run: "You have to specify the option you want to set.",
permission: Command.PERMISSIONS.ADMIN,
subcommands: {
prefix: new Command({
description:
'Set a custom prefix for your guild. Removes your custom prefix if none is provided.',
usage: '(<prefix>)',
"Set a custom prefix for your guild. Removes your custom prefix if none is provided.",
usage: "(<prefix>)",
async run($: CommonLibrary): Promise<any> {
Storage.getGuild($.guild?.id || 'N/A').prefix = null;
Storage.getGuild($.guild?.id || "N/A").prefix = null;
Storage.save();
$.channel.send(
`The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.`,
`The custom prefix for this guild has been removed. My prefix is now back to \`${Config.prefix}\`.`
);
},
any: new Command({
async run($: CommonLibrary): Promise<any> {
Storage.getGuild($.guild?.id || 'N/A').prefix = $.args[0];
Storage.getGuild($.guild?.id || "N/A").prefix =
$.args[0];
Storage.save();
$.channel.send(
`The custom prefix for this guild is now \`${$.args[0]}\`.`,
`The custom prefix for this guild is now \`${$.args[0]}\`.`
);
},
}),
}),
},
}
})
})
}
}),
diag: new Command({
description: 'Requests a debug log with the "info" verbosity level.',
description:
'Requests a debug log with the "info" verbosity level.',
permission: Command.PERMISSIONS.BOT_SUPPORT,
async run($: CommonLibrary): Promise<any> {
$.channel.send(getLogBuffer('info'));
$.channel.send(getLogBuffer("info"));
},
any: new Command({
description: `Select a verbosity to listen to. Available levels: \`[${Object.keys(
logs,
).join(', ')}]\``,
logs
).join(", ")}]\``,
async run($: CommonLibrary): Promise<any> {
const type = $.args[0];
@ -80,21 +82,21 @@ export default new Command({
else
$.channel.send(
`Couldn't find a verbosity level named \`${type}\`! The available types are \`[${Object.keys(
logs,
).join(', ')}]\`.`,
logs
).join(", ")}]\`.`
);
},
}),
}
})
}),
status: new Command({
description: "Changes the bot's status.",
permission: Command.PERMISSIONS.BOT_SUPPORT,
async run($: CommonLibrary): Promise<any> {
$.channel.send('Setting status to `online`...');
$.channel.send("Setting status to `online`...");
},
any: new Command({
description: `Select a status to set to. Available statuses: \`[${statuses.join(
', ',
", "
)}]\`.`,
async run($: CommonLibrary): Promise<any> {
if (!statuses.includes($.args[0]))
@ -103,11 +105,11 @@ export default new Command({
$.client.user?.setStatus($.args[0]);
$.channel.send(`Setting status to \`${$.args[0]}\`...`);
}
},
}),
}
})
}),
purge: new Command({
description: 'Purges bot messages.',
description: "Purges bot messages.",
permission: Command.PERMISSIONS.BOT_SUPPORT,
async run($: CommonLibrary): Promise<any> {
if ($.message.channel instanceof discord.DMChannel) {
@ -115,21 +117,21 @@ export default new Command({
}
$.message.delete();
const msgs = await $.channel.messages.fetch({
limit: 100,
limit: 100
});
const travMessages = msgs.filter(
(m) => m.author.id === $.client.user?.id,
(m) => m.author.id === $.client.user?.id
);
await $.message.channel
.send(`Found ${travMessages.size} messages to delete.`)
.then((m) =>
m.delete({
timeout: 5000,
}),
timeout: 5000
})
);
await $.message.channel.bulkDelete(travMessages);
},
}
}),
clear: new Command({
description: "Clears a given amount of messages.",
@ -140,89 +142,96 @@ export default new Command({
async run($: CommonLibrary): Promise<any> {
$.message.delete();
const fetched = await $.channel.messages.fetch({
limit: $.args[0],
limit: $.args[0]
});
$.channel
/// @ts-ignore
$.channel.bulkDelete(fetched)
.catch((error: any) => $.channel.send(`Error: ${error}`));
.bulkDelete(fetched)
.catch((error: any) =>
$.channel.send(`Error: ${error}`)
);
}
})
}),
eval: new Command({
description: 'Evaluate code.',
usage: '<code>',
description: "Evaluate code.",
usage: "<code>",
permission: Command.PERMISSIONS.BOT_OWNER,
async run($: CommonLibrary): Promise<any> {
try {
const code = $.args.join(' ');
const code = $.args.join(" ");
let evaled = eval(code);
if (typeof evaled !== 'string')
evaled = require('util').inspect(evaled);
$.channel.send(clean(evaled), { code: 'x1' });
if (typeof evaled !== "string")
evaled = require("util").inspect(evaled);
$.channel.send(clean(evaled), {code: "x1"});
} catch (err) {
$.channel.send(`\`ERROR\` \`\`\`x1\n${clean(err)}\n\`\`\``);
}
},
}
}),
nick: new Command({
description: "Change the bot's nickname.",
permission: Command.PERMISSIONS.BOT_SUPPORT,
async run($: CommonLibrary): Promise<any> {
const nickName = $.args.join(' ');
const nickName = $.args.join(" ");
const trav = $.guild?.members.cache.find(
(member) => member.id === $.client.user?.id,
(member) => member.id === $.client.user?.id
);
await trav?.setNickname(nickName);
if (botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES))
if (
botHasPermission($.guild, Permissions.FLAGS.MANAGE_MESSAGES)
)
$.message.delete({timeout: 5000}).catch($.handler.bind($));
$.channel
.send(`Nickname set to \`${nickName}\``)
.then((m) => m.delete({timeout: 5000}));
},
}
}),
guilds: new Command({
description: 'Shows a list of all guilds the bot is a member of.',
description: "Shows a list of all guilds the bot is a member of.",
permission: Command.PERMISSIONS.BOT_SUPPORT,
async run($: CommonLibrary): Promise<any> {
const guildList = $.client.guilds.cache.array().map((e) => e.name);
const guildList = $.client.guilds.cache
.array()
.map((e) => e.name);
$.channel.send(guildList);
},
}
}),
activity: new Command({
description: 'Set the activity of the bot.',
description: "Set the activity of the bot.",
permission: Command.PERMISSIONS.BOT_SUPPORT,
usage: '<type> <string>',
usage: "<type> <string>",
async run($: CommonLibrary): Promise<any> {
$.client.user?.setActivity('.help', {
type: 'LISTENING',
$.client.user?.setActivity(".help", {
type: "LISTENING"
});
$.channel.send('Activity set to default.');
$.channel.send("Activity set to default.");
},
any: new Command({
description: `Select an activity type to set. Available levels: \`[${activities.join(
', ',
", "
)}]\``,
async run($: CommonLibrary): Promise<any> {
const type = $.args[0];
if (activities.includes(type)) {
$.client.user?.setActivity($.args.slice(1).join(' '), {
type: $.args[0].toUpperCase(),
$.client.user?.setActivity($.args.slice(1).join(" "), {
type: $.args[0].toUpperCase()
});
$.channel.send(
`Set activity to \`${$.args[0].toUpperCase()}\` \`${$.args
.slice(1)
.join(' ')}\`.`,
.join(" ")}\`.`
);
} else
$.channel.send(
`Couldn't find an activity type named \`${type}\`! The available types are \`[${activities.join(
', ',
)}]\`.`,
", "
)}]\`.`
);
},
}),
}),
},
}
})
})
}
});

View File

@ -1,38 +1,39 @@
import Command from '../../core/command';
import { CommonLibrary } from '../../core/lib';
import Command from "../../core/command";
import {CommonLibrary} from "../../core/lib";
const responses = [
'Most likely,',
'It is certain,',
'It is decidedly so,',
'Without a doubt,',
'Definitely,',
'You may rely on it,',
'As I see it, yes,',
'Outlook good,',
'Yes,',
'Signs point to yes,',
'Reply hazy, try again,',
'Ask again later,',
'Better not tell you now,',
'Cannot predict now,',
'Concentrate and ask again,',
"Most likely,",
"It is certain,",
"It is decidedly so,",
"Without a doubt,",
"Definitely,",
"You may rely on it,",
"As I see it, yes,",
"Outlook good,",
"Yes,",
"Signs point to yes,",
"Reply hazy, try again,",
"Ask again later,",
"Better not tell you now,",
"Cannot predict now,",
"Concentrate and ask again,",
"Don't count on it,",
'My reply is no,',
'My sources say no,',
'Outlook not so good,',
'Very doubtful,',
"My reply is no,",
"My sources say no,",
"Outlook not so good,",
"Very doubtful,"
];
export default new Command({
description: 'Answers your question in an 8-ball manner.',
description: "Answers your question in an 8-ball manner.",
endpoint: false,
usage: '<question>',
run: 'Please provide a question.',
usage: "<question>",
run: "Please provide a question.",
any: new Command({
description: 'Question to ask the 8-ball.',
description: "Question to ask the 8-ball.",
async run($: CommonLibrary): Promise<any> {
const sender = $.message.author;
$.channel.send($(responses).random() + ` <@${sender.id}>`);
},
}),
}
})
});

View File

@ -1,5 +1,5 @@
import Command from '../../core/command';
import { CommonLibrary } from '../../core/lib';
import Command from "../../core/command";
import {CommonLibrary} from "../../core/lib";
export default new Command({
description: "Gives specified user a cookie.",
@ -7,7 +7,8 @@ export default new Command({
run: ":cookie: Here's a cookie!",
any: new Command({
async run($: CommonLibrary): Promise<any> {
if ($.args[0] == "all") return $.channel.send(`${$.author} gave everybody a cookie!`)
if ($.args[0] == "all")
return $.channel.send(`${$.author} gave everybody a cookie!`);
}
}),
user: new Command({
@ -15,7 +16,9 @@ export default new Command({
async run($: CommonLibrary): Promise<any> {
const sender = $.author;
const mention = $.message.mentions.users.first();
if (!mention) return;
const cookies = [
`has given <@${mention.id}> a chocolate chip cookie!`,
`has given <@${mention.id}> a soft homemade oatmeal cookie!`,
@ -36,15 +39,16 @@ export default new Command({
`gives <@${mention.id}> an Oreo cookie with a glass of milk!`,
`gives <@${mention.id}> a rainbow cookie made with love :heart:`,
`gives <@${mention.id}> an old cookie that was left out in the rain, it's moldy.`,
`bakes <@${mention.id}> fresh cookies, it smells amazing.`,
`bakes <@${mention.id}> fresh cookies, it smells amazing.`
];
if (mention.id == sender.id)
return $.channel.send("You can't give yourself cookies!");
$.channel.send(
`:cookie: <@${sender.id}> ` +
cookies[Math.floor(Math.random() * cookies.length)],
cookies[Math.floor(Math.random() * cookies.length)]
);
}
})
})
});

View File

@ -1,11 +1,10 @@
import Command from '../../core/command';
import { isAuthorized, getMoneyEmbed } from './subcommands/eco-utils';
import { DailyCommand, PayCommand, GuildCommand } from './subcommands/eco-core';
import { BuyCommand, ShopCommand } from './subcommands/eco-shop';
import Command from "../../core/command";
import {isAuthorized, getMoneyEmbed} from "./subcommands/eco-utils";
import {DailyCommand, PayCommand, GuildCommand} from "./subcommands/eco-core";
import {BuyCommand, ShopCommand} from "./subcommands/eco-shop";
export default new Command({
description: 'Economy command for Monika.',
description: "Economy command for Monika.",
async run({guild, channel, author}) {
if (isAuthorized(guild, channel)) channel.send(getMoneyEmbed(author));
},
@ -14,22 +13,24 @@ export default new Command({
pay: PayCommand,
guild: GuildCommand,
buy: BuyCommand,
shop: ShopCommand,
shop: ShopCommand
},
user: new Command({
description:
'See how much money someone else has by using their user ID or pinging them.',
"See how much money someone else has by using their user ID or pinging them.",
async run({guild, channel, args}) {
if (isAuthorized(guild, channel)) channel.send(getMoneyEmbed(args[0]));
},
if (isAuthorized(guild, channel))
channel.send(getMoneyEmbed(args[0]));
}
}),
any: new Command({
description: 'See how much money someone else has by using their username.',
description:
"See how much money someone else has by using their username.",
async run({guild, channel, args, callMemberByUsername, message}) {
if (isAuthorized(guild, channel))
callMemberByUsername(message, args.join(' '), (member) => {
callMemberByUsername(message, args.join(" "), (member) => {
channel.send(getMoneyEmbed(member.user));
});
},
}),
}
})
});

View File

@ -1,26 +1,31 @@
/// @ts-nocheck
import { URL } from 'url'
import FileManager from '../../core/storage';
import Command from '../../core/command';
import { CommonLibrary, getContent } from '../../core/lib';
import {URL} from "url";
import FileManager from "../../core/storage";
import Command from "../../core/command";
import {CommonLibrary, getContent} from "../../core/lib";
const endpoints = FileManager.read('endpoints');
const endpoints = FileManager.read("endpoints");
export default new Command({
description: 'Provides you with a random image with the selected argument.',
description: "Provides you with a random image with the selected argument.",
async run($: CommonLibrary): Promise<any> {
console.log(endpoints.sfw)
$.channel.send(`Please provide an image type. Available arguments:\n\`[${Object.keys(endpoints.sfw).join(', ')}]\`.`)
console.log(endpoints.sfw);
$.channel.send(
`Please provide an image type. Available arguments:\n\`[${Object.keys(
endpoints.sfw
).join(", ")}]\`.`
);
},
any: new Command({
description: "Image type to send.",
async run($: CommonLibrary): Promise<any> {
if (!($.args[0] in endpoints.sfw))
return $.channel.send("Couldn't find that endpoint!");
let baseURL = 'https://nekos.life/api/v2';
let baseURL = "https://nekos.life/api/v2";
let url = new URL(`${baseURL}${endpoints.sfw[$.args[0]]}`);
const content = await getContent(url.toString())
$.channel.send(content.url)
},
const content = await getContent(url.toString());
$.channel.send(content.url);
}
})
});

View File

@ -1,68 +1,69 @@
import Command from '../../core/command';
import { CommonLibrary } from '../../core/lib';
import Command from "../../core/command";
import {CommonLibrary} from "../../core/lib";
export default new Command({
description: "Sends random ok message.",
async run($: CommonLibrary): Promise<any> {
const responses = [
'boomer',
'zoomer',
'the last generationer',
'the last airbender',
'fire nation',
'fire lord',
'guy fieri',
'guy from final fight',
'haggar',
'Max Thunder from Streets of Rage 2',
'police guy who fires bazookas',
'Mr. X',
'Leon Its Wrong If Its Not Ada Wong S. Kennedy.',
'Jill',
'JFK',
'george bush',
'obama',
'the world',
'copy of scott pilgrim vs the world',
'ok',
'ko',
'Hot Daddy Venomous',
'big daddy',
'John Cena',
'BubbleSpurJarJarBinks',
'T-Series',
'pewdiepie',
'markiplier',
'jacksepticeye',
'vanossgaming',
'miniladd',
'Traves',
'Wilbur Soot',
'sootrhianna',
'person with tiny ears',
'anti-rabbit',
'homo sapiens',
'homo',
'cute kitty',
'ugly kitty',
'sadness',
'doomer',
'gloomer',
'bloomer',
'edgelord',
'weeb',
"boomer",
"zoomer",
"the last generationer",
"the last airbender",
"fire nation",
"fire lord",
"guy fieri",
"guy from final fight",
"haggar",
"Max Thunder from Streets of Rage 2",
"police guy who fires bazookas",
"Mr. X",
"Leon Its Wrong If Its Not Ada Wong S. Kennedy.",
"Jill",
"JFK",
"george bush",
"obama",
"the world",
"copy of scott pilgrim vs the world",
"ok",
"ko",
"Hot Daddy Venomous",
"big daddy",
"John Cena",
"BubbleSpurJarJarBinks",
"T-Series",
"pewdiepie",
"markiplier",
"jacksepticeye",
"vanossgaming",
"miniladd",
"Traves",
"Wilbur Soot",
"sootrhianna",
"person with tiny ears",
"anti-rabbit",
"homo sapiens",
"homo",
"cute kitty",
"ugly kitty",
"sadness",
"doomer",
"gloomer",
"bloomer",
"edgelord",
"weeb",
"m'lady",
'Mr. Crabs',
'hand',
'lahoma',
'big man',
'fox',
'pear',
'cat',
'large man',
"Mr. Crabs",
"hand",
"lahoma",
"big man",
"fox",
"pear",
"cat",
"large man"
];
$.channel.send(
'ok ' + responses[Math.floor(Math.random() * responses.length)],
"ok " + responses[Math.floor(Math.random() * responses.length)]
);
}
})
});

View File

@ -1,12 +1,14 @@
/// @ts-nocheck
import Command from '../../core/command';
import { CommonLibrary, getContent } from '../../core/lib';
import Command from "../../core/command";
import {CommonLibrary, getContent} from "../../core/lib";
export default new Command({
description: 'OwO-ifies the input.',
description: "OwO-ifies the input.",
async run($: CommonLibrary): Promise<any> {
let url = new URL(`https://nekos.life/api/v2/owoify?text=${$.args.join(' ')}`);
let url = new URL(
`https://nekos.life/api/v2/owoify?text=${$.args.join(" ")}`
);
const content = await getContent(url.toString());
$.channel.send(content.owo);
},
}
});

View File

@ -1,28 +1,28 @@
import { MessageEmbed } from 'discord.js';
import Command from '../../core/command';
import { CommonLibrary } from '../../core/lib';
import {MessageEmbed} from "discord.js";
import Command from "../../core/command";
import {CommonLibrary} from "../../core/lib";
export default new Command({
description: 'Create a poll.',
usage: '<question>',
run: 'Please provide a question.',
description: "Create a poll.",
usage: "<question>",
run: "Please provide a question.",
any: new Command({
description: 'Question for the poll.',
description: "Question for the poll.",
async run($: CommonLibrary): Promise<any> {
const embed = new MessageEmbed()
.setAuthor(
`Poll created by ${$.message.author.username}`,
$.message.guild?.iconURL({ dynamic: true }) ?? undefined,
$.message.guild?.iconURL({dynamic: true}) ?? undefined
)
.setColor(0xffffff)
.setFooter('React to vote.')
.setDescription($.args.join(' '));
.setFooter("React to vote.")
.setDescription($.args.join(" "));
const msg = await $.channel.send(embed);
await msg.react('✅');
await msg.react('⛔');
await msg.react("✅");
await msg.react("⛔");
$.message.delete({
timeout: 1000,
timeout: 1000
});
},
}),
}
})
});

View File

@ -1,11 +1,11 @@
import Command from '../../../core/command';
import $ from '../../../core/lib';
import { Storage } from '../../../core/structures';
import { isAuthorized, getMoneyEmbed, getSendEmbed } from './eco-utils';
import Command from "../../../core/command";
import $ from "../../../core/lib";
import {Storage} from "../../../core/structures";
import {isAuthorized, getMoneyEmbed, getSendEmbed} from "./eco-utils";
export const DailyCommand = new Command({
description:
'Pick up your daily Mons. The cooldown is per user and every 22 hours to allow for some leeway.',
"Pick up your daily Mons. The cooldown is per user and every 22 hours to allow for some leeway.",
async run({author, channel, guild}) {
if (isAuthorized(guild, channel)) {
const user = Storage.getUser(author.id);
@ -17,28 +17,28 @@ export const DailyCommand = new Command({
Storage.save();
channel.send({
embed: {
title: 'Daily Reward',
description: 'You received 1 Mon!',
color: 0xf1c40f,
},
title: "Daily Reward",
description: "You received 1 Mon!",
color: 0xf1c40f
}
});
} else
channel.send({
embed: {
title: 'Daily Reward',
title: "Daily Reward",
description: `It's too soon to pick up your daily credits. You have about ${(
(user.lastReceived + 79200000 - now) /
3600000
).toFixed(1)} hours to go.`,
color: 0xf1c40f,
},
color: 0xf1c40f
}
});
}
},
}
});
export const GuildCommand = new Command({
description: 'See the richest players.',
description: "See the richest players.",
async run({guild, channel, client}) {
if (isAuthorized(guild, channel)) {
const users = Storage.users;
@ -52,25 +52,25 @@ export const GuildCommand = new Command({
fields.push({
name: `#${i + 1}. ${user.username}#${user.discriminator}`,
value: $(users[id].money).pluralise('credit', 's'),
value: $(users[id].money).pluralise("credit", "s")
});
}
channel.send({
embed: {
title: 'Top 10 Richest Players',
color: '#ffff00',
fields: fields,
},
title: "Top 10 Richest Players",
color: "#ffff00",
fields: fields
}
});
}
},
}
});
export const PayCommand = new Command({
description: 'Send money to someone.',
usage: '<user> <amount>',
run: 'Who are you sending this money to?',
description: "Send money to someone.",
usage: "<user> <amount>",
run: "Who are you sending this money to?",
user: new Command({
run: "You need to enter an amount you're sending!",
number: new Command({
@ -82,15 +82,15 @@ export const PayCommand = new Command({
const receiver = Storage.getUser(target.id);
if (amount <= 0)
return channel.send('You must send at least one Mon!');
return channel.send("You must send at least one Mon!");
else if (sender.money < amount)
return channel.send(
"You don't have enough Mons for that.",
getMoneyEmbed(author),
getMoneyEmbed(author)
);
else if (target.id === author.id)
return channel.send("You can't send Mons to yourself!");
else if (target.bot && process.argv[2] !== 'dev')
else if (target.bot && process.argv[2] !== "dev")
return channel.send("You can't send Mons to a bot!");
sender.money -= amount;
@ -98,11 +98,11 @@ export const PayCommand = new Command({
Storage.save();
return channel.send(getSendEmbed(author, target, amount));
}
},
}),
}
})
}),
number: new Command({
run: 'You must use the format `money send <user> <amount>`!',
run: "You must use the format `money send <user> <amount>`!"
}),
any: new Command({
async run({args, author, channel, guild, prompt}) {
@ -110,38 +110,40 @@ export const PayCommand = new Command({
const last = args.pop();
if (!/\d+/g.test(last) && args.length === 0)
return channel.send("You need to enter an amount you're sending!");
return channel.send(
"You need to enter an amount you're sending!"
);
const amount = Math.floor(last);
const sender = Storage.getUser(author.id);
if (amount <= 0)
return channel.send('You must send at least one credit!');
return channel.send("You must send at least one credit!");
else if (sender.money < amount)
return channel.send(
"You don't have enough money to do that!",
getMoneyEmbed(author),
getMoneyEmbed(author)
);
else if (!guild)
return channel.send(
'You have to use this in a server if you want to send money with a username!',
"You have to use this in a server if you want to send money with a username!"
);
const username = args.join(' ');
const username = args.join(" ");
const member = (
await guild.members.fetch({
query: username,
limit: 1,
limit: 1
})
).first();
if (!member)
return channel.send(
`Couldn't find a user by the name of \`${username}\`! If you want to send money to someone in a different server, you have to use their user ID!`,
`Couldn't find a user by the name of \`${username}\`! If you want to send money to someone in a different server, you have to use their user ID!`
);
else if (member.user.id === author.id)
return channel.send("You can't send money to yourself!");
else if (member.user.bot && process.argv[2] !== 'dev')
else if (member.user.bot && process.argv[2] !== "dev")
return channel.send("You can't send money to a bot!");
const target = member.user;
@ -149,21 +151,21 @@ export const PayCommand = new Command({
return prompt(
await channel.send(
`Are you sure you want to send ${$(amount).pluralise(
'credit',
's',
"credit",
"s"
)} to this person?\n*(This message will automatically be deleted after 10 seconds.)*`,
{
embed: {
color: '#ffff00',
color: "#ffff00",
author: {
name: `${target.username}#${target.discriminator}`,
icon_url: target.displayAvatarURL({
format: 'png',
dynamic: true,
}),
},
},
},
format: "png",
dynamic: true
})
}
}
}
),
author.id,
() => {
@ -172,9 +174,9 @@ export const PayCommand = new Command({
receiver.money += amount;
Storage.save();
channel.send(getSendEmbed(author, target, amount));
},
}
);
}
},
}),
}
})
});

View File

@ -1,5 +1,5 @@
import { Message } from 'discord.js';
import $ from '../../../core/lib';
import {Message} from "discord.js";
import $ from "../../../core/lib";
export interface ShopItem {
cost: number;
@ -12,62 +12,62 @@ export interface ShopItem {
export const ShopItems: ShopItem[] = [
{
cost: 1,
title: 'Hug',
description: 'Hug Monika.',
usage: 'hug',
title: "Hug",
description: "Hug Monika.",
usage: "hug",
run(message, cost) {
message.channel.send(
`Transaction of ${cost} Mon completed successfully. <@394808963356688394>`,
`Transaction of ${cost} Mon completed successfully. <@394808963356688394>`
);
},
}
},
{
cost: 2,
title: 'Handholding',
title: "Handholding",
description: "Hold Monika's hand.",
usage: 'handhold',
usage: "handhold",
run(message, cost) {
message.channel.send(
`Transaction of ${cost} Mons completed successfully. <@394808963356688394>`,
`Transaction of ${cost} Mons completed successfully. <@394808963356688394>`
);
},
}
},
{
cost: 1,
title: 'Cute',
description: 'Calls Monika cute.',
usage: 'cute',
title: "Cute",
description: "Calls Monika cute.",
usage: "cute",
run(message) {
message.channel.send('<:MoniCheeseBlushRed:637513137083383826>');
},
message.channel.send("<:MoniCheeseBlushRed:637513137083383826>");
}
},
{
cost: 3,
title: 'Laser Bridge',
description: 'Buys what is technically a laser bridge.',
usage: 'laser bridge',
title: "Laser Bridge",
description: "Buys what is technically a laser bridge.",
usage: "laser bridge",
run(message) {
message.channel.send($(lines).random(), {
files: [
{
attachment:
'https://raw.githubusercontent.com/keanuplayz/TravBot/dev/assets/TheUltimateLaser.gif',
},
],
"https://raw.githubusercontent.com/keanuplayz/TravBot/dev/assets/TheUltimateLaser.gif"
}
]
});
},
},
}
}
];
const lines = [
"It's technically a laser bridge. No refunds.",
'You want a laser bridge? You got one!',
"You want a laser bridge? You got one!",
"Now what'd they say about building bridges... Oh wait, looks like I nuked the planet again. Whoops!",
'I saw this redhead the other day who was so excited to buy what I was selling. Needless to say, she was not very happy with me afterward.',
"I saw this redhead the other day who was so excited to buy what I was selling. Needless to say, she was not very happy with me afterward.",
"Sorry, but you'll have to wait until the Laser Bridge Builder leaves early access.",
'Thank you for your purchase! For you see, this is the legendary laser of obliteration that has been defended and preserved for countless generations!',
'They say that a certain troll dwells under this laser bridge, waiting for an unlucky person to fall for th- I mean- Thank you for your purchase!',
"Thank you for your purchase! For you see, this is the legendary laser of obliteration that has been defended and preserved for countless generations!",
"They say that a certain troll dwells under this laser bridge, waiting for an unlucky person to fall for th- I mean- Thank you for your purchase!",
"Buy?! Hah! How about our new rental service for just under $9.99 a month? But wait, there's more! For just $99.99, you can rent this laser bridge for an entire year and save 16.67% as opposed to renting it monthly!",
'Good choice. Owning a laser bridge is the penultimate experience that all true seekers strive for!',
'I can already imagine the reviews...\n"9/10 needs more lasers"',
"Good choice. Owning a laser bridge is the penultimate experience that all true seekers strive for!",
'I can already imagine the reviews...\n"9/10 needs more lasers"'
];

View File

@ -1,15 +1,15 @@
import Command from '../../../core/command';
import $ from '../../../core/lib';
import { Storage, getPrefix } from '../../../core/structures';
import { isAuthorized, getMoneyEmbed, getSendEmbed } from './eco-utils';
import { ShopItems, ShopItem } from './eco-shop-items';
import { EmbedField } from 'discord.js';
import Command from "../../../core/command";
import $ from "../../../core/lib";
import {Storage, getPrefix} from "../../../core/structures";
import {isAuthorized} from "./eco-utils";
import {ShopItems, ShopItem} from "./eco-shop-items";
import {EmbedField} from "discord.js";
export const ShopCommand = new Command({
description: 'Displays the list of items you can buy in the shop.',
description: "Displays the list of items you can buy in the shop.",
async run({guild, channel, author}) {
if (isAuthorized(guild, channel)) {
function getShopEmbed(selection: ShopItem[], title = 'Shop') {
function getShopEmbed(selection: ShopItem[], title = "Shop") {
const fields: EmbedField[] = [];
for (const item of selection)
@ -17,11 +17,10 @@ export const ShopCommand = new Command({
name: `**${item.title}** (${getPrefix(guild)}eco buy ${
item.usage
})`,
value: `${item.description} Costs ${$(item.cost).pluralise(
'Mon',
's',
)}.`,
inline: false,
value: `${item.description} Costs ${$(
item.cost
).pluralise("Mon", "s")}.`,
inline: false
});
return {
@ -30,9 +29,9 @@ export const ShopCommand = new Command({
title: title,
fields: fields,
footer: {
text: 'Mon Shop | TravBot Services',
},
},
text: "Mon Shop | TravBot Services"
}
}
};
}
@ -42,25 +41,25 @@ export const ShopCommand = new Command({
const shopPages = $(ShopItems).split(5);
const pageAmount = shopPages.length;
const msg = await channel.send(
getShopEmbed(shopPages[0], `Shop (Page 1 of ${pageAmount})`),
getShopEmbed(shopPages[0], `Shop (Page 1 of ${pageAmount})`)
);
$.paginate(msg, author.id, pageAmount, (page) => {
msg.edit(
getShopEmbed(
shopPages[page],
`Shop (Page ${page + 1} of ${pageAmount})`,
),
`Shop (Page ${page + 1} of ${pageAmount})`
)
);
});
}
}
},
}
});
export const BuyCommand = new Command({
description: 'Buys an item from the shop.',
usage: '<item>',
description: "Buys an item from the shop.",
usage: "<item>",
async run({guild, channel, args, message, author}) {
if (isAuthorized(guild, channel)) {
let found = false;
@ -71,7 +70,7 @@ export const BuyCommand = new Command({
//if (/\d+/g.test(args[args.length - 1]))
//amount = parseInt(args.pop());
let requested = args.join(' '); // The item the user is buying.
let requested = args.join(" "); // The item the user is buying.
for (let item of ShopItems) {
if (item.usage === requested) {
@ -79,7 +78,7 @@ export const BuyCommand = new Command({
const cost = item.cost * amount;
if (cost > user.money) {
channel.send('Not enough Mons!');
channel.send("Not enough Mons!");
} else {
user.money -= cost;
Storage.save();
@ -93,8 +92,8 @@ export const BuyCommand = new Command({
if (!found)
channel.send(
`There's no item in the shop that goes by \`${requested}\`!`,
`There's no item in the shop that goes by \`${requested}\`!`
);
}
},
}
});

View File

@ -1,6 +1,6 @@
import $ from '../../../core/lib';
import { Storage } from '../../../core/structures';
import { User, Guild, TextChannel, DMChannel, NewsChannel } from 'discord.js';
import $ from "../../../core/lib";
import {Storage} from "../../../core/structures";
import {User, Guild, TextChannel, DMChannel, NewsChannel} from "discord.js";
export function getMoneyEmbed(user: User): object {
const profile = Storage.getUser(user.id);
@ -11,24 +11,24 @@ export function getMoneyEmbed(user: User): object {
author: {
name: user.username,
icon_url: user.displayAvatarURL({
format: 'png',
dynamic: true,
}),
format: "png",
dynamic: true
})
},
fields: [
{
name: 'Balance',
value: $(profile.money).pluralise('credit', 's'),
},
],
},
name: "Balance",
value: $(profile.money).pluralise("credit", "s")
}
]
}
};
}
export function getSendEmbed(
sender: User,
receiver: User,
amount: number,
amount: number
): object {
return {
embed: {
@ -36,45 +36,51 @@ export function getSendEmbed(
author: {
name: sender.username,
icon_url: sender.displayAvatarURL({
format: 'png',
dynamic: true,
}),
format: "png",
dynamic: true
})
},
title: 'Transaction',
title: "Transaction",
description: `${sender.toString()} has sent ${$(amount).pluralise(
'credit',
's',
"credit",
"s"
)} to ${receiver.toString()}!`,
fields: [
{
name: `Sender: ${sender.username}#${sender.discriminator}`,
value: $(Storage.getUser(sender.id).money).pluralise('credit', 's'),
value: $(Storage.getUser(sender.id).money).pluralise(
"credit",
"s"
)
},
{
name: `Receiver: ${receiver.username}#${receiver.discriminator}`,
value: $(Storage.getUser(receiver.id).money).pluralise('credit', 's'),
},
value: $(Storage.getUser(receiver.id).money).pluralise(
"credit",
"s"
)
}
],
footer: {
text: receiver.username,
icon_url: receiver.displayAvatarURL({
format: 'png',
dynamic: true,
}),
},
},
format: "png",
dynamic: true
})
}
}
};
}
export function isAuthorized(
guild: Guild | null,
channel: TextChannel | DMChannel | NewsChannel,
channel: TextChannel | DMChannel | NewsChannel
): boolean {
if (guild?.id === '637512823676600330' || process.argv[2] === 'dev')
if (guild?.id === "637512823676600330" || process.argv[2] === "dev")
return true;
else {
channel.send(
"Sorry, this command can only be used in Monika's emote server.",
"Sorry, this command can only be used in Monika's emote server."
);
return false;
}

View File

@ -1,13 +1,13 @@
import Command from '../core/command';
import { CommonLibrary } from '../core/lib';
import { loadCommands, categories } from '../core/command';
import { PermissionNames } from '../core/permissions';
import Command from "../core/command";
import {CommonLibrary} from "../core/lib";
import {loadCommands, categories} from "../core/command";
import {PermissionNames} from "../core/permissions";
export default new Command({
description:
'Lists all commands. If a command is specified, their arguments are listed as well.',
usage: '([command, [subcommand/type], ...])',
aliases: ['h'],
"Lists all commands. If a command is specified, their arguments are listed as well.",
usage: "([command, [subcommand/type], ...])",
aliases: ["h"],
async run($: CommonLibrary): Promise<any> {
const commands = await loadCommands();
let output = `Legend: \`<type>\`, \`[list/of/stuff]\`, \`(optional)\`, \`(<optional type>)\`, \`([optional/list/...])\``;
@ -16,12 +16,12 @@ export default new Command({
output += `\n\n===[ ${category} ]===`;
for (const header of headers) {
if (header !== 'test') {
if (header !== "test") {
const command = commands.get(header);
if (!command)
return $.warn(
`Command "${header}" of category "${category}" unexpectedly doesn't exist!`,
`Command "${header}" of category "${category}" unexpectedly doesn't exist!`
);
output += `\n- \`${header}\`: ${command.description}`;
@ -37,23 +37,26 @@ export default new Command({
let header = $.args.shift() as string;
let command = commands.get(header);
if (!command || header === 'test')
return $.channel.send(`No command found by the name \`${header}\`!`);
if (!command || header === "test")
return $.channel.send(
`No command found by the name \`${header}\`!`
);
if (command.originalCommandName) header = command.originalCommandName;
if (command.originalCommandName)
header = command.originalCommandName;
else $.warn(`originalCommandName isn't defined for ${header}?!`);
let permLevel = command.permission ?? Command.PERMISSIONS.NONE;
let usage = command.usage;
let invalid = false;
let selectedCategory = 'Unknown';
let selectedCategory = "Unknown";
for (const [category, headers] of categories) {
if (headers.includes(header)) {
if (selectedCategory !== 'Unknown')
if (selectedCategory !== "Unknown")
$.warn(
`Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.`,
`Command "${header}" is somehow in multiple categories. This means that the command loading stage probably failed in properly adding categories.`
);
else selectedCategory = category;
}
@ -69,13 +72,13 @@ export default new Command({
header += ` ${command.originalCommandName}`;
break;
case Command.TYPES.USER:
header += ' <user>';
header += " <user>";
break;
case Command.TYPES.NUMBER:
header += ' <number>';
header += " <number>";
break;
case Command.TYPES.ANY:
header += ' <any>';
header += " <any>";
break;
default:
header += ` ${param}`;
@ -89,57 +92,62 @@ export default new Command({
}
if (invalid)
return $.channel.send(`No command found by the name \`${header}\`!`);
return $.channel.send(
`No command found by the name \`${header}\`!`
);
let append = '';
let append = "";
if (usage === '') {
if (usage === "") {
const list: string[] = [];
command.subcommands.forEach((subcmd, subtag) => {
// Don't capture duplicates generated from aliases.
if (subcmd.originalCommandName === subtag) {
const customUsage = subcmd.usage ? ` ${subcmd.usage}` : '';
const customUsage = subcmd.usage
? ` ${subcmd.usage}`
: "";
list.push(
`- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}`,
`- \`${header} ${subtag}${customUsage}\` - ${subcmd.description}`
);
}
});
const addDynamicType = (cmd: Command | null, type: string) => {
if (cmd) {
const customUsage = cmd.usage ? ` ${cmd.usage}` : '';
const customUsage = cmd.usage ? ` ${cmd.usage}` : "";
list.push(
`- \`${header} <${type}>${customUsage}\` - ${cmd.description}`,
`- \`${header} <${type}>${customUsage}\` - ${cmd.description}`
);
}
};
addDynamicType(command.user, 'user');
addDynamicType(command.number, 'number');
addDynamicType(command.any, 'any');
addDynamicType(command.user, "user");
addDynamicType(command.number, "number");
addDynamicType(command.any, "any");
append =
'Usages:' + (list.length > 0 ? `\n${list.join('\n')}` : ' None.');
"Usages:" +
(list.length > 0 ? `\n${list.join("\n")}` : " None.");
} else append = `Usage: \`${header} ${usage}\``;
let aliases = 'None';
let aliases = "None";
if (command.aliases.length > 0) {
aliases = '';
aliases = "";
for (let i = 0; i < command.aliases.length; i++) {
const alias = command.aliases[i];
aliases += `\`${alias}\``;
if (i !== command.aliases.length - 1) aliases += ', ';
if (i !== command.aliases.length - 1) aliases += ", ";
}
}
$.channel.send(
`Command: \`${header}\`\nAliases: ${aliases}\nCategory: \`${selectedCategory}\`\nPermission Required: \`${PermissionNames[permLevel]}\` (${permLevel})\nDescription: ${command.description}\n${append}`,
{ split: true },
{split: true}
);
},
}),
}
})
});

View File

@ -1,80 +1,94 @@
import { MessageEmbed, version as djsversion } from 'discord.js';
import {MessageEmbed, version as djsversion} from "discord.js";
/// @ts-ignore
import { version } from '../../package.json';
import ms from 'ms';
import os from 'os';
import Command from '../core/command';
import { CommonLibrary, formatBytes, trimArray } from '../core/lib';
import { verificationLevels, filterLevels, regions, flags } from '../defs/info';
import moment from 'moment';
import utc from 'moment';
import {version} from "../../package.json";
import ms from "ms";
import os from "os";
import Command from "../core/command";
import {CommonLibrary, formatBytes, trimArray} from "../core/lib";
import {verificationLevels, filterLevels, regions, flags} from "../defs/info";
import moment from "moment";
import utc from "moment";
export default new Command({
description:
'Command to provide all sorts of info about the current server, a user, etc.',
run: 'Please provide an argument.\nFor help, run `%prefix%help info`.',
"Command to provide all sorts of info about the current server, a user, etc.",
run: "Please provide an argument.\nFor help, run `%prefix%help info`.",
subcommands: {
avatar: new Command({
description: "Shows your own, or another user's avatar.",
usage: '(<user>)',
usage: "(<user>)",
async run($: CommonLibrary): Promise<any> {
$.channel.send(
$.author.displayAvatarURL({ dynamic: true, size: 2048 }),
$.author.displayAvatarURL({dynamic: true, size: 2048})
);
},
user: new Command({
description: "Shows your own, or another user's avatar.",
async run($: CommonLibrary): Promise<any> {
$.channel.send(
$.args[0].displayAvatarURL({ dynamic: true, size: 2048 }),
$.args[0].displayAvatarURL({
dynamic: true,
size: 2048
})
);
},
}
})
}),
}),
bot: new Command({
description: 'Displays info about the bot.',
description: "Displays info about the bot.",
async run($: CommonLibrary): Promise<any> {
const core = os.cpus()[0];
const embed = new MessageEmbed()
.setThumbnail(
/// @ts-ignore
$.client.user?.displayAvatarURL({ dynamic: true, size: 2048 }),
$.client.user?.displayAvatarURL({
dynamic: true,
size: 2048
})
)
.setColor($.guild?.me?.displayHexColor || 'BLUE')
.addField('General', [
.setColor($.guild?.me?.displayHexColor || "BLUE")
.addField("General", [
`** Client:** ${$.client.user?.tag} (${$.client.user?.id})`,
`** Servers:** ${$.client.guilds.cache.size.toLocaleString()}`,
`** Users:** ${$.client.guilds.cache
.reduce((a: any, b: { memberCount: any }) => a + b.memberCount, 0)
.reduce(
(a: any, b: {memberCount: any}) =>
a + b.memberCount,
0
)
.toLocaleString()}`,
`** Channels:** ${$.client.channels.cache.size.toLocaleString()}`,
`** Creation Date:** ${utc($.client.user?.createdTimestamp).format(
'Do MMMM YYYY HH:mm:ss',
)}`,
`** Creation Date:** ${utc(
$.client.user?.createdTimestamp
).format("Do MMMM YYYY HH:mm:ss")}`,
`** Node.JS:** ${process.version}`,
`** Version:** v${version}`,
`** Discord.JS:** ${djsversion}`,
'\u200b',
"\u200b"
])
.addField('System', [
.addField("System", [
`** Platform:** ${process.platform}`,
`** Uptime:** ${ms(os.uptime() * 1000, { long: true })}`,
`** Uptime:** ${ms(os.uptime() * 1000, {
long: true
})}`,
`** CPU:**`,
`\u3000 • Cores: ${os.cpus().length}`,
`\u3000 • Model: ${core.model}`,
`\u3000 • Speed: ${core.speed}MHz`,
`** Memory:**`,
`\u3000 • Total: ${formatBytes(process.memoryUsage().heapTotal)}`,
`\u3000 • Used: ${formatBytes(process.memoryUsage().heapTotal)}`,
`\u3000 • Total: ${formatBytes(
process.memoryUsage().heapTotal
)}`,
`\u3000 • Used: ${formatBytes(
process.memoryUsage().heapTotal
)}`
])
.setTimestamp();
$.channel.send(embed);
},
}
}),
guild: new Command({
description: 'Displays info about the current guild.',
description: "Displays info about the current guild.",
async run($: CommonLibrary): Promise<any> {
if ($.guild) {
const roles = $.guild.roles.cache
@ -83,113 +97,136 @@ export default new Command({
const members = $.guild.members.cache;
const channels = $.guild.channels.cache;
const emojis = $.guild.emojis.cache;
const iconURL = $.guild.iconURL({dynamic: true});
const embed = new MessageEmbed()
.setDescription(`**Guild information for __${$.guild.name}__**`)
.setColor('BLUE');
.setDescription(
`**Guild information for __${$.guild.name}__**`
)
.setColor("BLUE");
if (iconURL)
embed
.setThumbnail(iconURL)
.addField('General', [
.addField("General", [
`** Name:** ${$.guild.name}`,
`** ID:** ${$.guild.id}`,
`** Owner:** ${$.guild.owner?.user.tag} (${$.guild.ownerID})`,
`** Region:** ${regions[$.guild.region]}`,
`** Boost Tier:** ${
$.guild.premiumTier ? `Tier ${$.guild.premiumTier}` : 'None'
$.guild.premiumTier
? `Tier ${$.guild.premiumTier}`
: "None"
}`,
`** Explicit Filter:** ${
filterLevels[$.guild.explicitContentFilter]
}`,
`** Verification Level:** ${
verificationLevels[$.guild.verificationLevel]
verificationLevels[
$.guild.verificationLevel
]
}`,
`** Time Created:** ${moment($.guild.createdTimestamp).format(
'LT',
)} ${moment($.guild.createdTimestamp).format('LL')} ${moment(
$.guild.createdTimestamp,
`** Time Created:** ${moment(
$.guild.createdTimestamp
).format("LT")} ${moment(
$.guild.createdTimestamp
).format("LL")} ${moment(
$.guild.createdTimestamp
).fromNow()})`,
'\u200b',
"\u200b"
])
.addField('Statistics', [
.addField("Statistics", [
`** Role Count:** ${roles.length}`,
`** Emoji Count:** ${emojis.size}`,
`** Regular Emoji Count:** ${
emojis.filter((emoji) => !emoji.animated).size
emojis.filter((emoji) => !emoji.animated)
.size
}`,
`** Animated Emoji Count:** ${
emojis.filter((emoji) => emoji.animated).size
emojis.filter((emoji) => emoji.animated)
.size
}`,
`** Member Count:** ${$.guild.memberCount}`,
`** Humans:** ${
members.filter((member) => !member.user.bot).size
members.filter((member) => !member.user.bot)
.size
}`,
`** Bots:** ${
members.filter((member) => member.user.bot).size
members.filter((member) => member.user.bot)
.size
}`,
`** Text Channels:** ${
channels.filter((channel) => channel.type === 'text').size
channels.filter(
(channel) => channel.type === "text"
).size
}`,
`** Voice Channels:** ${
channels.filter((channel) => channel.type === 'voice').size
channels.filter(
(channel) => channel.type === "voice"
).size
}`,
`** Boost Count:** ${$.guild.premiumSubscriptionCount || '0'}`,
`\u200b`,
`** Boost Count:** ${
$.guild.premiumSubscriptionCount || "0"
}`,
`\u200b`
])
.addField('Presence', [
.addField("Presence", [
`** Online:** ${
members.filter(
(member) => member.presence.status === 'online',
(member) =>
member.presence.status === "online"
).size
}`,
`** Idle:** ${
members.filter((member) => member.presence.status === 'idle')
.size
members.filter(
(member) =>
member.presence.status === "idle"
).size
}`,
`** Do Not Disturb:** ${
members.filter((member) => member.presence.status === 'dnd')
.size
members.filter(
(member) =>
member.presence.status === "dnd"
).size
}`,
`** Offline:** ${
members.filter(
(member) => member.presence.status === 'offline',
(member) =>
member.presence.status === "offline"
).size
}`,
'\u200b',
"\u200b"
])
.addField(
`Roles [${roles.length - 1}]`,
roles.length < 10
? roles.join(', ')
? roles.join(", ")
: roles.length > 10
? trimArray(roles)
: 'None',
: "None"
)
.setTimestamp();
$.channel.send(embed);
} else {
$.channel.send('Please execute this command in a guild.');
$.channel.send("Please execute this command in a guild.");
}
},
}),
}
})
},
user: new Command({
description: 'Displays info about mentioned user.',
description: "Displays info about mentioned user.",
async run($: CommonLibrary): Promise<any> {
// Transforms the User object into a GuildMember object of the current guild.
const member = $.guild?.members.resolve($.args[0]);
if (!member)
return $.channel.send(
'No member object was found by that user! Are you sure you used this command in a server?',
"No member object was found by that user! Are you sure you used this command in a server?"
);
const roles = member.roles.cache
.sort(
(a: {position: number}, b: {position: number}) =>
b.position - a.position,
b.position - a.position
)
.map((role: {toString: () => any}) => role.toString())
.slice(0, -1);
@ -198,46 +235,54 @@ export default new Command({
const embed = new MessageEmbed()
.setThumbnail(
member.user.displayAvatarURL({ dynamic: true, size: 512 }),
member.user.displayAvatarURL({dynamic: true, size: 512})
)
.setColor(member.displayHexColor || 'BLUE')
.addField('User', [
.setColor(member.displayHexColor || "BLUE")
.addField("User", [
`** Username:** ${member.user.username}`,
`** Discriminator:** ${member.user.discriminator}`,
`** ID:** ${member.id}`,
`** Flags:** ${userFlags.length ? userFlags.join(', ') : 'None'}`,
`** Avatar:** [Link to avatar](${member.user.displayAvatarURL({
dynamic: true,
})})`,
`** Time Created:** ${moment(member.user.createdTimestamp).format(
'LT',
)} ${moment(member.user.createdTimestamp).format('LL')} ${moment(
member.user.createdTimestamp,
`** Flags:** ${
userFlags.length ? userFlags.join(", ") : "None"
}`,
`** Avatar:** [Link to avatar](${member.user.displayAvatarURL(
{
dynamic: true
}
)})`,
`** Time Created:** ${moment(
member.user.createdTimestamp
).format("LT")} ${moment(
member.user.createdTimestamp
).format("LL")} ${moment(
member.user.createdTimestamp
).fromNow()}`,
`** Status:** ${member.user.presence.status}`,
`** Game:** ${
member.user.presence.activities || 'Not playing a game.'
}`,
member.user.presence.activities || "Not playing a game."
}`
])
.addField('Member', [
.addField("Member", [
`** Highest Role:** ${
member.roles.highest.id === $.guild?.id
? 'None'
? "None"
: member.roles.highest.name
}`,
`** Server Join Date:** ${moment(member.joinedAt).format('LL LTS')}`,
`** Server Join Date:** ${moment(member.joinedAt).format(
"LL LTS"
)}`,
`** Hoist Role:** ${
member.roles.hoist ? member.roles.hoist.name : 'None'
member.roles.hoist ? member.roles.hoist.name : "None"
}`,
`** Roles:** [${roles.length}]: ${
roles.length < 10
? roles.join(', ')
? roles.join(", ")
: roles.length > 10
? this.client.utils.trimArray(roles)
: 'None'
}`,
: "None"
}`
]);
$.channel.send(embed);
},
}),
}
})
});

View File

@ -1,13 +1,13 @@
import Command from '../core/command';
import { CommonLibrary } from '../core/lib';
import moment from 'moment';
import { Collection, TextChannel } from 'discord.js';
import Command from "../core/command";
import {CommonLibrary} from "../core/lib";
import moment from "moment";
import {Collection, TextChannel} from "discord.js";
const lastUsedTimestamps: {[id: string]: number} = {};
export default new Command({
description:
'Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.',
"Scans all text channels in the current guild and returns the number of times each emoji specific to the guild has been used. Has a cooldown of 24 hours per guild.",
async run($: CommonLibrary): Promise<any> {
if (!$.guild)
return $.channel.send(`You must use this command on a server!`);
@ -22,7 +22,7 @@ export default new Command({
// If it's been less than an hour since the command was last used, prevent it from executing.
if (difference < cooldown)
return $.channel.send(
`This command requires a day to cooldown. You'll be able to activate this command ${howLong}.`,
`This command requires a day to cooldown. You'll be able to activate this command ${howLong}.`
);
else lastUsedTimestamps[$.guild.id] = startTime;
@ -37,13 +37,13 @@ export default new Command({
let totalUserEmoteUsage = 0;
// IMPORTANT: You MUST check if the bot actually has access to the channel in the first place. It will get the list of all channels, but that doesn't mean it has access to every channel. Without this, it'll require admin access and throw an annoying unhelpful DiscordAPIError: Missing Access otherwise.
const allTextChannelsInCurrentGuild = $.guild.channels.cache.filter(
(channel) => channel.type === 'text' && channel.viewable,
(channel) => channel.type === "text" && channel.viewable
) as Collection<string, TextChannel>;
let messagesSearched = 0;
let channelsSearched = 0;
let currentChannelName = '';
let currentChannelName = "";
const totalChannels = allTextChannelsInCurrentGuild.size;
const statusMessage = await $.channel.send('Gathering emotes...');
const statusMessage = await $.channel.send("Gathering emotes...");
let warnings = 0;
$.channel.startTyping();
@ -53,15 +53,17 @@ export default new Command({
// If you don't include the "a" for animated emotes, it'll not only not show up, but also cause all other emotes in the same message to not show up. The emote name is self-correcting but it's better to keep the right value since it'll be used to calculate message lengths that fit.
stats[emote.id] = {
name: emote.name,
formatted: `<${emote.animated ? 'a' : ''}:${emote.name}:${emote.id}>`,
formatted: `<${emote.animated ? "a" : ""}:${emote.name}:${
emote.id
}>`,
users: 0,
bots: 0,
bots: 0
};
}
const interval = setInterval(() => {
statusMessage.edit(
`Searching channel \`${currentChannelName}\`... (${messagesSearched} messages scanned, ${channelsSearched}/${totalChannels} channels scanned)`,
`Searching channel \`${currentChannelName}\`... (${messagesSearched} messages scanned, ${channelsSearched}/${totalChannels} channels scanned)`
);
}, 5000);
@ -74,7 +76,7 @@ export default new Command({
// Unfortunately, any kind of .fetch call is limited to 100 items at once by Discord's API.
const messages = await channel.messages.fetch({
limit: 100,
before: selected,
before: selected
});
if (messages.size > 0) {
@ -111,7 +113,7 @@ export default new Command({
// After logging users, it seems like the order is strictly numerical. As long as that stays consistent, this should work fine.
const users = await reaction.users.fetch({
limit: 100,
after: lastUserID,
after: lastUserID
});
if (users.size > 0) {
@ -131,9 +133,12 @@ export default new Command({
// Then halt the loop and send warnings of any inconsistencies.
continueReactionLoop = false;
if (reaction.count !== userReactions + botReactions) {
if (
reaction.count !==
userReactions + botReactions
) {
$.warn(
`[Channel: ${channel.id}, Message: ${msg.id}] A reaction count of ${reaction.count} was expected but was given ${userReactions} user reactions and ${botReactions} bot reactions.`,
`[Channel: ${channel.id}, Message: ${msg.id}] A reaction count of ${reaction.count} was expected but was given ${userReactions} user reactions and ${botReactions} bot reactions.`
);
warnings++;
}
@ -159,10 +164,10 @@ export default new Command({
`Finished operation in ${moment
.duration(finishTime - startTime)
.humanize()} with ${$(warnings).pluralise(
'inconsistenc',
'ies',
'y',
)}.`,
"inconsistenc",
"ies",
"y"
)}.`
);
$.log(`Finished operation in ${finishTime - startTime} ms.`);
$.channel.stopTyping();
@ -170,7 +175,7 @@ export default new Command({
// Display stats on emote usage.
// This can work outside the loop now that it's synchronous, and now it's clearer what code is meant to execute at the end.
let sortedEmoteIDs = Object.keys(stats).sort(
(a, b) => stats[b].users - stats[a].users,
(a, b) => stats[b].users - stats[a].users
);
const lines: string[] = [];
let rank = 1;
@ -178,14 +183,14 @@ export default new Command({
// It's better to send all the lines at once rather than paginate the data because it's quite a memory-intensive task to search all the messages in a server for it, and I wouldn't want to activate the command again just to get to another page.
for (const emoteID of sortedEmoteIDs) {
const emote = stats[emoteID];
const botInfo = emote.bots > 0 ? ` (Bots: ${emote.bots})` : '';
const botInfo = emote.bots > 0 ? ` (Bots: ${emote.bots})` : "";
lines.push(
`\`#${rank++}\` ${emote.formatted} x ${emote.users} - ${(
(emote.users / totalUserEmoteUsage) * 100 || 0
).toFixed(3)}%` + botInfo,
).toFixed(3)}%` + botInfo
);
}
$.channel.send(lines, {split: true}).catch($.handler.bind($));
},
}
});

View File

@ -1,29 +1,31 @@
import Command from '../../core/command';
import { CommonLibrary } from '../../core/lib';
import Command from "../../core/command";
import {CommonLibrary} from "../../core/lib";
export default new Command({
description: "Renames current voice channel.",
usage: "<name>",
async run($: CommonLibrary): Promise<any> {
const voiceChannel = $.message.member?.voice.channel;
if (!voiceChannel)
return $.channel.send('You are not in a voice channel.');
if (!$.guild?.me?.hasPermission('MANAGE_CHANNELS'))
return $.channel.send("You are not in a voice channel.");
if (!$.guild?.me?.hasPermission("MANAGE_CHANNELS"))
return $.channel.send(
'I am lacking the required permissions to perform this action.',
"I am lacking the required permissions to perform this action."
);
if ($.args.length === 0)
return $.channel.send(
'Please provide a new voice channel name.',
);
return $.channel.send("Please provide a new voice channel name.");
const changeVC = $.guild.channels.resolve(voiceChannel.id);
$.channel
.send(
`Changed channel name from "${voiceChannel}" to "${$.args.join(
' ',
)}".`,
" "
)}".`
)
/// @ts-ignore
.then(changeVC?.setName($.args.join(' ')));
.then(changeVC?.setName($.args.join(" ")));
}
})
});

View File

@ -1,21 +1,21 @@
import { MessageEmbed } from 'discord.js';
import Command from '../../core/command';
import { CommonLibrary } from '../../core/lib';
import {MessageEmbed} from "discord.js";
import Command from "../../core/command";
import {CommonLibrary} from "../../core/lib";
export default new Command({
description: 'Send the specified emote.',
run: 'Please provide a command name.',
description: "Send the specified emote.",
run: "Please provide a command name.",
any: new Command({
description: 'The emote to send.',
usage: '<emote>',
description: "The emote to send.",
usage: "<emote>",
async run($: CommonLibrary): Promise<any> {
const search = $.args[0].toLowerCase();
const emote = $.client.emojis.cache.find((emote) =>
emote.name.toLowerCase().includes(search),
emote.name.toLowerCase().includes(search)
);
if (!emote) return $.channel.send("That's not a valid emote name!");
$.message.delete();
$.channel.send(`${emote}`);
},
}),
}
})
});

View File

@ -1,6 +1,6 @@
import { MessageEmbed } from 'discord.js';
import Command from '../../core/command';
import { CommonLibrary } from '../../core/lib';
import {MessageEmbed} from "discord.js";
import Command from "../../core/command";
import {CommonLibrary} from "../../core/lib";
export default new Command({
description: "Lists all emotes the bot has in it's registry,",
@ -12,21 +12,25 @@ export default new Command({
.array();
const pagesSplit = $(pages).split(20);
$.log(pagesSplit);
var embed = new MessageEmbed().setTitle('**Emoji list!**').setColor('AQUA');
let desc = '';
var embed = new MessageEmbed()
.setTitle("**Emoji list!**")
.setColor("AQUA");
let desc = "";
for (const emote of pagesSplit[0]) {
desc += `${emote} | ${emote.name}\n`;
}
embed.setDescription(desc);
const msg = await $.channel.send({embed});
$.paginate(msg, $.author.id, pages.length, (page) => {
let desc = '';
let desc = "";
for (const emote of pagesSplit[page]) {
desc += `${emote} | ${emote.name}\n`;
}
embed.setDescription(desc);
msg.edit(embed);
});
},
}
});

View File

@ -1,10 +1,10 @@
import Command from '../../core/command';
import { CommonLibrary } from '../../core/lib';
import Command from "../../core/command";
import {CommonLibrary} from "../../core/lib";
export default new Command({
description:
'Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.',
usage: 'react <emote name> (<message ID / distance>)',
"Reacts to the a previous message in your place. You have to react with the same emote before the bot removes that reaction.",
usage: "react <emote name> (<message ID / distance>)",
async run($: CommonLibrary): Promise<any> {
let target;
let distance = 1;
@ -17,9 +17,10 @@ export default new Command({
target = await $.channel.messages.fetch(last);
} catch {
return $.channel.send(
`No valid message found by the ID \`${last}\`!`,
`No valid message found by the ID \`${last}\`!`
);
}
$.args.pop();
}
// The entire string has to be a number for this to match. Prevents leaCheeseAmerican1 from triggering this.
@ -27,7 +28,10 @@ export default new Command({
distance = parseInt(last);
if (distance >= 0 && distance <= 99) $.args.pop();
else return $.channel.send('Your distance must be between 0 and 99!');
else
return $.channel.send(
"Your distance must be between 0 and 99!"
);
}
}
@ -36,7 +40,7 @@ export default new Command({
// You also have to add 1 as well because fetchMessages includes your own message.
target = (
await $.message.channel.messages.fetch({
limit: distance + 1,
limit: distance + 1
})
).last();
}
@ -45,7 +49,7 @@ export default new Command({
for (const search of $.args) {
const emoji = $.client.emojis.cache.find(
(emoji) => emoji.name === search,
(emoji) => emoji.name === search
);
if (emoji) {
@ -63,6 +67,6 @@ export default new Command({
}
}
if (!anyEmoteIsValid && !$.message.deleted) $.message.react('❓');
},
if (!anyEmoteIsValid && !$.message.deleted) $.message.react("❓");
}
});

View File

@ -1,5 +1,5 @@
import Command from '../../core/command';
import { CommonLibrary } from '../../core/lib';
import Command from "../../core/command";
import {CommonLibrary} from "../../core/lib";
export default new Command({
description: "Repeats your message.",
@ -8,7 +8,7 @@ export default new Command({
any: new Command({
description: "Message to repeat.",
async run($: CommonLibrary): Promise<any> {
$.channel.send(`*${$.author} says:*\n${$.args.join(' ')}`);
$.channel.send(`*${$.author} says:*\n${$.args.join(" ")}`);
}
})
})
});

View File

@ -1,25 +1,25 @@
import Command from '../../core/command';
import { CommonLibrary } from '../../core/lib';
import * as https from 'https';
import Command from "../../core/command";
import {CommonLibrary} from "../../core/lib";
import * as https from "https";
export default new Command({
description: 'Shortens a given URL.',
run: 'Please provide a URL.',
description: "Shortens a given URL.",
run: "Please provide a URL.",
any: new Command({
async run($: CommonLibrary): Promise<any> {
https.get(
'https://is.gd/create.php?format=simple&url=' +
"https://is.gd/create.php?format=simple&url=" +
encodeURIComponent($.args[0]),
function (res) {
var body = '';
res.on('data', function (chunk) {
var body = "";
res.on("data", function (chunk) {
body += chunk;
});
res.on('end', function () {
res.on("end", function () {
$.channel.send(`<${body}>`);
});
},
}
);
},
}),
}
})
});

View File

@ -1,9 +1,9 @@
import $, { isType, parseVars, CommonLibrary } from './lib';
import { Collection } from 'discord.js';
import { generateHandler } from './storage';
import { promises as ffs, existsSync, writeFile } from 'fs';
import { PERMISSIONS } from './permissions';
import { getPrefix } from '../core/structures';
import $, {isType, parseVars, CommonLibrary} from "./lib";
import {Collection} from "discord.js";
import {generateHandler} from "./storage";
import {promises as ffs, existsSync, writeFile} from "fs";
import {PERMISSIONS} from "./permissions";
import {getPrefix} from "../core/structures";
interface CommandOptions {
description?: string;
@ -23,7 +23,7 @@ export enum TYPES {
USER,
NUMBER,
ANY,
NONE,
NONE
}
export default class Command {
@ -42,13 +42,13 @@ export default class Command {
public static readonly PERMISSIONS = PERMISSIONS;
constructor(options?: CommandOptions) {
this.description = options?.description || 'No description.';
this.description = options?.description || "No description.";
this.endpoint = options?.endpoint || false;
this.usage = options?.usage || '';
this.usage = options?.usage || "";
this.permission = options?.permission ?? null;
this.aliases = options?.aliases ?? [];
this.originalCommandName = null;
this.run = options?.run || 'No action was set on this command!';
this.run = options?.run || "No action was set on this command!";
this.subcommands = new Collection(); // Populate this collection after setting subcommands.
this.user = options?.user || null;
this.number = options?.number || null;
@ -71,11 +71,11 @@ export default class Command {
for (const alias of aliases) {
if (baseSubcommands.includes(alias))
$.warn(
`"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)`,
`"${alias}" in subcommand "${name}" was attempted to be declared as an alias but it already exists in the base commands! (Look at the next "Loading Command" line to see which command is affected.)`
);
else if (this.subcommands.has(alias))
$.warn(
`Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)`,
`Duplicate alias "${alias}" at subcommand "${name}"! (Look at the next "Loading Command" line to see which command is affected.)`
);
else this.subcommands.set(alias, subcmd);
}
@ -84,15 +84,17 @@ export default class Command {
if (this.user && this.user.aliases.length > 0)
$.warn(
`There are aliases defined for a "user"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`,
`There are aliases defined for a "user"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`
);
if (this.number && this.number.aliases.length > 0)
$.warn(
`There are aliases defined for a "number"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`,
`There are aliases defined for a "number"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`
);
if (this.any && this.any.aliases.length > 0)
$.warn(
`There are aliases defined for an "any"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`,
`There are aliases defined for an "any"-type subcommand, but those aliases won't be used. (Look at the next "Loading Command" line to see which command is affected.)`
);
}
@ -103,10 +105,10 @@ export default class Command {
this.run as string,
{
author: $.author.toString(),
prefix: getPrefix($.guild),
prefix: getPrefix($.guild)
},
'???',
),
"???"
)
);
} else (this.run as Function)($).catch($.handler.bind($));
}
@ -118,8 +120,8 @@ export default class Command {
// Disallow infinity and allow for 0.
else if (
this.number &&
(Number(param) || param === '0') &&
!param.includes('Infinity')
(Number(param) || param === "0") &&
!param.includes("Infinity")
)
return TYPES.NUMBER;
else if (this.any) return TYPES.ANY;
@ -160,24 +162,24 @@ export const aliases: Collection<string, string> = new Collection(); // Top-leve
export async function loadCommands(): Promise<Collection<string, Command>> {
if (commands) return commands;
if (process.argv[2] === 'dev' && !existsSync('src/commands/test.ts'))
if (process.argv[2] === "dev" && !existsSync("src/commands/test.ts"))
writeFile(
'src/commands/test.ts',
"src/commands/test.ts",
template,
generateHandler(
'"test.ts" (testing/template command) successfully generated.',
),
'"test.ts" (testing/template command) successfully generated.'
)
);
commands = new Collection();
const dir = await ffs.opendir('dist/commands');
const dir = await ffs.opendir("dist/commands");
const listMisc: string[] = [];
let selected;
// There will only be one level of directory searching (per category).
while ((selected = await dir.read())) {
if (selected.isDirectory()) {
if (selected.name === 'subcommands') continue;
if (selected.name === "subcommands") continue;
const subdir = await ffs.opendir(`dist/commands/${selected.name}`);
const category = $(selected.name).toTitleCase();
@ -186,10 +188,10 @@ export async function loadCommands(): Promise<Collection<string, Command>> {
while ((cmd = await subdir.read())) {
if (cmd.isDirectory()) {
if (cmd.name === 'subcommands') continue;
if (cmd.name === "subcommands") continue;
else
$.warn(
`You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"`,
`You can't have multiple levels of directories! From: "dist/commands/${cmd.name}"`
);
} else loadCommand(cmd.name, list, selected.name);
}
@ -200,7 +202,7 @@ export async function loadCommands(): Promise<Collection<string, Command>> {
}
dir.close();
categories.set('Miscellaneous', listMisc);
categories.set("Miscellaneous", listMisc);
return commands;
}
@ -208,22 +210,21 @@ export async function loadCommands(): Promise<Collection<string, Command>> {
async function loadCommand(
filename: string,
list: string[],
category?: string,
category?: string
) {
if (!commands)
return $.error(
`Function "loadCommand" was called without first initializing commands!`,
`Function "loadCommand" was called without first initializing commands!`
);
const prefix = category ?? '';
const header = filename.substring(0, filename.indexOf('.js'));
const command = (await import(`../commands/${prefix}/${header}`)).default as
| Command
| undefined;
const prefix = category ?? "";
const header = filename.substring(0, filename.indexOf(".js"));
const command = (await import(`../commands/${prefix}/${header}`))
.default as Command | undefined;
if (!command)
return $.warn(
`Command "${header}" has no default export which is a Command instance!`,
`Command "${header}" has no default export which is a Command instance!`
);
command.originalCommandName = header;
@ -231,22 +232,22 @@ async function loadCommand(
if (commands.has(header))
$.warn(
`Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories!`,
`Command "${header}" already exists! Make sure to make each command uniquely identifiable across categories!`
);
else commands.set(header, command);
for (const alias of command.aliases) {
if (commands.has(alias))
$.warn(
`Top-level alias "${alias}" from command "${header}" already exists either as a command or alias!`,
`Top-level alias "${alias}" from command "${header}" already exists either as a command or alias!`
);
else commands.set(alias, command);
}
$.log(
`Loading Command: ${header} (${
category ? $(category).toTitleCase() : 'Miscellaneous'
})`,
category ? $(category).toTitleCase() : "Miscellaneous"
})`
);
}

View File

@ -1,6 +1,6 @@
import { Client, ClientEvents, Constants } from 'discord.js';
import Storage from './storage';
import $ from './lib';
import {Client, ClientEvents, Constants} from "discord.js";
import Storage from "./storage";
import $ from "./lib";
interface EventOptions<K extends keyof ClientEvents> {
readonly on?: (...args: ClientEvents[K]) => void;
@ -24,10 +24,10 @@ export default class Event<K extends keyof ClientEvents> {
}
export async function loadEvents(client: Client) {
for (const file of Storage.open('dist/events', (filename: string) =>
filename.endsWith('.js'),
for (const file of Storage.open("dist/events", (filename: string) =>
filename.endsWith(".js")
)) {
const header = file.substring(0, file.indexOf('.js'));
const header = file.substring(0, file.indexOf(".js"));
const event = (await import(`../events/${header}`)).default;
if ((Object.values(Constants.Events) as string[]).includes(header)) {
@ -35,7 +35,7 @@ export async function loadEvents(client: Client) {
$.log(`Loading Event: ${header}`);
} else
$.warn(
`"${header}" is not a valid event type! Did you misspell it? (Note: If you fixed the issue, delete "dist" because the compiler won't automatically delete any extra files.)`,
`"${header}" is not a valid event type! Did you misspell it? (Note: If you fixed the issue, delete "dist" because the compiler won't automatically delete any extra files.)`
);
}
}

View File

@ -2,8 +2,8 @@ import {
GenericWrapper,
NumberWrapper,
StringWrapper,
ArrayWrapper,
} from './wrappers';
ArrayWrapper
} from "./wrappers";
import {
Client,
Message,
@ -13,13 +13,13 @@ import {
Guild,
User,
GuildMember,
Permissions,
} from 'discord.js';
import chalk from 'chalk';
import { get } from 'https';
import FileManager from './storage';
import { eventListeners } from '../events/messageReactionRemove';
import { client } from '../index';
Permissions
} from "discord.js";
import chalk from "chalk";
import {get} from "https";
import FileManager from "./storage";
import {eventListeners} from "../events/messageReactionRemove";
import {client} from "../index";
/** A type that describes what the library module does. */
export interface CommonLibrary {
@ -43,22 +43,22 @@ export interface CommonLibrary {
senderID: string,
total: number,
callback: (page: number) => void,
duration?: number,
duration?: number
) => void;
prompt: (
message: Message,
senderID: string,
onConfirm: () => void,
duration?: number,
duration?: number
) => void;
getMemberByUsername: (
guild: Guild,
username: string,
username: string
) => Promise<GuildMember | undefined>;
callMemberByUsername: (
message: Message,
username: string,
onSuccess: (member: GuildMember) => void,
onSuccess: (member: GuildMember) => void
) => Promise<void>;
// Dynamic Properties //
@ -90,11 +90,11 @@ $.handler = function (this: CommonLibrary, error: Error) {
this.channel.send(
`There was an error while trying to execute that command!\`\`\`${
error.stack ?? error
}\`\`\``,
}\`\`\``
);
else
$.warn(
'No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!',
"No context was attached to $.handler! Make sure to use .catch($.handler.bind($)) or .catch(error => $.handler(error)) instead!"
);
$.error(error);
@ -102,13 +102,14 @@ $.handler = function (this: CommonLibrary, error: Error) {
// Logs with different levels of verbosity.
export const logs: {[type: string]: string} = {
error: '',
warn: '',
info: '',
verbose: '',
error: "",
warn: "",
info: "",
verbose: ""
};
let enabled = true;
export function setConsoleActivated(activated: boolean) {
enabled = activated;
}
@ -119,10 +120,11 @@ $.log = (...args: any[]) => {
if (enabled)
console.log(
chalk.white.bgGray(formatTimestamp()),
chalk.black.bgWhite('INFO'),
...args,
chalk.black.bgWhite("INFO"),
...args
);
const text = `[${formatUTCTimestamp()}] [INFO] ${args.join(' ')}\n`;
const text = `[${formatUTCTimestamp()}] [INFO] ${args.join(" ")}\n`;
logs.info += text;
logs.verbose += text;
};
@ -131,10 +133,11 @@ $.warn = (...args: any[]) => {
if (enabled)
console.warn(
chalk.white.bgGray(formatTimestamp()),
chalk.black.bgYellow('WARN'),
...args,
chalk.black.bgYellow("WARN"),
...args
);
const text = `[${formatUTCTimestamp()}] [WARN] ${args.join(' ')}\n`;
const text = `[${formatUTCTimestamp()}] [WARN] ${args.join(" ")}\n`;
logs.warn += text;
logs.info += text;
logs.verbose += text;
@ -144,10 +147,11 @@ $.error = (...args: any[]) => {
if (enabled)
console.error(
chalk.white.bgGray(formatTimestamp()),
chalk.white.bgRed('ERROR'),
...args,
chalk.white.bgRed("ERROR"),
...args
);
const text = `[${formatUTCTimestamp()}] [ERROR] ${args.join(' ')}\n`;
const text = `[${formatUTCTimestamp()}] [ERROR] ${args.join(" ")}\n`;
logs.error += text;
logs.warn += text;
logs.info += text;
@ -157,13 +161,14 @@ $.error = (...args: any[]) => {
// $.debug(`core/lib::parseArgs("testing \"in progress\"") = ["testing", "in progress"]`) --> <path>/::(<object>.)<function>(<args>) = <value>
// Would probably be more suited for debugging program logic rather than function logic, which can be checked using unit tests.
$.debug = (...args: any[]) => {
if (process.argv[2] === 'dev' && enabled)
if (process.argv[2] === "dev" && enabled)
console.debug(
chalk.white.bgGray(formatTimestamp()),
chalk.white.bgBlue('DEBUG'),
...args,
chalk.white.bgBlue("DEBUG"),
...args
);
const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(' ')}\n`;
const text = `[${formatUTCTimestamp()}] [DEBUG] ${args.join(" ")}\n`;
logs.verbose += text;
};
// Used once at the start of the program when the bot loads.
@ -171,37 +176,38 @@ $.ready = (...args: any[]) => {
if (enabled)
console.log(
chalk.white.bgGray(formatTimestamp()),
chalk.black.bgGreen('READY'),
...args,
chalk.black.bgGreen("READY"),
...args
);
const text = `[${formatUTCTimestamp()}] [READY] ${args.join(' ')}\n`;
const text = `[${formatUTCTimestamp()}] [READY] ${args.join(" ")}\n`;
logs.info += text;
logs.verbose += text;
};
export function formatTimestamp(now = new Date()) {
const year = now.getFullYear();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
const hour = now.getHours().toString().padStart(2, '0');
const minute = now.getMinutes().toString().padStart(2, '0');
const second = now.getSeconds().toString().padStart(2, '0');
const month = (now.getMonth() + 1).toString().padStart(2, "0");
const day = now.getDate().toString().padStart(2, "0");
const hour = now.getHours().toString().padStart(2, "0");
const minute = now.getMinutes().toString().padStart(2, "0");
const second = now.getSeconds().toString().padStart(2, "0");
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}
export function formatUTCTimestamp(now = new Date()) {
const year = now.getUTCFullYear();
const month = (now.getUTCMonth() + 1).toString().padStart(2, '0');
const day = now.getUTCDate().toString().padStart(2, '0');
const hour = now.getUTCHours().toString().padStart(2, '0');
const minute = now.getUTCMinutes().toString().padStart(2, '0');
const second = now.getUTCSeconds().toString().padStart(2, '0');
const month = (now.getUTCMonth() + 1).toString().padStart(2, "0");
const day = now.getUTCDate().toString().padStart(2, "0");
const hour = now.getUTCHours().toString().padStart(2, "0");
const minute = now.getUTCMinutes().toString().padStart(2, "0");
const second = now.getUTCSeconds().toString().padStart(2, "0");
return `${year}-${month}-${day} ${hour}:${minute}:${second}`;
}
export function botHasPermission(
guild: Guild | null,
permission: number,
permission: number
): boolean {
return !!(
client.user &&
@ -216,7 +222,7 @@ $.paginate = async (
senderID: string,
total: number,
callback: (page: number) => void,
duration = 60000,
duration = 60000
) => {
let page = 0;
const turn = (amount: number) => {
@ -229,18 +235,18 @@ $.paginate = async (
};
const handle = (emote: string, reacterID: string) => {
switch (emote) {
case '⬅️':
case "⬅️":
turn(-1);
break;
case '➡️':
case "➡️":
turn(1);
break;
}
};
// Listen for reactions and call the handler.
await message.react('⬅️');
await message.react('➡️');
await message.react("⬅️");
await message.react("➡️");
eventListeners.set(message.id, handle);
await message.awaitReactions(
(reaction, user) => {
@ -249,20 +255,21 @@ $.paginate = async (
// This will dynamically adjust for that, switching modes depending on whether it currently has the "Manage Messages" permission.
const canDeleteEmotes = botHasPermission(
message.guild,
Permissions.FLAGS.MANAGE_MESSAGES,
Permissions.FLAGS.MANAGE_MESSAGES
);
handle(reaction.emoji.name, user.id);
if (canDeleteEmotes) reaction.users.remove(user);
}
return false;
},
{ time: duration },
{time: duration}
);
// When time's up, remove the bot's own reactions.
eventListeners.delete(message.id);
message.reactions.cache.get('⬅️')?.users.remove(message.author);
message.reactions.cache.get('➡️')?.users.remove(message.author);
message.reactions.cache.get("⬅️")?.users.remove(message.author);
message.reactions.cache.get("➡️")?.users.remove(message.author);
};
// Waits for the sender to either confirm an action or let it pass (and delete the message).
@ -270,15 +277,15 @@ $.prompt = async (
message: Message,
senderID: string,
onConfirm: () => void,
duration = 10000,
duration = 10000
) => {
let isDeleted = false;
message.react('✅');
message.react("✅");
await message.awaitReactions(
(reaction, user) => {
if (user.id === senderID) {
if (reaction.emoji.name === '✅') onConfirm();
if (reaction.emoji.name === "✅") onConfirm();
isDeleted = true;
message.delete();
}
@ -289,7 +296,7 @@ $.prompt = async (
// May as well just set it to false because I'm not concerned with collecting any reactions.
return false;
},
{ time: duration },
{time: duration}
);
if (!isDeleted) message.delete();
@ -299,7 +306,7 @@ $.getMemberByUsername = async (guild: Guild, username: string) => {
return (
await guild.members.fetch({
query: username,
limit: 1,
limit: 1
})
).first();
};
@ -308,7 +315,7 @@ $.getMemberByUsername = async (guild: Guild, username: string) => {
$.callMemberByUsername = async (
message: Message,
username: string,
onSuccess: (member: GuildMember) => void,
onSuccess: (member: GuildMember) => void
) => {
const guild = message.guild;
const send = message.channel.send;
@ -318,7 +325,7 @@ $.callMemberByUsername = async (
if (member) onSuccess(member);
else send(`Couldn't find a user by the name of \`${username}\`!`);
} else send('You must execute this command in a server!');
} else send("You must execute this command in a server!");
};
/**
@ -328,21 +335,21 @@ $.callMemberByUsername = async (
*/
export function parseArgs(line: string): string[] {
let result = [];
let selection = '';
let selection = "";
let inString = false;
let isEscaped = false;
for (let c of line) {
if (isEscaped) {
if (['"', '\\'].includes(c)) selection += c;
else selection += '\\' + c;
if (['"', "\\"].includes(c)) selection += c;
else selection += "\\" + c;
isEscaped = false;
} else if (c === '\\') isEscaped = true;
} else if (c === "\\") isEscaped = true;
else if (c === '"') inString = !inString;
else if (c === ' ' && !inString) {
else if (c === " " && !inString) {
result.push(selection);
selection = '';
selection = "";
} else selection += c;
}
@ -360,22 +367,22 @@ export function parseArgs(line: string): string[] {
export function parseVars(
line: string,
definitions: {[key: string]: string},
invalid: string | null = '',
invalid: string | null = ""
): string {
let result = '';
let result = "";
let inVariable = false;
let token = '';
let token = "";
for (const c of line) {
if (c === '%') {
if (c === "%") {
if (inVariable) {
if (token === '') result += '%';
if (token === "") result += "%";
else {
if (token in definitions) result += definitions[token];
else if (invalid === null) result += `%${token}%`;
else result += invalid;
token = '';
token = "";
}
}
@ -391,7 +398,9 @@ export function isType(value: any, type: any): boolean {
if (value === undefined && type === undefined) return true;
else if (value === null && type === null) return true;
else
return value !== undefined && value !== null && value.constructor === type;
return (
value !== undefined && value !== null && value.constructor === type
);
}
/**
@ -404,7 +413,7 @@ export function select<T>(
value: any,
fallback: T,
type: Function,
isArray = false,
isArray = false
): T {
if (isArray && isType(value, Array)) {
for (let item of value) if (!isType(item, type)) return fallback;
@ -416,10 +425,10 @@ export function select<T>(
}
export function clean(text: any) {
if (typeof text === 'string')
if (typeof text === "string")
return text
.replace(/`/g, '`' + String.fromCharCode(8203))
.replace(/@/g, '@' + String.fromCharCode(8203));
.replace(/`/g, "`" + String.fromCharCode(8203))
.replace(/@/g, "@" + String.fromCharCode(8203));
else return text;
}
@ -433,8 +442,8 @@ export function trimArray(arr: any, maxLen = 10) {
}
export function formatBytes(bytes: any) {
if (bytes === 0) return '0 Bytes';
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
if (bytes === 0) return "0 Bytes";
const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${parseFloat((bytes / Math.pow(1024, i)).toFixed(2))} ${sizes[i]}`;
}
@ -454,12 +463,12 @@ export function getContent(url: any) {
res.resume();
reject(`Request failed. Status code: ${statusCode}`);
}
res.setEncoding('utf8');
let rawData = '';
res.on('data', (chunk: string) => {
res.setEncoding("utf8");
let rawData = "";
res.on("data", (chunk: string) => {
rawData += chunk;
});
res.on('end', () => {
res.on("end", () => {
try {
const parsedData = JSON.parse(rawData);
resolve(parsedData);
@ -467,8 +476,8 @@ export function getContent(url: any) {
reject(`Error: ${e.message}`);
}
});
},
).on('error', (err: { message: any }) => {
}
).on("error", (err: {message: any}) => {
reject(`Error: ${err.message}`);
});
});
@ -479,7 +488,7 @@ export interface GenericJSON {
}
export abstract class GenericStructure {
private __meta__ = 'generic';
private __meta__ = "generic";
constructor(tag?: string) {
this.__meta__ = tag || this.__meta__;
@ -502,5 +511,5 @@ export const Random = {
chance: (decimal: number) => Math.random() < decimal,
sign: (number = 1) => number * (Random.chance(0.5) ? -1 : 1),
deviation: (base: number, deviation: number) =>
Random.num(base - deviation, base + deviation),
Random.num(base - deviation, base + deviation)
};

View File

@ -1,6 +1,6 @@
import { GuildMember, Permissions } from 'discord.js';
import { Config } from './structures';
import $ from './lib';
import {GuildMember, Permissions} from "discord.js";
import {Config} from "./structures";
import $ from "./lib";
export enum PERMISSIONS {
NONE,
@ -9,16 +9,17 @@ export enum PERMISSIONS {
OWNER,
BOT_SUPPORT,
BOT_ADMIN,
BOT_OWNER,
BOT_OWNER
}
export const PermissionNames = [
'User',
'Moderator',
'Administrator',
'Server Owner',
'Bot Support',
'Bot Admin',
'Bot Owner',
"User",
"Moderator",
"Administrator",
"Server Owner",
"Bot Support",
"Bot Admin",
"Bot Owner"
];
// Here is where you enter in the functions that check for permissions.
@ -46,7 +47,7 @@ const PermissionChecker: ((member: GuildMember) => boolean)[] = [
(member) => Config.admins.includes(member.id),
// BOT_OWNER //
(member) => Config.owner === member.id,
(member) => Config.owner === member.id
];
// After checking the lengths of these three objects, use this as the length for consistency.
@ -54,7 +55,7 @@ const length = Object.keys(PERMISSIONS).length / 2;
export function hasPermission(
member: GuildMember,
permission: PERMISSIONS,
permission: PERMISSIONS
): boolean {
for (let i = length - 1; i >= permission; i--)
if (PermissionChecker[i](member)) return true;
@ -75,6 +76,6 @@ export function getPermissionLevel(member: GuildMember): number {
// By transitive property, lenNames and lenChecker have to be equal to each other as well.
if (length !== lenNames || length !== lenChecker)
$.error(
`Permission object lengths aren't equal! Enum Length (${length}), Names Length (${lenNames}), and Functions Length (${lenChecker}). This WILL cause problems!`,
`Permission object lengths aren't equal! Enum Length (${length}), Names Length (${lenNames}), and Functions Length (${lenChecker}). This WILL cause problems!`
);
})();

View File

@ -1,29 +1,29 @@
import fs from 'fs';
import $ from './lib';
import fs from "fs";
import $ from "./lib";
const Storage = {
read(header: string): object {
this.open('data');
this.open("data");
const path = `data/${header}.json`;
let data = {};
if (fs.existsSync(path)) {
const file = fs.readFileSync(path, 'utf-8');
const file = fs.readFileSync(path, "utf-8");
try {
data = JSON.parse(file);
} catch (error) {
if (process.argv[2] !== 'dev') {
if (process.argv[2] !== "dev") {
$.warn(
`Malformed JSON data (header: ${header}), backing it up.`,
file,
file
);
fs.writeFile(
`${path}.backup`,
file,
generateHandler(
`Backup file of "${header}" successfully written as ${file}.`,
),
`Backup file of "${header}" successfully written as ${file}.`
)
);
}
}
@ -32,17 +32,19 @@ const Storage = {
return data;
},
write(header: string, data: object, asynchronous = true) {
this.open('data');
this.open("data");
const path = `data/${header}.json`;
if (process.argv[2] === 'dev' || header === 'config') {
const result = JSON.stringify(data, null, '\t');
if (process.argv[2] === "dev" || header === "config") {
const result = JSON.stringify(data, null, "\t");
if (asynchronous)
fs.writeFile(
path,
result,
generateHandler(`"${header}" sucessfully spaced and written.`),
generateHandler(
`"${header}" sucessfully spaced and written.`
)
);
else fs.writeFileSync(path, result);
} else {
@ -52,14 +54,14 @@ const Storage = {
fs.writeFile(
path,
result,
generateHandler(`"${header}" sucessfully written.`),
generateHandler(`"${header}" sucessfully written.`)
);
else fs.writeFileSync(path, result);
}
},
open(
path: string,
filter?: (value: string, index: number, array: string[]) => unknown,
filter?: (value: string, index: number, array: string[]) => unknown
): string[] {
if (!fs.existsSync(path)) fs.mkdirSync(path);
@ -72,7 +74,7 @@ const Storage = {
close(path: string) {
if (fs.existsSync(path) && fs.readdirSync(path).length === 0)
fs.rmdir(path, generateHandler(`"${path}" successfully closed.`));
},
}
};
export function generateHandler(message: string) {

View File

@ -1,7 +1,7 @@
import FileManager from './storage';
import $, { select, GenericJSON, GenericStructure } from './lib';
import { watch } from 'fs';
import { Guild as DiscordGuild } from 'discord.js';
import FileManager from "./storage";
import $, {select, GenericJSON, GenericStructure} from "./lib";
import {watch} from "fs";
import {Guild as DiscordGuild} from "discord.js";
class ConfigStructure extends GenericStructure {
public token: string;
@ -11,10 +11,10 @@ class ConfigStructure extends GenericStructure {
public support: string[];
constructor(data: GenericJSON) {
super('config');
this.token = select(data.token, '<ENTER YOUR TOKEN HERE>', String);
this.prefix = select(data.prefix, '$', String);
this.owner = select(data.owner, '', String);
super("config");
this.token = select(data.token, "<ENTER YOUR TOKEN HERE>", String);
this.prefix = select(data.prefix, "$", String);
this.owner = select(data.owner, "", String);
this.admins = select(data.admins, [], String, true);
this.support = select(data.support, [], String, true);
}
@ -43,22 +43,24 @@ class StorageStructure extends GenericStructure {
public guilds: {[id: string]: Guild};
constructor(data: GenericJSON) {
super('storage');
super("storage");
this.users = {};
this.guilds = {};
for (let id in data.users)
if (/\d{17,19}/g.test(id)) this.users[id] = new User(data.users[id]);
if (/\d{17,19}/g.test(id))
this.users[id] = new User(data.users[id]);
for (let id in data.guilds)
if (/\d{17,19}/g.test(id)) this.guilds[id] = new Guild(data.guilds[id]);
if (/\d{17,19}/g.test(id))
this.guilds[id] = new Guild(data.guilds[id]);
}
/** Gets a user's profile if they exist and generate one if not. */
public getUser(id: string): User {
if (!/\d{17,19}/g.test(id))
$.warn(
`"${id}" is not a valid user ID! It will be erased when the data loads again.`,
`"${id}" is not a valid user ID! It will be erased when the data loads again.`
);
if (id in this.users) return this.users[id];
@ -73,7 +75,7 @@ class StorageStructure extends GenericStructure {
public getGuild(id: string): Guild {
if (!/\d{17,19}/g.test(id))
$.warn(
`"${id}" is not a valid guild ID! It will be erased when the data loads again.`,
`"${id}" is not a valid guild ID! It will be erased when the data loads again.`
);
if (id in this.guilds) return this.guilds[id];
@ -86,27 +88,27 @@ class StorageStructure extends GenericStructure {
}
// Exports instances. Don't worry, importing it from different files will load the same instance.
export let Config = new ConfigStructure(FileManager.read('config'));
export let Storage = new StorageStructure(FileManager.read('storage'));
export let Config = new ConfigStructure(FileManager.read("config"));
export let Storage = new StorageStructure(FileManager.read("storage"));
// This part will allow the user to manually edit any JSON files they want while the program is running which'll update the program's cache.
// However, fs.watch is a buggy mess that should be avoided in production. While it helps test out stuff for development, it's not a good idea to have it running outside of development as it causes all sorts of issues.
if (process.argv[2] === 'dev') {
watch('data', (event, filename) => {
$.debug('File Watcher:', event, filename);
const header = filename.substring(0, filename.indexOf('.json'));
if (process.argv[2] === "dev") {
watch("data", (event, filename) => {
$.debug("File Watcher:", event, filename);
const header = filename.substring(0, filename.indexOf(".json"));
switch (header) {
case 'config':
Config = new ConfigStructure(FileManager.read('config'));
case "config":
Config = new ConfigStructure(FileManager.read("config"));
break;
case 'storage':
Storage = new StorageStructure(FileManager.read('storage'));
case "storage":
Storage = new StorageStructure(FileManager.read("storage"));
break;
}
});
}
export function getPrefix(guild: DiscordGuild | null): string {
return Storage.getGuild(guild?.id || 'N/A').prefix ?? Config.prefix;
return Storage.getGuild(guild?.id || "N/A").prefix ?? Config.prefix;
}

View File

@ -15,11 +15,11 @@ export class NumberWrapper extends GenericWrapper<number> {
*/
public pluralise(
word: string,
plural = '',
singular = '',
excludeNumber = false,
plural = "",
singular = "",
excludeNumber = false
): string {
let result = excludeNumber ? '' : `${this.value} `;
let result = excludeNumber ? "" : `${this.value} `;
if (this.value === 1) result += word + singular;
else result += word + plural;
@ -35,12 +35,17 @@ export class NumberWrapper extends GenericWrapper<number> {
*/
public pluraliseSigned(
word: string,
plural = '',
singular = '',
excludeNumber = false,
plural = "",
singular = "",
excludeNumber = false
): string {
const sign = this.value >= 0 ? '+' : '';
return `${sign}${this.pluralise(word, plural, singular, excludeNumber)}`;
const sign = this.value >= 0 ? "+" : "";
return `${sign}${this.pluralise(
word,
plural,
singular,
excludeNumber
)}`;
}
}
@ -57,7 +62,7 @@ export class StringWrapper extends GenericWrapper<string> {
public toTitleCase(): string {
return this.value.replace(
/([^\W_]+[^\s-]*) */g,
(txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase(),
(txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()
);
}
}
@ -73,13 +78,15 @@ export class ArrayWrapper<T> extends GenericWrapper<T[]> {
* `$([1,2,3,4,5,6,7,8,9,10]).split(3)` = `[[1,2,3],[4,5,6],[7,8,9],[10]]`
*/
public split(lengthOfEachSection: number): T[][] {
const amountOfSections = Math.ceil(this.value.length / lengthOfEachSection);
const amountOfSections = Math.ceil(
this.value.length / lengthOfEachSection
);
const sections: T[][] = new Array(amountOfSections);
for (let index = 0; index < amountOfSections; index++)
sections[index] = this.value.slice(
index * lengthOfEachSection,
(index + 1) * lengthOfEachSection,
(index + 1) * lengthOfEachSection
);
return sections;

View File

@ -1,45 +1,47 @@
// Flags a user can have.
// They're basically your profile badges.
export const flags: {[index: string]: any} = {
DISCORD_EMPLOYEE: 'Discord Employee',
DISCORD_PARTNER: 'Discord Partner',
BUGHUNTER_LEVEL_1: 'Bug Hunter (Level 1)',
BUGHUNTER_LEVEL_2: 'Bug Hunter (Level 2)',
HYPESQUAD_EVENTS: 'HypeSquad Events',
HOUSE_BRAVERY: 'House of Bravery',
HOUSE_BRILLIANCE: 'House of Brilliance',
HOUSE_BALANCE: 'House of Balance',
EARLY_SUPPORTER: 'Early Supporter',
TEAM_USER: 'Team User',
SYSTEM: 'System',
VERIFIED_BOT: 'Verified Bot',
VERIFIED_DEVELOPER: 'Verified Bot Developer',
DISCORD_EMPLOYEE: "Discord Employee",
DISCORD_PARTNER: "Discord Partner",
BUGHUNTER_LEVEL_1: "Bug Hunter (Level 1)",
BUGHUNTER_LEVEL_2: "Bug Hunter (Level 2)",
HYPESQUAD_EVENTS: "HypeSquad Events",
HOUSE_BRAVERY: "House of Bravery",
HOUSE_BRILLIANCE: "House of Brilliance",
HOUSE_BALANCE: "House of Balance",
EARLY_SUPPORTER: "Early Supporter",
TEAM_USER: "Team User",
SYSTEM: "System",
VERIFIED_BOT: "Verified Bot",
VERIFIED_DEVELOPER: "Verified Bot Developer"
};
export const filterLevels: {[index: string]: any} = {
DISABLED: 'Off',
MEMBERS_WITHOUT_ROLES: 'No Role',
ALL_MEMBERS: 'Everyone',
DISABLED: "Off",
MEMBERS_WITHOUT_ROLES: "No Role",
ALL_MEMBERS: "Everyone"
};
export const verificationLevels: {[index: string]: any} = {
NONE: 'None',
LOW: 'Low',
MEDIUM: 'Medium',
HIGH: '(╯°□°)╯︵ ┻━┻',
VERY_HIGH: '┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻',
NONE: "None",
LOW: "Low",
MEDIUM: "Medium",
HIGH: "(╯°□°)╯︵ ┻━┻",
VERY_HIGH: "┻━┻ ミヽ(ಠ益ಠ)ノ彡┻━┻"
};
export const regions: {[index: string]: any} = {
brazil: 'Brazil',
europe: 'Europe',
hongkong: 'Hong Kong',
india: 'India',
japan: 'Japan',
russia: 'Russia',
singapore: 'Singapore',
southafrica: 'South Africa',
sydney: 'Sydney',
'us-central': 'US Central',
'us-east': 'US East',
'us-west': 'US West',
'us-south': 'US South',
brazil: "Brazil",
europe: "Europe",
hongkong: "Hong Kong",
india: "India",
japan: "Japan",
russia: "Russia",
singapore: "Singapore",
southafrica: "South Africa",
sydney: "Sydney",
"us-central": "US Central",
"us-east": "US East",
"us-west": "US West",
"us-south": "US South"
};

View File

@ -1,16 +1,16 @@
import Event from '../core/event';
import { client } from '../index';
import $ from '../core/lib';
import * as discord from 'discord.js';
import Event from "../core/event";
import {client} from "../index";
import $ from "../core/lib";
import * as discord from "discord.js";
export default new Event<'channelCreate'>({
export default new Event<"channelCreate">({
async on(channel) {
const botGuilds = client.guilds;
if (channel instanceof discord.GuildChannel) {
const createdGuild = await botGuilds.fetch(channel.guild.id);
$.log(
`Channel created in '${createdGuild.name}' called '#${channel.name}'`,
`Channel created in '${createdGuild.name}' called '#${channel.name}'`
);
}
},
}
});

View File

@ -1,16 +1,16 @@
import Event from '../core/event';
import { client } from '../index';
import $ from '../core/lib';
import * as discord from 'discord.js';
import Event from "../core/event";
import {client} from "../index";
import $ from "../core/lib";
import * as discord from "discord.js";
export default new Event<'channelDelete'>({
export default new Event<"channelDelete">({
async on(channel) {
const botGuilds = client.guilds;
if (channel instanceof discord.GuildChannel) {
const createdGuild = await botGuilds.fetch(channel.guild.id);
$.log(
`Channel deleted in '${createdGuild.name}' called '#${channel.name}'`,
`Channel deleted in '${createdGuild.name}' called '#${channel.name}'`
);
}
},
}
});

View File

@ -1,18 +1,18 @@
import Event from '../core/event';
import Command, { loadCommands } from '../core/command';
import Event from "../core/event";
import Command, {loadCommands} from "../core/command";
import {
hasPermission,
getPermissionLevel,
PermissionNames,
} from '../core/permissions';
import { Permissions, Collection } from 'discord.js';
import { getPrefix } from '../core/structures';
import $ from '../core/lib';
PermissionNames
} from "../core/permissions";
import {Permissions, Collection} from "discord.js";
import {getPrefix} from "../core/structures";
import $ from "../core/lib";
// It's a rather hacky solution, but since there's no top-level await, I just have to make the loading conditional.
let commands: Collection<string, Command> | null = null;
export default new Event<'message'>({
export default new Event<"message">({
async on(message) {
// Load commands if it hasn't already done so. Luckily, it's called once at most.
if (!commands) commands = await loadCommands();
@ -23,9 +23,12 @@ export default new Event<'message'>({
const prefix = getPrefix(message.guild);
if (!message.content.startsWith(prefix)) {
if (message.client.user && message.mentions.has(message.client.user))
if (
message.client.user &&
message.mentions.has(message.client.user)
)
message.channel.send(
`${message.author.toString()}, my prefix on this guild is \`${prefix}\`.`,
`${message.author.toString()}, my prefix on this guild is \`${prefix}\`.`
);
return;
}
@ -37,9 +40,9 @@ export default new Event<'message'>({
if (!commands.has(header)) return;
if (
message.channel.type === 'text' &&
message.channel.type === "text" &&
!message.channel
.permissionsFor(message.client.user || '')
.permissionsFor(message.client.user || "")
?.has(Permissions.FLAGS.SEND_MESSAGES)
) {
let status;
@ -52,19 +55,19 @@ export default new Event<'message'>({
"Try using a different channel or contacting a server admin to change permissions of that channel if you think something's wrong.";
return message.author.send(
`I don't have permission to send messages in ${message.channel.toString()}. ${status}`,
`I don't have permission to send messages in ${message.channel.toString()}. ${status}`
);
}
$.log(
`${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".`,
`${message.author.username}#${message.author.discriminator} executed the command "${header}" with arguments "${args}".`
);
// Subcommand Recursion //
let command = commands.get(header);
if (!command)
return $.warn(
`Command "${header}" was called but for some reason it's still undefined!`,
`Command "${header}" was called but for some reason it's still undefined!`
);
const params: any[] = [];
let isEndpoint = false;
@ -79,7 +82,7 @@ export default new Event<'message'>({
command.any
)
$.warn(
`An endpoint cannot have subcommands! Check ${prefix}${header} again.`,
`An endpoint cannot have subcommands! Check ${prefix}${header} again.`
);
isEndpoint = true;
break;
@ -94,25 +97,28 @@ export default new Event<'message'>({
try {
params.push(await message.client.users.fetch(id));
} catch (error) {
return message.channel.send(`No user found by the ID \`${id}\`!`);
return message.channel.send(
`No user found by the ID \`${id}\`!`
);
}
} else if (type === Command.TYPES.NUMBER) params.push(Number(param));
} else if (type === Command.TYPES.NUMBER)
params.push(Number(param));
else if (type !== Command.TYPES.SUBCOMMAND) params.push(param);
}
if (!message.member)
return $.warn(
'This command was likely called from a DM channel meaning the member object is null.',
"This command was likely called from a DM channel meaning the member object is null."
);
if (!hasPermission(message.member, permLevel)) {
const userPermLevel = getPermissionLevel(message.member);
return message.channel.send(
`You don't have access to this command! Your permission level is \`${PermissionNames[userPermLevel]}\` (${userPermLevel}), but this command requires a permission level of \`${PermissionNames[permLevel]}\` (${permLevel}).`,
`You don't have access to this command! Your permission level is \`${PermissionNames[userPermLevel]}\` (${userPermLevel}), but this command requires a permission level of \`${PermissionNames[permLevel]}\` (${permLevel}).`
);
}
if (isEndpoint) return message.channel.send('Too many arguments!');
if (isEndpoint) return message.channel.send("Too many arguments!");
// Execute with dynamic library attached. //
// The purpose of using $.bind($) is to clone the function so as to not modify the original $.
@ -128,10 +134,10 @@ export default new Event<'message'>({
client: message.client,
guild: message.guild,
member: message.member,
message: message,
message: message
},
$,
),
$
)
);
},
}
});

View File

@ -1,6 +1,6 @@
import Event from '../core/event';
import { Permissions } from 'discord.js';
import { botHasPermission } from '../core/lib';
import Event from "../core/event";
import {Permissions} from "discord.js";
import {botHasPermission} from "../core/lib";
// A list of message ID and callback pairs. You get the emote name and ID of the user reacting.
export const eventListeners: Map<
@ -9,16 +9,16 @@ export const eventListeners: Map<
> = new Map();
// Attached to the client, there can be one event listener attached to a message ID which is executed if present.
export default new Event<'messageReactionRemove'>({
export default new Event<"messageReactionRemove">({
on(reaction, user) {
const canDeleteEmotes = botHasPermission(
reaction.message.guild,
Permissions.FLAGS.MANAGE_MESSAGES,
Permissions.FLAGS.MANAGE_MESSAGES
);
if (!canDeleteEmotes) {
const callback = eventListeners.get(reaction.message.id);
callback && callback(reaction.emoji.name, user.id);
}
},
}
});

View File

@ -1,18 +1,18 @@
import Event from '../core/event';
import { client } from '../index';
import $ from '../core/lib';
import { Config } from '../core/structures';
import Event from "../core/event";
import {client} from "../index";
import $ from "../core/lib";
import {Config} from "../core/structures";
export default new Event<'ready'>({
export default new Event<"ready">({
once() {
if (client.user) {
$.ready(
`Logged in as ${client.user.username}#${client.user.discriminator}.`,
`Logged in as ${client.user.username}#${client.user.discriminator}.`
);
client.user.setActivity({
type: 'LISTENING',
name: `${Config.prefix}help`,
type: "LISTENING",
name: `${Config.prefix}help`
});
}
},
}
});

View File

@ -1,10 +1,10 @@
import { Client } from 'discord.js';
import setup from './setup';
import { Config } from './core/structures';
import { loadCommands } from './core/command';
import { loadEvents } from './core/event';
import 'discord.js-lavalink-lib';
import LavalinkMusic from 'discord.js-lavalink-lib';
import {Client} from "discord.js";
import setup from "./setup";
import {Config} from "./core/structures";
import {loadCommands} from "./core/command";
import {loadEvents} from "./core/event";
import "discord.js-lavalink-lib";
import LavalinkMusic from "discord.js-lavalink-lib";
// This is here in order to make it much less of a headache to access the client from other files.
// This of course won't actually do anything until the setup process is complete and it logs in.
@ -13,21 +13,21 @@ export const client = new Client();
(client as any).music = LavalinkMusic(client, {
lavalink: {
restnode: {
host: 'localhost',
host: "localhost",
port: 2333,
password: 'youshallnotpass',
password: "youshallnotpass"
},
nodes: [
{
host: 'localhost',
host: "localhost",
port: 2333,
password: 'youshallnotpass',
password: "youshallnotpass"
}
]
},
],
},
prefix: '!!',
helpCmd: 'mhelp',
admins: ['717352467280691331'],
prefix: "!!",
helpCmd: "mhelp",
admins: ["717352467280691331"]
});
// Begin the command loading here rather than when it's needed like in the message event.

View File

@ -1,64 +1,65 @@
import { existsSync as exists } from 'fs';
import inquirer from 'inquirer';
import Storage from './core/storage';
import { Config } from './core/structures';
import $, { setConsoleActivated } from './core/lib';
import {existsSync as exists} from "fs";
import inquirer from "inquirer";
import Storage from "./core/storage";
import {Config} from "./core/structures";
import $, {setConsoleActivated} from "./core/lib";
// This file is called (or at least should be called) automatically as long as a config file doesn't exist yet.
// And that file won't be written until the data is successfully initialized.
const prompts = [
{
type: 'password',
name: 'token',
type: "password",
name: "token",
message: "What's your bot's token?",
mask: true,
mask: true
},
{
type: 'input',
name: 'prefix',
type: "input",
name: "prefix",
message: "What do you want your bot's prefix to be?",
default: '$',
default: "$"
},
{
type: 'input',
name: 'owner',
message: "Enter the owner's user ID here.",
type: "input",
name: "owner",
message: "Enter the owner's user ID here."
},
{
type: 'input',
name: 'admins',
message: 'Enter a list of bot admins (by their IDs) separated by spaces.',
},
{
type: 'input',
name: 'support',
type: "input",
name: "admins",
message:
'Enter a list of bot troubleshooters (by their IDs) separated by spaces.',
"Enter a list of bot admins (by their IDs) separated by spaces."
},
{
type: "input",
name: "support",
message:
"Enter a list of bot troubleshooters (by their IDs) separated by spaces."
}
];
export default {
async init() {
while (!exists('data/config.json')) {
while (!exists("data/config.json")) {
const answers = await inquirer.prompt(prompts);
Storage.open('data');
Storage.open("data");
Config.token = answers.token as string;
Config.prefix = answers.prefix as string;
Config.owner = answers.owner as string;
const admins = answers.admins as string;
Config.admins = admins !== '' ? admins.split(' ') : [];
Config.admins = admins !== "" ? admins.split(" ") : [];
const support = answers.support as string;
Config.support = support !== '' ? support.split(' ') : [];
Config.support = support !== "" ? support.split(" ") : [];
Config.save(false);
}
},
/** Prompt the user to set their token again. */
async again() {
$.error('It seems that the token you provided is invalid.');
$.error("It seems that the token you provided is invalid.");
setConsoleActivated(false);
const answers = await inquirer.prompt(prompts.slice(0, 1));
Config.token = answers.token as string;
Config.save(false);
process.exit();
},
}
};

View File

@ -1,109 +1,105 @@
import { strict as assert } from 'assert';
import {
NumberWrapper,
StringWrapper,
ArrayWrapper,
} from '../src/core/wrappers';
import {strict as assert} from "assert";
import {NumberWrapper, StringWrapper, ArrayWrapper} from "../src/core/wrappers";
// I can't figure out a way to run the test suite while running the bot.
describe('Wrappers', () => {
describe('NumberWrapper', () => {
describe('#pluralise()', () => {
describe("Wrappers", () => {
describe("NumberWrapper", () => {
describe("#pluralise()", () => {
it('should return "5 credits"', () => {
assert.strictEqual(
new NumberWrapper(5).pluralise('credit', 's'),
'5 credits',
new NumberWrapper(5).pluralise("credit", "s"),
"5 credits"
);
});
it('should return "1 credit"', () => {
assert.strictEqual(
new NumberWrapper(1).pluralise('credit', 's'),
'1 credit',
new NumberWrapper(1).pluralise("credit", "s"),
"1 credit"
);
});
it('should return "-1 credits"', () => {
assert.strictEqual(
new NumberWrapper(-1).pluralise('credit', 's'),
'-1 credits',
new NumberWrapper(-1).pluralise("credit", "s"),
"-1 credits"
);
});
it('should be able to work with a plural suffix', () => {
it("should be able to work with a plural suffix", () => {
assert.strictEqual(
new NumberWrapper(2).pluralise('part', 'ies', 'y'),
'2 parties',
new NumberWrapper(2).pluralise("part", "ies", "y"),
"2 parties"
);
});
it('should be able to work with a singular suffix', () => {
it("should be able to work with a singular suffix", () => {
assert.strictEqual(
new NumberWrapper(1).pluralise('part', 'ies', 'y'),
'1 party',
new NumberWrapper(1).pluralise("part", "ies", "y"),
"1 party"
);
});
it('should be able to exclude the number', () => {
it("should be able to exclude the number", () => {
assert.strictEqual(
new NumberWrapper(1).pluralise('credit', 's', '', true),
'credit',
new NumberWrapper(1).pluralise("credit", "s", "", true),
"credit"
);
});
});
describe('#pluraliseSigned()', () => {
describe("#pluraliseSigned()", () => {
it('should return "-1 credits"', () => {
assert.strictEqual(
new NumberWrapper(-1).pluraliseSigned('credit', 's'),
'-1 credits',
new NumberWrapper(-1).pluraliseSigned("credit", "s"),
"-1 credits"
);
});
it('should return "+0 credits"', () => {
assert.strictEqual(
new NumberWrapper(0).pluraliseSigned('credit', 's'),
'+0 credits',
new NumberWrapper(0).pluraliseSigned("credit", "s"),
"+0 credits"
);
});
it('should return "+1 credit"', () => {
assert.strictEqual(
new NumberWrapper(1).pluraliseSigned('credit', 's'),
'+1 credit',
new NumberWrapper(1).pluraliseSigned("credit", "s"),
"+1 credit"
);
});
});
});
describe('StringWrapper', () => {
describe('#replaceAll()', () => {
describe("StringWrapper", () => {
describe("#replaceAll()", () => {
it('should convert "test" to "zesz"', () => {
assert.strictEqual(
new StringWrapper('test').replaceAll('t', 'z'),
'zesz',
new StringWrapper("test").replaceAll("t", "z"),
"zesz"
);
});
});
describe('#toTitleCase()', () => {
it('should capitalize the first letter of each word', () => {
describe("#toTitleCase()", () => {
it("should capitalize the first letter of each word", () => {
assert.strictEqual(
new StringWrapper(
'yeetus deletus find salvation from jesus',
"yeetus deletus find salvation from jesus"
).toTitleCase(),
'Yeetus Deletus Find Salvation From Jesus',
"Yeetus Deletus Find Salvation From Jesus"
);
});
});
});
describe('ArrayWrapper', () => {
describe('#split()', () => {
it('should split [1,2,3,4,5,6,7,8,9,10] into [[1,2,3],[4,5,6],[7,8,9],[10]]', () => {
describe("ArrayWrapper", () => {
describe("#split()", () => {
it("should split [1,2,3,4,5,6,7,8,9,10] into [[1,2,3],[4,5,6],[7,8,9],[10]]", () => {
assert.deepStrictEqual(
new ArrayWrapper([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).split(3),
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]],
[[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]
);
});
});