mirror of
https://github.com/DarthKilroy/Spot.git
synced 2025-12-19 18:26:48 +00:00
Initial commit
This commit is contained in:
commit
8ae81f1cc6
24 changed files with 1425 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
.idea
|
||||||
|
*.iml
|
||||||
|
node_modules
|
||||||
|
deploy.sh
|
||||||
|
config.*json*
|
||||||
|
!config.json.sample
|
||||||
105
bot.js
Normal file
105
bot.js
Normal file
|
|
@ -0,0 +1,105 @@
|
||||||
|
const Logger = require('@lilywonhalf/pretty-logger');
|
||||||
|
|
||||||
|
const mainProcess = () => {
|
||||||
|
const ChildProcess = require('child_process');
|
||||||
|
|
||||||
|
process.on('uncaughtException', Logger.exception);
|
||||||
|
|
||||||
|
Logger.info('Spawning bot subprocess...');
|
||||||
|
const args = [process.argv[1], 'bot'];
|
||||||
|
let botProcess = ChildProcess.spawn(process.argv[0], args);
|
||||||
|
|
||||||
|
const stdLog = (callback) => {
|
||||||
|
return (data) => {
|
||||||
|
const wantToDie = data.toString().toLowerCase().indexOf('killbotpls') > -1;
|
||||||
|
const reboot = data.toString().toLowerCase().indexOf('reboot') > -1
|
||||||
|
|| data.toString().toLowerCase().indexOf('econnreset') > -1
|
||||||
|
|| data.toString().toLowerCase().indexOf('etimedout') > -1;
|
||||||
|
|
||||||
|
data = data.toString().replace(/\n$/, '').split('\n');
|
||||||
|
data.map(datum => callback('|-- ' + datum));
|
||||||
|
|
||||||
|
if (wantToDie) {
|
||||||
|
Logger.info('Asked to kill');
|
||||||
|
botProcess.kill('SIGHUP');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reboot) {
|
||||||
|
botProcess.kill();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const bindProcess = (subprocess) => {
|
||||||
|
subprocess.stdout.on('data', stdLog(console.log));
|
||||||
|
subprocess.stderr.on('data', stdLog(console.error));
|
||||||
|
subprocess.on('close', (code) => {
|
||||||
|
Logger.error(`Bot subprocess exited with code ${code}`);
|
||||||
|
|
||||||
|
if (code !== 0) {
|
||||||
|
botProcess = ChildProcess.spawn(
|
||||||
|
process.argv[0],
|
||||||
|
args.concat(['--reboot'])
|
||||||
|
);
|
||||||
|
bindProcess(botProcess);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
bindProcess(botProcess);
|
||||||
|
Logger.info('Bot subprocess spawned');
|
||||||
|
};
|
||||||
|
|
||||||
|
const botProcess = () => {
|
||||||
|
const { Client } = require('discord.js');
|
||||||
|
|
||||||
|
global.bot = new Client({ fetchAllMembers: true });
|
||||||
|
|
||||||
|
const Config = require('./config.json');
|
||||||
|
const Command = require('./model/command');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
|
require('./model/globals');
|
||||||
|
require('./model/timer');
|
||||||
|
|
||||||
|
const crashRecover = (exception) => {
|
||||||
|
Logger.exception(exception);
|
||||||
|
Logger.notice('Need reboot');
|
||||||
|
};
|
||||||
|
|
||||||
|
process.on('uncaughtException', crashRecover);
|
||||||
|
bot.on('error', crashRecover);
|
||||||
|
|
||||||
|
Command.init();
|
||||||
|
|
||||||
|
bot.on('ready', () => {
|
||||||
|
fs.readdirSync('./event/bot/')
|
||||||
|
.filter(filename => filename.endsWith('.js'))
|
||||||
|
.map(filename => filename.substr(0, filename.length - 3))
|
||||||
|
.forEach(filename => {
|
||||||
|
const event = filename.replace(/([_-][a-z])/gu, character => `${character.substr(1).toUpperCase()}`);
|
||||||
|
|
||||||
|
if (filename !== 'ready') {
|
||||||
|
bot.on(event, require(`./event/bot/${filename}`));
|
||||||
|
} else {
|
||||||
|
require(`./event/bot/${filename}`)();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Logger.info('--------');
|
||||||
|
|
||||||
|
Logger.info('Logging in...');
|
||||||
|
bot.login(Config.token);
|
||||||
|
};
|
||||||
|
|
||||||
|
switch (process.argv[2]) {
|
||||||
|
case 'bot':
|
||||||
|
botProcess();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
mainProcess();
|
||||||
|
break;
|
||||||
|
}
|
||||||
12
config.json.sample
Normal file
12
config.json.sample
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"token": "",
|
||||||
|
"prefix": ".",
|
||||||
|
"guild": "",
|
||||||
|
"mom": "",
|
||||||
|
"roles": {
|
||||||
|
"mod": ""
|
||||||
|
},
|
||||||
|
"channels": {
|
||||||
|
"staff": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
13
emoji-characters.json
Normal file
13
emoji-characters.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"a": "🇦", "b": "🇧", "c": "🇨", "d": "🇩",
|
||||||
|
"e": "🇪", "f": "🇫", "g": "🇬", "h": "🇭",
|
||||||
|
"i": "🇮", "j": "🇯", "k": "🇰", "l": "🇱",
|
||||||
|
"m": "🇲", "n": "🇳", "o": "🇴", "p": "🇵",
|
||||||
|
"q": "🇶", "r": "🇷", "s": "🇸", "t": "🇹",
|
||||||
|
"u": "🇺", "v": "🇻", "w": "🇼", "x": "🇽",
|
||||||
|
"y": "🇾", "z": "🇿", "0": "0⃣", "1": "1⃣",
|
||||||
|
"2": "2⃣", "3": "3⃣", "4": "4⃣", "5": "5⃣",
|
||||||
|
"6": "6⃣", "7": "7⃣", "8": "8⃣", "9": "9⃣",
|
||||||
|
"10": "🔟", "#": "#⃣", "*": "*⃣",
|
||||||
|
"!": "❗", "?": "❓"
|
||||||
|
}
|
||||||
12
event/bot/message.js
Normal file
12
event/bot/message.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
const Command = require('../../model/command');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
*/
|
||||||
|
module.exports = async (message) => {
|
||||||
|
const user = message.author;
|
||||||
|
|
||||||
|
if (!user.bot) {
|
||||||
|
await Command.parseMessage(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
19
event/bot/ready.js
Normal file
19
event/bot/ready.js
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
const Logger = require('@lilywonhalf/pretty-logger');
|
||||||
|
const Config = require('../../config.json');
|
||||||
|
const Guild = require('../../model/guild');
|
||||||
|
|
||||||
|
module.exports = async () => {
|
||||||
|
Logger.info('Logged in as ' + bot.user.username + '#' + bot.user.discriminator);
|
||||||
|
|
||||||
|
Logger.info('--------');
|
||||||
|
|
||||||
|
Logger.info('Syncing guilds...');
|
||||||
|
await Guild.init();
|
||||||
|
Logger.info('Guilds synced. Serving in ' + Guild.discordGuild.name);
|
||||||
|
|
||||||
|
Logger.info('--------');
|
||||||
|
|
||||||
|
if (process.argv[3] === '--reboot') {
|
||||||
|
bot.users.cache.get(Config.mom).send(`I'm back :) !`);
|
||||||
|
}
|
||||||
|
};
|
||||||
11
model/command-category.js
Normal file
11
model/command-category.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
const CommandCategory = {
|
||||||
|
MODERATION: 'moderation',
|
||||||
|
ADMINISTRATION: 'administration',
|
||||||
|
BOT_MANAGEMENT: 'bot_management',
|
||||||
|
FUN: 'fun',
|
||||||
|
INFO: 'info',
|
||||||
|
ROLE: 'role',
|
||||||
|
RESOURCE: 'resource'
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = CommandCategory;
|
||||||
34
model/command-permission.js
Normal file
34
model/command-permission.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
const Config = require('../config.json');
|
||||||
|
const Guild = require('./guild');
|
||||||
|
|
||||||
|
const CommandPermission = {
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
* @returns {Promise.<boolean>}
|
||||||
|
*/
|
||||||
|
isMommy: async (message) => {
|
||||||
|
const member = await Guild.getMemberFromMessage(message);
|
||||||
|
|
||||||
|
return member.id === Config.mom;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
* @returns {Promise.<boolean>}
|
||||||
|
*/
|
||||||
|
isMemberMod: async (message) => {
|
||||||
|
const member = await Guild.getMemberFromMessage(message);
|
||||||
|
|
||||||
|
return member.id === Config.mom || await Guild.isMemberMod(member);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
* @returns {Promise.<boolean>}
|
||||||
|
*/
|
||||||
|
yes: async (message) => {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = CommandPermission;
|
||||||
108
model/command.js
Normal file
108
model/command.js
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const Discord = require('discord.js');
|
||||||
|
const Config = require('../config.json');
|
||||||
|
const Guild = require('./guild');
|
||||||
|
|
||||||
|
const cachelessRequire = (path) => {
|
||||||
|
if (typeof path === 'string') {
|
||||||
|
delete require.cache[require.resolve(path)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof path === 'string' ? require(path) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Command = {
|
||||||
|
commandList: new Discord.Collection(),
|
||||||
|
commandAliases: {},
|
||||||
|
|
||||||
|
init: () => {
|
||||||
|
Command.commandList = new Discord.Collection();
|
||||||
|
Command.commandAliases = {};
|
||||||
|
|
||||||
|
fs.readdirSync('model/command/').forEach(file => {
|
||||||
|
if (file.substr(file.lastIndexOf('.')).toLowerCase() === '.js') {
|
||||||
|
const commandPath = `./command/${file}`;
|
||||||
|
const commandInstance = cachelessRequire(commandPath);
|
||||||
|
|
||||||
|
if (commandInstance !== null) {
|
||||||
|
const commandName = file.substr(0, file.lastIndexOf('.')).toLowerCase();
|
||||||
|
|
||||||
|
Command.commandList.set(commandName, commandPath);
|
||||||
|
|
||||||
|
if (commandInstance.aliases !== undefined && commandInstance.aliases !== null) {
|
||||||
|
commandInstance.aliases.forEach(alias => {
|
||||||
|
Command.commandAliases[alias.toLowerCase()] = commandName;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
parseMessage: async (message) => {
|
||||||
|
let isCommand = false;
|
||||||
|
|
||||||
|
if (message.content.toLowerCase().substr(0, Config.prefix.length) === Config.prefix) {
|
||||||
|
let content = message.content.substr(Config.prefix.length).trim().split(' ');
|
||||||
|
const calledCommand = content.shift().toLowerCase();
|
||||||
|
|
||||||
|
if (await Command.isValid(calledCommand, message)) {
|
||||||
|
const member = await Guild.getMemberFromMessage(message);
|
||||||
|
|
||||||
|
if (member === null) {
|
||||||
|
message.reply('sorry, you do not seem to be on the server.');
|
||||||
|
} else {
|
||||||
|
let commandName = calledCommand;
|
||||||
|
isCommand = true;
|
||||||
|
|
||||||
|
if (Command.commandAliases.hasOwnProperty(calledCommand)) {
|
||||||
|
commandName = Command.commandAliases[calledCommand];
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandInstance = cachelessRequire(Command.commandList.get(commandName));
|
||||||
|
|
||||||
|
if (commandInstance !== null) {
|
||||||
|
commandInstance.process(message, content, Command);
|
||||||
|
} else {
|
||||||
|
Command.commandList.delete(commandName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isCommand;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} command
|
||||||
|
* @param {Message} message
|
||||||
|
* @return {Promise.<boolean>}
|
||||||
|
*/
|
||||||
|
isValid: async (command, message) => {
|
||||||
|
let canonicalCommand = command.toLowerCase();
|
||||||
|
let valid = Command.commandList.has(canonicalCommand);
|
||||||
|
|
||||||
|
if (!valid && Command.commandAliases.hasOwnProperty(canonicalCommand)) {
|
||||||
|
canonicalCommand = Command.commandAliases[command];
|
||||||
|
valid = Command.commandList.has(canonicalCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandInstance = cachelessRequire(Command.commandList.get(canonicalCommand));
|
||||||
|
|
||||||
|
if (commandInstance === null) {
|
||||||
|
Command.commandList.delete(canonicalCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
valid = valid
|
||||||
|
&& commandInstance !== null
|
||||||
|
&& await commandInstance.isAllowedForContext(message);
|
||||||
|
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Command;
|
||||||
56
model/command/avatar.js
Normal file
56
model/command/avatar.js
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
const Logger = require('@lilywonhalf/pretty-logger');
|
||||||
|
const Discord = require('discord.js');
|
||||||
|
const CommandCategory = require('../command-category');
|
||||||
|
const CommandPermission = require('../command-permission');
|
||||||
|
const Guild = require('../guild');
|
||||||
|
|
||||||
|
class Avatar
|
||||||
|
{
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (Avatar.instance !== null) {
|
||||||
|
return Avatar.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.aliases = ['av'];
|
||||||
|
this.category = CommandCategory.FUN;
|
||||||
|
this.isAllowedForContext = CommandPermission.yes;
|
||||||
|
this.description = 'Displays the avatar of the specified member.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
* @param {Array} args
|
||||||
|
*/
|
||||||
|
async process(message, args) {
|
||||||
|
let user = null;
|
||||||
|
|
||||||
|
if (args.length > 0) {
|
||||||
|
const result = Guild.findDesignatedMemberInMessage(message);
|
||||||
|
|
||||||
|
if (result.foundMembers.length > 0) {
|
||||||
|
if (result.foundMembers[0].user !== undefined) {
|
||||||
|
user = result.foundMembers[0].user;
|
||||||
|
} else {
|
||||||
|
user = result.foundMembers[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user = message.author;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user !== null) {
|
||||||
|
const url = user.displayAvatarURL({ dynamic: true });
|
||||||
|
|
||||||
|
message.channel.send(new Discord.MessageAttachment(
|
||||||
|
url + '?size=2048',
|
||||||
|
user.id + url.substr(url.lastIndexOf('.'))
|
||||||
|
)).catch(error => Logger.warning(error.toString()));
|
||||||
|
} else {
|
||||||
|
message.reply('I... Have no idea who that could be, sorry.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Avatar();
|
||||||
81
model/command/changelog.js
Normal file
81
model/command/changelog.js
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
const Logger = require('@lilywonhalf/pretty-logger');
|
||||||
|
const Config = require('../../config.json');
|
||||||
|
const CommandCategory = require('../command-category');
|
||||||
|
const CommandPermission = require('../command-permission');
|
||||||
|
const { search } = require('../jira');
|
||||||
|
|
||||||
|
const MAX_CHARACTERS = 1950;
|
||||||
|
|
||||||
|
class Changelog
|
||||||
|
{
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (Changelog.instance !== null) {
|
||||||
|
return Changelog.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.aliases = ['change-log', 'cl'];
|
||||||
|
this.category = CommandCategory.MODERATION;
|
||||||
|
this.isAllowedForContext = CommandPermission.isMemberMod;
|
||||||
|
this.description = 'Builds the changelog for a given version';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
* @param {Array} args
|
||||||
|
*/
|
||||||
|
async process(message, args) {
|
||||||
|
const errorHandler = async (error) => {
|
||||||
|
if (error) {
|
||||||
|
Logger.exception(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
await message.reactions.removeAll();
|
||||||
|
await message.react('❌');
|
||||||
|
}
|
||||||
|
|
||||||
|
await message.react('⏳').catch(() => {});
|
||||||
|
const issues = args.length > 0 && args[0] ? await search(args[0]).catch(errorHandler) : await search().catch(errorHandler);
|
||||||
|
|
||||||
|
const taskType = Config.jira.issueTypes.task;
|
||||||
|
const bugType = Config.jira.issueTypes.bug;
|
||||||
|
const features = issues.filter(issue => parseInt(issue.fields.issuetype.id) === taskType);
|
||||||
|
const bugs = issues.filter(issue => parseInt(issue.fields.issuetype.id) === bugType);
|
||||||
|
|
||||||
|
if (features.length < 1 && bugs.length < 1) {
|
||||||
|
await message.channel.send('No issues found.');
|
||||||
|
await message.reactions.removeAll().catch(() => {});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const output = `${features.map(
|
||||||
|
issue => `* Feature: ${issue.key} - ${issue.fields.summary}`
|
||||||
|
).join('\n')}\n\n${bugs.map(
|
||||||
|
issue => `* Fixed: ${issue.key} - ${issue.fields.summary}`
|
||||||
|
).join('\n')}`.trim();
|
||||||
|
const messages = [];
|
||||||
|
let currentMessage = '```';
|
||||||
|
|
||||||
|
for (let line of output.split('\n')) {
|
||||||
|
if (currentMessage.length + line.length >= MAX_CHARACTERS) {
|
||||||
|
messages.push(`${currentMessage}\`\`\``);
|
||||||
|
currentMessage = '```';
|
||||||
|
}
|
||||||
|
|
||||||
|
currentMessage = `${currentMessage}\n${line}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
messages.push(`${currentMessage}\`\`\``);
|
||||||
|
|
||||||
|
for (let messageToSend of messages) {
|
||||||
|
await message.channel.send(messageToSend).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
|
await message.reactions.removeAll().catch(() => {});
|
||||||
|
await message.react('✔').catch(() => {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Changelog();
|
||||||
34
model/command/clean.js
Normal file
34
model/command/clean.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
const Logger = require('@lilywonhalf/pretty-logger');
|
||||||
|
const Config = require('../../config.json');
|
||||||
|
const CommandCategory = require('../command-category');
|
||||||
|
const CommandPermission = require('../command-permission');
|
||||||
|
|
||||||
|
class Clean
|
||||||
|
{
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (Clean.instance !== null) {
|
||||||
|
return Clean.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.aliases = ['clear', 'purge'];
|
||||||
|
this.category = CommandCategory.MODERATION;
|
||||||
|
this.isAllowedForContext = CommandPermission.isMemberMod;
|
||||||
|
this.description = 'Kills the bot process';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
* @param {Array} args
|
||||||
|
*/
|
||||||
|
async process(message, args) {
|
||||||
|
if (args.length > 0 && parseInt(args[0]) > 0) {
|
||||||
|
await message.channel.bulkDelete(Math.min(parseInt(args[0]) + 1, 100));
|
||||||
|
} else {
|
||||||
|
message.reply(`You have to tell me how many messages I should clean. \`${Config.prefix}clean 10\` for example.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Clean();
|
||||||
42
model/command/custom-front.js
Normal file
42
model/command/custom-front.js
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
const { MessageEmbed } = require('discord.js');
|
||||||
|
const CommandCategory = require('../command-category');
|
||||||
|
const CommandPermission = require('../command-permission');
|
||||||
|
|
||||||
|
class CustomFront
|
||||||
|
{
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (CustomFront.instance !== null) {
|
||||||
|
return CustomFront.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.aliases = ['custom-fronts', 'customfronts', 'customfront', 'cf'];
|
||||||
|
this.category = CommandCategory.FUN;
|
||||||
|
this.isAllowedForContext = CommandPermission.yes;
|
||||||
|
this.description = 'Explains what "custom front" is.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
* @param {Array} args
|
||||||
|
*/
|
||||||
|
async process(message, args) {
|
||||||
|
const embed = new MessageEmbed();
|
||||||
|
|
||||||
|
embed.setColor(APP_MAIN_COLOUR);
|
||||||
|
embed.setAuthor('Custom fronts', bot.user.displayAvatarURL({ dynamic: true }));
|
||||||
|
embed.setDescription(
|
||||||
|
'Custom fronts is a kind of status for fronts, like "blurred", "unknown member", "dissociated", etc... \n' +
|
||||||
|
'\n' +
|
||||||
|
'You don\'t want those to show up as real members in your system list but you still want to be able to ' +
|
||||||
|
'set front as one of them — that\'s where custom fronts kick in.\n' +
|
||||||
|
'\n' +
|
||||||
|
'They\'re highly customizable (as per popular request) so you can name them anything you want.'
|
||||||
|
);
|
||||||
|
|
||||||
|
return message.channel.send(embed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new CustomFront();
|
||||||
55
model/command/eval.js
Normal file
55
model/command/eval.js
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
const Discord = require('discord.js');
|
||||||
|
const Logger = require('@lilywonhalf/pretty-logger');
|
||||||
|
const Config = require('../../config.json');
|
||||||
|
const CommandCategory = require('../command-category');
|
||||||
|
const CommandPermission = require('../command-permission');
|
||||||
|
|
||||||
|
// Including every model here so that it's ready to be used by the command
|
||||||
|
const Guild = require('../guild');
|
||||||
|
|
||||||
|
const JAVASCRIPT_LOGO_URL = 'https://i.discord.fr/IEV8.png';
|
||||||
|
|
||||||
|
class Eval
|
||||||
|
{
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (Eval.instance !== null) {
|
||||||
|
return Eval.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.aliases = [];
|
||||||
|
this.category = CommandCategory.BOT_MANAGEMENT;
|
||||||
|
this.isAllowedForContext = CommandPermission.isMommy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
*/
|
||||||
|
async process(message) {
|
||||||
|
const code = message.content
|
||||||
|
.substr(Config.prefix.length + 'eval'.length)
|
||||||
|
.trim()
|
||||||
|
.replace(/(`{3})js\n(.+)\n\1/iu, '$2')
|
||||||
|
.trim();
|
||||||
|
|
||||||
|
await message.react('✔');
|
||||||
|
Logger.notice('Eval: ' + code);
|
||||||
|
let output = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
output = eval(`${code}`); // Spoopy! 🎃 🦇 👻 ☠ 🕷
|
||||||
|
} catch (exception) {
|
||||||
|
output = `**${exception.name}: ${exception.message}**\n${exception.stack}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new Discord.MessageEmbed()
|
||||||
|
.setAuthor('Eval', JAVASCRIPT_LOGO_URL)
|
||||||
|
.setColor(0x00FF00)
|
||||||
|
.setDescription(output);
|
||||||
|
|
||||||
|
message.channel.send(embed).catch(error => Logger.warning(error.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Eval();
|
||||||
179
model/command/help.js
Normal file
179
model/command/help.js
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
const Discord = require('discord.js');
|
||||||
|
const Logger = require('@lilywonhalf/pretty-logger');
|
||||||
|
const Config = require('../../config.json');
|
||||||
|
const EmojiCharacters = require('../../emoji-characters.json');
|
||||||
|
const Guild = require('../guild');
|
||||||
|
const CommandCategory = require('../command-category');
|
||||||
|
const CommandPermission = require('../command-permission');
|
||||||
|
|
||||||
|
const cachelessRequire = (path) => {
|
||||||
|
if (typeof path === 'string') {
|
||||||
|
delete require.cache[require.resolve(path)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof path === 'string' ? require(path) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
class HelpDialog
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
*/
|
||||||
|
constructor(message) {
|
||||||
|
this.categoriesMapping = new Discord.Collection();
|
||||||
|
this.categoriesEmbed = new Discord.MessageEmbed();
|
||||||
|
this.originalMessage = message;
|
||||||
|
this.categoryCommandMapping = new Discord.Collection();
|
||||||
|
this.postedMessage = null;
|
||||||
|
this.usedEmojis = [];
|
||||||
|
this.stopAddingReactions = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Command} Command
|
||||||
|
*/
|
||||||
|
async init(Command) {
|
||||||
|
const callableCommands = new Discord.Collection();
|
||||||
|
const commandList = Command.commandList.keyArray();
|
||||||
|
|
||||||
|
for (let i = 0; i < commandList.length; i++) {
|
||||||
|
const commandName = commandList[i];
|
||||||
|
const command = cachelessRequire(`../${Command.commandList.get(commandName)}`);
|
||||||
|
const isAllowed = await command.isAllowedForContext(this.originalMessage);
|
||||||
|
|
||||||
|
if (isAllowed) {
|
||||||
|
callableCommands.set(commandName, command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callableCommands.forEach((command, commandName) => {
|
||||||
|
if (!this.categoryCommandMapping.has(command.category)) {
|
||||||
|
this.categoryCommandMapping.set(command.category, new Discord.Collection());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.categoryCommandMapping.get(command.category).set(commandName, command);
|
||||||
|
});
|
||||||
|
|
||||||
|
let i = 1;
|
||||||
|
|
||||||
|
const categories = callableCommands.reduce((accumulator, command) => {
|
||||||
|
if (!this.categoriesMapping.array().includes(command.category)) {
|
||||||
|
let commandCategory = command.category.replace('_', ' ');
|
||||||
|
commandCategory = `${commandCategory.slice(0, 1).toUpperCase()}${commandCategory.slice(1)}`;
|
||||||
|
|
||||||
|
this.categoriesMapping.set(EmojiCharacters[i], command.category);
|
||||||
|
this.usedEmojis.push(EmojiCharacters[i]);
|
||||||
|
accumulator += `${EmojiCharacters[i]} ${commandCategory}\n`;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return accumulator;
|
||||||
|
}, '');
|
||||||
|
|
||||||
|
this.categoriesEmbed.setTitle('Help command');
|
||||||
|
this.categoriesEmbed.setDescription(categories);
|
||||||
|
|
||||||
|
return this.listCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Collection<Snowflake, MessageReaction>} [collection]
|
||||||
|
*/
|
||||||
|
async listCategories(collection) {
|
||||||
|
this.stopAddingReactions = true;
|
||||||
|
|
||||||
|
if (collection !== undefined && collection.size < 1) {
|
||||||
|
await this.postedMessage.reactions.removeAll();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = await Guild.getMemberFromMessage(this.originalMessage);
|
||||||
|
const filter = (reaction, user) => {
|
||||||
|
const emoji = reaction.emoji.name;
|
||||||
|
|
||||||
|
return this.usedEmojis.includes(emoji) && user.id === member.user.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.postedMessage === null) {
|
||||||
|
this.postedMessage = await this.originalMessage.channel.send(this.categoriesEmbed).catch(error => Logger.warning(error.toString()));
|
||||||
|
} else {
|
||||||
|
await this.postedMessage.reactions.removeAll();
|
||||||
|
await this.postedMessage.edit('', this.categoriesEmbed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5 minutes
|
||||||
|
this.postedMessage.awaitReactions(filter, { time: 300000, max: 1 }).then(this.listCommands.bind(this)).catch(Logger.exception);
|
||||||
|
this.stopAddingReactions = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < this.usedEmojis.length && !this.stopAddingReactions; i++) {
|
||||||
|
await this.postedMessage.react(this.usedEmojis[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Collection<Snowflake, MessageReaction>} collection
|
||||||
|
*/
|
||||||
|
async listCommands(collection) {
|
||||||
|
this.stopAddingReactions = true;
|
||||||
|
await this.postedMessage.reactions.removeAll();
|
||||||
|
|
||||||
|
if (collection.size < 1) {
|
||||||
|
this.postedMessage.edit('Timed out! You may send the command again.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const member = await Guild.getMemberFromMessage(this.originalMessage);
|
||||||
|
const filter = (reaction, user) => {
|
||||||
|
const emoji = reaction.emoji.name;
|
||||||
|
|
||||||
|
return emoji === '↩️' && user.id === member.user.id;
|
||||||
|
};
|
||||||
|
|
||||||
|
const category = this.categoriesMapping.get(collection.first().emoji.name);
|
||||||
|
const commandsEmbed = new Discord.MessageEmbed();
|
||||||
|
const commands = this.categoryCommandMapping.get(category).reduce((accumulator, command, commandName) => {
|
||||||
|
accumulator += `**${Config.prefix}${commandName}** ${command.description}\n\n`;
|
||||||
|
|
||||||
|
return accumulator;
|
||||||
|
}, '');
|
||||||
|
|
||||||
|
commandsEmbed.setTitle('Help command');
|
||||||
|
commandsEmbed.setDescription(commands);
|
||||||
|
|
||||||
|
this.postedMessage.edit('', commandsEmbed);
|
||||||
|
|
||||||
|
// 5 minutes
|
||||||
|
this.postedMessage.awaitReactions(filter, { time: 300000, max: 1 }).then(this.listCategories.bind(this)).catch(Logger.exception);
|
||||||
|
|
||||||
|
await this.postedMessage.react('↩️');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Help
|
||||||
|
{
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (Help.instance !== null) {
|
||||||
|
return Help.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.aliases = [];
|
||||||
|
this.category = CommandCategory.INFO;
|
||||||
|
this.isAllowedForContext = CommandPermission.yes;
|
||||||
|
this.description = 'Provides the list of commands';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
* @param {Array} args
|
||||||
|
* @param {Command} Command
|
||||||
|
*/
|
||||||
|
async process(message, args, Command) {
|
||||||
|
const dialog = new HelpDialog(message);
|
||||||
|
|
||||||
|
return dialog.init(Command);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Help();
|
||||||
30
model/command/kill.js
Normal file
30
model/command/kill.js
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
const Logger = require('@lilywonhalf/pretty-logger');
|
||||||
|
const Config = require('../../config.json');
|
||||||
|
const CommandCategory = require('../command-category');
|
||||||
|
const CommandPermission = require('../command-permission');
|
||||||
|
|
||||||
|
class Kill
|
||||||
|
{
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (Kill.instance !== null) {
|
||||||
|
return Kill.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.aliases = [];
|
||||||
|
this.category = CommandCategory.BOT_MANAGEMENT;
|
||||||
|
this.isAllowedForContext = CommandPermission.isMemberMod;
|
||||||
|
this.description = 'Kills the bot process';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
*/
|
||||||
|
async process(message) {
|
||||||
|
await message.react('✔');
|
||||||
|
Logger.notice('killbotpls');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Kill();
|
||||||
29
model/command/reload.js
Normal file
29
model/command/reload.js
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
const Logger = require('@lilywonhalf/pretty-logger');
|
||||||
|
const CommandCategory = require('../command-category');
|
||||||
|
const CommandPermission = require('../command-permission');
|
||||||
|
|
||||||
|
class Reload
|
||||||
|
{
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (Reload.instance !== null) {
|
||||||
|
return Reload.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.aliases = ['reboot'];
|
||||||
|
this.category = CommandCategory.BOT_MANAGEMENT;
|
||||||
|
this.isAllowedForContext = CommandPermission.isMemberMod;
|
||||||
|
this.description = 'Reboots the bot';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
*/
|
||||||
|
async process(message) {
|
||||||
|
await message.reply(`OK, I'm rebooting now.`);
|
||||||
|
Logger.notice('Reboot asked');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Reload();
|
||||||
34
model/command/set-avatar.js
Normal file
34
model/command/set-avatar.js
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
const Logger = require('@lilywonhalf/pretty-logger');
|
||||||
|
const CommandCategory = require('../command-category');
|
||||||
|
const CommandPermission = require('../command-permission');
|
||||||
|
|
||||||
|
class SetAvatar
|
||||||
|
{
|
||||||
|
static instance = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (SetAvatar.instance !== null) {
|
||||||
|
return SetAvatar.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.aliases = ['setavatar'];
|
||||||
|
this.category = CommandCategory.BOT_MANAGEMENT;
|
||||||
|
this.isAllowedForContext = CommandPermission.isMemberMod;
|
||||||
|
this.description = 'Set the bot avatar';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
* @param {Array} args
|
||||||
|
*/
|
||||||
|
async process(message, args) {
|
||||||
|
bot.user.setAvatar(args.join(' ')).then(() => {
|
||||||
|
message.reply('my avatar has been changed!')
|
||||||
|
}).catch((error) => {
|
||||||
|
message.reply('there has been an error changing my avatar. Check the logs for more details.');
|
||||||
|
Logger.exception(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new SetAvatar();
|
||||||
1
model/globals.js
Normal file
1
model/globals.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
global.APP_MAIN_COLOUR = 'A95B44';
|
||||||
170
model/guild.js
Normal file
170
model/guild.js
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
const Logger = require('@lilywonhalf/pretty-logger');
|
||||||
|
const Config = require('../config.json');
|
||||||
|
const Discord = require('discord.js');
|
||||||
|
|
||||||
|
const cachelessRequire = (path) => {
|
||||||
|
if (typeof path === 'string') {
|
||||||
|
delete require.cache[require.resolve(path)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof path === 'string' ? require(path) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Guild = {
|
||||||
|
/** {Guild} */
|
||||||
|
discordGuild: null,
|
||||||
|
|
||||||
|
/** {TextChannel} */
|
||||||
|
modsReviewChannel: null,
|
||||||
|
|
||||||
|
/** {TextChannel} */
|
||||||
|
speakerReviewChannel: null,
|
||||||
|
|
||||||
|
/** {TextChannel} */
|
||||||
|
officeChannel: null,
|
||||||
|
|
||||||
|
/** {Collection} */
|
||||||
|
messagesCache: new Discord.Collection(),
|
||||||
|
|
||||||
|
init: async () => {
|
||||||
|
Guild.discordGuild = bot.guilds.cache.find(guild => guild.id === Config.guild);
|
||||||
|
Guild.modsReviewChannel = Guild.discordGuild.channels.cache.find(channel => channel.id === Config.channels.modsReview);
|
||||||
|
Guild.speakerReviewChannel = Guild.discordGuild.channels.cache.find(channel => channel.id === Config.channels.speakerReview);
|
||||||
|
Guild.officeChannel = Guild.discordGuild.channels.cache.find(channel => channel.id === Config.channels.office);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param message
|
||||||
|
* @returns {Promise.<GuildMember|null>}
|
||||||
|
*/
|
||||||
|
getMemberFromMessage: async (message) => {
|
||||||
|
return await Guild.discordGuild.members.fetch(message.author).catch(exception => {
|
||||||
|
Logger.error(exception.toString());
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {GuildMember} member
|
||||||
|
*/
|
||||||
|
isMemberMod: (member) => {
|
||||||
|
return member !== undefined && member !== null && member.roles.cache.has(Config.roles.mod);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} roleName
|
||||||
|
* @returns {Role|null}
|
||||||
|
*/
|
||||||
|
getRoleByName: (roleName) => {
|
||||||
|
return roleName === undefined || roleName === null ? null : Guild.discordGuild.roles.cache.find(
|
||||||
|
role => role.name.toLowerCase() === roleName.toLowerCase()
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {GuildMember} member
|
||||||
|
* @param {Snowflake} snowflake - The Role snowflake.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
memberHasRole: (member, snowflake) => {
|
||||||
|
return member !== undefined && member !== null && member.roles.cache.some(role => role.id === snowflake);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
* @returns {Discord.MessageEmbed}
|
||||||
|
*/
|
||||||
|
messageToEmbed: async (message) => {
|
||||||
|
const member = await Guild.getMemberFromMessage(message);
|
||||||
|
const suffix = member !== null && member.nickname !== null && member.nickname !== undefined ? ` aka ${member.nickname}` : '';
|
||||||
|
const embeds = message.embeds.filter(embed => embed.author.name && embed.author.iconURL);
|
||||||
|
|
||||||
|
let authorName = `${message.author.username}#${message.author.discriminator}${suffix}`;
|
||||||
|
let authorImage = message.author.displayAvatarURL({ dynamic: true });
|
||||||
|
let description = message.content;
|
||||||
|
let timestamp = message.createdTimestamp;
|
||||||
|
let image = null;
|
||||||
|
|
||||||
|
if (message.attachments.size > 0) {
|
||||||
|
image = message.attachments.first().url;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (description.length < 1 && embeds.length > 0) {
|
||||||
|
const embed = embeds[0];
|
||||||
|
description = embed.description ? embed.description.trim() : '';
|
||||||
|
|
||||||
|
if (message.author.bot) {
|
||||||
|
if (embed.author) {
|
||||||
|
authorName = embed.author.name;
|
||||||
|
authorImage = embed.author.iconURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embed.timestamp) {
|
||||||
|
timestamp = embed.timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (embed.image) {
|
||||||
|
image = embed.image.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Discord.MessageEmbed()
|
||||||
|
.setAuthor(authorName, authorImage)
|
||||||
|
.setColor(0x00FF00)
|
||||||
|
.setDescription(description)
|
||||||
|
.setTimestamp(timestamp)
|
||||||
|
.setImage(image);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Message} message
|
||||||
|
* @returns {{certain: boolean, foundMembers: Array}}
|
||||||
|
*/
|
||||||
|
findDesignatedMemberInMessage: (message) => {
|
||||||
|
let foundMembers = [];
|
||||||
|
let certain = true;
|
||||||
|
const memberList = bot.users.cache.concat(Guild.discordGuild.members.cache);
|
||||||
|
|
||||||
|
if (message.mentions.members !== null && message.mentions.members.size > 0) {
|
||||||
|
foundMembers = message.mentions.members.array();
|
||||||
|
} else if (message.content.match(/[0-9]{18}/u) !== null) {
|
||||||
|
const ids = message.content.match(/[0-9]{18}/gu);
|
||||||
|
|
||||||
|
ids.map(id => {
|
||||||
|
if (memberList.has(id)) {
|
||||||
|
foundMembers.push(memberList.get(id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
certain = false;
|
||||||
|
memberList.forEach(member => {
|
||||||
|
const user = member.user === undefined ? member : member.user;
|
||||||
|
|
||||||
|
const hasNickname = member.nickname !== undefined && member.nickname !== null;
|
||||||
|
const nickname = hasNickname ? `${member.nickname.toLowerCase()}#${user.discriminator}` : '';
|
||||||
|
const username = `${user.username.toLowerCase()}#${user.discriminator}`;
|
||||||
|
const content = message.cleanContent.toLowerCase().split(' ').splice(1).join(' ');
|
||||||
|
|
||||||
|
if (content.length > 0) {
|
||||||
|
const contentInNickname = hasNickname ? nickname.indexOf(content) > -1 : false;
|
||||||
|
const contentInUsername = username.indexOf(content) > -1;
|
||||||
|
const nicknameInContent = hasNickname ? content.indexOf(nickname) > -1 : false;
|
||||||
|
const usernameInContent = content.indexOf(username) > -1;
|
||||||
|
|
||||||
|
if (contentInNickname || contentInUsername || nicknameInContent || usernameInContent) {
|
||||||
|
foundMembers.push(member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
certain,
|
||||||
|
foundMembers
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Guild;
|
||||||
37
model/jira.js
Normal file
37
model/jira.js
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
const axios = require('axios');
|
||||||
|
const Config = require('../config.json');
|
||||||
|
|
||||||
|
const ENDPOINTS = {
|
||||||
|
search: '/search'
|
||||||
|
};
|
||||||
|
|
||||||
|
class Jira
|
||||||
|
{
|
||||||
|
async search(version) {
|
||||||
|
const params = {
|
||||||
|
'jql': 'project%20%3D%20SP%20AND%20status%20in%20(%22Claimed%20Fixed%22%2C%20%22In%20Pre-Testing%22)%20AND%20updated%20%3E%3D%20-24h%20ORDER%20BY%20created%20DESC',
|
||||||
|
'maxResults': 100,
|
||||||
|
'startAt': 0
|
||||||
|
};
|
||||||
|
|
||||||
|
if (version) {
|
||||||
|
params.jql = `project%20%3D%20SP%20AND%20status%20in%20(%22Claimed%20Fixed%22%2C%20%22In%20Pre-Testing%22)%20AND%20%22Fixed%20In%20Version%5BNumber%5D%22%20%3D%20%22${version}%22%20ORDER%20BY%20created%20DESC`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const issues = [];
|
||||||
|
const httpParams = Object.keys(params).map(key => `${key}=${params[key]}`).join('&');
|
||||||
|
const url = `${Config.jira.baseUrl}${ENDPOINTS.search}?${httpParams}`;
|
||||||
|
let response = null;
|
||||||
|
|
||||||
|
do {
|
||||||
|
response = await axios(url);
|
||||||
|
|
||||||
|
issues.push(...response.data.issues);
|
||||||
|
params.startAt += 100;
|
||||||
|
} while (response.data.issues.length > 99 && response.data.total > 100);
|
||||||
|
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new Jira();
|
||||||
9
model/timer.js
Normal file
9
model/timer.js
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* @param {int} milliseconds
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
global.sleep = async (milliseconds) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, milliseconds);
|
||||||
|
});
|
||||||
|
};
|
||||||
332
package-lock.json
generated
Normal file
332
package-lock.json
generated
Normal file
|
|
@ -0,0 +1,332 @@
|
||||||
|
{
|
||||||
|
"name": "simplypluralbot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "simplypluralbot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@lilywonhalf/pretty-logger": "^1.1.3",
|
||||||
|
"axios": "^0.21.1",
|
||||||
|
"discord.js": "^12.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/collection": {
|
||||||
|
"version": "0.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz",
|
||||||
|
"integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ=="
|
||||||
|
},
|
||||||
|
"node_modules/@discordjs/form-data": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lilywonhalf/pretty-logger": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lilywonhalf/pretty-logger/-/pretty-logger-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-DcEGjCrQV2j6Hl29G4zD5M/84hzrt1xw7uPOZtZLWFzrCB5OWS/64RWl/y5/om0walTIYf7Nv00hCzfRS8gw4A=="
|
||||||
|
},
|
||||||
|
"node_modules/abort-controller": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||||
|
"dependencies": {
|
||||||
|
"event-target-shim": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "0.21.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||||
|
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/discord.js": {
|
||||||
|
"version": "12.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz",
|
||||||
|
"integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@discordjs/collection": "^0.1.6",
|
||||||
|
"@discordjs/form-data": "^3.0.1",
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"node-fetch": "^2.6.1",
|
||||||
|
"prism-media": "^1.2.9",
|
||||||
|
"setimmediate": "^1.0.5",
|
||||||
|
"tweetnacl": "^1.0.3",
|
||||||
|
"ws": "^7.4.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/event-target-shim": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.0.tgz",
|
||||||
|
"integrity": "sha512-0vRwd7RKQBTt+mgu87mtYeofLFZpTas2S9zY+jIeuLJMNvudIgF52nr19q40HOwH5RrhWIPuj9puybzSJiRrVg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.47.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz",
|
||||||
|
"integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz",
|
||||||
|
"integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.47.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/node-fetch": {
|
||||||
|
"version": "2.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||||
|
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==",
|
||||||
|
"engines": {
|
||||||
|
"node": "4.x || >=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prism-media": {
|
||||||
|
"version": "1.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.9.tgz",
|
||||||
|
"integrity": "sha512-UHCYuqHipbTR1ZsXr5eg4JUmHER8Ss4YEb9Azn+9zzJ7/jlTtD1h0lc4g6tNx3eMlB8Mp6bfll0LPMAV4R6r3Q==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@discordjs/opus": "^0.5.0",
|
||||||
|
"ffmpeg-static": "^4.2.7 || ^3.0.0 || ^2.4.0",
|
||||||
|
"node-opus": "^0.3.3",
|
||||||
|
"opusscript": "^0.0.8"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@discordjs/opus": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"ffmpeg-static": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"node-opus": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"opusscript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/setimmediate": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||||
|
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
||||||
|
},
|
||||||
|
"node_modules/tweetnacl": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
|
||||||
|
},
|
||||||
|
"node_modules/ws": {
|
||||||
|
"version": "7.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
|
||||||
|
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": "^5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@discordjs/collection": {
|
||||||
|
"version": "0.1.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz",
|
||||||
|
"integrity": "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ=="
|
||||||
|
},
|
||||||
|
"@discordjs/form-data": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==",
|
||||||
|
"requires": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@lilywonhalf/pretty-logger": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lilywonhalf/pretty-logger/-/pretty-logger-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-DcEGjCrQV2j6Hl29G4zD5M/84hzrt1xw7uPOZtZLWFzrCB5OWS/64RWl/y5/om0walTIYf7Nv00hCzfRS8gw4A=="
|
||||||
|
},
|
||||||
|
"abort-controller": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
|
||||||
|
"requires": {
|
||||||
|
"event-target-shim": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||||
|
},
|
||||||
|
"axios": {
|
||||||
|
"version": "0.21.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||||
|
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
||||||
|
"requires": {
|
||||||
|
"follow-redirects": "^1.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"requires": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
||||||
|
},
|
||||||
|
"discord.js": {
|
||||||
|
"version": "12.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz",
|
||||||
|
"integrity": "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==",
|
||||||
|
"requires": {
|
||||||
|
"@discordjs/collection": "^0.1.6",
|
||||||
|
"@discordjs/form-data": "^3.0.1",
|
||||||
|
"abort-controller": "^3.0.0",
|
||||||
|
"node-fetch": "^2.6.1",
|
||||||
|
"prism-media": "^1.2.9",
|
||||||
|
"setimmediate": "^1.0.5",
|
||||||
|
"tweetnacl": "^1.0.3",
|
||||||
|
"ws": "^7.4.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"event-target-shim": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="
|
||||||
|
},
|
||||||
|
"follow-redirects": {
|
||||||
|
"version": "1.14.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.0.tgz",
|
||||||
|
"integrity": "sha512-0vRwd7RKQBTt+mgu87mtYeofLFZpTas2S9zY+jIeuLJMNvudIgF52nr19q40HOwH5RrhWIPuj9puybzSJiRrVg=="
|
||||||
|
},
|
||||||
|
"mime-db": {
|
||||||
|
"version": "1.47.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz",
|
||||||
|
"integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw=="
|
||||||
|
},
|
||||||
|
"mime-types": {
|
||||||
|
"version": "2.1.30",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz",
|
||||||
|
"integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==",
|
||||||
|
"requires": {
|
||||||
|
"mime-db": "1.47.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node-fetch": {
|
||||||
|
"version": "2.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||||
|
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
|
||||||
|
},
|
||||||
|
"prism-media": {
|
||||||
|
"version": "1.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.2.9.tgz",
|
||||||
|
"integrity": "sha512-UHCYuqHipbTR1ZsXr5eg4JUmHER8Ss4YEb9Azn+9zzJ7/jlTtD1h0lc4g6tNx3eMlB8Mp6bfll0LPMAV4R6r3Q==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
|
"setimmediate": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||||
|
"integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
|
||||||
|
},
|
||||||
|
"tweetnacl": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||||
|
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw=="
|
||||||
|
},
|
||||||
|
"ws": {
|
||||||
|
"version": "7.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
|
||||||
|
"integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
|
||||||
|
"requires": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
package.json
Normal file
16
package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "simplypluralbot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Simply Plural bot for Discord",
|
||||||
|
"main": "bot.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "Lily Wonhalf <lilywonhalf@gmail.com>",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@lilywonhalf/pretty-logger": "^1.1.3",
|
||||||
|
"axios": "^0.21.1",
|
||||||
|
"discord.js": "^12.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue