diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..fc0a8421 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +# EditorConfig is awesome: https://EditorConfig.org + +root = true + +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.{js, ts, json, yml}] +indent_style = space +indent_size = 2 + +[*.md] +indent_style = space +indent_size = 4 diff --git a/.gitattributes b/.gitattributes index d5488513..19ed10aa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,3 +5,6 @@ *.gif binary *.png binary *.webp binary + +# 4 teh 100% +*.js linguist-vendored diff --git a/.gitignore b/.gitignore index d182fb9b..981785d6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,9 @@ yarn-error.log #Build build +docs logs +*.tsbuildinfo #Private Source auth.js diff --git a/.nodemon.json b/.nodemon.json index 6a109c6b..e9c9ff6b 100644 --- a/.nodemon.json +++ b/.nodemon.json @@ -1,7 +1,6 @@ { "verbose": true, "watch": [ "build" ], - "ignore": [ "provider", ".vscode", "typings" ], "env": { "NODE_ENV": "development" }, "ext": "js" } diff --git a/.travis.yml b/.travis.yml index 99ac9681..b79bed94 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ sudo: false jobs: include: - stage: docs-build - script: yarn run test && yarn run docs:parse + script: bash ./deploy/travis.sh cache: directories: - node_modules diff --git a/.vscode/settings.json b/.vscode/settings.json index 1ea990aa..bde94db2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,7 @@ { "npm.autoDetect": "on", - "tslint.configFile": ".\\.tslint.js", + "tslint.configFile": ".tslint.js", "tslint.exclude": "build/**/*", "tslint.jsEnable": true, - "typescript.tsdk": "node_modules\\typescript\\lib", + "typescript.tsdk": "node_modules/typescript/lib", } diff --git a/LICENSE b/LICENSE index dfc20d4d..aba37bf6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2018 gazmull +Copyright (c) 2018-present gazmull Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index a21ac0ba..1d731cd0 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ - [**Bot Guide**](https://docs.thegzm.space/eros-bot) # Features -- Realtime Character/Weapon information pulls from [**Kamihime Project Nutaku Fandom**](https://kamihime-project.fandom.com) +- Realtime Character/Weapon information pulls from [**Kamihime PROJECT EN Fandom**](https://kamihime-project.fandom.com) - Kamihime Database (Harem Scenes) - - Only Nutaku version is available. + - Only Kamihime EN (Nutaku) version is available. - Uses REST API (JSON) from [**Kamihime Database**](https://github.com/gazmull/kamihime-database) -- Tweets updates from [**@Kamihime_Nutaku**](https://twitter.com/kamihime_nutaku) +- Tweets updates from [**@Kamihime_EN**](https://twitter.com/kamihime_en) - Customise the `Twitter Channel` with `?set twitterchannel ` - Get notified with in-game events via Countdown notification system - Customise the `Countdown Channel` with `?set cdchannel ` @@ -48,12 +48,12 @@ - **Tag** - Main: `tag`, `tags` - **Fun** - - Main: `ask`, `insult`, `say`, `quiz` + - Main: `ask`, `insult`, `say`, `mock`, `owo`, `quiz` - **Leveling System** - Main: `level` - **Utility** - Main: `memberinfo`, `serverinfo`, `ping`, `stats` - - Bot Owner: `eval`, `clear` + - Bot Owner: `eval` # Self-Hosting > ### [**Add the bot instead? (24/7)**](http://addbot.thegzm.space) @@ -142,4 +142,6 @@ If one cannot afford to do a pull request, submitting documentation contribution 2. Run `$ yarn run dev:start` # License - MIT +> [**MIT**](https://github.com/gazmull/discord-paginationembed/blob/master/LICENSE) + +© 2018-present [**Euni (gazmull)**](https://github.com/gazmull) diff --git a/auth.example.js b/auth.example.js index e379cadc..f0460868 100644 --- a/auth.example.js +++ b/auth.example.js @@ -65,6 +65,6 @@ module.exports = { access_token_secret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx', consumer_key: 'xxx', consumer_secret: 'xxxxxxxxxxxxxx', - user: '806331327108653057' // current ID: Kamihime_Nutaku (gettwitterid.com) + user: '806331327108653057' // current ID: Kamihime_EN (gettwitterid.com) } }; diff --git a/deploy/travis.sh b/deploy/travis.sh new file mode 100644 index 00000000..dd49c001 --- /dev/null +++ b/deploy/travis.sh @@ -0,0 +1,29 @@ +#!/bin/bash +# Based on https://github.com/hydrabolt/discord.js-site/blob/master/deploy/deploy.sh + +set -e + +if [ "$TRAVIS_BRANCH" != "master" -o -n "$TRAVIS_TAG" -o "$TRAVIS_PULL_REQUEST" != "false" ]; then + echo -e "Not building for a non master branch push - building without deploying." + yarn test + exit 0 +fi + +echo -e "Building for a master branch push - building and deploying." + +REPO=$(git config remote.origin.url) +SHA=$(git rev-parse --verify HEAD) +TARGET_BRANCH="gh-pages" + +git clone $REPO gh -b $TARGET_BRANCH + +yarn docs:build + +rsync --delete-before --exclude='.git' --exclude='index.html' --exclude='gitbook.yaml' -avh docs/ gh/ + +cd gh +git add --all . +git config user.name "Travis CI" +git config user.email "${COMMIT_EMAIL}" +git commit -m "Build: ${SHA}" || true +git push "https://${GITHUB_TOKEN}@github.com/gazmull/eros-bot.git" $TARGET_BRANCH diff --git a/package.json b/package.json index 46f4f3f6..365edfc2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "eros-bot", "version": "3.4.0", - "description": "A simple bot for pulling character information and harem episodes from Kamihime Project Fandom and Kamihime Database.", + "description": "A simple bot for pulling character information and harem episodes from Kamihime PROJECT Fandom and Kamihime Database.", "main": "build/index.js", "typings": "typings", "engines": { @@ -41,33 +41,35 @@ "dependencies": { "bufferutil": "^4.0.1", "chalk": "^2.4.2", - "discord-akairo": "8.0.0-beta.1", - "discord-paginationembed": "^1.0.0-beta.1", + "discord-akairo": "^8.0.0-beta.8", + "discord-paginationembed": "^1.0.0-beta.4", "discord.js": "github:discordjs/discord.js", "erlpack": "github:discordapp/erlpack", "fs-extra": "^7.0.1", "infobox-parser": "^3.2.0", "mariadb": "^2.0.3", - "moment-timezone": "^0.5.23", - "node-fetch": "^2.3.0", + "moment-timezone": "^0.5.25", + "node-fetch": "^2.4.1", "nodemw": "^0.12.2", "reflect-metadata": "^0.1.13", - "sequelize": "5.0.0-beta.15", - "sequelize-typescript": "^0.6.8-beta.0", - "twitter-lite": "^0.9.2", + "sequelize": "^5.7.6", + "sequelize-typescript": "1.0.0-beta.1", + "twitter-lite": "^0.9.4", "winston": "^3.2.1", - "winston-daily-rotate-file": "^3.8.0", + "winston-daily-rotate-file": "^3.9.0", "zlib-sync": "^0.1.4" }, "devDependencies": { + "@types/bluebird": "^3.5.26", "@types/fs-extra": "^5.0.5", "@types/json2md": "^1.5.0", "@types/moment-timezone": "^0.5.12", - "@types/node": "^11.11.4", - "@types/node-fetch": "^2.1.7", + "@types/node": "^11.13.8", + "@types/node-fetch": "^2.3.3", + "@types/validator": "^10.11.0", "json2md": "^1.6.3", - "tslint": "^5.14.0", + "tslint": "^5.16.0", "tslint-eslint-rules": "^5.4.0", - "typescript": "^3.3.4000" + "typescript": "^3.4.5" } } diff --git a/src/commands/countdown/add.ts b/src/commands/countdown/add.ts index f77efdda..0e5f72fb 100644 --- a/src/commands/countdown/add.ts +++ b/src/commands/countdown/add.ts @@ -1,9 +1,9 @@ import { Message } from 'discord.js'; import * as moment from 'moment'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; import CountdownCommand from './countdown'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('countdown-add', { description: { @@ -27,7 +27,7 @@ export default class extends ErosCommand { match: 'rest', prompt: { start: 'what should the new countdown be named?', - retry: (_, __, input: { phrase: string }) => + retry: (_, input: { phrase: string }) => `**${input.phrase}** already exists. Please provide again.` } }, diff --git a/src/commands/countdown/countdown.ts b/src/commands/countdown/countdown.ts index 7342feb9..3e836548 100644 --- a/src/commands/countdown/countdown.ts +++ b/src/commands/countdown/countdown.ts @@ -1,17 +1,18 @@ -import { Collection, Message, User } from 'discord.js'; +import { Flag } from 'discord-akairo'; +import { Collection, Message } from 'discord.js'; import * as fs from 'fs-extra'; import * as moment from 'moment-timezone'; import { ICountdown } from '../../../typings'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; import prettifyMs from '../../util/prettifyMs'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('countdown', { aliases: [ 'countdown', 'cd' ], description: { content: [ - 'Displays countdowns related to Kamihime Project in-game events.', + 'Displays countdowns related to Kamihime PROJECT in-game events.', 'It includes special and some regular events.', 'Available Methods:', '- `help`', @@ -30,11 +31,7 @@ export default class extends ErosCommand { 'delete A User\'s Birthday', 'test 2018-05-16T20:00', ] - }, - args: [ - { id: 'method', type: [ 'help', 'current', 'add', 'remove', 'delete', 'del', 'test', 'check', 'subscribe' ] }, - { id: 'details', match: 'rest', default: '' }, - ] + } }); this.init(); @@ -48,36 +45,23 @@ export default class extends ErosCommand { public userCountdowns: Collection = new Collection(); - public authorized (user: User) { - return this.client.config.countdownAuthorized.includes(user.id); - } - - public async exec (message: Message, { method, details }: { method: string, details: string }) { - const authorized = this.authorized(message.author); - - if ( - !method || - (!authorized && ![ 'test', 'check', 'subscribe', 'current', 'help' ].includes(method)) - ) return this.defaultCommand(message); - if (method === 'current') return message.util.reply(`Current time is: ${moment.tz(this.timezone)}`); - if (method === 'help') return this.authorisedHelp(message); - - const commands: { [key: string]: ErosCommand } = { - add: this.handler.modules.get('countdown-add'), - del: this.handler.modules.get('countdown-delete'), - delete: this.handler.modules.get('countdown-delete'), - remove: this.handler.modules.get('countdown-delete'), - test: this.handler.modules.get('countdown-test'), - check: this.handler.modules.get('countdown-test'), - subscribe: this.handler.modules.get('countdown-subscribe') + public * args () { + const child = yield { + type: [ + [ 'countdown-add', 'add' ], + [ 'countdown-delete', 'remove', 'del', 'delete' ], + [ 'countdown-test', 'test', 'check' ], + [ 'countdown-subscribe', 'subscribe', 'sub' ], + [ 'countdown-current', 'current', 'now' ], + [ 'countdown-help', 'help' ], + ] }; - const command = commands[method]; - return this.handler.handleDirectCommand(message, details, command); + return child ? Flag.continue(child) : { }; } - public async defaultCommand (message: Message) { - const embed = this.util.embed(message); + public async exec (message: Message) { + const embed = this.client.embed(message); await this.prepareCountdowns(); @@ -87,25 +71,6 @@ export default class extends ErosCommand { return message.util.send(embed); } - public async authorisedHelp (message: Message) { - const prefix = await this.handler.prefix(message); - const embed = this.util.embed(message) - .setColor(0xFF00AE) - .addField('Adding a Countdown', [ - `❯ Usage: ${prefix}countdown add `, - '❯ Date Format: [YYYY]-[MM]-[DD]T[HH]:[mm]', - '❯ Note: Date has to be provided in PDT. https://time.is/PDT', - '❯ Note: Naming can also affect the countdown notifications, so be careful when to append `- End`!', - ]) - .addField('Removing a Countdown', `❯ Usage: ${prefix}countdown remove `) - .addField('Testing a Countdown', [ - `❯ Usage: ${prefix}countdown test `, - '❯ Same date format from adding a countdown.', - ]); - - return message.util.send(embed); - } - public async prepareCountdowns () { const now = moment.tz(this.timezone); diff --git a/src/commands/countdown/current.ts b/src/commands/countdown/current.ts new file mode 100644 index 00000000..3f7d19ba --- /dev/null +++ b/src/commands/countdown/current.ts @@ -0,0 +1,19 @@ +import { Message } from 'discord.js'; +import * as moment from 'moment-timezone'; +import Command from '../../struct/command'; +import CountdownCommand from './countdown'; + +export default class extends Command { + constructor () { + super('countdown-current', { + description: { content: 'Shows the current date and time.' }, + ratelimit: 2 + }); + } + + public async exec (message: Message) { + const parent = this.handler.modules.get('countdown') as CountdownCommand; + + return message.util.reply(`Current time is: ${moment.tz(parent.timezone)}`); + } +} diff --git a/src/commands/countdown/delete.ts b/src/commands/countdown/delete.ts index 00071244..98756bf5 100644 --- a/src/commands/countdown/delete.ts +++ b/src/commands/countdown/delete.ts @@ -1,8 +1,8 @@ import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; import CountdownCommand from './countdown'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('countdown-delete', { description: { @@ -18,7 +18,7 @@ export default class extends ErosCommand { match: 'rest', prompt: { start: 'what countdown should be deleted?', - retry: (_, __, input: { phrase: string }) => + retry: (_, input: { phrase: string }) => ` **${input.phrase}** does not exist. Please provide again.` } }, @@ -32,6 +32,7 @@ export default class extends ErosCommand { if (!found) return message.util.reply(`countdown named \`${name}\` not found.`); + parent.userCountdowns.delete(found); parent.countdowns.delete(found); await parent.save(); diff --git a/src/commands/countdown/help.ts b/src/commands/countdown/help.ts new file mode 100644 index 00000000..a1c3a562 --- /dev/null +++ b/src/commands/countdown/help.ts @@ -0,0 +1,30 @@ +import { Message } from 'discord.js'; +import Command from '../../struct/command'; + +export default class extends Command { + constructor () { + super('countdown-help', { + description: { content: 'Shows countdown sub-commands\'s guidelines.' }, + ratelimit: 2 + }); + } + + public async exec (message: Message) { + const prefix = await this.handler.prefix(message); + const embed = this.client.embed(message) + .setColor(0xFF00AE) + .addField('Adding a Countdown', [ + `**Usage**: ${prefix}countdown add `, + '**Date** Format: [YYYY]-[MM]-[DD]T[HH]:[mm]', + '**Note**: Date has to be provided in PDT. https://time.is/PDT', + '**Note**: Naming can also affect the countdown notifications, so be careful when to append `- End`!', + ]) + .addField('Removing a Countdown', `**Usage**: ${prefix}countdown remove `) + .addField('Testing a Countdown', [ + `**Usage**: ${prefix}countdown test `, + 'Same date format from adding a countdown.', + ]); + + return message.util.send(embed); + } +} diff --git a/src/commands/countdown/subscribe.ts b/src/commands/countdown/subscribe.ts index e891bf84..5032e3c8 100644 --- a/src/commands/countdown/subscribe.ts +++ b/src/commands/countdown/subscribe.ts @@ -1,11 +1,11 @@ import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('countdown-subscribe', { description: { - content: 'Lets you subscribe to countdown notifications to this server. Toggle-able command.' + content: 'Lets you subscribe to countdown notifications to the server. Toggle-able command.' }, clientPermissions: [ 'MANAGE_ROLES' ], channel: 'guild', diff --git a/src/commands/countdown/test.ts b/src/commands/countdown/test.ts index 68698639..1aee46d0 100644 --- a/src/commands/countdown/test.ts +++ b/src/commands/countdown/test.ts @@ -1,9 +1,9 @@ import { Message } from 'discord.js'; import * as moment from 'moment'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; import CountdownCommand from './countdown'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('countdown-test', { description: { diff --git a/src/commands/fun/ask.ts b/src/commands/fun/ask.ts index 31aa7004..cce7a5f0 100644 --- a/src/commands/fun/ask.ts +++ b/src/commands/fun/ask.ts @@ -15,7 +15,7 @@ export default class extends ErosComamnd { super('ask', { aliases: [ 'ask', '8ball' ], description: { - content: 'Ask a question to a random Kamihime Project Character.', + content: 'Ask a question to a random Kamihime PROJECT Character.', usage: '?', examples: [ 'Am I a rawricon?', 'Will I pull hecatonchires again (oh gods please no)?' ] }, @@ -48,12 +48,12 @@ export default class extends ErosComamnd { if (!response.ok) return message.util.edit('There was a problem: ' + response.statusText); const { id, name, avatar } = (await cherryResponse.json() as IKamihimeDB[]).shift(); - const embed = this.util.embed() + const embed = this.client.embed() .setAuthor(name, null, url.root + `info/${id}`) .setThumbnail(url.root + encodeURIComponent(`img/wiki/${avatar}`)) .setDescription(punctations(type, answer)); - return message.util.edit(embed); + return message.util.edit(null, embed); } } diff --git a/src/commands/fun/mock.ts b/src/commands/fun/mock.ts new file mode 100644 index 00000000..172ea0dd --- /dev/null +++ b/src/commands/fun/mock.ts @@ -0,0 +1,36 @@ +import { Message } from 'discord.js'; +import ErosComamnd from '../../struct/command'; + +export default class extends ErosComamnd { + constructor () { + super('mock', { + aliases: [ 'mock' ], + description: { + content: 'Lets you say something in the chat in my stead but it is retarded.', + usage: '', + examples: [ 'rawr', 'ur mum ghei' ] + }, + channel: 'guild', + lock: 'user', + args: [ + { + id: 'words', + match: 'rest', + default: null, + prompt: { + start: 'what would you like to say?', + retry: 'you got nothing to say? The hell?' + } + }, + ] + }); + } + + public async exec (message: Message, { words }: { words: string }) { + if (message.deletable) await message.delete(); + + const retardedWords = words.split('').map((v, i) => !(i % 2) ? v.toLowerCase() : v.toUpperCase()).join(''); + + return message.util.send(retardedWords); + } +} diff --git a/src/commands/fun/owo.ts b/src/commands/fun/owo.ts new file mode 100644 index 00000000..a7f54aca --- /dev/null +++ b/src/commands/fun/owo.ts @@ -0,0 +1,67 @@ +import { Flag } from 'discord-akairo'; +import { Message } from 'discord.js'; +import fetch from 'node-fetch'; +import { URL, URLSearchParams } from 'url'; +import ErosComamnd from '../../struct/command'; + +const FULL_OF_DETERMINATION = [ + 'Ny— not gonna happen.', + 'Are you taking me for a cat?', + 'Hee... you have that kind of fetish?', + 'I-it\'s not like I d-don\'t want to... b-but... ny... nyaaAAAAaaaA *runs away from embarassment*', + 'H-how did you know that I have... c-cat ears? *quivers in fear*', +]; + +export default class extends ErosComamnd { + constructor () { + super('owo', { + aliases: [ 'owo', 'owoify' ], + description: { + content: 'Wets you say somethinyg iny the chat iny my stead (=ↀωↀ=)✧', + usage: '', + examples: [ 'rawr', 'nyaaa' ] + }, + channel: 'guild', + lock: 'user' + }); + } + + public async * args (message: Message) { + if (([ 0, 1, 0 ][Math.floor(Math.random() * 3)]) === 0) { + await message.channel.send(FULL_OF_DETERMINATION[Math.floor(Math.random() * FULL_OF_DETERMINATION.length)]); + + return Flag.cancel(); + } + + const words = yield { + match: 'rest', + default: null, + prompt: { + modifyStart: (msg: Message) => [ + `${msg.author}, *tilts head* what wouwd you wike to say?`, + '\ntype `canycew` to canycew this commanyd ヾ(=`ω´=)ノ', + ], + cancelWord: 'canycew', + cancel: '*nods* okay if that\'s what you wanyt!', + timeout: '*yawns* awe you fwozeny ow what?' + } + }; + + return { words }; + } + + public async exec (message: Message, { words: text }: { words: string }) { + if (message.deletable) await message.delete(); + + const url = new URL('https://nekos.life/api/v2/owoify'); + url.search = new URLSearchParams({ text }).toString(); + + const res = await fetch(url.toString()); + + if (!res.ok) return message.util.send('*sobs* sowwy onyii-chany, I canynyot finyd nyeko-nyee...'); + + const { owo }: { owo: string } = await res.json(); + + return message.util.send(owo); + } +} diff --git a/src/commands/fun/quiz.ts b/src/commands/fun/quiz.ts index 332385eb..2431e070 100644 --- a/src/commands/fun/quiz.ts +++ b/src/commands/fun/quiz.ts @@ -1,4 +1,3 @@ -import { Control } from 'discord-akairo'; import { Message } from 'discord.js'; import fetch from 'node-fetch'; import { IKamihimeDB } from '../../../typings'; @@ -6,20 +5,30 @@ import ErosComamnd from '../../struct/command'; import shuffle from '../../util/shuffle'; type PromptType = 'name' | 'element' | 'rarity' | 'tier' | 'type'; +type ChoicesSupplier = (isWeapon?: boolean) => string[]; +interface IQuestionnaireSupplier { + text: string; + type: PromptType; + choices: ChoicesSupplier; +} -const QUESTIONS: Array<{ text: string, type: PromptType, choices: string[] }> = [ - { text: 'Who is this character?', type: 'name', choices: null }, +const QUESTIONS: IQuestionnaireSupplier[] = [ + { text: 'What is the name of this {{type}}?', type: 'name', choices: null }, { - text: 'What is this character\'s element?', + text: 'What is this {{type}}\'s element?', type: 'element', - choices: [ 'Fire', 'Water', 'Wind', 'Thunder', 'Light', 'Dark', 'Phantom', 'Weapon Dependent' ] + choices: () => [ 'Fire', 'Water', 'Wind', 'Thunder', 'Light', 'Dark', 'Phantom', 'Weapon Dependent' ] }, - { text: 'What is this character\'s rarity?', type: 'rarity', choices: [ 'SSR+', 'SSR', 'SR', 'R' ] }, - { text: 'What is this character\'s tier?', type: 'tier', choices: [ 'Legendary', 'Elite', 'Standard' ] }, + { text: 'What is this {{type}}\'s rarity?', type: 'rarity', choices: () => [ 'SSR+', 'SSR', 'SR', 'R' ] }, + { text: 'What is this {{type}}\'s tier?', type: 'tier', choices: () => [ 'Legendary', 'Elite', 'Standard' ] }, { - text: 'What is this character\'s type?', + text: 'What is this {{type}}\'s type?', type: 'type', - choices: [ 'Offense', 'Defense', 'Balance', 'Tricky', 'Healer' ] + choices (isWeapon) { + return isWeapon + ? [ 'Hammer', 'Lance', 'Glaive', 'Arcane', 'Staff', 'Axe', 'Gun', 'Bow', 'Sword' ] + : [ 'Offense', 'Defense', 'Balance', 'Tricky', 'Healer' ]; + } }, ]; @@ -35,52 +44,44 @@ export default class extends ErosComamnd { lock: 'channel', channel: 'guild', noTrash: true, - ratelimit: 1, - args: [ - { - id: 'rotation', - type: 'integer', - default: 1 - }, - Control.if((_, { rotation }) => rotation >= 2, [ - { - id: 'interval', - type: 'interval', - prompt: { - retry: 'interval can only be **up to 120 seconds**. Try again!' - } - }, - ], []), - ] + ratelimit: 1 }); } + public * args () { + const rotation = yield { + type: 'integer', + default: 1 + }; + + const interval = rotation >= 2 + ? yield { + type: 'interval', + prompt: { + retry: 'interval can only be **up to 120 seconds**. Try again!' + } + } + : null; + + return { rotation, interval }; + } + public async exec (message: Message, { rotation, interval }: { rotation: number, interval: number }) { if (!message.member.hasPermission('MANAGE_GUILD') && rotation > 5) rotation = 5; else if (message.member.hasPermission('MANAGE_GUILD') && rotation > 10) rotation = 10; - try { - for (let i = 0; i < rotation; i++) { - const isNotLast = i !== rotation - 1 ? interval / 1000 : null; + for (let i = 0; i < rotation; i++) { + const isNotLast = i !== rotation - 1 ? interval / 1000 : null; - await this.createQuestionnare(message, { isNotLast, rotation, current: i + 1 }); - if (interval && isNotLast) await this.sleep(interval); - } - } catch (err) { - this.emitError(err, message, this); + await this.createQuestionnare(message, { isNotLast, rotation, current: i + 1 }); + if (interval && isNotLast) await this.client.util.sleep(interval); } return true; } - protected sleep (interval: number) { - return new Promise(resolve => { - this.client.setTimeout(resolve, interval); - }); - } - // isNotLast - If a true number is passed, it'll append a notification to members that there will be another question // with the value of isNotLast (interval) protected async createQuestionnare ( @@ -102,22 +103,31 @@ export default class extends ErosComamnd { const name = `shady_pic_${Date.now()}.webp`; const filteredQuestions = QUESTIONS.filter(q => selected[q.type] !== null); const question = filteredQuestions[Math.floor(Math.random() * filteredQuestions.length)]; + const isWeapon = selected.id.charAt(0) === 'w'; + const parsed = { + text: question.text + .replace(/\{\{type\}\}/, isWeapon ? 'weapon' : 'character'), + choices: isWeapon + ? question.choices(true) + : (typeof question.choices === 'function' ? question.choices() : question.choices) + }; + const answer = selected[question.type]; let exp = Math.abs(Math.floor(Math.random() * 1000)); if (message.member.hasPermission('MANAGE_GUILD')) exp += 500; - if (question.type === 'name') question.choices = characters.map(c => c.name); + if (question.type === 'name') parsed.choices = characters.map(c => c.name); - shuffle(question.choices); + shuffle(parsed.choices); - const embed = this.util.embed() + const embed = this.client.embed() .setImage(`attachment://${name}`) .setAuthor( `Questionnaire (${current} of ${rotation}) triggered by ${message.author.tag}`, message.author.displayAvatarURL({ format: 'webp' }) ) - .setTitle(question.text) - .setDescription(question.choices.map((v, i) => `**${i + 1}** - \`${v}\`\n`)) + .setTitle(parsed.text) + .setDescription(parsed.choices.map((v, i) => `**${i + 1}** - \`${v}\`\n`)) .setFooter(`For ${exp} EXP • Ends within 30 seconds`); const attachment = this.client.util.attachment(avatar, name); const nextQuestionMsg = isNotLast @@ -133,7 +143,7 @@ export default class extends ErosComamnd { if (isNaN(content)) return false; - const isAnswer = question.choices[content - 1] === answer; + const isAnswer = parsed.choices[content - 1] === answer; return isAnswer; }, { max: 1, time: 30 * 1000, errors: [ 'time' ] }); @@ -143,8 +153,7 @@ export default class extends ErosComamnd { where: { user: userResponse.author.id, guild: message.guild.id - }, - attributes: [ 'id' ] + } }); await member.increment('exp', { by: exp }); diff --git a/src/commands/general/guide-pages/commands/countdown/assets/current.ts b/src/commands/general/guide-pages/commands/countdown/assets/current.ts new file mode 100644 index 00000000..f39485bf --- /dev/null +++ b/src/commands/general/guide-pages/commands/countdown/assets/current.ts @@ -0,0 +1,6 @@ +/* tslint:disable:max-line-length */ + +export default { + contributors: [ 'Euni' ], + command: 'countdown-current' +} as IDialog; diff --git a/src/commands/general/guide-pages/commands/countdown/assets/help.ts b/src/commands/general/guide-pages/commands/countdown/assets/help.ts new file mode 100644 index 00000000..413a1ed4 --- /dev/null +++ b/src/commands/general/guide-pages/commands/countdown/assets/help.ts @@ -0,0 +1,6 @@ +/* tslint:disable:max-line-length */ + +export default { + contributors: [ 'Euni' ], + command: 'countdown-help' +} as IDialog; diff --git a/src/commands/general/guide-pages/commands/set/assets/loli.ts b/src/commands/general/guide-pages/commands/fun/assets/mock.ts similarity index 81% rename from src/commands/general/guide-pages/commands/set/assets/loli.ts rename to src/commands/general/guide-pages/commands/fun/assets/mock.ts index 670d4fe0..5c8e6006 100644 --- a/src/commands/general/guide-pages/commands/set/assets/loli.ts +++ b/src/commands/general/guide-pages/commands/fun/assets/mock.ts @@ -2,5 +2,5 @@ export default { contributors: [ 'Euni' ], - command: 'set-loli' + command: 'mock' } as IDialog; diff --git a/src/commands/general/guide-pages/commands/fun/assets/owo.ts b/src/commands/general/guide-pages/commands/fun/assets/owo.ts new file mode 100644 index 00000000..c200e0bc --- /dev/null +++ b/src/commands/general/guide-pages/commands/fun/assets/owo.ts @@ -0,0 +1,6 @@ +/* tslint:disable:max-line-length */ + +export default { + contributors: [ 'Euni' ], + command: 'owo' +} as IDialog; diff --git a/src/commands/general/guide-pages/commands/index.ts b/src/commands/general/guide-pages/commands/index.ts index a13c3553..5c716c8c 100644 --- a/src/commands/general/guide-pages/commands/index.ts +++ b/src/commands/general/guide-pages/commands/index.ts @@ -16,8 +16,7 @@ export default [ contributors: [ 'Euni' ], title: 'Commands', description: [ - '`help [command]` will display brief information and `guide` command will display newbie-friendly information of the command specified.', - 'Make sure you gather information for that command on both `help` and `guide` commands!', + '`help ` will display information about a command.', `\nStill not clear enough? Submit an issue [**here**](${bugs.url})`, ], fields: [ @@ -27,6 +26,10 @@ export default [ 'Texts that are enclosed with `[]` or `<>` meant they are placeholders.', 'You use it as **`@Eros info eros -tw`**, not as **`@Eros info [flags]`**!', '\n`[]` means __optional__ | `<>` means __required__', + '---', + 'Some commands that has multiple arguments can take an argument that has spaces, only if the argument is surrounded by double quotes.', + 'Example: **`@Eros tag edit "am i joke to you" i think so`**', + 'Where `"am i joke to you"` is the tag name while `i think so` is the tag content.', ] }, ] diff --git a/src/commands/general/guide-pages/commands/kamihime/assets/glossary.ts b/src/commands/general/guide-pages/commands/kamihime/assets/glossary.ts new file mode 100644 index 00000000..ee779d34 --- /dev/null +++ b/src/commands/general/guide-pages/commands/kamihime/assets/glossary.ts @@ -0,0 +1,6 @@ +/* tslint:disable:max-line-length */ + +export default { + contributors: [ 'Euni' ], + command: 'glossary' +} as IDialog; diff --git a/src/commands/general/guide-pages/commands/kamihime/assets/hareminfo.ts b/src/commands/general/guide-pages/commands/kamihime/assets/hareminfo.ts index 65b5b98e..f0be485d 100644 --- a/src/commands/general/guide-pages/commands/kamihime/assets/hareminfo.ts +++ b/src/commands/general/guide-pages/commands/kamihime/assets/hareminfo.ts @@ -20,7 +20,6 @@ export default { value: [ '`@Eros set nsfwchannel` must be set or I will decline your request.', '`@Eros set nsfwrole` must be set if you would like me to assign NSFW role to gain access to the NSFW channel.', - '`@Eros set loli` is optional if you hate embedding loli contents from the game. Toggle-able command.', ] }, { diff --git a/src/commands/general/guide-pages/commands/kamihime/assets/info.ts b/src/commands/general/guide-pages/commands/kamihime/assets/info.ts index 64b3386a..14dd6808 100644 --- a/src/commands/general/guide-pages/commands/kamihime/assets/info.ts +++ b/src/commands/general/guide-pages/commands/kamihime/assets/info.ts @@ -28,14 +28,18 @@ export default { '`-p`, `--preview` requests to show the item\'s image', '\u200b', '__Each flag is only compatible with Souls__', - '`-m`, `--mex` requests character\'s Master Extra Abilities', + '`-m`, `--mex` requests character\'s Master Extra Abilities (MEX)', + '\u200b', + '__Each flag is only compatible with Weapons and Kamihime with FLB-able Weapon__', + '`-f`, `--flb` requests weapon\'s Final Limit Break (FLB) values. Will work with or without `--release` flag.', ] }, { name: 'Emoji Reacts To Interact', value: [ '🖼 — Toggle image', - '🔄 — **only for kamihime/weapon/soul**— See Kamihime / Weapon / Soul\'s Master Extra Abilities', + '🔄 — **only for kamihime/weapon/soul**— See Kamihime / Weapon / Soul\'s MEX', + '`SSR+ Emoji` — **only for weapons**— Toggle FLB values', ] }, ] diff --git a/src/commands/general/guide-pages/commands/kamihime/assets/list.ts b/src/commands/general/guide-pages/commands/kamihime/assets/list.ts index 1bace534..f666e1a7 100644 --- a/src/commands/general/guide-pages/commands/kamihime/assets/list.ts +++ b/src/commands/general/guide-pages/commands/kamihime/assets/list.ts @@ -11,5 +11,19 @@ export default { 'Required variables can be seen via `@Eros list variables`.', 'Variables can be combined, but variables will always start with __Primary Variables__ such as:', '\t`kamihime`, `eidolon`, `soul`', + ], + fields: [ + { + name: 'Flags: Options For Sorting', + value: [ + [ + 'Default is by name. Other options are:', + [ 'rarity', 'tier', 'element', 'type', 'atk', 'hp', 'ttl' ] + .map(el => `\`${el}\``) + .join(', '), + 'Append `-asc` or `-desc` to sort by `Ascending` or `Descending` respectively.', + ], + ] + }, ] } as IDialog; diff --git a/src/commands/general/guide-pages/commands/tag/assets/1_tag.ts b/src/commands/general/guide-pages/commands/tag/assets/1_tag.ts index d2cca684..6d939dd8 100644 --- a/src/commands/general/guide-pages/commands/tag/assets/1_tag.ts +++ b/src/commands/general/guide-pages/commands/tag/assets/1_tag.ts @@ -6,7 +6,7 @@ export default { fields: [ { name: 'Warning', - value: 'You can affect tags created in this server only.' + value: 'You can affect tags created in the server only.' }, { name: 'How It Works', diff --git a/src/commands/general/guide-pages/commands/tag/assets/add.ts b/src/commands/general/guide-pages/commands/tag/assets/add.ts index b41736ac..be6c60f4 100644 --- a/src/commands/general/guide-pages/commands/tag/assets/add.ts +++ b/src/commands/general/guide-pages/commands/tag/assets/add.ts @@ -6,7 +6,7 @@ export default { fields: [ { name: 'Warning', - value: 'Tags created in this server cannot be carried over to other servers.' + value: 'Tags created in the server cannot be carried over to other servers.' }, ] } as IDialog; diff --git a/src/commands/general/guide-pages/index.ts b/src/commands/general/guide-pages/index.ts index 9316610c..8ce50c3c 100644 --- a/src/commands/general/guide-pages/index.ts +++ b/src/commands/general/guide-pages/index.ts @@ -19,21 +19,22 @@ export default [ description: 'Are you familiar with the game? No? These links may help you... but maybe for the last section...', fields: [ { - name: 'Kamihime Project Wiki', - value: `[DMM Wiki](https://goo.gl/xPVW9t) | [Nutaku Version Fandom](https://kamihime-project.fandom.com) ([Discord](${wikiDiscord}))`, + name: 'Kamihime PROJECT Wiki', + value: `[DMM JP Wiki](https://goo.gl/xPVW9t) | [DMM/Nutaku EN Fandom](https://kamihime-project.fandom.com) ([Discord](${wikiDiscord}))`, inline: true }, { - name: 'Kamihime Project Forums', - value: '[Kamihime Project - Harem Time Forums](http://harem-battle.club/kamihime-project/)', + name: 'Kamihime PROJECT Forums', + value: '[Kamihime PROJECT - Harem Time Forums](http://harem-battle.club/kamihime-project/)', inline: true }, { name: '[Beginner] Guides', value: [ - 'Nutaku Version Fandom: [Various Game Guide Articles](https://kamihime-project.fandom.com/wiki/Category:Mechanics)', + 'DMM/Nutaku EN Fandom: [Various Game Guide Articles](https://kamihime-project.fandom.com/wiki/Category:Mechanics)', 'Sanahtlig: [Toolbox](https://goo.gl/bP43qi) | [Game Guide](https://goo.gl/YMcg1h) | [Re-rolling: How to get FREE SSR Kamihime](https://goo.gl/eJffLx)', 'J-Star: [Weapon Guide](https://goo.gl/gGwvUX) | [Weapon Grids Template](https://goo.gl/dhrwgk)', + 'Sfayne: [Accessory Calculator](https://docs.google.com/spreadsheets/d/1aTlsOgX0o7obAhpRK2GIABRMCE0A5UitAfWzze8fQjA/edit#gid=2129149280)', ], inline: true }, @@ -71,13 +72,12 @@ export default [ 'Set Countdown Subscriber role: `@Eros set cdrole `', 'Set hareminfo-allowed channel: `@Eros set nsfwchannel `', 'Set NSFW role: `@Eros set nsfwrole `', - 'Disable/Enable loli contents (hareminfo): `@Eros set loli`', 'To view your server\'s current settings: `@Eros settings`', ] }, { name: 'Getting Familiar With the Commands', - value: 'Please refer to [Web Documentation](https://docs.thegzm.space/eros-bot) or to `guide` command\'s pages 5 and above.' + value: 'Please refer to [Web Documentation](https://docs.thegzm.space/eros-bot/commands) or do `@Eros help`.' }, ] }, @@ -131,7 +131,7 @@ export default [ { name: 'Contacts', value: [ - `[**Nutaku Fandom Discord** (further gameplay questions)](${wikiDiscord})`, + `[**Kamihime EN Version Fandom Discord** (further gameplay questions)](${wikiDiscord})`, `[**Github Issue Tracker** (bot-related only)](${bugs.url})`, ] }, diff --git a/src/commands/general/guide.ts b/src/commands/general/guide.ts index a7a25f7d..86ad07e4 100644 --- a/src/commands/general/guide.ts +++ b/src/commands/general/guide.ts @@ -1,10 +1,11 @@ -import { Message, StringResolvable, TextChannel, Util } from 'discord.js'; +import { Message, StringResolvable, Util } from 'discord.js'; +import { MessageEmbed } from 'discord.js'; import * as fs from 'fs-extra'; import * as json2md from 'json2md'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; import toTitleCase from '../../util/toTitleCase'; -export default class extends ErosCommand { +export default class GuideCommand extends Command { constructor () { super('guide', { aliases: [ 'guide' ], @@ -23,138 +24,142 @@ export default class extends ErosCommand { ] }); - this.init(); + this.dialogs = require('./guide-pages').default; } public dialogs: IDialog[]; + public formattedGeneralDialogs: MessageEmbed[]; + + public formattedCommandDialogs: { [id: string]: MessageEmbed }; + public init () { - this.dialogs = require('./guide-pages').default; + this.formattedCommandDialogs = this.formatCommandDialogs(); + this.formattedGeneralDialogs = this.formatGeneralDialogs(); + + return this.client.logger.info('Guide Command: Initialised.'); } public async exec (message: Message, { page }: { page: number }) { - try { - const { docs, emojis } = this.client.config; - const embeds = this.dialogs.map((v, i) => { - if (!v) - throw new Error( - `Empty Dialog: Before ${ - this.dialogs[i - 1].title || - this.dialogs[i - 1].category || - this.dialogs[i - 1].command - }` - ); + return this.client.embeds(message, this.formattedGeneralDialogs) + .setAuthorizedUsers([ message.author.id ]) + .setChannel(message.channel) + .setPage(page) + .setTimeout(120e3) + .build(); + } - let title = v.title || null; - let description: StringResolvable = v.description || ''; - let example: string[] = null; - const embed = this.util.embed(); + public formatGeneralDialogs () { + const { docs } = this.client.config; + const generalDialogs = this.dialogs.filter(d => !d.category && !d.command); + const embeds = generalDialogs.map((v, i) => { + if (!v) + throw new Error(`Empty Dialog: Before ${ generalDialogs[i - 1].title}`); - if (v.category) { - const category = this.handler.categories.get(v.category); + const embed = this.client.embed(); - if (!category) throw new Error('Invalid Category ' + v.category); + embed + .setTitle(v.title) + .setDescription(v.description || '') + .setImage(v.image); - title = `Category: ${v.category.toLowerCase()}`; - description = [ - 'Commands within this category:', - category.map(c => { - let content: string | string[] = c.description.content; - content = Array.isArray(content) ? content[0] : content; - const id = /-/.test(c.id) ? c.id.split('-').join(' ') : c.id; + if (v.fields) + for (const field of v.fields) + embed.addField(field.name, field.value, field.inline || false); - return `**\`${id}\`** - ${content}`; - }).join('\n'), - ]; - } else if (v.command) { - const command = this.handler.modules.get(v.command); + if (v.contributors) + embed + .addBlankField() + .addField('Contributors', v.contributors.join(', ')); - if (!command) throw new Error('Invalid Command ' + v.command); + return embed; + }); + const longest = generalDialogs.reduce((long, v) => Math.max(long, v.title.length), 0); + const tableOfContents = this.client.embed() + .setTitle('Table of Contents (Pages)') + .setDescription([ + '```asciidoc', + generalDialogs + .map((v, i) => `${v.title}${' '.repeat(longest - v.title.length)} :: ${i + 2}`) + .join('\n'), + '```', + ]) + .addField('Navigation Tip', [ + 'React with the emoji below to navigate. ↗ to skip a page.', + `You may also do \`@Eros guide \` to jump to a page immediately.`, + ]) + .addField('Documentation', 'Visiting the web documentation may be better to see what is new: ' + docs); + + embeds.unshift(tableOfContents); + + return embeds; + } - const hasAliases = command.aliases && command.aliases.length; - let content: string | string[] = command.description.content; - content = Array.isArray(content) ? content.join('\n') : content; - const id = /-/.test(v.command) ? v.command.split('-').join(' ') : v.command; + public formatCommandDialogs () { + const embeds: { [id: string]: MessageEmbed } = {}; + const commandDialogs = this.dialogs.filter(d => d.command); - title = `Command: ${id.toLowerCase()}`; - description = [ - `**Usage**: \`@Eros ${id} ${command.description.usage || ''}\``, - `**Aliases**: ${hasAliases ? command.aliases.map(c => `\`${c}\``).join(', ') : 'None'}`, - `**Brief Description**: ${content}`, - '', - ...description, - ]; + commandDialogs.map((d, i) => { + if (!d) + throw new Error(`Empty Dialog: Before ${commandDialogs[i - 1].command}`); - if (command.description.examples) - example = command.description.examples.map(c => `@Eros ${id} ${c}`); - } + let title = d.title || null; + let description: StringResolvable = d.description || ''; + let example: string[] = null; + const embed = this.client.embed(); + const command = this.handler.modules.get(d.command); - embed - .setTitle(title) - .setDescription(description) - .setImage(v.image); + if (!command) throw new Error('Invalid Command ' + d.command); - if (v.fields) - for (const field of v.fields) - embed.addField(field.name, field.value, field.inline || false); + const hasAliases = command.aliases && command.aliases.length; + let content: string | string[] = command.description.content; + content = Array.isArray(content) ? content.join('\n') : content; + const id = /-/.test(d.command) ? d.command.split('-').join(' ') : d.command; + const clientPermissions = command.clientPermissions as string[]; + const userPermissions = command.userPermissions as string[]; - if (example) - embed.addField('Examples', example); + title = `Command: ${id.toLowerCase()}`; + description = [ + `**Usage**: \`@Eros ${id} ${command.description.usage || ''}\``, + `**Aliases**: ${hasAliases ? command.aliases.map(c => `\`${c}\``).join(', ') : 'None'}`, + `**Brief Description**: ${content}`, + '', + ...description, + ]; - if (v.contributors) - embed - .addBlankField() - .addField('Contributors', v.contributors.join(', ')); + if (command.description.examples) + example = command.description.examples.map(c => `@Eros ${id} ${c}`); - return embed; - }); - const longest = this.dialogs.reduce((long, v) => { - let title = v.title; - - if (v.category) title = ` + CATEGORY: ${v.category.toLowerCase()}`; - if (v.command) title = ` | ${v.command.toLowerCase()}`; - - return Math.max(long, title.length); - }, 0); - const tableOfContents = this.util.embed() - .setTitle('Table of Contents (Pages)') - .setDescription([ - '```asciidoc', - this.dialogs - .map((v, i) => { - let title = v.title; - - if (v.category) title = ` + CATEGORY: ${v.category.toLowerCase()}`; - if (v.command) title = ` | ${v.command.toLowerCase()}`; - - const space = ' '.repeat(longest - title.length); - - return `${title}${space} :: ${i + 2}`; - }) - .join('\n'), - '```', - ]) - .addField('Navigation Tip', [ - 'React with the emoji below to navigate. ↗ to skip a page.', - `You may also do \`${ - await this.handler.prefix(message)}guide \` to jump to a page immediately.`, - ]) - .addField('Documentation', 'Visiting the web documentation may be better to see what is new: ' + docs); - - embeds.unshift(tableOfContents); - - return this.util.embeds(message, embeds) - .setAuthorizedUsers([ message.author.id ]) - .setChannel(message.channel as TextChannel) - .setClientAssets({ prepare: `${emojis.loading} Preparing...` }) - .setPage(page) - .setTimeout(240 * 1000) - .showPageIndicator(true) - .build(); - } catch (err) { this.emitError(err, message, this); } + embed + .setTitle(title) + .setDescription(description) + .setImage(d.image); + + if (clientPermissions) + embed.addField('Required Bot Permissions', clientPermissions.map(p => `\`${toTitleCase(p)}\``).join(', ')); + + if (userPermissions) + embed.addField('Required User Permissions', userPermissions.map(p => `\`${toTitleCase(p)}\``).join(', ')); + + if (d.fields) + for (const field of d.fields) + embed.addField(field.name, field.value, field.inline || false); + + if (example) + embed.addField('Examples', example); + + if (d.contributors) + embed + .addBlankField() + .addField('Contributors', d.contributors.join(', ')); + + embeds[d.command] = embed; + }); + + return embeds; } - public async parseDialogs () { + public async renderDialogs () { try { const dialogs = this.dialogs.map((v, i) => { if (!v) @@ -299,7 +304,7 @@ export default class extends ErosCommand { for (const dialog of dialogs) try { - await fs.outputFile(`${__dirname}/../../../../eros-docs/${dialog[0]}`, json2md(dialog[1])); + await fs.outputFile(`${__dirname}/../../../docs/${dialog[0]}`, json2md(dialog[1])); this.client.logger.info(`-- Successfully parsed ${dialog[0]}`); } catch (err) { throw new Error(`Failed to write ${dialog[0]}: ${err}`); } @@ -307,7 +312,7 @@ export default class extends ErosCommand { await Promise.all( [ 'CHANGELOG', 'MIGRATING', 'README' ].map(async f => { const src = `${__dirname}/../../../${f}.md`; - const dest = `${__dirname}/../../../../eros-docs/${f}.md`; + const dest = `${__dirname}/../../../docs/${f}.md`; await fs.copyFile(src, dest); this.client.logger.info(`-- Successfully copied ${f}`); diff --git a/src/commands/general/help.ts b/src/commands/general/help.ts index a6b94cc3..b4648d63 100644 --- a/src/commands/general/help.ts +++ b/src/commands/general/help.ts @@ -1,8 +1,8 @@ +import { Argument } from 'discord-akairo'; import { Message, TextChannel } from 'discord.js'; -import ErosCommand from '../../struct/command'; -import toTitleCase from '../../util/toTitleCase'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('help', { aliases: [ 'help', 'commands' ], @@ -14,7 +14,7 @@ export default class extends ErosCommand { args: [ { id: 'command', - type: 'commandAlias' + type: Argument.union('commandAlias', 'command') }, { id: 'pub', @@ -26,42 +26,22 @@ export default class extends ErosCommand { }); } - public async exec (message: Message, { command, pub }: { command: ErosCommand, pub: boolean }) { - const prefix = await this.handler.prefix(message); + public async exec (message: Message, { command, pub }: { command: Command, pub: boolean }) { if (!command) return this.defaultHelp(message, pub); - const clientPermissions = command.clientPermissions as string[]; - const userPermissions = command.userPermissions as string[]; - const examples: string[] = command.description.examples; - const guidePage = command.guidePage; - - const embed = this.util.embed(message) - .setTitle(`${prefix}${command} ${command.description.usage ? command.description.usage : ''}`) - .setDescription(command.description.content); - - if (clientPermissions) - embed.addField('Required Bot Permissions', clientPermissions.map(p => `\`${toTitleCase(p)}\``).join(', ')); - if (userPermissions) - embed.addField('Required User Permissions:', userPermissions.map(p => `\`${toTitleCase(p)}\``).join(', ')); - if (command.aliases.length > 1) - embed.addField('Aliases', command.aliases.slice(1).map(a => `\`${a}\``).join(', ')); - if (examples) - embed.addField('Examples', examples.map(e => `${prefix}${command} ${e}`).join('\n')); - if (guidePage) - embed.addField('Guide Page', `To see more description of this command: \`${prefix}guide ${guidePage}\``); - - return message.util.send(embed); + return message.util.send(command.guidePage); } public async defaultHelp (message: Message, pub = false) { const prefix = await this.handler.prefix(message); - const embed = this.util.embed(message) + const embed = this.client.embed(message) .setColor(0xFF00AE) .setTitle('Commands') .setDescription([ message.guild ? `This server's prefix is \`${prefix}\`` : '', - `For more info about a command, see: \`${prefix}help [command name]\``, - `For an in-depth guide on how to use this bot, see: \`${prefix}guide\``, + `For more info about a command, see: \`${prefix}help \``, + `For a guide on how to set up this bot, see: \`${prefix}guide\``, + `To view the full documentation, see: ${this.client.config.docs}`, !message.guild // tslint:disable-next-line:prefer-template ? '\nThere are commands that are only usable in servers.' + diff --git a/src/commands/general/invite.ts b/src/commands/general/invite.ts index f0c37ea5..66394555 100644 --- a/src/commands/general/invite.ts +++ b/src/commands/general/invite.ts @@ -1,7 +1,7 @@ import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('invite', { aliases: [ 'invite', 'addbot', 'inviteme' ], @@ -12,7 +12,7 @@ export default class extends ErosCommand { public async exec (message: Message) { const { docs, inviteLink } = this.client.config; const owner = await this.client.users.fetch(this.client.ownerID as string); - const embed = this.util.embed(message) + const embed = this.client.embed(message) .setThumbnail(this.client.user.displayAvatarURL()) .setDescription([ `Documentation: <${docs}>`, diff --git a/src/commands/kamihime/glossary.ts b/src/commands/kamihime/glossary.ts new file mode 100644 index 00000000..e57e1745 --- /dev/null +++ b/src/commands/kamihime/glossary.ts @@ -0,0 +1,74 @@ +import { Message } from 'discord.js'; +import Command from '../../struct/command'; +import InfoCommand from './info.js'; + +export default class extends Command { + constructor () { + super('glossary', { + aliases: [ 'glossary', 'g', 'dictionary', 'dict', 'term' ], + description: { + content: [ + 'Displays definition(s) from Kamihime EN Fandom\'s Glossary page.', + '', + 'To display specific results:', + '- **Single Character**: Returns a list of definitions starting with that character.', + '- **Word**: Returns a definition.', + ], + usage: '', + examples: [ 'a', '1', 'mvp' ] + }, + args: [ + { + id: 'keyword', + match: 'text', + type: 'lowercase', + prompt: { + start: 'what definition(s) from glossary would you like to see?' + } + }, + ] + }); + } + + public glossary: { [character: string]: string[] } = {}; + + public async exec (message: Message, { keyword }: { keyword: string }) { + const section = this.glossary[keyword.charAt(0)]; + + if (!section) return message.util.reply(`There's no section for input **${keyword}**.`); + + const data = keyword.length === 1 + ? section.map(el => el.replace(/(.*) -/g, '- **$1**: ')) + : section.find(l => l.toLowerCase().startsWith(keyword)); + + if (!data) return message.util.reply(`Nothing found with input **${keyword}**`); + + return message.util.send(Array.isArray(data) ? data : data.replace(/.* - /, '') ); + } + + public async initGlossary () { + try { + const infoCommand = this.handler.modules.get('info') as InfoCommand; + const data = await infoCommand.parseArticle('Glossary', false) as string; + const sectionRegex = /==\s?(\w+)\s?==([^=]*)/g; + const capturedSections: RegExpExecArray[] = []; + let temp: RegExpExecArray; + + // tslint:disable-next-line: no-conditional-assignment + while ((temp = sectionRegex.exec(data)) != null) capturedSections.push(temp); + + for (const section of capturedSections) + Object.assign(this.glossary, { + [section[1].toLowerCase()]: section[2] + .replace(/\* +/g, '') + .replace(/'{3}(.+)'{3}/g, '$1') + .split('\n') + .filter(s => s) + }); + + this.client.setTimeout(this.initGlossary.bind(this), 36e5 * 12); + + return this.client.logger.info('Glossary Command: Initialised Glossary'); + } catch (err) { return this.client.logger.error(`Glossary Command: Error on initialising Glossary: ${err.stack}`); } + } +} diff --git a/src/commands/kamihime/hareminfo.ts b/src/commands/kamihime/hareminfo.ts index 5dd9131e..2d92e05e 100644 --- a/src/commands/kamihime/hareminfo.ts +++ b/src/commands/kamihime/hareminfo.ts @@ -1,8 +1,8 @@ import { Message, Message as MSG, TextChannel } from 'discord.js'; import { IKamihimeDB } from '../../../typings'; -import ErosInfoCommand from '../../struct/command/ErosInfoCommand'; +import InfoCommand from '../../struct/command/InfoCommand'; -export default class extends ErosInfoCommand { +export default class extends InfoCommand { constructor () { super('hareminfo', { aliases: [ 'hareminfo', 'hinfo', 'hi', 'peek', 'p' ], @@ -16,7 +16,7 @@ export default class extends ErosInfoCommand { { id: 'item', match: 'text', - type: name => { + type: (_, name) => { if (!name || name.length < 2) return null; return name; @@ -50,7 +50,7 @@ export default class extends ErosInfoCommand { if (!character || character instanceof MSG) return; return this.triggerDialog(message, character); - } catch (err) { this.emitError(err, message, this, 1); } + } catch (err) { this.handler.emitError(err, message, this, 1); } } public async triggerDialog (message: Message, result: IKamihimeDB) { @@ -60,18 +60,15 @@ export default class extends ErosInfoCommand { const guild = message.guild ? await this.client.db.Guild.findOne({ where: { id: message.guild.id }, - attributes: [ 'loli', 'nsfwChannel', 'nsfwRole' ] + attributes: [ 'nsfwChannel', 'nsfwRole' ] }) : null; - if (result.loli && guild && guild.loli) - return message.util.edit( - `${message.author}, this character has loli contents, but loli contents are restricted within this server.${ - message.author.id === message.guild.ownerID - ? ` Please configure your Loli Contents Restriction via \`${prefix}set loli\`` - : ' Please contact the server owner.' - }` - ); + if (result.loli) + return message.util.edit([ + `${message.author}, loli contents are restricted within this bot due to Discord Guidelines.`, + 'If you are insisting to see such contents, please visit https://kamihimedb.thegzm.space instead.', + ]); const harems = [ { @@ -112,14 +109,13 @@ export default class extends ErosInfoCommand { }, ]; const thumbnail = encodeURI(`${url.root}img/wiki/portrait/${result.name} Portrait.png`); - const embed = this.util.embed(message) + const embed = this.client.embed(message) .setColor(0xFF75F1) .setAuthor(result.name, null, `${url.fandom}${encodeURI(result.name)}`) .setDescription( - `${result.loli ? '**Flagged as Loli**' : ''}${ - new RegExp(result.name.replace(/[()]/g, '\\$&')).test(this.client.user.username) - ? `\n... ${this.rassedMsg[Math.floor(Math.random() * this.rassedMsg.length)]} ${emojis.embarassed}` - : ''}` + new RegExp(result.name.replace(/[()]/g, '\\$&')).test(this.client.user.username) + ? `\n... ${this.rassedMsg[Math.floor(Math.random() * this.rassedMsg.length)]} ${emojis.embarassed}` + : '' ) .setThumbnail(thumbnail); @@ -147,7 +143,7 @@ export default class extends ErosInfoCommand { const channel = message.channel as TextChannel; if (!channel.guild) - return message.util.edit(embed); + return message.util.edit(null, embed); const nsfwChannel = message.guild.channels.get(guild!.nsfwChannel) as TextChannel; @@ -159,7 +155,7 @@ export default class extends ErosInfoCommand { }`); if (channel.id === guild.nsfwChannel) - return message.util.edit(embed); + return message.util.edit(null, embed); await nsfwChannel.send(embed); @@ -167,6 +163,6 @@ export default class extends ErosInfoCommand { `${message.author}, I have sent my response at ${nsfwChannel}.`, guild.nsfwRole ? ` If you have no access to that channel, say \`${prefix}nsfw\`.` : '', ]); - } catch (err) { this.emitError(err, message, this, 1); } + } catch (err) { this.handler.emitError(err, message, this, 1); } } } diff --git a/src/commands/kamihime/info.ts b/src/commands/kamihime/info.ts index 47639fdd..88b4a0c4 100644 --- a/src/commands/kamihime/info.ts +++ b/src/commands/kamihime/info.ts @@ -1,21 +1,17 @@ -import Embeds from 'discord-paginationembed/typings/Embeds'; -import { Message, Message as MSG, MessageEmbed, TextChannel } from 'discord.js'; +import { Embeds } from 'discord-paginationembed'; +import { Message, Message as MSG, MessageEmbed } from 'discord.js'; import * as parseInfo from 'infobox-parser'; // tslint:disable-next-line:max-line-length import { IKamihimeDB, IKamihimeFandom, IKamihimeFandomKamihime, IKamihimeFandomSoul, IKamihimeFandomWeapon } from '../../../typings'; -import ErosInfoCommand from '../../struct/command/ErosInfoCommand'; -import { Eidolon, Kamihime, Soul, Weapon } from '../../struct/Info'; -import EidolonInfo from '../../struct/info/sub/EidolonInfo'; -import KamihimeInfo from '../../struct/info/sub/KamihimeInfo'; -import SoulInfo from '../../struct/info/sub/SoulInfo'; -import WeaponInfo from '../../struct/info/sub/WeaponInfo'; - -export default class extends ErosInfoCommand { +import InfoCommand from '../../struct/command/InfoCommand'; +import { EidolonInfo, KamihimeInfo, SoulInfo, WeaponInfo } from '../../struct/Info'; + +export default class extends InfoCommand { constructor () { super('info', { aliases: [ 'info', 'i', 'khinfo', 'khi', 'kh' ], description: { - content: 'Looks up for a Kamihime Project Character/Weapon at Kamihime Project Nutaku Fandom.', + content: 'Looks up for a Kamihime PROJECT Character/Weapon at Kamihime PROJECT EN Fandom.', usage: ' [flags]', examples: [ 'eros', @@ -25,6 +21,7 @@ export default class extends ErosInfoCommand { 'hell staff -tw -r', 'ea -tk -r', 'arthur -ts -m', + 'holy sword ascalon -f', ] }, cooldown: 5000, @@ -35,7 +32,7 @@ export default class extends ErosInfoCommand { { id: 'item', match: 'text', - type: name => { + type: (_, name) => { if (!name || name.length < 2) return null; return name; @@ -65,6 +62,11 @@ export default class extends ErosInfoCommand { match: 'flag', flag: [ '-m', '--mex' ] }, + { + id: 'flb', + match: 'flag', + flag: [ '-f', '--flb' ] + }, { id: 'type', match: 'option', @@ -77,12 +79,13 @@ export default class extends ErosInfoCommand { public async exec ( message: IMessage, - { item, preview, release, accurate, mex, type }: { + { item, preview, release, accurate, mex, flb, type }: { item: string, preview: boolean, release: boolean, accurate: boolean, mex: boolean, + flb: boolean, type: string } ) { @@ -90,6 +93,7 @@ export default class extends ErosInfoCommand { if (preview) message.needsPreview = true; if (release) message.needsRelease = true; if (mex) message.needsMex = true; + if (flb) message.needsFLB = true; if (type !== null) { type = { s: 'soul', @@ -106,7 +110,7 @@ export default class extends ErosInfoCommand { if (!character || character instanceof MSG) return; return this.triggerDialog(message, character); - } catch (err) { this.emitError(err, message, this, 1); } + } catch (err) { this.handler.emitError(err, message, this, 1); } } public async triggerDialog (message: IMessage, result: IKamihimeDB) { @@ -114,23 +118,23 @@ export default class extends ErosInfoCommand { await message.util.edit(`${this.client.config.emojis.loading} Awaiting Fandom's response...`, { embed: null }); const prefix = await this.handler.prefix(message) as string; const category = this.getCategory(result.id); - const info = await this.parseArticle(result.name); - let template: KamihimeInfo | EidolonInfo | SoulInfo | WeaponInfo | true; - let template2: KamihimeInfo | WeaponInfo | SoulInfo | true; + const info = await this.parseArticle(result.name) as IKamihimeFandom; + let template: KamihimeInfo | EidolonInfo | SoulInfo | WeaponInfo; + let template2: KamihimeInfo | WeaponInfo | SoulInfo; let format2: MessageEmbed; switch (category) { case 'kamihime': - template = new Kamihime(this.client, prefix, result, info); + template = new KamihimeInfo(this.client, prefix, result, info); break; case 'eidolon': - template = new Eidolon(this.client, prefix, result, info); + template = new EidolonInfo(this.client, prefix, result, info); break; case 'soul': - template = new Soul(this.client, prefix, result, info); + template = new SoulInfo(this.client, prefix, result, info); break; case 'weapon': - template = new Weapon(this.client, prefix, result, info); + template = new WeaponInfo(this.client, prefix, result, info); break; default: return message.util.edit(':x: Invalid article.'); } @@ -140,68 +144,79 @@ export default class extends ErosInfoCommand { const mex = (template.character as IKamihimeFandomSoul).mex1Name; if (releases) - template2 = await this.parseKamihime(template as WeaponInfo, message).catch(() => null); + template2 = await this.parseKamihime(template as WeaponInfo, message).catch(() => undefined); else if (releaseWeapon) - template2 = await this.parseWeapon(template as KamihimeInfo).catch(() => null); + template2 = await this.parseWeapon(template as KamihimeInfo).catch(() => undefined); else if (mex) - template2 = true; + template2 = null; + + const hpFlb = template2 + ? (template2 as WeaponInfo).character.hpFlb + : (template.character as IKamihimeFandomWeapon).hpFlb; + const format = template.format(); + const assets = { [template.constructor.name]: template }; + const array = [ format ]; + + if (typeof template2 !== 'undefined') { + format2 = mex ? (template as SoulInfo).formatMex() : template2.format(); + + array.push(format2); + Object.assign(assets, { [mex ? template.constructor.name : template2.constructor.name]: template2 }); + } + + const embed: IEmbedsEx = this.client.embeds(null, array) + .setChannel(message.channel) + .setClientAssets({ message: message.util.lastResponse }) + .setAuthorizedUsers([ message.author.id ]) + .setPageIndicator(false) + .setDisabledNavigationEmojis([ 'BACK', 'JUMP', 'FORWARD' ]) + .setTimeout(10e3); if ( ( ( message.needsRelease && ( - (template instanceof Kamihime && releaseWeapon) || - (template instanceof Weapon && releases) + (template instanceof KamihimeInfo && releaseWeapon) || + (template instanceof WeaponInfo && releases) ) ) || - (message.needsMex && template instanceof Soul && mex) - ) && template2 - ) { - const tmp = template2; - template2 = template; - template = tmp as KamihimeInfo | WeaponInfo | SoulInfo; - } - - const format = typeof template === 'boolean' ? (template2 as SoulInfo).formatMex() : template.format(); - const assets = { [template.constructor.name]: template }; - const array = [ format ]; - - if (template2) { - format2 = typeof template2 === 'boolean' ? (template as SoulInfo).formatMex() : template2.format(); - - array.push(format2); - Object.assign(assets, { [template2.constructor.name]: template2 }); - } + (message.needsMex && template instanceof SoulInfo && mex) + ) && typeof template2 !== 'undefined' + ) + embed.setPage(2); + if (message.needsFLB && hpFlb && (template instanceof WeaponInfo || template2 instanceof WeaponInfo)) + embed.setPage(template2 ? 3 : 2); if (message.needsPreview) - format.setImage(template.character.preview); + format.setImage(embed.page === 1 || (message.needsMex && mex) + ? template.character.preview + : template2.character.preview + ); - const embed: IEmbedsEx = this.util.embeds(null, array) - .setChannel(message.channel as TextChannel) - .setClientAssets({ message: message.util.lastResponse, prepare: '\u200B' }) - .setAuthorizedUsers([ message.author.id ]) - .showPageIndicator(false) - .setDisabledNavigationEmojis([ 'BACK', 'JUMP', 'FORWARD' ]) - .setTimeout(1000 * 60 * 1) - .setFunctionEmojis({ - '🖼': (_, instance: IEmbedsEx) => { - instance.needsPreview = instance.needsPreview ? false : true; + if (template.character.preview) + embed.addFunctionEmoji('🖼', (_, instance: IEmbedsEx) => { + instance.needsPreview = instance.needsPreview ? false : true; - instance.currentEmbed.setImage(instance.needsPreview ? instance.preview : null); - } + instance.currentEmbed.setImage(instance.needsPreview ? instance.preview : null); }); Object.assign(embed, { assets, needsPreview: message.needsPreview, preview: template.character ? template.character.preview : (template2 as SoulInfo).character.preview, - currentClass: typeof template !== 'boolean' ? template.constructor.name : null, - oldClass: template2 ? template2.constructor.name : null + currentClass: !message.needsMex + ? (message.needsRelease || message.needsFLB ? template2.constructor.name : template.constructor.name) + : null, + oldClass: template2 && !message.needsMex + ? (message.needsRelease || message.needsFLB ? template.constructor.name : template2.constructor.name) + : null }); - if ((releaseWeapon || releases || mex) && template && template2) + if ((releaseWeapon || releases || mex) && template && typeof template2 !== 'undefined') embed.addFunctionEmoji('🔄', (_, instance: IEmbedsEx) => { + if (instance.page === 3) return; + const tmp = instance.currentClass; instance.currentClass = instance.oldClass; instance.oldClass = tmp; @@ -213,17 +228,49 @@ export default class extends ErosInfoCommand { if (instance.needsPreview) instance.currentEmbed.setImage(instance.preview); + else + instance.currentEmbed.setImage(null); }); - return embed.build(); - } catch (err) { this.emitError(err, message, this, 2); } + if (hpFlb) { + let flb: MessageEmbed; + const weaponOnTemplate = template instanceof WeaponInfo; + + if (weaponOnTemplate) + flb = (template as WeaponInfo).formatFLB(); + else + flb = (template2 as WeaponInfo).formatFLB(); + + array.push(flb); + embed.addFunctionEmoji( + this.client.config.emojis['SSR+'].replace(//, '$1'), + (_, instance: IEmbedsEx) => { + if (instance.currentClass !== 'WeaponInfo') return; + + const page = weaponOnTemplate ? 1 : 2; + const flbPage = weaponOnTemplate ? 2 : 3; + + instance.setPage(instance.page === page ? flbPage : page); + + if (instance.needsPreview) + instance.currentEmbed.setImage(instance.preview); + else + instance.currentEmbed.setImage(null); + } + ); + } + + embed.build(); + + return true; + } catch (err) { this.handler.emitError(err, message, this, 2); } } - public async parseArticle (item: string) { + public async parseArticle (item: string, infobox = true) { const rawData = await this.client.util.getArticle(item); const sanitisedData = (data: string) => { - if (!data) throw new Error(`API returned no item named ${item} found.`); - const slicedData = data.indexOf('==') === -1 + if (!data) throw new Error(`Wiki returned no item named ${item} found.`); + const slicedData = !infobox || data.indexOf('==') === -1 ? data : data.slice(data.indexOf('{{'), data.indexOf('==')); @@ -238,6 +285,8 @@ export default class extends ErosInfoCommand { .replace(/\|([^|])/g, '\n| $1'); // Fix ugly Infobox format }; + if (!infobox) return sanitisedData(rawData); + const { general: info }: { general: IKamihimeFandom } = parseInfo(sanitisedData(rawData)); info.name = info.name.replace(/(?:\[)(.+)(?:\])/g, '($1)'); @@ -246,9 +295,9 @@ export default class extends ErosInfoCommand { public async parseKamihime (template: WeaponInfo, message: Message) { const db = await this.acquire(template.character.releases, true, true); - const infoSub = await this.parseArticle(template.character.releases); + const infoSub = await this.parseArticle(template.character.releases) as IKamihimeFandom; - return new Kamihime( + return new KamihimeInfo( this.client, await this.handler.prefix(message) as string, db.info, @@ -258,9 +307,9 @@ export default class extends ErosInfoCommand { public async parseWeapon (template: KamihimeInfo) { const db = await super.acquire(template.character.releaseWeapon, false, true); - const infoSub = await this.parseArticle(template.character.releaseWeapon); + const infoSub = await this.parseArticle(template.character.releaseWeapon) as IKamihimeFandom; - return new Weapon(this.client, null, db.info, infoSub); + return new WeaponInfo(this.client, null, db.info, infoSub); } public getCategory (id: string) { @@ -281,6 +330,7 @@ interface IMessage extends Message { needsPreview?: boolean; needsRelease?: boolean; needsMex?: boolean; + needsFLB?: boolean; } interface IEmbedsEx extends Embeds { @@ -289,6 +339,6 @@ interface IEmbedsEx extends Embeds { currentClass?: string; oldClass?: string; assets?: { - [name: string]: KamihimeInfo | EidolonInfo | SoulInfo | WeaponInfo + [name: string]: KamihimeInfo | EidolonInfo | SoulInfo | WeaponInfo; }; } diff --git a/src/commands/kamihime/leaderboard.ts b/src/commands/kamihime/leaderboard.ts index b865510a..0090e48a 100644 --- a/src/commands/kamihime/leaderboard.ts +++ b/src/commands/kamihime/leaderboard.ts @@ -1,9 +1,9 @@ -import { Message, TextChannel } from 'discord.js'; +import { Message } from 'discord.js'; import fetch from 'node-fetch'; import { IKamihimeDB } from '../../../typings'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('leaderboard', { aliases: [ 'leaderboard', 'lb', 'toppeeks', 'top' ], @@ -42,13 +42,12 @@ export default class extends ErosCommand { let list: IKamihimeDB[] = characters.filter(c => c.peeks !== 0); list = list.sort((a, b) => b.peeks - a.peeks); - const Pagination = this.util.fields(message) + const Pagination = this.client.fields(message) .setAuthorizedUsers([ message.author.id ]) - .setChannel(message.channel as TextChannel) - .setClientAssets({ message: message.util.lastResponse, prepare: `${emojis.loading} Preparing...` }) + .setChannel(message.channel) + .setClientAssets({ message: message.util.lastResponse }) .setArray(list) - .setPage(page) - .setTimeout(240 * 1000); + .setPage(page); Pagination.embed .setTitle('Most Views Leaderboard (Harem Scenes)') @@ -68,6 +67,6 @@ export default class extends ErosCommand { .formatField('Views', i => i.peeks); return Pagination.build(); - } catch (err) { this.emitError(err, message, this, 1); } + } catch (err) { this.handler.emitError(err, message, this, 1); } } } diff --git a/src/commands/kamihime/list.ts b/src/commands/kamihime/list.ts index a7c7c0fe..a8caf011 100644 --- a/src/commands/kamihime/list.ts +++ b/src/commands/kamihime/list.ts @@ -1,16 +1,16 @@ -import { Message, TextChannel } from 'discord.js'; +import { Message } from 'discord.js'; import fetch from 'node-fetch'; import { IKamihimeDB } from '../../../typings'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('list', { aliases: [ 'list', 'l' ], description: { content: 'Displays character/weapon names based on your arguments.', - usage: '', - examples: [ 'kamihime', 'eidolon', 'eidolon dark' ] + usage: ' [--sort=-[asc/desc]]', + examples: [ 'kamihime', 'eidolon', 'eidolon dark', 'kamihime ssr --sort=ttl', 'kamihime ssr --sort=ttl-desc' ] }, cooldown: 5 * 1000, lock: 'user', @@ -18,6 +18,7 @@ export default class extends ErosCommand { args: [ { id: 'filter', + type: 'lowercase', match: 'text', prompt: { start: [ @@ -31,31 +32,55 @@ export default class extends ErosCommand { match: 'flag', flag: [ '-d', '--dev', '--advanced' ] }, + { + id: 'sort', + match: 'option', + flag: [ '-s', '--sort=' ], + type: /^(?:name|rarity|tier|element|type|atk|hp|ttl)(?:-desc)?$/i, + default: [ 'name' ] + }, ] }); } - public async exec (message: Message, { filter, advanced }: { filter: string, advanced: boolean }) { + public async exec ( + message: Message, + { filter, advanced, sort: sortMatches }: { filter: string, advanced: boolean, sort: RegExpMatchArray } + ) { try { - if (filter.toLowerCase() === 'variables') return this.helpDialog(message); + if (filter === 'variables') return this.helpDialog(message); const { emojis, url } = this.client.config; - message.util.send(`${emojis.loading} Awaiting Kamihime DB's response...`); + await message.util.send(`${emojis.loading} Awaiting Kamihime DB's response...`); - const args = filter.toLowerCase().trim().split(/ +/g); - const rawData = await fetch(`${url.api}list/${args.join('/')}`, { headers: { Accept: 'application/json' } }); + const sort = sortMatches.shift().toLowerCase(); + const args = filter.trim().split(/ +/g).join('/'); + const isTtl = /^ttl/.test(sort); + const query = sort && !isTtl ? `?sort=${sort}` : ''; + const rawData = await fetch(`${url.api}list/${args}${query}`, { headers: { Accept: 'application/json' } }); const result: IKamihimeDB[] = await rawData.json(); if ((result as any).error) throw (result as any).error.message; + if (!result.length) throw RangeError(`There are no matching items found with ${filter.toUpperCase()}`); + + const [ sortBy, sortType = 'asc' ] = sort.split('-'); + const sorted = isTtl + ? result + .sort((a, b) => + sortType === 'desc' + ? (b.atk + b.hp) - (a.atk + a.hp) + : (a.atk + a.hp) - (b.atk + b.hp) + ) + .map(v => ({ name: v.name, id: v.id, ttl: v.atk + v.hp })) as unknown as IKamihimeDB[] + : result; - const embed = this.util.fields(message) + const Pagination = this.client.fields(message) .setAuthorizedUsers([ message.author.id ]) - .setChannel(message.channel as TextChannel) - .setClientAssets({ message: message.util.lastResponse, prepare: `${emojis.loading} Preparing...` }) - .setArray(result) - .setTimeout(240 * 1000); + .setChannel(message.channel) + .setClientAssets({ message: message.util.lastResponse }) + .setArray(sorted); - embed.embed + Pagination.embed .setTitle(`${filter.toUpperCase()} | Found: ${result.length}`) .setDescription([ '**NOTE**: This is a list of characters registered in', @@ -63,19 +88,23 @@ export default class extends ErosCommand { ].join(' ')) .addField('Help', 'React with the emoji below to navigate. ↗ to skip a page.'); - if (advanced) embed.formatField('# - ID', i => `${result.indexOf(i) + 1} - ${i.id}`, true); - embed.formatField( - `${advanced ? '' : '# - '}Name`, i => `${advanced ? '' : `${result.indexOf(i) + 1} - `}${i.name}`, - true - ); + if (advanced) Pagination.formatField('# - ID', i => `${result.indexOf(i) + 1} - ${i.id}`, true); - return embed.build(); - } catch (err) { this.emitError(err, message, this, 1); } + Pagination + .formatField( + `${advanced ? '' : '# - '}Name`, i => `${advanced ? '' : `${result.indexOf(i) + 1} - `}${i.name}`, + true + ); + + if (!/^name/.test(sort)) Pagination.formatField(sort.toUpperCase(), i => i[sortBy]); + + return Pagination.build(); + } catch (err) { this.handler.emitError(err, message, this, 1); } } public async helpDialog (message: Message) { const prefix = await this.handler.prefix(message); - const embed = this.util.embed(message) + const embed = this.client.embed(message) .setTitle('Filter Variables') .setDescription( [ @@ -95,6 +124,16 @@ export default class extends ErosCommand { '**Souls**\n\tlegendary, elite, standard', '**Eidolons/Kamihime**\n\tfire, water, wind, thunder, dark, light, phantom\n\tr, sr, ssr, ssr+', '**Souls/Kamihime**\n\toffense, defense, balance, tricky, healer', + ]) + .addBlankField() + .addField('Options for --sort= Flag', [ + [ + 'Default is by name. Other options are:', + [ 'name', 'rarity', 'tier', 'element', 'type', 'atk', 'hp', 'ttl' ] + .map(el => `\`${el}\``) + .join(', '), + 'Append `-asc` or `-desc` to sort by `Ascending` or `Descending` respectively. (e.g: `rarity-desc`)', + ], ]); return message.util.send(embed); diff --git a/src/commands/kamihime/nsfw.ts b/src/commands/kamihime/nsfw.ts index 11d2ddc1..251a75de 100644 --- a/src/commands/kamihime/nsfw.ts +++ b/src/commands/kamihime/nsfw.ts @@ -1,11 +1,11 @@ import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('nsfw', { aliases: [ 'nsfw', 'nsfwaccess', 'access' ], - description: { content: 'Grants you access to marked `NSFW Channel` in this server.' }, + description: { content: 'Grants you access to marked `NSFW Channel` in the server.' }, clientPermissions: [ 'MANAGE_ROLES' ], channel: 'guild', ratelimit: 1 @@ -34,7 +34,7 @@ export default class extends ErosCommand { await message.react('✅'); return message.util.reply([ - `granted! Proceed to ${resolvedChannel} when accessing Harem Scenes.`, + `granted! Proceed to ${resolvedChannel} when accessing Harem Episodes.`, `Say \`${prefix}guide 20\` for more info.`, ]); } diff --git a/src/commands/kamihime/search.ts b/src/commands/kamihime/search.ts index 4c6fb5f1..404ba5a7 100644 --- a/src/commands/kamihime/search.ts +++ b/src/commands/kamihime/search.ts @@ -1,9 +1,9 @@ -import { Message, TextChannel } from 'discord.js'; +import { Message } from 'discord.js'; import fetch from 'node-fetch'; import { IKamihimeDB } from '../../../typings'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('search', { aliases: [ 'search', 'get', 'find' ], @@ -18,7 +18,7 @@ export default class extends ErosCommand { { id: 'character', match: 'text', - type: word => { + type: (_, word) => { if (!word || word.length < 2) return null; return word; @@ -59,14 +59,14 @@ export default class extends ErosCommand { const result = await data.json(); if (result.error) throw result.error.message; + if (!result.length) throw RangeError(`There are no matching items found with ${character.toUpperCase()}`); - const Pagination = this.util.fields(message) + const Pagination = this.client.fields(message) .setAuthorizedUsers([ message.author.id ]) - .setChannel(message.channel as TextChannel) - .setClientAssets({ message: message.util.lastResponse, prepare: `${emojis.loading} Preparing...` }) + .setChannel(message.channel) + .setClientAssets({ message: message.util.lastResponse }) .setArray(result) - .showPageIndicator(false) - .setTimeout(60 * 1000); + .setPageIndicator(false); Pagination.embed .setTitle(`${character.toUpperCase()} | Found: ${result.length}`) @@ -77,7 +77,7 @@ export default class extends ErosCommand { Pagination.formatField('Name', i => i.name); return Pagination.build(); - } catch (err) { this.emitError(err, message, this, 1); } + } catch (err) { this.handler.emitError(err, message, this, 1); } } public async searchID (message: Message, character: string) { @@ -91,6 +91,6 @@ export default class extends ErosCommand { if (_character.error) throw new Error(`ID ${character} does not exist.`); return message.util.edit(`ID ${character} does exist.`); - } catch (err) { this.emitError(err, message, this, 1); } + } catch (err) { this.handler.emitError(err, message, this, 1); } } } diff --git a/src/commands/level/leaderboard.ts b/src/commands/level/leaderboard.ts index 9ffd4d9a..7fb27652 100644 --- a/src/commands/level/leaderboard.ts +++ b/src/commands/level/leaderboard.ts @@ -1,8 +1,8 @@ -import { Message, TextChannel } from 'discord.js'; -import ErosCommand from '../../struct/command'; -import { Level } from '../../struct/models/factories/Level'; +import { Message } from 'discord.js'; +import Command from '../../struct/command'; +import { Level } from '../../struct/models/Level'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('level-leaderboard', { description: { @@ -21,48 +21,44 @@ export default class extends ErosCommand { } public async exec (message: Message, { page }: { page: number }) { - try { - const factory = this.client.db.Level; - const { emojis } = this.client.config; + const factory = this.client.db.Level; - const levels = await factory.findAll({ - where: { guild: message.guild.id }, - order: [ [ 'exp', 'DESC' ] ], - attributes: [ 'user', 'exp' ] - }); + const levels = await factory.findAll({ + where: { guild: message.guild.id }, + order: [ [ 'exp', 'DESC' ] ], + attributes: [ 'user', 'exp' ] + }); - if (!levels.length) return message.util.send('Looks like this is a ghost guild...'); + if (!levels.length) return message.util.send('Looks like this is a ghost guild...'); - const Pagination = this.util.fields(message) - .setAuthorizedUsers([ message.author.id ]) - .setChannel(message.channel as TextChannel) - .setClientAssets({ message: message.util.lastResponse, prepare: `${emojis.loading} Preparing...` }) - .setArray(levels) - .setTimeout(240 * 1000) - .setPage(page); + const Pagination = this.client.fields(message) + .setAuthorizedUsers([ message.author.id ]) + .setChannel(message.channel) + .setClientAssets({ message: message.util.lastResponse }) + .setArray(levels) + .setPage(page); - Pagination.embed - .setTitle('Highest Level Members') - .setAuthor(`${message.guild.name} (${message.guild.id})`) - .setThumbnail(message.guild.iconURL({ format: 'webp' })) - .addField('Help', [ - 'React with the emoji below to navigate. ↗ to skip a page.', - `See a members's information with \`${await this.handler.prefix(message)}level info \``, - ]); + Pagination.embed + .setTitle('Highest Level Members') + .setAuthor(`${message.guild.name} (${message.guild.id})`) + .setThumbnail(message.guild.iconURL({ format: 'webp' })) + .addField('Help', [ + 'React with the emoji below to navigate. ↗ to skip a page.', + `See a members's information with \`${await this.handler.prefix(message)}level info \``, + ]); - Pagination - .formatField( - '#) Name', - el => { - const user = this.client.users.get(el.user); - const tag = user ? user.tag : 'UNKNOWN_USER'; + Pagination + .formatField( + '#) Name', + el => { + const user = this.client.users.get(el.user); + const tag = user ? user.tag : 'UNKNOWN_USER'; - return `${levels.findIndex(l => l.user === el.user) + 1}) ${tag}`; - } - ) - .formatField('EXP', el => el.exp); + return `${levels.findIndex(l => l.user === el.user) + 1}) ${tag}`; + } + ) + .formatField('EXP', el => el.exp); - return Pagination.build(); - } catch (err) { this.emitError(err, message, this); } + return Pagination.build(); } } diff --git a/src/commands/level/level.ts b/src/commands/level/level.ts index 6e996de8..97d8437d 100644 --- a/src/commands/level/level.ts +++ b/src/commands/level/level.ts @@ -1,8 +1,8 @@ -import { Command } from 'discord-akairo'; +import { Flag } from 'discord-akairo'; import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('level', { aliases: [ 'level' ], @@ -22,36 +22,24 @@ export default class extends ErosCommand { ] }, channel: 'guild', - ratelimit: 2, - args: [ - { - id: 'method', - type: [ - 'i', - 'info', - 'leaderboard', - ] - }, - { - id: 'details', - match: 'rest', - default: '' - }, - ] + ratelimit: 2 }); } - public async exec (message: Message, { method, details }: { method: string, details: string }) { - if (!method) - return this.handler.modules.get('help').exec(message, { command: this }); + public * args () { + const child = yield { + type: [ + [ 'memberinfo', 'i', 'info' ], + [ 'level-leaderboard', 'leaderboard' ], + ], + otherwise: (message: Message) => { + const HelpCommand = this.handler.modules.get('help'); + this.handler.runCommand(message, HelpCommand, { command: this }); - const commands: { [key: string]: Command } = { - i: this.handler.modules.get('memberinfo'), - info: this.handler.modules.get('memberinfo'), - leaderboard: this.handler.modules.get('level-leaderboard') + return null; + } }; - const command = commands[method]; - return this.handler.handleDirectCommand(message, details, command, true); + return Flag.continue(child, true); } } diff --git a/src/commands/set/countdownchannel.ts b/src/commands/set/countdownchannel.ts index 33b8a1fb..3e82b0c8 100644 --- a/src/commands/set/countdownchannel.ts +++ b/src/commands/set/countdownchannel.ts @@ -1,11 +1,11 @@ import { Message, TextChannel } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('set-countdownchannel', { description: { - content: 'Changes this server\'s Countdown Channel.', + content: 'Changes the server\'s Countdown Channel.', usage: '', examples: [ '#newbie-lib-questions-not-other-js', '312874659902115460', 'countdown' ] }, diff --git a/src/commands/set/countdownrole.ts b/src/commands/set/countdownrole.ts index 1b4781b1..45e4d689 100644 --- a/src/commands/set/countdownrole.ts +++ b/src/commands/set/countdownrole.ts @@ -1,11 +1,11 @@ import { Message, Role } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('set-countdownrole', { description: { - content: 'Changes this server\'s Countdown Subscriber Role.', + content: 'Changes the server\'s Countdown Subscriber Role.', usage: '', examples: [ '@cd', '2138547751248890915', 'countdown' ] }, diff --git a/src/commands/set/loli.ts b/src/commands/set/loli.ts deleted file mode 100644 index a5e0aade..00000000 --- a/src/commands/set/loli.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; - -// [DEPRECATED] -// `hareminfo` command on a character with loli contents will be restricted ALWAYS. -// This is to completely follow Discord Guidelines, due to increase of server count. -export default class extends ErosCommand { - constructor () { - super('set-loli', { - description: { content: 'Changes this server\'s Loli restriction condition. Toggle-able command.' } - }); - } - - public async exec (message: Message) { - const guild = await this.client.db.Guild.findOne({ - where: { id: message.guild.id }, - attributes: [ 'id', 'loli' ] - }); - - await guild.update({ loli: !guild.loli }); - - return message.util.reply( - !guild.loli - ? 'I have disabled Loli contents restriction in this server.' - : 'I have enabled Loli contents restriction in this server.' - ); - } -} diff --git a/src/commands/set/nsfwchannel.ts b/src/commands/set/nsfwchannel.ts index d48c943f..9f036f62 100644 --- a/src/commands/set/nsfwchannel.ts +++ b/src/commands/set/nsfwchannel.ts @@ -1,11 +1,11 @@ import { Message, TextChannel } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('set-nsfwchannel', { description: { - content: 'Changes this server\'s NSFW Channel.', + content: 'Changes the server\'s NSFW Channel.', usage: '', examples: [ '#newbie-lib-questions-not-other-js', '312874659902115460', 'nsfw' ] }, diff --git a/src/commands/set/nsfwrole.ts b/src/commands/set/nsfwrole.ts index a744563f..c97a19db 100644 --- a/src/commands/set/nsfwrole.ts +++ b/src/commands/set/nsfwrole.ts @@ -1,11 +1,11 @@ import { Message, Role } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('set-nsfwrole', { description: { - content: 'Changes this server\'s NSFW Role.', + content: 'Changes the server\'s NSFW Role.', usage: '', examples: [ '@lewd', '2138547751248890915', 'nsfw' ] }, diff --git a/src/commands/set/prefix.ts b/src/commands/set/prefix.ts index d4ee3170..bb89ee94 100644 --- a/src/commands/set/prefix.ts +++ b/src/commands/set/prefix.ts @@ -1,21 +1,21 @@ import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; // tslint:disable-next-line:no-var-requires const { defaultPrefix }: { defaultPrefix: string } = require('../../../auth'); -export default class extends ErosCommand { +export default class extends Command { constructor () { super('set-prefix', { description: { - content: 'Changes this server\'s prefix.', + content: 'Changes the server\'s prefix.', usage: '', examples: [ 'e?', 'eros' ] }, args: [ { id: 'prefix', - type: word => { + type: (_, word) => { if (!word) return null; if (/\s/.test(word) || word.length > 10) return null; diff --git a/src/commands/set/set.ts b/src/commands/set/set.ts index 2baf87f2..d8ad07ed 100644 --- a/src/commands/set/set.ts +++ b/src/commands/set/set.ts @@ -1,8 +1,8 @@ -import { Command } from 'discord-akairo'; +import { Flag } from 'discord-akairo'; import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('set', { aliases: [ 'set' ], @@ -14,20 +14,18 @@ export default class extends ErosCommand { '\t- `countdownchannel`', '\t- `cdrole`', '\t- `countdownrole`', - '\t- `loli`', '\t- `nsfwchannel`', '\t- `nsfwrole`', '\t- `prefix`', '\t- `twitter`', '\t- `twitterchannel`', ], - usage: ' [value]', + usage: ' ', examples: [ 'cdchannel #re-countdown', 'countdownchannel re-countdown', 'cdrole @cd subscriber', 'countdownrole cd subscriber', - 'loli', 'nsfwchannel re-nsfw', 'nsfwrole pervert', 'prefix e?', @@ -37,50 +35,28 @@ export default class extends ErosCommand { }, userPermissions: [ 'MANAGE_GUILD' ], channel: 'guild', - ratelimit: 2, - args: [ - { - id: 'method', - type: [ - 'cdchannel', - 'countdownchannel', - 'cdrole', - 'countdownrole', - 'loli', - 'nsfwchannel', - 'nsfwrole', - 'prefix', - 'twitter', - 'twitterchannel', - ] - }, - { - id: 'details', - match: 'rest', - default: '' - }, - ] + ratelimit: 2 }); } - public async exec (message: Message, { method, details }: { method: string, details: string }) { - if (!method) - return this.handler.modules.get('help').exec(message, { command: this }); + public * args () { + const child = yield { + type: [ + [ 'set-countdownchannel', 'cdchannel', 'countdownchannel' ], + [ 'set-countdownrole', 'cdrole', 'countdownrole' ], + [ 'set-nsfwchannel', 'nsfwchannel' ], + [ 'set-nsfrole', 'nsfwrole' ], + [ 'set-prefix', 'prefix' ], + [ 'set-twitterchannel', 'twitter', 'twitterchannel' ], + ], + otherwise: (message: Message) => { + const HelpCommand = this.handler.modules.get('help'); + this.handler.runCommand(message, HelpCommand, { command: this }); - const commands: { [key: string]: Command } = { - cdchannel: this.handler.modules.get('set-countdownchannel'), - countdownchannel: this.handler.modules.get('set-countdownchannel'), - cdrole: this.handler.modules.get('set-countdownrole'), - countdownrole: this.handler.modules.get('set-countdownrole'), - loli: this.handler.modules.get('set-loli'), - nsfwchannel: this.handler.modules.get('set-nsfwchannel'), - nsfwrole: this.handler.modules.get('set-nsfwrole'), - prefix: this.handler.modules.get('set-prefix'), - twitter: this.handler.modules.get('set-twitterchannel'), - twitterchannel: this.handler.modules.get('set-twitterchannel') + return null; + } }; - const command = commands[method]; - return this.handler.handleDirectCommand(message, details, command, true); + return Flag.continue(child); } } diff --git a/src/commands/set/twitterchannel.ts b/src/commands/set/twitterchannel.ts index 6ca6ab4f..4dbfe8cb 100644 --- a/src/commands/set/twitterchannel.ts +++ b/src/commands/set/twitterchannel.ts @@ -1,11 +1,11 @@ import { Message, TextChannel } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('set-twitterchannel', { description: { - content: 'Changes this server\'s Twitter Channel.', + content: 'Changes the server\'s Twitter Channel.', usage: '', examples: [ '#newbie-lib-questions-not-other-js', '312874659902115460', 'twitter' ] }, diff --git a/src/commands/tag/add.ts b/src/commands/tag/add.ts index 611f2eeb..f2ce76b6 100644 --- a/src/commands/tag/add.ts +++ b/src/commands/tag/add.ts @@ -1,7 +1,7 @@ import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('tag-add', { description: { @@ -15,7 +15,7 @@ export default class extends ErosCommand { type: 'existingTag', prompt: { start: 'what should the new tag be named?', - retry: (_, __, input: { phrase: string }) => + retry: (_, input: { phrase: string }) => `**${input.phrase}** already exists or it's too long. Please provide again.` } }, @@ -44,12 +44,12 @@ export default class extends ErosCommand { if (name.length > 256) { message.util.reply('why bother to name a tag too long?'); - return this.fail(message); + return this.handler.reactFail(message); } if (content.length > 1950) { message.util.reply('uh oh, your content exceeds the 2000 characters limit!'); - return this.fail(message); + return this.handler.reactFail(message); } const isManager = message.member.hasPermission('MANAGE_GUILD'); diff --git a/src/commands/tag/delete.ts b/src/commands/tag/delete.ts index ea1b6b1d..f142e373 100644 --- a/src/commands/tag/delete.ts +++ b/src/commands/tag/delete.ts @@ -1,40 +1,71 @@ import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; -import { Tag } from '../../struct/models/factories/Tag'; +import Command from '../../struct/command'; +import { Tag } from '../../struct/models/Tag'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('tag-delete', { description: { - content: 'Deletes a tag.', - usage: '' + content: [ + 'Deletes a tag.', + 'Append `--purge` to delete all tags based on the REGEX provided ( as REGEX).', + 'The provided REGEX must be on [PCRE](https://mariadb.com/kb/en/library/pcre) flavour.', + ], + usage: ' [--purge]', + examples: [ + 'myText', + '^my(?!Text) --purge', + ] }, - args: [ - { - id: 'tag', - type: 'tag', - match: 'content', - prompt: { - start: 'what is the name of the tag?', - retry: (_, __, input: { phrase: string }) => - `**${input.phrase}** does not exist. Please provide again.` - } - }, - ] + flags: [ '-p', '--purge' ] }); } - public async exec (message: Message, { tag }: { tag: Tag }) { - const isManager = message.member.hasPermission('MANAGE_GUILD'); + public * args () { + const purge = yield { + match: 'flag', + flag: [ '-p', '--purge' ] + }; + + const tag = yield { + type: purge ? 'string' : 'tag', + match: 'rest', + prompt: { + start: purge + ? 'what is the pattern for purging tags?' + : 'what is the name of the tag?', + retry: purge + ? (_, input: { phrase: string }) => `**${input.phrase}** does not exist. Please provide again.` + : (_, input: { phrase: string }) => `**${input.phrase}** does not exist. Please provide again.` + } + }; + + return { purge, tag }; + } - if (tag.author !== message.author.id && !isManager) { + public async exec (message: Message, { tag, purge }: { tag: Tag | string, purge: boolean }) { + const isManager = message.member.hasPermission('MANAGE_GUILD'); + const fail = () => { message.util.reply('you have no power here!'); - return this.fail(message); + return this.handler.reactFail(message); + }; + + if ((tag as Tag).author !== message.author.id && !isManager) return fail(); + if (purge) { + if (!isManager) return fail(); + + const deleted = await this.client.db.Tag.destroy({ + where: { + name: { [this.client.db.Op.regexp]: tag } + } + }); + + return message.util.reply(`Done! **${deleted}** tags named similarly to **${tag}** has been deleted.`); } - await tag.destroy(); + await (tag as Tag).destroy(); - return message.util.reply(`Done! tag **${tag.name}** has been deleted.`); + return message.util.reply(`Done! tag **${(tag as Tag).name}** has been deleted.`); } } diff --git a/src/commands/tag/edit.ts b/src/commands/tag/edit.ts index 2fbc2ab5..62f84b69 100644 --- a/src/commands/tag/edit.ts +++ b/src/commands/tag/edit.ts @@ -1,9 +1,8 @@ -import { Control } from 'discord-akairo'; import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; -import { Tag } from '../../struct/models/factories/Tag'; +import Command from '../../struct/command'; +import { Tag } from '../../struct/models/Tag'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('tag-edit', { description: { @@ -11,41 +10,48 @@ export default class extends ErosCommand { usage: ' [tag content] [--hoist/--unhoist]', examples: [ 'codes ***breathes heavily*** CODES', 'thisIsHoisted hoisted, sir --hoist' ] }, - args: [ - { - id: 'tag', - type: 'tag', - prompt: { - start: 'what is the name of the tag?', - retry: (_, __, input: { phrase: string }) => - `**${input.phrase}** does not exist. Please provide again.` - } - }, - { - id: 'hoisted', - match: 'flag', - flag: [ '-h', '--hoist', '-p', '--pin' ] - }, - { - id: 'unhoisted', - match: 'flag', - flag: [ '-u', '--unhoist', '--unpin' ] - }, - Control.if((_, args) => args.hoisted || args.unhoisted, [], [ - { - id: 'content', - match: 'rest', - type: 'tagContent', - prompt: { - start: 'what should the future tag contain?', - retry: 'content should not be empty or not be exceeding 1950 characters. Please provide again.' - } - }, - ]), + flags: [ + '-h', '--hoist', '-p', '--pin', + '-u', '--unhoist', '--unpin', ] }); } + public * args () { + const tag = yield { + type: 'tag', + prompt: { + start: 'what is the name of the tag?', + retry: (_, __, input: { phrase: string }) => + `**${input.phrase}** does not exist. Please provide again.` + } + }; + + const hoisted = yield { + match: 'flag', + flag: [ '-h', '--hoist', '-p', '--pin' ] + }; + + const unhoisted = yield { + match: 'flag', + flag: [ '-u', '--unhoist', '--unpin' ] + }; + + const content = hoisted || unhoisted + ? null + : yield { + id: 'content', + match: 'rest', + type: 'tagContent', + prompt: { + start: 'what should the future tag contain?', + retry: 'content should not be empty or not be exceeding 1950 characters. Please provide again.' + } + }; + + return { tag, hoisted, unhoisted, content }; + } + public async exec ( message: Message, { tag, content, hoisted, unhoisted }: { tag: Tag, content: string, hoisted: boolean, unhoisted: boolean } @@ -55,12 +61,12 @@ export default class extends ErosCommand { if (tag.author !== message.author.id && !isManager) { message.util.reply('are you trying to vandalise?'); - return this.fail(message); + return this.handler.reactFail(message); } if (content && content.length > 1950) { message.util.reply('uh oh, your content exceeds the 2000 characters limit!'); - return this.fail(message); + return this.handler.reactFail(message); } const updateValues: Partial = { modifiedBy: message.author.id }; diff --git a/src/commands/tag/info.ts b/src/commands/tag/info.ts index d82d8d17..686e54f7 100644 --- a/src/commands/tag/info.ts +++ b/src/commands/tag/info.ts @@ -1,9 +1,9 @@ import { Message, User } from 'discord.js'; import * as moment from 'moment-timezone'; -import ErosCommand from '../../struct/command'; -import { Tag } from '../../struct/models/factories/Tag'; +import Command from '../../struct/command'; +import { Tag } from '../../struct/models/Tag'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('tag-info', { description: { @@ -17,7 +17,7 @@ export default class extends ErosCommand { type: 'tag', prompt: { start: 'what is the name of the tag?', - retry: (_, __, input: { phrase: string }) => + retry: (_, input: { phrase: string }) => `**${input.phrase}** does not exist. Please provide again.` } }, @@ -34,7 +34,7 @@ export default class extends ErosCommand { try { modifiedBy = await this.client.users.fetch(tag.modifiedBy); } catch { modifiedBy = null; } - const embed = this.util.embed(message) + const embed = this.client.embed(message) .setTitle('Tag: ' + tag.name) .setThumbnail(user.displayAvatarURL({ format: 'webp' })) .addField('Created By', user ? `${user.tag} (${user.id})` : 'Cannot resolve user') diff --git a/src/commands/tag/leaderboard.ts b/src/commands/tag/leaderboard.ts index eff10366..aef1e697 100644 --- a/src/commands/tag/leaderboard.ts +++ b/src/commands/tag/leaderboard.ts @@ -1,107 +1,97 @@ -import { ArgumentOptions, Control } from 'discord-akairo'; -import { GuildMember, Message, TextChannel } from 'discord.js'; -import ErosCommand from '../../struct/command'; -import { Tag } from '../../struct/models/factories/Tag'; +import { GuildMember, Message } from 'discord.js'; +import Command from '../../struct/command'; +import { Tag } from '../../struct/models/Tag'; -const pageArg: ArgumentOptions = { - id: 'page', - type: 'integer', - default: 1 -}; - -export default class extends ErosCommand { +export default class extends Command { constructor () { super('tag-leaderboard', { description: { content: 'Displays a leaderboard of tags from the current server or the specified member.', usage: ' [page number]' }, - noTrash: true, - args: [ - Control.if(message => !isNaN(Number(message.util.parsed.content.split(/ +/g).slice(1)[0])), - [ pageArg ], [ - { - id: 'member', - type: 'member' - }, - pageArg, - ] - ), - ] + noTrash: true }); } - public async exec (message: Message, { member, page }: { member: GuildMember, page: number }) { - try { - const factory = this.client.db.Tag; - const { emojis } = this.client.config; - const prefix = await this.handler.prefix(message); - - if (member) { - const memberTags = await factory.findAll({ - where: { - author: member.id, - guild: message.guild.id - }, - order: [ [ 'uses', 'DESC' ] ], - attributes: [ 'name', 'uses' ] - }); - - if (!memberTags.length) { - if (member.id !== message.author.id) - return message.util.reply(`**${member.displayName}** has no tags here.`); + public * args (message: Message) { + const member = isNaN(Number(message.util.parsed.content.split(/ +/g).slice(1)[0])) + ? yield { type: 'member' } + : null; - return message.util.reply('... uh... yeah, no... you do not have one.'); - } + const page = yield { + type: 'integer', + default: 1 + }; - const memberEmbed = this.util.fields(message) - .setAuthorizedUsers([ message.author.id ]) - .setChannel(message.channel as TextChannel) - .setClientAssets({ message: message.util.lastResponse, prepare: `${emojis.loading} Preparing...` }) - .setArray(memberTags) - .setTimeout(240 * 1000) - .setPage(page); - - memberEmbed.embed.addField('Help', [ - 'React with the emoji below to navigate. ↗ to skip a page.', - `See a tag's information with \`${prefix}\``, - ]); - memberEmbed - .formatField('#) Name', el => `${tags.findIndex(t => t.name === el.name) + 1} ${el.name}`) - .formatField('Times Used', el => el.uses); + return { member, page }; + } - return memberEmbed.build(); - } + public async exec (message: Message, { member, page }: { member: GuildMember, page: number }) { + const factory = this.client.db.Tag; + const prefix = await this.handler.prefix(message); - const tags = await factory.findAll({ - where: { guild: message.guild.id }, + if (member) { + const memberTags = await factory.findAll({ + where: { + author: member.id, + guild: message.guild.id + }, order: [ [ 'uses', 'DESC' ] ], attributes: [ 'name', 'uses' ] }); - if (!tags.length) return message.util.send('We do not have any tag here. Be the first one to create one here!'); + if (!memberTags.length) + return message.util.reply( + member.id !== message.author.id + ? `**${member.displayName}** has no tags here.` + : '... uh... yeah, no... you do not have one.' + ); - const serverEmbed = this.util.fields(message) + const memberEmbed = this.client.fields(message) .setAuthorizedUsers([ message.author.id ]) - .setChannel(message.channel as TextChannel) - .setClientAssets({ message: message.util.lastResponse, prepare: `${emojis.loading} Preparing...` }) - .setArray(tags) - .setTimeout(240 * 1000) + .setChannel(message.channel) + .setClientAssets({ message: message.util.lastResponse }) + .setArray(memberTags) .setPage(page); - serverEmbed.embed - .setTitle('Most Used Tags') - .setAuthor(`${message.guild.name} (${message.guild.id})`) - .setThumbnail(message.guild.iconURL({ format: 'webp' })) - .addField('Help', [ - 'React with the emoji below to navigate. ↗ to skip a page.', - `See a tag's information with \`${prefix}tag info \``, - ]); - serverEmbed - .formatField('#) Name', el => `${tags.findIndex(t => t.name === el.name) + 1}) ${el.name}`) + memberEmbed.embed.addField('Help', [ + 'React with the emoji below to navigate. ↗ to skip a page.', + `See a tag's information with \`${prefix}\``, + ]); + memberEmbed + .formatField('#) Name', el => `${tags.findIndex(t => t.name === el.name) + 1} ${el.name}`) .formatField('Times Used', el => el.uses); - return serverEmbed.build(); - } catch (err) { this.emitError(err, message, this); } + return memberEmbed.build(); + } + + const tags = await factory.findAll({ + where: { guild: message.guild.id }, + order: [ [ 'uses', 'DESC' ] ], + attributes: [ 'name', 'uses' ] + }); + + if (!tags.length) return message.util.send('We do not have any tag here. Be the first one to create one here!'); + + const serverEmbed = this.client.fields(message) + .setAuthorizedUsers([ message.author.id ]) + .setChannel(message.channel) + .setClientAssets({ message: message.util.lastResponse }) + .setArray(tags) + .setPage(page); + + serverEmbed.embed + .setTitle('Most Used Tags') + .setAuthor(`${message.guild.name} (${message.guild.id})`) + .setThumbnail(message.guild.iconURL({ format: 'webp' })) + .addField('Help', [ + 'React with the emoji below to navigate. ↗ to skip a page.', + `See a tag's information with \`${prefix}tag info \``, + ]); + serverEmbed + .formatField('#) Name', el => `${tags.findIndex(t => t.name === el.name) + 1}) ${el.name}`) + .formatField('Times Used', el => el.uses); + + return serverEmbed.build(); } } diff --git a/src/commands/tag/search.ts b/src/commands/tag/search.ts index 67a9454f..0e651e93 100644 --- a/src/commands/tag/search.ts +++ b/src/commands/tag/search.ts @@ -1,7 +1,7 @@ import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('tag-search', { description: { @@ -43,7 +43,7 @@ export default class extends ErosCommand { if (pretty.length >= 1440) return message.util.reply('ah, the result is too long. Care to search with better specifics again?'); - const embed = this.util.embed(message) + const embed = this.client.embed(message) .setAuthor(`${message.guild} (${message.guild.id})`) .setThumbnail(message.guild.iconURL({ format: 'webp' })) .setDescription(pretty); diff --git a/src/commands/tag/show.ts b/src/commands/tag/show.ts index 80225b87..e228223a 100644 --- a/src/commands/tag/show.ts +++ b/src/commands/tag/show.ts @@ -1,7 +1,7 @@ import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('tag-show', { description: { @@ -14,8 +14,8 @@ export default class extends ErosCommand { { id: 'name', match: 'content', - type: name => { - if (!name || name.length < 1) return null; + type: (_, name) => { + if (!name) return null; return name; }, diff --git a/src/commands/tag/source.ts b/src/commands/tag/source.ts index 7e1015d7..88da443d 100644 --- a/src/commands/tag/source.ts +++ b/src/commands/tag/source.ts @@ -1,8 +1,8 @@ import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; -import { Tag } from '../../struct/models/factories/Tag'; +import Command from '../../struct/command'; +import { Tag } from '../../struct/models/Tag'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('tag-source', { description: { @@ -16,7 +16,7 @@ export default class extends ErosCommand { type: 'tag', prompt: { start: 'what is the name of the tag?', - retry: (_, __, input: { phrase: string }) => + retry: (_, input: { phrase: string }) => `**${input.phrase}** does not exist. Please provide again.` } }, diff --git a/src/commands/tag/tag.ts b/src/commands/tag/tag.ts index ecef845b..1a2964e8 100644 --- a/src/commands/tag/tag.ts +++ b/src/commands/tag/tag.ts @@ -1,8 +1,8 @@ -import { Command } from 'discord-akairo'; +import { Flag } from 'discord-akairo'; import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('tag', { aliases: [ 'tag' ], @@ -24,67 +24,46 @@ export default class extends ErosCommand { ], usage: ' [arguments]', examples: [ - 'add SoS Yo --hoist', - 'add xd ROFL', - 'delete SoS', - 'edit Jump In the caAc --hoist', - 'edit SoS caAc', - 'find big smoke', - 'info Leon', + 'add myText Content --hoist', + 'add myText Content', + 'delete myText', + 'edit myText --hoist', + 'edit "With Spaces" Content', + 'find with spaces', + 'info myText', 'leaderboard 5', - 'list @Eros', - 'search memes', - 'show Leon', - 'source xd', + 'list nutaku employee impersonator', + 'search stale kh memes', + 'show myText', + 'source burst attack', ] }, channel: 'guild', - ratelimit: 2, - args: [ - { - id: 'method', - type: [ - 'add', - 'del', - 'delete', - 'edit', - 'info', - 'leaderboard', - 'list', - 'show', - 'source', - 'search', - 'find', - ] - }, - { - id: 'details', - match: 'rest', - default: '' - }, - ] + ratelimit: 2 }); } - public async exec (message: Message, { method, details }: { method: string, details: string }) { - if (!method) - return this.handler.modules.get('help').exec(message, { command: this }); + public * args () { + const child = yield { + type: [ + [ 'tag-add', 'add' ], + [ 'tag-delete', 'del', 'delete' ], + [ 'tag-edit', 'edit' ], + [ 'tag-info', 'i', 'info' ], + [ 'tag-leaderboard', 'leaderboard', 'lb' ], + [ 'tag-list', 'list', 'l' ], + [ 'tag-show', 'show' ], + [ 'tag-source', 'source', 'raw' ], + [ 'tag-search', 'search', 'find' ], + ], + otherwise: (message: Message) => { + const HelpCommand = this.handler.modules.get('help'); + this.handler.runCommand(message, HelpCommand, { command: this }); - const commands: { [key: string]: Command } = { - add: this.handler.modules.get('tag-add'), - del: this.handler.modules.get('tag-delete'), - delete: this.handler.modules.get('tag-delete'), - edit: this.handler.modules.get('tag-edit'), - info: this.handler.modules.get('tag-info'), - leaderboard: this.handler.modules.get('tag-leaderboard'), - list: this.handler.modules.get('tag-list'), - show: this.handler.modules.get('tag-show'), - source: this.handler.modules.get('tag-source'), - search: this.handler.modules.get('tag-search'), - find: this.handler.modules.get('tag-search') + return null; + } }; - const command = commands[method]; - return this.handler.handleDirectCommand(message, details, command, true); + return Flag.continue(child, true); } } diff --git a/src/commands/tag/tags.ts b/src/commands/tag/tags.ts index 6397f3cc..5e629a35 100644 --- a/src/commands/tag/tags.ts +++ b/src/commands/tag/tags.ts @@ -1,7 +1,7 @@ import { GuildMember, Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('tag-list', { aliases: [ 'tags' ], @@ -42,7 +42,7 @@ export default class extends ErosCommand { } else if (memberTags.length > 35) return replyFail(); - const memberEmbed = this.util.embed(message) + const memberEmbed = this.client.embed(message) .setAuthor(`${member.user.tag} (${member.id})`) .setThumbnail(member.user.displayAvatarURL({ format: 'webp' })); @@ -77,7 +77,7 @@ export default class extends ErosCommand { else if (tags.length > 35) return replyFail(); - const embed = this.util.embed(message) + const embed = this.client.embed(message) .setAuthor(`${message.guild} (${message.guild.id})`) .setThumbnail(message.guild.iconURL({ format: 'webp' })); diff --git a/src/commands/util/clear.ts b/src/commands/util/clear.ts deleted file mode 100644 index 9e88b600..00000000 --- a/src/commands/util/clear.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Message, TextChannel } from 'discord.js'; -import ErosCommand from '../../struct/command'; - -export default class extends ErosCommand { - constructor () { - super('clear', { - aliases: [ 'clear' ], - description: { - content: 'Clears a text channel from based on limit specified messages. Default: 99 messages.', - usage: '[limit] [me | bot | all]', - examples: [ '25', 'me 25' ] - }, - ownerOnly: true, - channel: 'guild', - args: [ - { - id: 'amount', - type: 'integer', - default: 100 - }, - { - id: 'type', - type: 'string', - default: null - }, - ] - }); - } - - public async exec (message: Message, { amount, type }: { amount: number, type: string }) { - try { - const channel = message.channel as TextChannel; - if (channel.topic && channel.topic.match(//gi)) - return message.util.reply('clear command for this channel is restricted.'); - - const messages = await channel.messages.fetch({ limit: amount }); - - switch (type) { - case 'all': { - channel.bulkDelete(messages, true); - break; - } - case 'me': { - const filterMessages = messages.filter( - m => m.author.id === message.author.id && !m.pinned && - !m.content.match(//gi) && Date.now() - m.createdTimestamp < 1210000000 - ); - channel.bulkDelete(filterMessages); - break; - } - case 'bot': { - const filterMessages = messages.filter( - m => m.author.id === this.client.user.id && !m.pinned && - !m.content.match(//gi) && Date.now() - m.createdTimestamp < 1210000000 - ); - channel.bulkDelete(filterMessages); - break; - } - default: { - const filterMessages = messages.filter( - m => !m.pinned && !m.content.match(//gi) && - Date.now() - m.createdTimestamp < 1210000000 - ); - channel.bulkDelete(filterMessages); - } - } - } catch (err) { this.emitError(err, message, this); } - } -} diff --git a/src/commands/util/eval.ts b/src/commands/util/eval.ts index 255bc6f7..65e764d6 100644 --- a/src/commands/util/eval.ts +++ b/src/commands/util/eval.ts @@ -1,6 +1,6 @@ import { Message } from 'discord.js'; import * as util from 'util'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; function clean (text: string) { if (typeof text === 'string') @@ -9,7 +9,7 @@ function clean (text: string) { return text; } -export default class extends ErosCommand { +export default class extends Command { constructor () { super('eval', { aliases: [ 'eval', 'ev', 'e' ], diff --git a/src/commands/util/memberInfo.ts b/src/commands/util/memberInfo.ts index 1a918775..4ab658bf 100644 --- a/src/commands/util/memberInfo.ts +++ b/src/commands/util/memberInfo.ts @@ -1,7 +1,7 @@ import { GuildMember, Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('memberinfo', { aliases: [ 'memberinfo', 'minfo', 'mi', 'userinfo', 'uinfo', 'ui', 'profile', 'kp' ], @@ -27,51 +27,48 @@ export default class extends ErosCommand { } public async displayInfo (message: Message, member: GuildMember) { - try { - const fetchedMember = member || await message.guild.members.fetch(member.id); + const fetchedMember = member || await message.guild.members.fetch(member.id); - if (!fetchedMember) throw new Error('Member cache missing'); + if (!fetchedMember) throw new Error('Member cache missing'); - const [ levelMember ] = await this.client.db.Level.findOrCreate({ - where: { user: fetchedMember.id, guild: message.guild.id }, - attributes: [ 'title', 'exp' ] - }); + const [ levelMember ] = await this.client.db.Level.findOrCreate({ + where: { user: fetchedMember.id, guild: message.guild.id } + }); - const ranking = await this.client.db.Level.findAll({ - where: { guild: message.guild.id }, - order: [ [ 'exp', 'DESC' ] ], - attributes: [ 'user' ] - }); - const titlesMember = await this.client.db.Title.findAll({ - where: { [this.client.db.Op.or]: [ { id: levelMember.title }, { id: levelMember.title + 1 } ] }, - order: [ [ 'id', 'ASC' ] ], - attributes: [ 'id', 'name', 'threshold' ] - }); - const nextTitle = titlesMember[1]; + const ranking = await this.client.db.Level.findAll({ + where: { guild: message.guild.id }, + order: [ [ 'exp', 'DESC' ] ], + attributes: [ 'user' ] + }); + const titlesMember = await this.client.db.Title.findAll({ + where: { [this.client.db.Op.or]: [ { id: levelMember.title }, { id: levelMember.title + 1 } ] }, + order: [ [ 'id', 'ASC' ] ], + attributes: [ 'id', 'name', 'threshold' ] + }); + const nextTitle = titlesMember[1]; - const embed = this.util.embed(message) - .setTitle(`${fetchedMember.user.tag} | ${this.memberStatus(member)}`) - .setDescription(`**ID**: ${fetchedMember.id}${ - member.nickname ? `, also known as **\`${member.nickname}\`**` : ''}` - ) - .setThumbnail(member.user.displayAvatarURL()) - .addField('Level System', [ - `**Current Title**: ${titlesMember[0].name}`, - `**Next Title**: ${nextTitle ? nextTitle.name : '∞'}`, - `**Progress**: ${levelMember.exp} / ${nextTitle ? nextTitle.threshold : '∞'}`, - `**Server Ranking**: ${ranking.findIndex(v => v.user === member.id) + 1} / ${ranking.length}`, - ]) - .addField('Roles', - member.roles.map(r => member.roles.array().indexOf(r) % 3 === 0 ? `\n${r}` : `${r}`).join(', ') - ) - .addField('Creation Date', member.user.createdAt.toUTCString(), true) - .addField('Join Date (This server)', member.joinedAt.toUTCString(), true); + const embed = this.client.embed(message) + .setTitle(`${fetchedMember.user.tag} | ${this.memberStatus(member)}`) + .setDescription(`**ID**: ${fetchedMember.id}${ + member.nickname ? `, also known as **\`${member.nickname}\`**` : ''}` + ) + .setThumbnail(member.user.displayAvatarURL()) + .addField('Level System', [ + `**Current Title**: ${titlesMember[0].name}`, + `**Next Title**: ${nextTitle ? nextTitle.name : '∞'}`, + `**Progress**: ${levelMember.exp} / ${nextTitle ? nextTitle.threshold : '∞'}`, + `**Server Ranking**: ${ranking.findIndex(v => v.user === member.id) + 1} / ${ranking.length}`, + ]) + .addField('Roles', + member.roles.map(r => member.roles.array().indexOf(r) % 3 === 0 ? `\n${r}` : `${r}`).join(', ') + ) + .addField('Creation Date', member.user.createdAt.toUTCString(), true) + .addField('Join Date (This server)', member.joinedAt.toUTCString(), true); - if (member.user.presence.activity) - embed.addField('Activity', this.memberActivity(member)); + if (member.user.presence.activity) + embed.addField('Activity', this.memberActivity(member)); - return message.util.send(embed); - } catch (err) { this.emitError(err, message, this); } + return message.util.send(embed); } public memberStatus (member: GuildMember) { diff --git a/src/commands/util/ping.ts b/src/commands/util/ping.ts index 3fd06e91..faaf4153 100644 --- a/src/commands/util/ping.ts +++ b/src/commands/util/ping.ts @@ -1,7 +1,7 @@ import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('ping', { aliases: [ 'ping', 'pong', 'trace' ], diff --git a/src/commands/util/serverInfo.ts b/src/commands/util/serverInfo.ts index 44fb1ae4..96965be0 100644 --- a/src/commands/util/serverInfo.ts +++ b/src/commands/util/serverInfo.ts @@ -1,11 +1,11 @@ import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('serverinfo', { aliases: [ 'serverinfo', 'sinfo', 'si', 'guildinfo', 'ginfo', 'gi', 'settings' ], - description: { content: 'Displays information and bot settings of this server.' }, + description: { content: 'Displays information and bot settings of the server.' }, clientPermissions: [ 'EMBED_LINKS' ], channel: 'guild' }); @@ -29,7 +29,7 @@ export default class extends ErosCommand { return value; }; - const embed = this.util.embed(message) + const embed = this.client.embed(message) .setTitle(message.guild.name) .setDescription(`Created at ${message.guild.createdAt.toUTCString()}`) .setThumbnail(message.guild.iconURL() ? message.guild.iconURL() : this.client.user.displayAvatarURL()) @@ -46,7 +46,7 @@ export default class extends ErosCommand { .addField('Countdown Subscriber Role', getRecord('cdRole'), true) .addField('NSFW Channel', getRecord('nsfwChannel'), true) .addField('NSFW Role', getRecord('nsfwRole'), true) - .addField('Loli Restricted?', getRecord('loli') ? 'Yes. :triumph:' : 'No. :sweat_smile:'); + .addField('Loli Restricted?', 'Yes. **Always** :triumph:'); return message.util.send(embed); } diff --git a/src/commands/util/stats.ts b/src/commands/util/stats.ts index f4183de0..def7a837 100644 --- a/src/commands/util/stats.ts +++ b/src/commands/util/stats.ts @@ -5,10 +5,10 @@ import fetch from 'node-fetch'; import * as os from 'os'; // @ts-ignore import { description, homepage, version as erosVersion } from '../../../package.json'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; import prettifyMs from '../../util/prettifyMs'; -export default class extends ErosCommand { +export default class extends Command { constructor () { super('stats', { aliases: [ 'stats', 'status', 'about', 'aboutme' ], @@ -32,7 +32,7 @@ export default class extends ErosCommand { ]; return message.util.send( - this.util.embed(message) + this.client.embed(message) .setTitle('Eros') .setDescription(_description) .setThumbnail(this.client.user.displayAvatarURL({ format: 'webp', size: 128 })) diff --git a/src/functions/CountdownScheduler.ts b/src/functions/CountdownScheduler.ts index e2135463..57ceb3f9 100644 --- a/src/functions/CountdownScheduler.ts +++ b/src/functions/CountdownScheduler.ts @@ -38,12 +38,9 @@ export default class extends EventEmitter { where: { cdChannel: { [this.client.db.Op.ne]: null } }, attributes: [ 'id', 'cdChannel', 'cdRole' ] }); - const tick = this.client.setInterval(async () => { - if (!guilds.length) { - this.client.logger.info('CountdownScheduler Module: Distributed ' + names.join(', ')); - - return this.client.clearInterval(tick); - } + const send = async () => { + if (!guilds.length) + return this.client.logger.info('CountdownScheduler Module: Distributed ' + names.join(', ')); const spliced = guilds.splice(0, 5); @@ -89,7 +86,13 @@ export default class extends EventEmitter { await channel.send(`${roleText}${prettyNames.join(', ')} ${isPlural} ${action}!`); } - }, 3000); + + await this.client.util.sleep(3e3); + + return await send(); + }; + + await send(); } catch (err) { this.client.logger.warn('CountdownScheduler Module: Error Sending Notification: ' + err); } await this.provider.prepareCountdowns(); @@ -137,7 +140,10 @@ export default class extends EventEmitter { public destroy (date: number | 'this') { if (date === 'this') { - this.client.destroy(); + for (const d of this.schedules.values()) + this.client.clearTimeout(d.fn); + + this.schedules.clear(); return this.client.logger.warn('CountdownScheduler Module: Self Destructed'); } diff --git a/src/functions/Twitter.ts b/src/functions/Twitter.ts index 4f317a30..c51bb00c 100644 --- a/src/functions/Twitter.ts +++ b/src/functions/Twitter.ts @@ -11,8 +11,6 @@ export default class { protected stream = null; - protected tick: NodeJS.Timer = null; - protected recon: NodeJS.Timer = null; protected lastTweetId: string = null; @@ -22,7 +20,6 @@ export default class { if (!config) return this.client.logger.warn('Twitter Module: Config is not set; skipped.'); - const ownerID = this.client.ownerID as string; const twitter = new TwitterClient(config); this.stream = twitter.stream('statuses/filter', { follow: config.user }); @@ -42,9 +39,9 @@ export default class { where: { twitterChannel: { [this.client.db.Op.ne]: null } }, attributes: [ 'id', 'twitterChannel' ] }); - - this.tick = this.client.setInterval(() => { - if (!guilds.length) return this.client.clearInterval(this.tick); + const send = async () => { + if (!guilds.length) + return this.client.logger.info(`Twitter Module: Sent tweet ${tweet.id_str}`); const spliced = guilds.splice(0, 5); @@ -66,43 +63,28 @@ export default class { continue; } - channel.send(`https://twitter.com/${tweet.user.screen_name}/status/${tweet.id_str}`) - .catch(); + await channel.send(`https://twitter.com/${tweet.user.screen_name}/status/${tweet.id_str}`); } - }, 3000); - - const msg = `Twitter Module: Sent tweet ${tweet.id_str}`; - const owner = await this.client.users.fetch(ownerID); - owner.send(msg); + await this.client.util.sleep(3e3); - this.client.logger.info(msg); - }) - .on('start', async () => { - const msg = 'Twitter Module: Connected'; - const owner = await this.client.users.fetch(ownerID); - - owner.send(msg); + return await send(); + }; - this.client.logger.info(msg); + try { + await send(); + } catch (err) { this.client.logger.warn('Twitter Module: Error Sending Tweet Update: ' + err); } }) + .on('start', async () => this.client.logger.info('Twitter Module: Connected')) .on('end', async () => { - const msg = 'Twitter Module: Disconnected'; - const owner = await this.client.users.fetch(ownerID); - - owner.send(msg); - this.client.logger.info(msg); - this.client.clearInterval(this.tick); + this.client.logger.info('Twitter Module: Disconnected'); this.client.clearInterval(this.recon); this.stream.destroy(); this.recon = this.client.setTimeout(() => this.init(), 3e5); }) .on('error', async (err: Error) => { - const msg = `Twitter Module: Error ${err}`; - const owner = await this.client.users.fetch(ownerID); - owner.send(msg); - this.client.logger.info(msg); + this.client.logger.info(`Twitter Module: Error ${err}`); this.stream.emit('end'); }); diff --git a/src/inhibitors/commandHandler/countdown.ts b/src/inhibitors/commandHandler/countdown.ts new file mode 100644 index 00000000..285dd719 --- /dev/null +++ b/src/inhibitors/commandHandler/countdown.ts @@ -0,0 +1,18 @@ +import { Command, Inhibitor } from 'discord-akairo'; +import { Message } from 'discord.js'; + +export default class extends Inhibitor { + constructor () { + super('countdownInhibitor', { + reason: 'countdown unauthorized' + }); + } + + public exec (message: Message, command: Command) { + const [ child ] = message.util.parsed.content.split(/ +/g); + + return command.id === 'countdown' + && [ 'add', 'remove', 'del', 'delete' ].includes(child) + && !this.client.config.countdownAuthorized.includes(message.author.id); + } +} diff --git a/src/inhibitors/guild/guild.ts b/src/inhibitors/guild/guild.ts index 29e9563c..2ae0fbee 100644 --- a/src/inhibitors/guild/guild.ts +++ b/src/inhibitors/guild/guild.ts @@ -3,7 +3,7 @@ import { Message } from 'discord.js'; export default class extends Inhibitor { constructor () { - super('guildInhibit', { + super('guildInhibitor', { reason: 'blacklisted server', type: 'all' }); diff --git a/src/listeners/client/guildCreate.ts b/src/listeners/client/guildCreate.ts index 85bc4079..455e6974 100644 --- a/src/listeners/client/guildCreate.ts +++ b/src/listeners/client/guildCreate.ts @@ -24,7 +24,6 @@ export default class extends Listener { await this.client.db.Guild.create({ id: guild.id, - loli: false, cdChannel: cdChannel ? cdChannel.id : null, cdRole: cdRole ? cdRole.id : null, nsfwChannel: nsfwChannel ? nsfwChannel.id : null, @@ -50,10 +49,11 @@ export default class extends Listener { nsfwChannel ? `\t\`${defaultPrefix}nsfwchannel \`` : '', nsfwRole ? `\t\`${defaultPrefix}nsfwrole \`` : '', twitterChannel ? `\n\t\`${defaultPrefix}twitterchannel \`` : '', - `\n Or refer to \`${defaultPrefix}guide\` (better yet, ${docs}) which is recommended.` + `\n Or refer to \`${defaultPrefix}guide\` (better yet, ${docs}/using-the-bot) which is recommended.` ); - guild.owner.send(welcomeMessage.join('\n')); + await guild.owner.send(welcomeMessage.join('\n')) + .catch(() => this.client.logger.warn(`Could not send message to ${guild.name} (ID: ${guild.id})'s owner.`)); this.client.logger.info(`${guild.name} (ID: ${guild.id}) created. ${guildSize} total guilds.`); } catch (err) { @@ -64,7 +64,8 @@ export default class extends Listener { 'I left your guild because there was a problem initiating your guild.', `If the issue persists, please contact ${guild.client.users.get(this.client.ownerID as string)}`, `Error: ${err}`, - ]); + ]) + .catch(() => this.client.logger.warn(`Could not send message to ${guild.name} (ID: ${guild.id})'s owner.`)); guild.leave(); this.client.logger.error(err); diff --git a/src/listeners/client/ready.ts b/src/listeners/client/ready.ts index 1274318d..e49de971 100644 --- a/src/listeners/client/ready.ts +++ b/src/listeners/client/ready.ts @@ -1,4 +1,6 @@ import { Listener } from 'discord-akairo'; +import GuideCommand from '../../commands/general/guide'; +import GlossaryCommand from '../../commands/kamihime/glossary'; import CountdownScheduler from '../../functions/CountdownScheduler'; import Twitter from '../../functions/Twitter'; @@ -10,7 +12,7 @@ export default class extends Listener { }); } - public exec () { + public async exec () { try { const me = this.client.user; const guildSize = this.client.guilds.size; @@ -33,10 +35,16 @@ export default class extends Listener { this.client.scheduler .on('add', (date, name) => this.client.scheduler.add(date, name)) .on('delete', (date, name) => this.client.scheduler.delete(date, name)); + this.client.twitter.init(); - return this.client.twitter.init(); + const glossaryCommnad = this.client.commandHandler.modules.get('glossary') as GlossaryCommand; + const guideCommand = this.client.commandHandler.modules.get('guide') as GuideCommand; + + await glossaryCommnad.initGlossary(); + + return guideCommand.init.call(guideCommand); } catch (err) { - this.client.logger.error(err); + return this.client.logger.error(err); } } } diff --git a/src/listeners/commandHandler/commandFinished.ts b/src/listeners/commandHandler/commandFinished.ts index 0502c03d..bd2919d9 100644 --- a/src/listeners/commandHandler/commandFinished.ts +++ b/src/listeners/commandHandler/commandFinished.ts @@ -1,6 +1,6 @@ import { Listener } from 'discord-akairo'; import { Message, TextChannel } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; export default class extends Listener { constructor () { @@ -10,7 +10,7 @@ export default class extends Listener { }); } - public async exec (message: Message, command: ErosCommand) { + public async exec (message: Message, command: Command) { const channel = message.channel as TextChannel; if (command.noTrash) return; @@ -25,7 +25,7 @@ export default class extends Listener { await dialog.react('🗑'); const toDelete = await dialog.awaitReactions((r, u) => r.emoji.name === '🗑' && u.id === message.author.id, - { max: 1, time: 30 * 1000, errors: [ 'time' ] } + { max: 1, time: 5e3, errors: [ 'time' ] } ); if (toDelete.first()) await dialog.delete(); diff --git a/src/listeners/commandHandler/commandLoad.ts b/src/listeners/commandHandler/commandLoad.ts index c9385874..83714ac7 100644 --- a/src/listeners/commandHandler/commandLoad.ts +++ b/src/listeners/commandHandler/commandLoad.ts @@ -1,5 +1,5 @@ import { Listener } from 'discord-akairo'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; export default class extends Listener { constructor () { @@ -9,7 +9,7 @@ export default class extends Listener { }); } - public exec (command: ErosCommand) { + public exec (command: Command) { const perms = [ 'SEND_MESSAGES', 'MANAGE_MESSAGES', 'ADD_REACTIONS', 'EMBED_LINKS' ]; const cp = command.clientPermissions as string[]; diff --git a/src/listeners/commandHandler/commandLocked.ts b/src/listeners/commandHandler/commandLocked.ts index 732fc4b7..9d9b2b0d 100644 --- a/src/listeners/commandHandler/commandLocked.ts +++ b/src/listeners/commandHandler/commandLocked.ts @@ -1,6 +1,6 @@ import { Listener } from 'discord-akairo'; import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; export default class extends Listener { constructor () { @@ -10,10 +10,10 @@ export default class extends Listener { }); } - public exec (message: Message, command: ErosCommand) { + public exec (message: Message, command: Command) { const userBased = [ 'you have an existing command that is waiting for you to respond.', - 'If you wish to continue with a new command, please say `cancel` first.', + 'If you wish to continue with a new command, please say \`cancel\` or press the 🗑 emoji above first.', ]; const defaultBased = [ `looks like this command is currently being used in this channel.`, diff --git a/src/listeners/commandHandler/error.ts b/src/listeners/commandHandler/error.ts index 0b2fc5cc..a9e21f18 100644 --- a/src/listeners/commandHandler/error.ts +++ b/src/listeners/commandHandler/error.ts @@ -1,6 +1,6 @@ import { Listener } from 'discord-akairo'; import { Message } from 'discord.js'; -import ErosCommand from '../../struct/command'; +import Command from '../../struct/command'; import ErosError from '../../struct/ErosError'; export default class extends Listener { @@ -11,7 +11,7 @@ export default class extends Listener { }); } - public exec (err: IError, message: Message, command: ErosCommand) { + public exec (err: IError, message: Message, command: Command) { if (command && message) return new ErosError(message, command, err, err.step); } } diff --git a/src/listeners/commandHandler/messageInvalid.ts b/src/listeners/commandHandler/messageInvalid.ts index 8fa44833..4e56c70d 100644 --- a/src/listeners/commandHandler/messageInvalid.ts +++ b/src/listeners/commandHandler/messageInvalid.ts @@ -14,17 +14,18 @@ export default class extends Listener { const parsed = message.util.parsed; if (!message.guild) return; - if (!parsed) return; - if (!parsed.prefix || !parsed.afterPrefix || !parsed.alias) { + if (!parsed.prefix) { await this.validateExp(message); await this.validateStalk(message); return; } + if (!parsed.afterPrefix) return; + const commandHandler = this.client.commandHandler; const command = commandHandler.modules.get('tag-show'); - const args = await command.parse(message, message.util.parsed.afterPrefix); + const args = await command.parse(message, parsed.afterPrefix); return commandHandler.runCommand(message, command, args); } @@ -36,8 +37,7 @@ export default class extends Listener { where: { user: message.author.id, guild: message.guild.id - }, - attributes: [ 'id', 'exp', 'title', 'updatedAt' ] + } }); const eligible = Date.now() > (new Date(member.updatedAt).getTime() + 10e3); diff --git a/src/struct/models/index.ts b/src/struct/Database.ts similarity index 55% rename from src/struct/models/index.ts rename to src/struct/Database.ts index 5df230b2..eb4ab0a4 100644 --- a/src/struct/models/index.ts +++ b/src/struct/Database.ts @@ -1,20 +1,21 @@ -import { ISequelizeConfig, Sequelize } from 'sequelize-typescript'; -import IErosClientOptions from '../../../typings/auth'; -import { Guild } from './factories/Guild'; -import { Level } from './factories/Level'; -import { Storage } from './factories/Storage'; -import { Tag } from './factories/Tag'; -import { Title } from './factories/Title'; +import { Op } from 'sequelize'; +import { Sequelize } from 'sequelize-typescript'; +import IErosClientOptions from '../../typings/auth'; +import { Guild } from './models/Guild'; +import { Level } from './models/Level'; +import { Storage } from './models/Storage'; +import { Tag } from './models/Tag'; +import { Title } from './models/Title'; // tslint:disable-next-line:no-var-requires -const { db }: { db: IErosClientOptions['db'] } = require('../../../auth'); +const { db }: { db: IErosClientOptions['db'] } = require('../../auth'); export const sequelize = new Sequelize({ - database: db.database, + dialect: 'mariadb', host: db.host, + database: db.database, username: db.username, password: db.password, - dialect: 'mariadb', define: { freezeTableName: true, charset: 'utf8', @@ -22,19 +23,18 @@ export const sequelize = new Sequelize({ timestamps: true }, logging: false, - modelPaths: [ __dirname + '/factories' ], - operatorAliases: false, + modelPaths: [ __dirname + '/models' ], pool: { acquire: 30e3, max: 10, min: 0 } -} as ISequelizeConfig); +}); export const create = () => { return { sequelize, - Op: Sequelize.Op, + Op, Guild, Level, Storage, diff --git a/src/struct/ErosClient.ts b/src/struct/ErosClient.ts index 6cacec75..1bf756b2 100644 --- a/src/struct/ErosClient.ts +++ b/src/struct/ErosClient.ts @@ -1,15 +1,16 @@ import IErosClientOptions from 'auth'; import { AkairoClient, InhibitorHandler, ListenerHandler } from 'discord-akairo'; -import { Message } from 'discord.js'; +import { Embeds, FieldsEmbed } from 'discord-paginationembed'; +import { Message, MessageEmbed } from 'discord.js'; import * as Fandom from 'nodemw'; import { promisify } from 'util'; import GuideCommand from '../commands/general/guide'; import ErosError from '../struct/ErosError'; -import { create } from '../struct/models'; import Winston from '../util/console'; import Command from './command'; import ErosCommandHandler from './command/commandHandler'; import CommandHandlerResolverTypes from './command/resolverTypes'; +import { create } from './Database'; import Selection from './util/Selection'; const db = create(); @@ -39,6 +40,8 @@ export default class ErosClient extends AkairoClient { this.util.selection = new Selection(this); + this.util.sleep = ms => new Promise(res => this.setTimeout(res, ms)); + this.commandHandler.resolver.addTypes(new CommandHandlerResolverTypes(this).distribute()); } @@ -49,14 +52,16 @@ export default class ErosClient extends AkairoClient { commandUtil: true, commandUtilLifetime: 1000 * 60 * 3, defaultCooldown: 5000, - defaultPrompt: { - cancel: (msg: Message) => `${msg.author}, command cancelled.`, - ended: (msg: Message) => `${msg.author}, command declined.`, - modifyRetry: (text, msg) => text && `${msg.author}, ${text}\n\nType \`cancel\` to cancel this command.`, - modifyStart: (text, msg) => text && `${msg.author}, ${text}\n\nType \`cancel\` to cancel this command.`, - retries: 3, - time: 30000, - timeout: (msg: Message) => `${msg.author}, command expired.` + argumentDefaults: { + prompt: { + cancel: (msg: Message) => `${msg.author}, command cancelled.`, + ended: (msg: Message) => `${msg.author}, command declined.`, + modifyRetry: (msg, text) => text && `${msg.author}, ${text}\n\nType \`cancel\` to cancel this command.`, + modifyStart: (msg, text) => text && `${msg.author}, ${text}\n\nType \`cancel\` to cancel this command.`, + retries: 3, + time: 30000, + timeout: (msg: Message) => `${msg.author}, command expired.` + } }, directory: `${__dirname}/../commands`, prefix: async message => { @@ -64,7 +69,6 @@ export default class ErosClient extends AkairoClient { const [ guild ] = await this.db.Guild.findOrCreate({ where: { id: message.guild.id }, - attributes: [ 'prefix' ], defaults: { prefix: this.config.defaultPrefix, id: message.guild.id, @@ -114,7 +118,7 @@ export default class ErosClient extends AkairoClient { const docs = this.commandHandler.modules.get('guide') as GuideCommand; - return docs.parseDialogs(); + return docs.renderDialogs(); } const force = [ '-f', '--force' ].some(f => process.argv.includes(f)); @@ -136,6 +140,51 @@ export default class ErosClient extends AkairoClient { return this.login(this.config.token); } + public embed (message: Message = null) { + const instance = new MessageEmbed() + .setColor(0xFF00ae); + + if (message) + instance + .setFooter(`Executed by: ${message.author.tag} (${message.author.id})`) + .setTimestamp(new Date()); + + return instance; + } + + public embeds (message: Message = null, array: MessageEmbed[] = null) { + const instance = new Embeds() + .on('error', e => this.logger.error(e)); + + if (array) instance.setArray(array); + + for (const embed of array) + if (!embed.color) embed.setColor(0xFF00AE); + + if (message) + instance + .setFooter(`Executed by: ${message.author.tag} (${message.author.id})`) + .setTimestamp(); + + return instance; + } + + public fields (message: Message = null) { + const instance = new FieldsEmbed() + .on('error', e => this.logger.error(e)); + + instance.embed + .setColor(0xFF00AE); + + if (message) + instance + .embed + .setFooter(`Executed by: ${message.author.tag} (${message.author.id})`) + .setTimestamp(new Date()); + + return instance; + } + get db () { return db; } diff --git a/src/struct/ErosError.ts b/src/struct/ErosError.ts index 06e176b2..9986db08 100644 --- a/src/struct/ErosError.ts +++ b/src/struct/ErosError.ts @@ -1,6 +1,6 @@ import { Message } from 'discord.js'; import { supportLink } from '../../auth'; -import ErosCommand from './command'; +import Command from './command'; export default class ErosError { @@ -15,7 +15,7 @@ export default class ErosError { * - `2`: Kamihime Fandom Request * - `3`: Menu Selection */ - constructor (message: Message, command: ErosCommand, error: Error = null, code = 0) { + constructor (message: Message, command: Command, error: Error = null, code = 0) { this.message = message; this.command = command; @@ -29,7 +29,7 @@ export default class ErosError { protected message: Message; - protected command: ErosCommand; + protected command: Command; protected err: Error; diff --git a/src/struct/Info.ts b/src/struct/Info.ts index 3c926de7..d8d9298b 100644 --- a/src/struct/Info.ts +++ b/src/struct/Info.ts @@ -1,9 +1,4 @@ -import _Eidolon from './info/sub/EidolonInfo'; -import _Kamihime from './info/sub/KamihimeInfo'; -import _Soul from './info/sub/SoulInfo'; -import _Weapon from './info/sub/WeaponInfo'; - -export const Eidolon = _Eidolon; -export const Kamihime = _Kamihime; -export const Soul = _Soul; -export const Weapon = _Weapon; +export * from './info/sub/EidolonInfo'; +export * from './info/sub/KamihimeInfo'; +export * from './info/sub/SoulInfo'; +export * from './info/sub/WeaponInfo'; diff --git a/src/struct/command/ErosInfoCommand.ts b/src/struct/command/InfoCommand.ts similarity index 93% rename from src/struct/command/ErosInfoCommand.ts rename to src/struct/command/InfoCommand.ts index c7db95f2..47bfcbe2 100644 --- a/src/struct/command/ErosInfoCommand.ts +++ b/src/struct/command/InfoCommand.ts @@ -1,9 +1,9 @@ import { Message } from 'discord.js'; import fetch from 'node-fetch'; -import ErosCommand, { ICommandOptions } from '.'; +import Command, { ICommandOptions } from '.'; import { IKamihimeDB } from '../../../typings'; -export default abstract class ErosInfoCommand extends ErosCommand { +export default abstract class InfoCommand extends Command { constructor (id: string, options: ICommandOptions) { super(id, options); } diff --git a/src/struct/command/commandHandler.ts b/src/struct/command/commandHandler.ts index a927802c..97ff71e3 100644 --- a/src/struct/command/commandHandler.ts +++ b/src/struct/command/commandHandler.ts @@ -1,6 +1,6 @@ import { Category, CommandHandler, CommandHandlerOptions, PrefixSupplier } from 'discord-akairo'; -import { Collection } from 'discord.js'; -import ErosCommand from '../command'; +import { Collection, Message } from 'discord.js'; +import Command from '../command'; import ErosClient from '../ErosClient'; export default class ErosCommandHandler extends CommandHandler { @@ -8,17 +8,34 @@ export default class ErosCommandHandler extends CommandHandler { super(client, options); } - public categories: Collection>; + public categories: Collection>; - public modules: Collection; + public modules: Collection; public prefix: PrefixSupplier; + /** + * @param step - Available Codes + * - `0`: Client Error (default) + * - `1`: KamihimeDB Request + * - `2`: Kamihime Fandom Request + * - `3`: Menu Selection + */ + public emitError (err: Error, message: Message, command?: Command, step = 0) { + Object.assign(err, { step }); + + return super.emitError(err, message, command); + } + + public reactFail (message: Message) { + return message.react('❌'); + } + public findCategory (name: string) { - return super.findCategory(name) as Category; + return super.findCategory(name) as Category; } public findCommand (name: string) { - return super.findCommand(name) as ErosCommand; + return super.findCommand(name) as Command; } } diff --git a/src/struct/command/index.ts b/src/struct/command/index.ts index 98dd175f..24c835f2 100644 --- a/src/struct/command/index.ts +++ b/src/struct/command/index.ts @@ -1,11 +1,9 @@ -import { Command, CommandOptions } from 'discord-akairo'; -import { Embeds, FieldsEmbed } from 'discord-paginationembed'; -import { Message, MessageEmbed } from 'discord.js'; +import { Command as AkairoCommand, CommandOptions } from 'discord-akairo'; import GuideCommand from '../../commands/general/guide'; import ErosClient from '../ErosClient'; import ErosCommandHandler from './commandHandler'; -export default class ErosCommand extends Command { +export default class ErosCommand extends AkairoCommand { constructor (id: string, options: ICommandOptions) { super(id, options); @@ -18,69 +16,11 @@ export default class ErosCommand extends Command { public noTrash: boolean; - public util = { - embed: this.embed, - embeds: this.embeds, - fields: this.fields - }; - - public embed (message: Message = null) { - const instance = new MessageEmbed() - .setColor(0xFF00ae); - - if (message) - instance - .setFooter(`Executed by: ${message.author.tag} (${message.author.id})`) - .setTimestamp(new Date()); - - return instance; - } - - public embeds (message: Message = null, array: MessageEmbed[] = null) { - const instance = new Embeds(); - - if (array) instance.setArray(array); - - instance.setColor(0xFF00AE); - - if (message) - instance - .setFooter(`Executed by: ${message.author.tag} (${message.author.id})`) - .setTimestamp(); - - return instance; - } - - public fields (message: Message = null) { - const instance = new FieldsEmbed(); - - instance.embed - .setColor(0xFF00AE); - - if (message) - instance - .embed - .setFooter(`Executed by: ${message.author.tag} (${message.author.id})`) - .setTimestamp(new Date()); - - return instance; - } - - public emitError (err: Error, message: Message, command?: ErosCommand, step?: number) { - Object.assign(err, { step }); - - return this.handler.emitError(err, message, command); - } - - public fail (message: Message) { - return message.react('❌'); - } - get guidePage () { - const page = (this.handler.modules.get('guide') as GuideCommand).dialogs - .findIndex(c => c.command && c.command === this.id); + const page = (this.handler.modules.get('guide') as GuideCommand) + .formattedCommandDialogs[this.id]; - return page !== -1 ? page + 2 : null; + return page; } } diff --git a/src/struct/command/resolverTypes.ts b/src/struct/command/resolverTypes.ts index cd240b12..21b5c58c 100644 --- a/src/struct/command/resolverTypes.ts +++ b/src/struct/command/resolverTypes.ts @@ -13,12 +13,12 @@ export default class CommandHandlerResolverTypes { public distribute (): { [name: string]: ArgumentTypeCaster } { return { - question: phrase => { + question: (_, phrase) => { if (!phrase) return null; return phrase.endsWith('?') ? phrase : null; }, - interval: phrase => { + interval: (_, phrase) => { if (!phrase) return phrase; let seconds = Math.abs(parseInt(phrase)); @@ -31,7 +31,7 @@ export default class CommandHandlerResolverTypes { return trueSeconds > 120 ? null : seconds; }, - existingCountdown: phrase => { + existingCountdown: (_, phrase) => { if (!phrase) return null; const parent = this.client.commandHandler.modules.get('countdown') as CountdownCommand; @@ -39,7 +39,7 @@ export default class CommandHandlerResolverTypes { return countdown ? null : phrase; }, - countdown: phrase => { + countdown: (_, phrase) => { if (!phrase) return null; const parent = this.client.commandHandler.modules.get('countdown') as CountdownCommand; @@ -47,7 +47,7 @@ export default class CommandHandlerResolverTypes { return countdown || null; }, - countdownDate: phrase => { + countdownDate: (_, phrase) => { if (!phrase) return null; const parent = this.client.commandHandler.modules.get('countdown') as CountdownCommand; @@ -67,7 +67,7 @@ export default class CommandHandlerResolverTypes { return result; }, - existingTag: async (phrase, message) => { + existingTag: async (message, phrase) => { if (!phrase) return null; phrase = Util.cleanContent(phrase.toLowerCase(), message); @@ -82,7 +82,7 @@ export default class CommandHandlerResolverTypes { return phrase; }, - tag: async (phrase, message) => { + tag: async (message, phrase) => { if (!phrase) return null; phrase = Util.cleanContent(phrase.toLowerCase(), message); @@ -95,7 +95,7 @@ export default class CommandHandlerResolverTypes { return tag || null; }, - tagContent: async (content, message) => { + tagContent: async (message, content) => { if (!content || content.length > 1950) return null; content = Util.cleanContent(content, message); diff --git a/src/struct/info/base/Info.ts b/src/struct/info/base/Info.ts index 4ea3ef1d..e626488b 100644 --- a/src/struct/info/base/Info.ts +++ b/src/struct/info/base/Info.ts @@ -1,4 +1,5 @@ import { MessageEmbed } from 'discord.js'; +import { EmbedField } from 'discord.js'; import { IKamihimeDB, IKamihimeFandom, IKamihimeFandomFormatted } from '../../../../typings'; import IErosClientOptions from '../../../../typings/auth'; import ErosClient from '../../ErosClient'; @@ -60,7 +61,7 @@ export default class Info { .setThumbnail(character.thumbnail) .setAuthor(character.name, null, character.link); - if (character.rarity) + if (character.rarity && !new RegExp(`^${emojis['SSR+']}`).test(embed.description)) embed.description = `${emojis[character.rarity]} ${embed.description}`; if (includePreset) { @@ -70,17 +71,16 @@ export default class Info { value: `**HP: ${character.hp}** | **ATK: ${character.atk}**` }; - if (character.atkFBL && character.hpFBL) - stats.value += `\n★ [Final Break Limit]: **${character.hpFBL}** | **${character.atkFBL}**`; - if (embed.fields.length) { const oldFields = embed.fields; - embed.fields = []; + const existingStatsIdx = oldFields.findIndex(el => el.name === stats.name); + let existingStats: EmbedField; - embed.fields.push(stats); + if (existingStatsIdx >= 0) + existingStats = oldFields.splice(existingStatsIdx, 1).shift(); - for (const field of oldFields) - embed.fields.push(field); + embed.fields = []; + embed.fields.push(existingStats || stats, ...oldFields); } else embed.fields.push(stats); } @@ -112,7 +112,7 @@ export default class Info { if (!assistAbility) continue; embed.addField(`:sparkle:: ${assistAbility.name}`, - [ assistAbility.description, assistAbility.upgradeDescription ], + [ assistAbility.description, assistAbility.upgrades ? assistAbility.upgrades.join('\n') : '' ], true ); } diff --git a/src/struct/info/sub/EidolonInfo.ts b/src/struct/info/sub/EidolonInfo.ts index 3157502b..5e18633f 100644 --- a/src/struct/info/sub/EidolonInfo.ts +++ b/src/struct/info/sub/EidolonInfo.ts @@ -2,7 +2,7 @@ import { MessageEmbed } from 'discord.js'; import { IKamihimeFandomEidolon, IKamihimeFandomFormatted } from '../../../../typings'; import Info from '../base/Info'; -export default class EidolonInfo extends Info { +export class EidolonInfo extends Info { public character: IKamihimeFandomEidolon; public format () { diff --git a/src/struct/info/sub/KamihimeInfo.ts b/src/struct/info/sub/KamihimeInfo.ts index eae53a36..e9e1a680 100644 --- a/src/struct/info/sub/KamihimeInfo.ts +++ b/src/struct/info/sub/KamihimeInfo.ts @@ -2,7 +2,7 @@ import { MessageEmbed } from 'discord.js'; import { IKamihimeFandomFormatted, IKamihimeFandomKamihime } from '../../../../typings'; import Info from '../base/Info'; -export default class KamihimeInfo extends Info { +export class KamihimeInfo extends Info { public character: IKamihimeFandomKamihime; public format () { @@ -81,7 +81,11 @@ export default class KamihimeInfo extends Info { burst: { name: character.burstName, - description: character.burstDesc || burstDescParse(), + description: character.burstDesc + ? (character.burstDesc.startsWith(character.element) + ? character.burstDesc + : `${burstDescParse} and ${character.burstDesc}`) + : burstDescParse(), upgradeDescription: ( character.rarity === 'SSR+' ? null @@ -123,6 +127,16 @@ export default class KamihimeInfo extends Info { duration: character.ability3Dur || null } : null, + + character.ability4Name + ? { + name: character.ability4Name, + description: character.ability4Desc, + upgradeDescription: abilityDescParse(character.ability4PowerupDesc, 3), + cooldown: character.ability4Cd, + duration: character.ability4Dur || null + } + : null, ], assistAbilities: [ @@ -130,9 +144,28 @@ export default class KamihimeInfo extends Info { ? { name: character.assistName, description: character.assistDesc, - upgradeDescription: character.assistPowerupDesc - ? abilityDescParse(character.assistPowerupDesc, 4) - : null + upgrades: [ + character.assistPowerupDesc + ? abilityDescParse(character.assistPowerupDesc, 4) + : null, + character.assistPowerup2Desc + ? abilityDescParse(character.assistPowerup2Desc, 2) + : null, + ] + } + : null, + character.assist2Name + ? { + name: character.assist2Name, + description: character.assist2Desc, + upgrades: [ + character.assist2PowerupDesc + ? abilityDescParse(character.assist2PowerupDesc, 4) + : null, + character.assist2Powerup2Desc + ? abilityDescParse(character.assist2Powerup2Desc, 2) + : null, + ] } : null, ], diff --git a/src/struct/info/sub/SoulInfo.ts b/src/struct/info/sub/SoulInfo.ts index 387eceb7..bb1233a0 100644 --- a/src/struct/info/sub/SoulInfo.ts +++ b/src/struct/info/sub/SoulInfo.ts @@ -2,7 +2,7 @@ import { MessageEmbed } from 'discord.js'; import { IKamihimeFandomFormatted, IKamihimeFandomSoul } from '../../../../typings'; import Info from '../base/Info'; -export default class SoulInfo extends Info { +export class SoulInfo extends Info { public character: IKamihimeFandomSoul; public format () { diff --git a/src/struct/info/sub/WeaponInfo.ts b/src/struct/info/sub/WeaponInfo.ts index ad2c5b38..bb160397 100644 --- a/src/struct/info/sub/WeaponInfo.ts +++ b/src/struct/info/sub/WeaponInfo.ts @@ -2,72 +2,103 @@ import { MessageEmbed } from 'discord.js'; import { IKamihimeFandomFormatted, IKamihimeFandomWeapon } from '../../../../typings'; import Info from '../base/Info'; -export default class WeaponInfo extends Info { +export class WeaponInfo extends Info { public character: IKamihimeFandomWeapon; public format () { - const { fandomURI, colors } = this; const weapon = this.template(); - const list = []; - const discriminator = { - SSR: { - Fire: 'Inferno', - Water: 'Cocytus', - Wind: 'Turbulence', - Thunder: 'Impulse', - Light: 'Lumina', - Dark: 'Schwarz' - }, - SR: { - Fire: 'Burning', - Water: 'Blizzard', - Wind: 'Storm', - Thunder: 'Plasma', - Light: 'Shine', - Dark: 'Abyss' - }, - R: { - Fire: 'Fire', - Water: 'Aqua', - Wind: 'Aero', - Thunder: 'Thunder', - Light: 'Ray', - Dark: 'Dark' - } - }; - const scaleDiscriminator = { - SSR: '(++)', - SR: '(+)', - R: '' - }; - const burstScaleDiscriminator = { - SSR: '(++++)', - SR: '(++)', - R: '(+)' - }; - const skillParser = { - Upgrade: { - SSR: '**Large Chalice of Deceit**: Weapon Enhance skill Lv up chance↑ (++)', - SR: '**Chalice of Deceit**: Weapon Enhance skill Lv up chance↑ (+)', - R: '**Vessel of Sorcery**: Weapon Enhance skill Lv up chance↑' - }, - Assault: 'Characters\' ATK↑', - Defender: 'Characters\' HP↑', - Pride: 'Characters with low HP, ATK↑', - Rush: 'Characters\' Double Attack Rate↑', - Barrage: 'Characters\' Triple Attack Rate↑', - Stinger: 'Characters\' Critical Hit Rate↑', - Exceed: 'Characters\' Burst↑', - Ascension: 'Characters\' Recovery↑', - Elaborate: 'Characters\' Ability↑' - }; + const embed = this.generateEmbed(weapon); + const skills = weapon.skill.filter(el => el); + + if (skills.length) { + const formattedSkills = skills.map(skill => { + if (/\s/.test(skill.name)) + return `**${skill.name}**: ${skill.description}`; + else if (skill.name === 'Upgrade') + return skillParser.Upgrade[weapon.rarity]; + + return [ + `**${discriminator[weapon.rarity][weapon.elements[0]]} ${skill.name}**:`, + weapon.elements[0], + skillParser[skill.name], + scaleDiscriminator[weapon.rarity], + ].join(' '); + }); + + embed.addField(`Weapon Skill Type${skills.length > 1 ? 's' : ''}`, formattedSkills, true); + } + + const bursts = weapon.burstDesc.filter(el => el); + const formattedBurst = `${weapon.elements[0]} DMG ${burstScaleDiscriminator[weapon.rarity]}`; + + if (bursts.length) { + const formattedBursts = bursts.map((b, i) => + bursts.length > 1 + ? `${'★'.repeat(i)}${'☆'.repeat(bursts.length - 1 - i)} | ${b}\n` + : `${formattedBurst} and ${b}` + ); + + embed.addField('Weapon Burst Effect', formattedBursts); + } else + embed.addField('Weapon Burst Effect', formattedBurst); + + return super.format(embed, weapon); + } + + public formatFLB () { + const weapon = this.template(); + const embed = this.generateEmbed(weapon, true); + + embed.addField('Maximum Basic Stats', `**HP: ${weapon.hpFLB}** | **ATK: ${weapon.atkFLB}**`); + + const skillFlbs = weapon.skillFLB.filter(el => el); + + if (skillFlbs.length) + embed.addField( + `Weapon Skill Type${skillFlbs.length > 1 ? 's' : ''}`, + skillFlbs.map(skill => { + if (/\s/.test(skill.name)) + return `**${skill.name}**: ${skill.description}`; + + return [ + `**${discriminator[weapon.rarity][weapon.elements[0]]} ${skill.name}**:`, + weapon.elements[0], + skillParser[skill.name], + scaleDiscriminator[weapon.rarity], + ].join(' '); + }).join('\n'), + true + ); + + if (weapon.burstFLBDesc) + embed.addField('Weapon Burst Effect', weapon.burstFLBDesc); + else { + const bursts = weapon.burstDesc.filter(el => el); + const formattedBurst = `${weapon.elements[0]} DMG ${burstScaleDiscriminator['SSR+']}`; + if (bursts.length) { + const formattedBursts = bursts.map(b => `${formattedBurst} and ${b}`); + + embed.addField('Weapon Burst Effect', formattedBursts); + } else + embed.addField('Weapon Burst Effect', formattedBurst); + } + + return super.format(embed, weapon); + } + + protected generateEmbed (weapon: IKamihimeFandomFormatted, flb = false) { + const { fandomURI, colors } = this; const cleanReleaseLink = `${fandomURI}${encodeURI(weapon.releases)}`.replace(/(\(|\))/g, '\\$&'); const elements = weapon.elements.every(e => Boolean(e)) ? weapon.elements.join('/') : weapon.elements[0]; const embed = new MessageEmbed() .setDescription( [ - `__**Weapon**__ | __**${weapon.type}**__ | __**${elements}**__${ + `${ + flb + ? `${this.client.config.emojis['SSR+']} ` + : '' + }__**Weapon**__ | __**${weapon.type}**__ | __**${elements}**__${ weapon.releases ? ` | __**[${weapon.releases}](${cleanReleaseLink} "Kamihime Release")**__` : '' @@ -75,59 +106,9 @@ export default class WeaponInfo extends Info { weapon.description, ] ) - .setColor(colors[weapon.rarity]); - - for (let skill of weapon.skills) - if (skill) { - const index = weapon.skills.indexOf(skill); - - if (/\s/.test(skill)) { - skill = `**${skill}**: ${weapon.skillDesc[weapon.skills.indexOf(skill)]}`; - - if (weapon.skillFBL[index]) - skill += `\n ★ [Final Break Limit]:\n ${weapon.skillFBL[index]}`; - } else if (skill === 'Upgrade') - skill = skillParser.Upgrade[weapon.rarity]; - else - skill = [ - `**${discriminator[weapon.rarity][weapon.elements[0]]} ${skill}**:`, - weapon.elements[0], - skillParser[skill], - scaleDiscriminator[weapon.rarity], - ].join(' '); - - list.push(skill); - } + .setColor(colors[flb ? 'SSRA' : weapon.rarity]); - if (list.length) - embed.addField(`Weapon Skill Type${list.length > 1 ? 's' : ''}`, list.join('\n'), true); - - const bursts = []; - - for (let burst of weapon.burstDesc) { - if (!burst) continue; - if (weapon.burstFBL) - burst += `\n ★ [Final Break Limit]:\n ${weapon.burstFBL}`; - - bursts.push(burst); - } - - if (bursts.length) - embed.addField( - 'Weapon Burst Effect', - bursts.map((b, i) => - bursts.length > 1 - ? `${'★'.repeat(i)}${'☆'.repeat(bursts.length - 1 - i)} | ${b}\n` - : b - ) - ); - else - embed.addField('Weapon Burst Effect', [ - `${weapon.elements[0]} DMG ${burstScaleDiscriminator[weapon.rarity]}`, - weapon.burstFBL ? ` ★ [Final Break Limit]:\n ${weapon.burstFBL}` : '', - ]); - - return super.format(embed, weapon); + return embed; } public template () { @@ -144,21 +125,25 @@ export default class WeaponInfo extends Info { preview, rarity: character.rarity, type: character.weaponType, - skills: [ + skill: [ character.skillType || character.skill || character.skill1 - ? character.skillType || character.skill || character.skill1 + ? { name: character.skillType || character.skill || character.skill1, description: character.skillDesc } : null, character.skillType2 || character.skill2 - ? character.skillType2 || character.skill2 + ? { name: character.skillType2 || character.skill2, description: character.skill2Desc } : null, ], - skillFBL: [ - character.skill1Fbl || '', - character.skill2Fbl || '', - ], - skillDesc: [ - character.skillDesc || '', - character.skill2Desc || '', + skillFLB: [ + character.skillFlb + ? { name: character.skillFlb, description: character.skillFlbDesc } + : character.skillType || character.skill || character.skill1 + ? { name: character.skillType || character.skill || character.skill1, description: character.skillDesc } + : null, + character.skill2Flb + ? { name: character.skill2Flb, description: character.skill2FlbDesc } + : character.skillType2 || character.skill2 + ? { name: character.skillType2 || character.skill2, description: character.skill2Desc } + : null, ], elements: [ character.element, @@ -169,10 +154,10 @@ export default class WeaponInfo extends Info { character.element6, ], atk: character.atkMax, - atkFBL: character.atkFbl, + atkFLB: character.atkFlb, hp: character.hpMax, - hpFBL: character.hpFbl, - burstFBL: character.burstFbl, + hpFLB: character.hpFlb, + burstFLBDesc: character.burstFlbDesc, burstDesc: [ character.burstDesc ? character.burstDesc : character.burstDesc0 || null, character.burstDesc1 || null, @@ -184,3 +169,57 @@ export default class WeaponInfo extends Info { } as IKamihimeFandomFormatted; } } + +const discriminator = { + SSR: { + Fire: 'Inferno', + Water: 'Cocytus', + Wind: 'Turbulence', + Thunder: 'Impulse', + Light: 'Lumina', + Dark: 'Schwarz' + }, + SR: { + Fire: 'Burning', + Water: 'Blizzard', + Wind: 'Storm', + Thunder: 'Plasma', + Light: 'Shine', + Dark: 'Abyss' + }, + R: { + Fire: 'Fire', + Water: 'Aqua', + Wind: 'Aero', + Thunder: 'Thunder', + Light: 'Ray', + Dark: 'Dark' + } +}; +const scaleDiscriminator = { + SSR: '(++)', + SR: '(+)', + R: '' +}; +const burstScaleDiscriminator = { + 'SSR+': '(+++++)', + SSR: '(++++)', + SR: '(++)', + R: '(+)' +}; +const skillParser = { + Upgrade: { + SSR: '**Large Chalice of Deceit**: Weapon Enhance skill Lv up chance↑ (++)', + SR: '**Chalice of Deceit**: Weapon Enhance skill Lv up chance↑ (+)', + R: '**Vessel of Sorcery**: Weapon Enhance skill Lv up chance↑' + }, + Assault: 'Characters\' ATK↑', + Defender: 'Characters\' HP↑', + Pride: 'Characters with low HP, ATK↑', + Rush: 'Characters\' Double Attack Rate↑', + Barrage: 'Characters\' Triple Attack Rate↑', + Stinger: 'Characters\' Critical Hit Rate↑', + Exceed: 'Characters\' Burst↑', + Ascension: 'Characters\' Recovery↑', + Elaborate: 'Characters\' Ability↑' +}; diff --git a/src/struct/models/factories/Guild.ts b/src/struct/models/Guild.ts similarity index 82% rename from src/struct/models/factories/Guild.ts rename to src/struct/models/Guild.ts index a45815a5..d5a2689c 100644 --- a/src/struct/models/factories/Guild.ts +++ b/src/struct/models/Guild.ts @@ -1,8 +1,8 @@ import { Column, DataType, Model, PrimaryKey, Table } from 'sequelize-typescript'; -import IErosClientOptions from '../../../../typings/auth'; +import IErosClientOptions from '../../../typings/auth'; // tslint:disable-next-line:no-var-requires -const { defaultPrefix }: { defaultPrefix: IErosClientOptions['defaultPrefix'] } = require('../../../../auth'); +const { defaultPrefix }: { defaultPrefix: IErosClientOptions['defaultPrefix'] } = require('../../../auth'); @Table({ tableName: 'guilds' }) export class Guild extends Model { @@ -16,9 +16,6 @@ export class Guild extends Model { @Column({ autoIncrement: false, type: DataType.STRING }) public id: string; - @Column({ defaultValue: false }) - public loli?: boolean; - @Column({ defaultValue: null }) public nsfwChannel?: string; diff --git a/src/struct/models/factories/Level.ts b/src/struct/models/Level.ts similarity index 100% rename from src/struct/models/factories/Level.ts rename to src/struct/models/Level.ts diff --git a/src/struct/models/factories/Storage.ts b/src/struct/models/Storage.ts similarity index 100% rename from src/struct/models/factories/Storage.ts rename to src/struct/models/Storage.ts diff --git a/src/struct/models/factories/Tag.ts b/src/struct/models/Tag.ts similarity index 100% rename from src/struct/models/factories/Tag.ts rename to src/struct/models/Tag.ts diff --git a/src/struct/models/factories/Title.ts b/src/struct/models/Title.ts similarity index 100% rename from src/struct/models/factories/Title.ts rename to src/struct/models/Title.ts diff --git a/src/struct/util/Selection.ts b/src/struct/util/Selection.ts index e5e297bc..4f2d1016 100644 --- a/src/struct/util/Selection.ts +++ b/src/struct/util/Selection.ts @@ -1,6 +1,6 @@ import { Message, TextChannel } from 'discord.js'; import { IKamihimeDB } from '../../../typings'; -import ErosCommand from '../command'; +import Command from '../command'; import ErosClient from '../ErosClient'; export default class { @@ -10,7 +10,7 @@ export default class { protected client: ErosClient; - public async exec (message: Message, command: ErosCommand, rows: IKamihimeDB[]) { + public async exec (message: Message, command: Command, rows: IKamihimeDB[]) { const client = this.client; const embed = client.util.embed() .setColor(0xFF00AE) @@ -25,7 +25,7 @@ export default class { .addField('#', rows.map(i => rows.indexOf(i) + 1).join('\n'), true) .addField('Name', rows.map(i => i.name).join('\n'), true); - await message.util.edit(embed); + await message.util.edit(null, embed); let character: IKamihimeDB = null; diff --git a/tsconfig.json b/tsconfig.json index 91b1cbfb..2a8d1588 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "paths": { "*": ["typings/*"] }, "removeComments": true, "rootDir": "src", - "target": "es2018" + "target": "es2018", + "incremental": true } } diff --git a/typings/index.d.ts b/typings/index.d.ts index ea932650..1a265c74 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -2,23 +2,26 @@ import { InhibitorHandler, ListenerHandler } from 'discord-akairo'; import { StringResolvable } from 'discord.js'; import * as Fandom from 'nodemw'; import { Sequelize } from 'sequelize-typescript'; +import { Op } from 'sequelize'; import { Logger } from 'winston'; import CountdownScheduler from '../src/functions/CountdownScheduler'; import Twitter from '../src/functions/Twitter'; import ErosCommandHandler from '../src/struct/command/commandHandler'; import ErosError from '../src/struct/ErosError'; -import { Guild } from '../src/struct/models/factories/Guild'; -import { Level } from '../src/struct/models/factories/Level'; -import { Storage } from '../src/struct/models/factories/Storage'; -import { Tag } from '../src/struct/models/factories/Tag'; -import { Title } from '../src/struct/models/factories/Title'; +import { Guild } from '../src/struct/models/Guild'; +import { Level } from '../src/struct/models/Level'; +import { Storage } from '../src/struct/models/Storage'; +import { Tag } from '../src/struct/models/Tag'; +import { Title } from '../src/struct/models/Title'; import Selection from '../src/struct/util/Selection'; import IErosClientOptions from './auth'; +import { Embeds, FieldsEmbed } from 'discord-paginationembed'; declare module 'discord-akairo' { export interface ClientUtil { selection: Selection; getArticle: (title: string) => Promise; + sleep: (ms: number) => Promise; } } @@ -54,7 +57,7 @@ interface ErosClient { ErosError: typeof ErosError; db: { sequelize: Sequelize; - Op: Sequelize["Op"]; + Op: typeof Op; Guild: typeof Guild; Level: typeof Level; Storage: typeof Storage; @@ -85,9 +88,9 @@ export interface IKamihimeFandom { export interface IKamihimeFandomFormatted { name: string; atk?: number; - atkFBL?: number; + atkFLB?: number; hp?: number; - hpFBL?: number; + hpFLB?: number; obtainedFrom?: string; releaseWeapon?: string; releases?: string; @@ -103,9 +106,14 @@ export interface IKamihimeFandomFormatted { preview: string; link?: string; type?: string; - skills?: string[]; - skillDesc?: string[]; - skillFBL?: string[]; + skill?: { + name: string; + description: string; + }[]; + skillFLB?: { + name: string; + description: string; + }[]; element?: string; elements?: string[]; burst?: { @@ -114,7 +122,7 @@ export interface IKamihimeFandomFormatted { upgradeDescription?: string; }; burstDesc?: string[]; - burstFBL?: string; + burstFLBDesc?: string; abilities?: { name: string; cooldown: string; @@ -125,7 +133,7 @@ export interface IKamihimeFandomFormatted { assistAbilities?: { name: string; description: string; - upgradeDescription?: string; + upgrades?: string[]; }[]; mex?: { @@ -177,9 +185,19 @@ export interface IKamihimeFandomKamihime extends IKamihimeFandom { ability3PowerupDesc?: string; ability3Cd?: string; ability3Dur?: string; + ability4Name?: string; + ability4Desc?: string; + ability4PowerupDesc?: string; + ability4Cd?: string; + ability4Dur?: string; assistName?: string; assistDesc?: string; assistPowerupDesc?: string; + assistPowerup2Desc?: string; + assist2Name?: string; + assist2Desc?: string; + assist2PowerupDesc?: string; + assist2Powerup2Desc?: string; favouriteWeapon?: string; releaseWeapon?: string; } @@ -212,8 +230,10 @@ export interface IKamihimeFandomWeapon extends IKamihimeFandomKamihime { skill1?: string; skillType2?: string; skill2?: string; - skill1Fbl?: string; - skill2Fbl?: string; + skillFlb?: string; + skillFlbDesc?: string; + skill2Flb?: string; + skill2FlbDesc?: string; skillDesc?: string; skill2Desc?: string; element2?: string; @@ -221,9 +241,9 @@ export interface IKamihimeFandomWeapon extends IKamihimeFandomKamihime { element4?: string; element5: string; element6: string; - atkFbl?: number; - hpFbl?: number; - burstFbl?: string; + atkFlb?: number; + hpFlb?: number; + burstFlbDesc?: string; burstDesc0?: string; burstDesc1?: string; burstDesc2?: string; @@ -251,4 +271,6 @@ export interface IKamihimeDB extends IKamihimeFandom { id: string; loli: number; peeks?: number; + atk?: number; + hp?: number; } diff --git a/yarn.lock b/yarn.lock index 0c40f1f7..22a3d63d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,23 +2,27 @@ # yarn lockfile v1 -"@types/bluebird@*": +"@babel/code-frame@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" + integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/highlight@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" + integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +"@types/bluebird@^3.5.26": version "3.5.26" resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.26.tgz#a38c438ae84fa02431d6892edf86e46edcbca291" integrity sha512-aj2mrBLn5ky0GmAg6IPXrQjnN0iB/ulozuJ+oZdrHRAzRbXjGmu4UXsNCjFvPbSaaPZmniocdOzsM392qLOlmQ== -"@types/bluebird@3.5.18": - version "3.5.18" - resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.18.tgz#6a60435d4663e290f3709898a4f75014f279c4d6" - integrity sha512-OTPWHmsyW18BhrnG5x8F7PzeZ2nFxmHGb42bZn79P9hl+GI5cMzyPgQTwNjbem0lJhoru/8vtjAFCUOu3+gE2w== - -"@types/continuation-local-storage@*": - version "3.2.1" - resolved "https://registry.yarnpkg.com/@types/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#a33e0df9dce9b424d1c98fc4fdebd8578dceec7e" - integrity sha1-oz4N+dzptCTRyY/E/evYV43O7H4= - dependencies: - "@types/node" "*" - "@types/fs-extra@^5.0.5": version "5.0.5" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.5.tgz#080d90a792f3fa2c5559eb44bd8ef840aae9104b" @@ -31,11 +35,6 @@ resolved "https://registry.yarnpkg.com/@types/json2md/-/json2md-1.5.0.tgz#0d940ac45f2ad19ff68fc43747953f53b1782204" integrity sha512-hHUubzEkXd3ciDP+TF0EiqwRSTp5beLIsnZ6eIfBu56S7AA7zPXxnkS2F7OtB65X4p1+o8SklRZoZii8jfx3ng== -"@types/lodash@*": - version "4.14.123" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.123.tgz#39be5d211478c8dd3bdae98ee75bb7efe4abfe4d" - integrity sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q== - "@types/moment-timezone@^0.5.12": version "0.5.12" resolved "https://registry.yarnpkg.com/@types/moment-timezone/-/moment-timezone-0.5.12.tgz#0fb680c03db194fe8ff4551eaeb1eec8d3d80e9f" @@ -43,37 +42,22 @@ dependencies: moment ">=2.14.0" -"@types/node-fetch@^2.1.7": - version "2.1.7" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.1.7.tgz#0231559340f6e3f3a0608692077d744c87b5b367" - integrity sha512-TZozHCDVrs0Aj1B9ZR5F4Q9MknDNcVd+hO5lxXOCzz07ELBey6s1gMUSZHUYHlPfRFKJFXiTnNuD7ePiI6S4/g== +"@types/node-fetch@^2.3.3": + version "2.3.3" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.3.3.tgz#eb9c2a0ce8e9424ebe0c0cbe6f1e8ea7576c1310" + integrity sha512-MIplfRxrDTsIbOLGyFqNWTmxho5Fs710Kul35tEcaqkx9He86mGbSCDvILL0LCMfmm+oJ8tDg51crE9+pJGgiQ== dependencies: "@types/node" "*" -"@types/node@*", "@types/node@^11.11.4": - version "11.11.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-11.11.4.tgz#8808bd5a82bbf6f5d412eff1c228d178e7c24bb3" - integrity sha512-02tIL+QIi/RW4E5xILdoAMjeJ9kYq5t5S2vciUdFPXv/ikFTb0zK8q9vXkg4+WAJuYXGiVT1H28AkD2C+IkXVw== +"@types/node@*", "@types/node@^11.13.8": + version "11.13.8" + resolved "https://registry.yarnpkg.com/@types/node/-/node-11.13.8.tgz#e5d71173c95533be9842b2c798978f095f912aab" + integrity sha512-szA3x/3miL90ZJxUCzx9haNbK5/zmPieGraZEe4WI+3srN0eGLiT22NXeMHmyhNEopn+IrxqMc7wdVwvPl8meg== -"@types/node@6.0.41": - version "6.0.41" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.41.tgz#578cf53aaec65887bcaf16792f8722932e8ff8ea" - integrity sha1-V4z1Oq7GWIe8rxZ5L4ciky6P+Oo= - -"@types/sequelize@4.27.34": - version "4.27.34" - resolved "https://registry.yarnpkg.com/@types/sequelize/-/sequelize-4.27.34.tgz#8ac436f23015d8a8ef3163db4400908b17ce3f91" - integrity sha512-s2NjoIbZewDn64SrZlHZWqJcotK/Ae9m9nh0jcV/5r3ufcHWguLfEr+165Un9eZtRZszY5vDDNzy4jWc5gptDQ== - dependencies: - "@types/bluebird" "*" - "@types/continuation-local-storage" "*" - "@types/lodash" "*" - "@types/validator" "*" - -"@types/validator@*": - version "10.9.0" - resolved "https://registry.yarnpkg.com/@types/validator/-/validator-10.9.0.tgz#747f36c7ad281da769458ab4c3b8837aee1578b6" - integrity sha512-mf0VpXk+NoTmkUmuJCsdwBYxjYZW41amCSzd4t/fABMKl+qGMViwFP0pR7ukFdZRXWI1LIkca3VIbXVBmWZ4kQ== +"@types/validator@^10.11.0": + version "10.11.0" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-10.11.0.tgz#aae9009ce28cc4f878e32c34d3900a81193c98d5" + integrity sha512-i1aY7RKb6HmQIEnK0cBmUZUp1URx0riIHw/GYNoZ46Su0GWfLiDmMI8zMRmaauMnOTg2bQag0qfwcyUFC9Tn+A== ajv@^6.5.5: version "6.10.0" @@ -85,16 +69,6 @@ ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-regex@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" - integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= - -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" @@ -163,15 +137,6 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== -babel-code-frame@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" - integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= - dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -192,9 +157,9 @@ bindings@^1.2.1: file-uri-to-path "1.0.0" bluebird@^3.5.0, bluebird@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" - integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== + version "3.5.4" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714" + integrity sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw== brace-expansion@^1.1.7: version "1.1.11" @@ -226,18 +191,7 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.3.0, chalk@^2.4.2: +chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -303,9 +257,9 @@ colors@^1.2.1: integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== colorspace@1.1.x: - version "1.1.1" - resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.1.tgz#9ac2491e1bc6f8fb690e2176814f8d091636d972" - integrity sha512-pI3btWyiuz7Ken0BWh9Elzsmv2bM9AhA7psXib4anUXy/orfZ/E0MbQwhSOG/9L8hLlalqrU0UhOuqxW1YjmVw== + version "1.1.2" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.2.tgz#e0128950d082b86a2168580796a0aa5d6c68d8c5" + integrity sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ== dependencies: color "3.0.x" text-hex "1.0.x" @@ -318,9 +272,9 @@ combined-stream@^1.0.6, combined-stream@~1.0.6: delayed-stream "~1.0.0" commander@^2.12.1: - version "2.19.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" - integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== + version "2.20.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" + integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== concat-map@0.0.1: version "0.0.1" @@ -333,9 +287,9 @@ core-util-is@1.0.2, core-util-is@~1.0.0: integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= cross-fetch@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.1.tgz#3f207bbd829a76e9aa2a953348bd1f2a3cf388a7" - integrity sha512-qWtpgBAF8ioqBOddRD+pHhrdzm/UWOArkrlIU7c08DlNbOxo5GfUbSY2vr90ZypWf0raW+HNN1F38pi5CEOjiQ== + version "3.0.2" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.2.tgz#b7136491967031949c7f86b15903aef4fa3f1768" + integrity sha512-a4Z0EJ5Nck6QtMy9ZqloLfpvu2uMV3sBfMCR+CgSBCZc6z5KR4bfEiD3dkepH8iZgJMXQpTqf8FjMmvu/GMFkg== dependencies: node-fetch "2.3.0" whatwg-fetch "3.0.0" @@ -352,7 +306,7 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -debug@^4.1.0: +debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== @@ -365,14 +319,9 @@ delayed-stream@~1.0.0: integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= denque@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.0.tgz#79e2f0490195502107f24d9553f374837dabc916" - integrity sha512-gh513ac7aiKrAgjiIBWZG0EASyDF9p4JMWwKA8YU5s9figrL5SRNEMT6FDynsegakuhWd1wVqTvqvqAoDxw7wQ== - -depd@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + version "1.4.1" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf" + integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ== diagnostics@^1.1.1: version "1.1.1" @@ -388,19 +337,19 @@ diff@^3.2.0, diff@^3.5.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== -discord-akairo@8.0.0-beta.1: - version "8.0.0-beta.1" - resolved "https://registry.yarnpkg.com/discord-akairo/-/discord-akairo-8.0.0-beta.1.tgz#ccd2e1e2dd26aca3d307c300eead4c4cbd6ba2d0" - integrity sha512-ChjKxdzjHr8DD3cchfCyydQbiOsLyud2PVLK+/jdu+1lPBOL31YajmcVn7UYjt59kHpAXFEGUB9qzeTKijkcKA== +discord-akairo@^8.0.0-beta.8: + version "8.0.0-beta.8" + resolved "https://registry.yarnpkg.com/discord-akairo/-/discord-akairo-8.0.0-beta.8.tgz#664c1f4fb819ec1d057ed7a36d2e38a68f53367b" + integrity sha512-aT64C1vvntCoSV/i/yAVAHrYyz1Y+5IAWagUaDsrv8M2zKmakcOvT62Iqhz5oK89k8UgVU23aIsLiq01U1bJDQ== -discord-paginationembed@^1.0.0-beta.1: - version "1.0.0-beta.1" - resolved "https://registry.yarnpkg.com/discord-paginationembed/-/discord-paginationembed-1.0.0-beta.1.tgz#aca88a503a1b9f022a0f3785cfab307db551c293" - integrity sha512-XjfrWi0Ha0yHN9V/Y/tCxGiwjGrr7FPxm3+SW3/hNQ+KzmurZQXwMlQbcP83KXhFtxLLyRr2/og0MLy5LBAH0A== +discord-paginationembed@^1.0.0-beta.4: + version "1.0.0-beta.4" + resolved "https://registry.yarnpkg.com/discord-paginationembed/-/discord-paginationembed-1.0.0-beta.4.tgz#0bed8300acda6f58ca932de7a15f6486a5732635" + integrity sha512-XPjnPFVSFkUX9bKDdYerG8a18E5Hcxh5yO3zPGMr3dxCdfk4AmYuuEszn9RFZPyNNVr118AKmfaYAneddXH2Mw== "discord.js@github:discordjs/discord.js": version "12.0.0-dev" - resolved "https://codeload.github.com/discordjs/discord.js/tar.gz/9b2bf03ff6041148a0c6c6351a2bd74c44b4166a" + resolved "https://codeload.github.com/discordjs/discord.js/tar.gz/e0cfb7fb36132abe4367d5ced1650e13a19dbb66" dependencies: form-data "^2.3.3" node-fetch "^2.3.0" @@ -450,12 +399,7 @@ env-variable@0.0.x: bindings "^1.2.1" nan "^2.1.0" -es6-shim@0.35.3: - version "0.35.3" - resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.3.tgz#9bfb7363feffff87a6cdb6cd93e405ec3c4b6f26" - integrity sha1-m/tzY/7//4emzbbNk+QF7DxLbyY= - -escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -604,13 +548,6 @@ har-validator@~5.1.0: ajv "^6.5.5" har-schema "^2.0.0" -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -697,15 +634,15 @@ isstream@0.1.x, isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" - integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== -js-yaml@^3.7.0: - version "3.13.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.0.tgz#38ee7178ac0eea2c97ff6d96fff4b18c7d8cf98e" - integrity sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ== +js-yaml@^3.13.0: + version "3.13.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" + integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -802,17 +739,17 @@ mariadb@^2.0.3: iconv-lite "^0.4.24" long "^4.0.0" -mime-db@~1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" - integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== +mime-db@1.40.0: + version "1.40.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" + integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.22" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd" - integrity sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog== + version "2.1.24" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" + integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ== dependencies: - mime-db "~1.38.0" + mime-db "1.40.0" minimatch@^3.0.4: version "3.0.4" @@ -833,14 +770,14 @@ mkdirp@^0.5.1: dependencies: minimist "0.0.8" -moment-timezone@^0.5.21, moment-timezone@^0.5.23: - version "0.5.23" - resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.23.tgz#7cbb00db2c14c71b19303cb47b0fb0a6d8651463" - integrity sha512-WHFH85DkCfiNMDX5D3X7hpNH3/PUhjTGcD0U1SgfBGZxJ3qUmJh5FdvaFjcClxOvB3rzdfj4oRffbI38jEnC1w== +moment-timezone@^0.5.21, moment-timezone@^0.5.25: + version "0.5.25" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.25.tgz#a11bfa2f74e088327f2cd4c08b3e7bdf55957810" + integrity sha512-DgEaTyN/z0HFaVcVbSyVCUU6HeFdnNC3vE4c9cgu2dgMTvjBUBdBzWfasTBmAW45u5OIMeCJtU8yNjM22DHucw== dependencies: moment ">= 2.9.0" -"moment@>= 2.9.0", moment@>=2.14.0, moment@^2.11.2, moment@^2.22.2: +"moment@>= 2.9.0", moment@>=2.14.0, moment@^2.11.2, moment@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== @@ -851,15 +788,20 @@ ms@^2.1.1: integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== nan@^2.1.0, nan@^2.8.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.1.tgz#a15bee3790bde247e8f38f1d446edcdaeb05f2dd" - integrity sha512-I6YB/YEuDeUZMmhscXKxGgZlFnhsn5y0hgOZBadkzfTRrZBtJDZeg6eQf7PYMIEclwmorTKK8GztsyOUSVBREA== + version "2.13.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" + integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== -node-fetch@2.3.0, node-fetch@^2.3.0: +node-fetch@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.3.0.tgz#1a1d940bbfb916a1d3e0219f037e89e71f8c5fa5" integrity sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA== +node-fetch@^2.3.0, node-fetch@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.4.1.tgz#b2e38f1117b8acbedbe0524f041fb3177188255d" + integrity sha512-P9UbpFK87NyqBZzUuDBDz4f6Yiys8xm8j7ACDbi6usvFm6KItklQUKjeoqTrYS/S1k6I8oaOC2YLLDr/gg26Mw== + node-gyp-build@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d" @@ -974,9 +916,9 @@ readable-stream@^2.3.6: util-deprecate "~1.0.1" readable-stream@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d" - integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw== + version "3.3.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.3.0.tgz#cb8011aad002eb717bf040291feba8569c986fb9" + integrity sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" @@ -1014,9 +956,9 @@ request@^2.88.0: uuid "^3.3.2" resolve@^1.3.2: - version "1.10.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" - integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== + version "1.10.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18" + integrity sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA== dependencies: path-parse "^1.0.6" @@ -1038,49 +980,44 @@ safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== semver@^5.3.0, semver@^5.6.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" - integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + version "5.7.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b" + integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA== -sequelize-pool@^1.0.0: +sequelize-pool@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/sequelize-pool/-/sequelize-pool-1.0.2.tgz#89c767882bbdb8a41dac66922ed9820939a5401e" integrity sha512-VMKl/gCCdIvB1gFZ7p+oqLFEyZEz3oMMYjkKvfEC7GoO9bBcxmfOOU9RdkoltfXGgBZFigSChihRly2gKtsh2w== dependencies: bluebird "^3.5.3" -sequelize-typescript@^0.6.8-beta.0: - version "0.6.8" - resolved "https://registry.yarnpkg.com/sequelize-typescript/-/sequelize-typescript-0.6.8.tgz#29c7ee4e14f0fdb1756f8d9915c30695975b7b8c" - integrity sha512-mJqXeXwEwpFtr66aTWiNccp1itlunVqafHNWMzkoLDMHQVZ3+gEIeMFe2Pv2m7FoEYa9G/5tnM7pSZ79iksldw== +sequelize-typescript@1.0.0-beta.1: + version "1.0.0-beta.1" + resolved "https://registry.yarnpkg.com/sequelize-typescript/-/sequelize-typescript-1.0.0-beta.1.tgz#402279fec52669cbd78ecbf50e189638483a7360" + integrity sha512-xD28kqa1rIKujlmgA4hWQgtwFfRM6tLv1/mnZOrOFEZxvSWazUbTzqGB7OZydZDNj3iJnyrV1l6i6HOfvrpvEw== dependencies: - "@types/bluebird" "3.5.18" - "@types/node" "6.0.41" - "@types/sequelize" "4.27.34" - es6-shim "0.35.3" glob "7.1.2" -sequelize@5.0.0-beta.15: - version "5.0.0-beta.15" - resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.0.0-beta.15.tgz#ed3ae4e2c4d63577d1b4a452394f26a133a6c2e1" - integrity sha512-03AP0+j6L4/YYVAhP6RENCXlSckvnsAFoG3NwAfmlrvF3VUNaNNliTqOen93loHpa3zXXUyV2Qm/UJNHfRjGiA== +sequelize@^5.7.6: + version "5.7.6" + resolved "https://registry.yarnpkg.com/sequelize/-/sequelize-5.7.6.tgz#ed531a338d40749c7a80ba3a8a2120490d57b2ac" + integrity sha512-eBOgOnhNpoFaizRZpJm9Z3+PuVgVbJHApHqRaIkzkw7t049kYxdjQvTkKuQ4uZI8jncVIx9SlofRi5QhO7t/oA== dependencies: bluebird "^3.5.0" cls-bluebird "^2.1.0" - debug "^4.1.0" - depd "^2.0.0" + debug "^4.1.1" dottie "^2.0.0" inflection "1.12.0" lodash "^4.17.11" - moment "^2.22.2" + moment "^2.24.0" moment-timezone "^0.5.21" retry-as-promised "^3.1.0" semver "^5.6.0" - sequelize-pool "^1.0.0" + sequelize-pool "^1.0.2" toposort-class "^1.0.1" uuid "^3.2.1" - validator "^10.4.0" - wkx "^0.4.5" + validator "^10.11.0" + wkx "^0.4.6" setimmediate@^1.0.5: version "1.0.5" @@ -1138,18 +1075,6 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -strip-ansi@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" - integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= - dependencies: - ansi-regex "^2.0.0" - -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -1199,18 +1124,18 @@ tslint-eslint-rules@^5.4.0: tslib "1.9.0" tsutils "^3.0.0" -tslint@^5.14.0: - version "5.14.0" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.14.0.tgz#be62637135ac244fc9b37ed6ea5252c9eba1616e" - integrity sha512-IUla/ieHVnB8Le7LdQFRGlVJid2T/gaJe5VkjzRVSRR6pA2ODYrnfR1hmxi+5+au9l50jBwpbBL34txgv4NnTQ== +tslint@^5.16.0: + version "5.16.0" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.16.0.tgz#ae61f9c5a98d295b9a4f4553b1b1e831c1984d67" + integrity sha512-UxG2yNxJ5pgGwmMzPMYh/CCnCnh0HfPgtlVRDs1ykZklufFBL1ZoTlWFRz2NQjcoEiDoRp+JyT0lhBbbH/obyA== dependencies: - babel-code-frame "^6.22.0" + "@babel/code-frame" "^7.0.0" builtin-modules "^1.1.1" chalk "^2.3.0" commander "^2.12.1" diff "^3.2.0" glob "^7.1.1" - js-yaml "^3.7.0" + js-yaml "^3.13.0" minimatch "^3.0.4" mkdirp "^0.5.1" resolve "^1.3.2" @@ -1226,9 +1151,9 @@ tsutils@^2.29.0: tslib "^1.8.1" tsutils@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.9.1.tgz#2a40dc742943c71eca6d5c1994fcf999956be387" - integrity sha512-hrxVtLtPqQr//p8/msPT1X1UYXUjizqSit5d9AQ5k38TcV38NyecL5xODNxa73cLe/5sdiJ+w1FqzDhRBA/anA== + version "3.10.0" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.10.0.tgz#6f1c95c94606e098592b0dff06590cf9659227d6" + integrity sha512-q20XSMq7jutbGB8luhKKsQldRKWvyBO2BGqni3p4yq8Ys9bEP/xQw3KepKmMRt9gJ4lvQSScrihJrcKdKoSU7Q== dependencies: tslib "^1.8.1" @@ -1249,18 +1174,18 @@ tweetnacl@^1.0.1: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.1.tgz#2594d42da73cd036bd0d2a54683dd35a6b55ca17" integrity sha512-kcoMoKTPYnoeS50tzoqjPY3Uv9axeuuFAZY9M/9zFnhoVvRfxz9K29IMPD7jGmt2c8SW7i3gT9WqDl2+nV7p4A== -twitter-lite@^0.9.2: - version "0.9.2" - resolved "https://registry.yarnpkg.com/twitter-lite/-/twitter-lite-0.9.2.tgz#f4138c88b47a724a5660455a7f540116dcbcfb35" - integrity sha512-2PN10E2FZHHPySW2mnofJFPT2AaEkmeKQxgTvu13fLR4V+K0Fnm96tKDVE+pkrT3HEDOovv/eEtYCJHqQGZuyg== +twitter-lite@^0.9.4: + version "0.9.4" + resolved "https://registry.yarnpkg.com/twitter-lite/-/twitter-lite-0.9.4.tgz#c19123d43519ccb87cc6617167a4f19ada9719a2" + integrity sha512-PpElcyKOk+0/8+6dBRqTKIICfTCZe6PyqJvbKI3OfPJzCO3luY+Q6MVyqoFnNxY+xnMgoA1E8PSEoyAmVNaXDA== dependencies: cross-fetch "^3.0.0" oauth-1.0a "^2.2.4" -typescript@^3.3.4000: - version "3.3.4000" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.4000.tgz#76b0f89cfdbf97827e1112d64f283f1151d6adf0" - integrity sha512-jjOcCZvpkl2+z7JFn0yBOoLQyLoIkNZAs/fYJkUG6VKy6zLPHJGfQJYFHzibB6GJaF/8QrcECtlQ5cpvRHSMEA== +typescript@^3.4.5: + version "3.4.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99" + integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw== underscore@^1.9.1: version "1.9.1" @@ -1289,7 +1214,7 @@ uuid@^3.2.1, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== -validator@^10.4.0: +validator@^10.11.0: version "10.11.0" resolved "https://registry.yarnpkg.com/validator/-/validator-10.11.0.tgz#003108ea6e9a9874d31ccc9e5006856ccd76b228" integrity sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw== @@ -1317,10 +1242,10 @@ winston-compat@^0.1.4: logform "^1.6.0" triple-beam "^1.2.0" -winston-daily-rotate-file@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/winston-daily-rotate-file/-/winston-daily-rotate-file-3.8.0.tgz#887de6dcfad798374d4f4ed0598afd587541ffa4" - integrity sha512-k3usQWe2Iqudi4Ys/tAiGJODSXvqMF+esOIiMJRpWNYnrbuAXBccpaODttDP3GiGVx3H8tE/pS8K3CvkNMqXiw== +winston-daily-rotate-file@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/winston-daily-rotate-file/-/winston-daily-rotate-file-3.9.0.tgz#2a9f4f327761b0c096c0649c275945148dd4b6f7" + integrity sha512-KicvoaLQapqjSDSCIUKik0dDb90vFkqx1/udUiAt6iOqAtBl2qBCrpSDNy3xp7WnT6xHCKAWEEp2XILtiN+zpQ== dependencies: file-stream-rotator "^0.4.1" object-hash "^1.3.0" @@ -1364,7 +1289,7 @@ winston@^3.2.1: triple-beam "^1.3.0" winston-transport "^4.3.0" -wkx@^0.4.5: +wkx@^0.4.6: version "0.4.6" resolved "https://registry.yarnpkg.com/wkx/-/wkx-0.4.6.tgz#228ab592e6457382ea6fb79fc825058d07fce523" integrity sha512-LHxXlzRCYQXA9ZHgs8r7Gafh0gVOE8o3QmudM1PIkOdkXXjW7Thcl+gb2P2dRuKgW8cqkitCRZkkjtmWzpHi7A== @@ -1377,9 +1302,9 @@ wrappy@1: integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= ws@^6.1.3: - version "6.2.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.0.tgz#13806d9913b2a5f3cbb9ba47b563c002cbc7c526" - integrity sha512-deZYUNlt2O4buFCa3t5bKLf8A7FPP/TVjwOeVNpw818Ma5nk4MLXls2eoEGS39o8119QIYxTrTDoPQ5B/gTD6w== + version "6.2.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb" + integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA== dependencies: async-limiter "~1.0.0"