├── .eslintrc.json
├── .github
├── FUNDING.yml
├── config.yml
├── CODEOWNERS
├── PULL_REQUEST_TEMPLATE
│ └── New_command.md
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── Bug_report.md
├── auto_assign.yml
└── workflows
│ └── ci.yml
├── src
├── assets
│ ├── png
│ │ ├── ashwga.png
│ │ ├── coins.png
│ │ ├── petpet.png
│ │ ├── oldPlate.png
│ │ ├── quieres.png
│ │ ├── daily_clock.png
│ │ ├── default_avatar.png
│ │ ├── default_song.png
│ │ ├── placaMercosul.png
│ │ ├── triggered_label.png
│ │ ├── backgrounds
│ │ │ ├── default.png
│ │ │ ├── testing.png
│ │ │ └── default_gray.png
│ │ └── kanna_paper_template.png
│ ├── fonts
│ │ ├── Fe-Font.ttf
│ │ ├── Mandatory.ttf
│ │ ├── Comic-Sans-MS.ttf
│ │ ├── Montserrat-Black.ttf
│ │ ├── Montserrat-Bold.ttf
│ │ ├── Montserrat-Italic.ttf
│ │ ├── Montserrat-Light.ttf
│ │ ├── Montserrat-Medium.ttf
│ │ ├── Montserrat-Thin.ttf
│ │ ├── Montserrat-Regular.ttf
│ │ ├── Montserrat-SemiBold.ttf
│ │ ├── Montserrat-BlackItalic.ttf
│ │ ├── Montserrat-BoldItalic.ttf
│ │ ├── Montserrat-ExtraBold.ttf
│ │ ├── Montserrat-LightItalic.ttf
│ │ ├── Montserrat-ThinItalic.ttf
│ │ ├── SFProDisplay-Regular.ttf
│ │ ├── Montserrat-MediumItalic.ttf
│ │ ├── Montserrat-ExtraBoldItalic.ttf
│ │ └── Montserrat-SemiBoldItalic.ttf
│ ├── jpg
│ │ └── presidential_alert.jpg
│ └── svg
│ │ ├── arrow.svg
│ │ └── brands
│ │ ├── twitch.svg
│ │ ├── mixer.svg
│ │ └── deezer.svg
├── music
│ ├── index.js
│ ├── structures
│ │ ├── index.js
│ │ ├── SongSearchResult.js
│ │ └── Playlist.js
│ └── sources
│ │ ├── deezer
│ │ ├── DeezerPlaylist.js
│ │ └── DeezerSong.js
│ │ ├── spotify
│ │ ├── SpotifyPlaylist.js
│ │ └── SpotifySong.js
│ │ ├── http
│ │ └── HTTPSong.js
│ │ ├── index.js
│ │ └── youtube
│ │ ├── YoutubeSong.js
│ │ └── YoutubePlaylist.js
├── database
│ ├── mongo
│ │ ├── repositories
│ │ │ ├── index.js
│ │ │ ├── GuildRepository.js
│ │ │ └── UserRepository.js
│ │ ├── schemas
│ │ │ ├── GuildSchema.js
│ │ │ └── UserSchema.js
│ │ └── MongoDB.js
│ ├── index.js
│ └── DBWrapper.js
├── test
│ └── codestyle.js
├── games
│ └── Connect4
│ │ ├── constants.js
│ │ └── Player.js
├── structures
│ ├── command
│ │ ├── parameters
│ │ │ └── types
│ │ │ │ ├── BooleanFlagParameter.js
│ │ │ │ ├── TimeParameter.js
│ │ │ │ ├── GuildParameter.js
│ │ │ │ ├── URLParameter.js
│ │ │ │ ├── ColorParameter.js
│ │ │ │ ├── MemberParameter.js
│ │ │ │ ├── BooleanParameter.js
│ │ │ │ ├── RoleParameter.js
│ │ │ │ ├── index.js
│ │ │ │ ├── Parameter.js
│ │ │ │ ├── NumberParameter.js
│ │ │ │ └── EmojiParameter.js
│ │ └── CommandError.js
│ ├── game
│ │ ├── player
│ │ │ ├── Player.js
│ │ │ └── PlayerManager.js
│ │ ├── index.js
│ │ ├── Game.js
│ │ └── Matrix.js
│ ├── index.js
│ ├── EventListener.js
│ ├── Loader.js
│ ├── APIWrapper.js
│ ├── Controller.js
│ ├── SwitchbladeEmbed.js
│ ├── Route.js
│ ├── Webhook.js
│ └── Connection.js
├── locales
│ └── en-US
│ │ ├── moderation.json
│ │ ├── lolservers.json
│ │ ├── categories.json
│ │ ├── game.json
│ │ └── permissions.json
├── commands
│ ├── reddit
│ │ ├── keanu.js
│ │ ├── parrot.js
│ │ ├── showerthoughts.js
│ │ ├── copypasta.js
│ │ ├── tesla.js
│ │ ├── fursuit.js
│ │ ├── crappydesign.js
│ │ ├── softwaregore.js
│ │ ├── oddlysatisfying.js
│ │ ├── aww.js
│ │ ├── birb.js
│ │ ├── thinking.js
│ │ ├── cat.js
│ │ └── hmmm.js
│ ├── utility
│ │ ├── info.js
│ │ ├── stoptyping.js
│ │ ├── avatar.js
│ │ ├── restrictemoji
│ │ │ ├── reset.js
│ │ │ ├── add.js
│ │ │ └── remove.js
│ │ ├── math.js
│ │ ├── restrictemoji.js
│ │ ├── guildicon.js
│ │ ├── hastebin.js
│ │ ├── deleteemoji.js
│ │ └── isitup.js
│ ├── misc
│ │ ├── qrcode.js
│ │ ├── cow.js
│ │ ├── covid.js
│ │ ├── github.js
│ │ ├── spotify.js
│ │ ├── tcdne.js
│ │ ├── deezer.js
│ │ ├── tpdne.js
│ │ ├── cowsay.js
│ │ ├── googleplay.js
│ │ ├── httpcat.js
│ │ ├── httpdog.js
│ │ ├── xkcd37.js
│ │ ├── fox.js
│ │ ├── emoji.js
│ │ ├── dicksize.js
│ │ ├── geekjokes.js
│ │ ├── lmgtfy.js
│ │ ├── qrcode
│ │ │ ├── generate.js
│ │ │ └── read.js
│ │ ├── asciify.js
│ │ ├── shiba.js
│ │ ├── inspirobot.js
│ │ ├── dog.js
│ │ ├── goat.js
│ │ ├── fliptext.js
│ │ ├── lorem.js
│ │ ├── 8ball.js
│ │ ├── hibp.js
│ │ ├── imageoftheday.js
│ │ ├── lastfm.js
│ │ ├── image.js
│ │ ├── numberfacts.js
│ │ ├── google.js
│ │ ├── time.js
│ │ ├── uigradient.js
│ │ └── whatlanguage.js
│ ├── nsfw
│ │ ├── ass.js
│ │ ├── boobs.js
│ │ ├── porn.js
│ │ ├── pussy.js
│ │ ├── yaoi.js
│ │ ├── yiff.js
│ │ ├── yuri.js
│ │ ├── hentai.js
│ │ ├── porngifs.js
│ │ └── crossdressing.js
│ ├── games
│ │ ├── freefire.js
│ │ ├── minecraft.js
│ │ ├── legendsofruneterra.js
│ │ ├── leagueoflegends.js
│ │ ├── osu.js
│ │ ├── coinflip.js
│ │ └── connect4.js
│ ├── bot
│ │ ├── ping.js
│ │ ├── support.js
│ │ ├── vote.js
│ │ ├── i18n.js
│ │ ├── invite.js
│ │ └── shards.js
│ ├── social
│ │ ├── leaderboard
│ │ │ ├── leaderboard.js
│ │ │ ├── reputation.js
│ │ │ └── money.js
│ │ ├── profile.js
│ │ └── favcolor.js
│ ├── configuration
│ │ ├── config.js
│ │ └── prefix.js
│ ├── memes
│ │ ├── tipsfedora.js
│ │ ├── smart.js
│ │ ├── reversetext.js
│ │ ├── bolinadegorfe.js
│ │ ├── piratespeak.js
│ │ ├── owo.js
│ │ ├── ashwga.js
│ │ ├── quieres.js
│ │ └── clapify.js
│ ├── text
│ │ ├── vaporwave.js
│ │ └── emojify.js
│ ├── music
│ │ ├── queue
│ │ │ ├── clear.js
│ │ │ └── shuffle.js
│ │ ├── stop.js
│ │ ├── bassboost.js
│ │ ├── loop.js
│ │ ├── skip.js
│ │ └── pause.js
│ ├── developers
│ │ ├── unblacklist.js
│ │ ├── welcometranslator.js
│ │ ├── blacklist.js
│ │ ├── reloadlocales.js
│ │ ├── clientvalues.js
│ │ └── whyblacklisted.js
│ ├── image-manipulation
│ │ ├── kannapaper.js
│ │ ├── petpet.js
│ │ ├── presidentialalert.js
│ │ └── triggered.js
│ ├── anime
│ │ ├── waifu.js
│ │ ├── nekogif.js
│ │ ├── neko.js
│ │ ├── kemonomimi.js
│ │ └── kitsune.js
│ ├── gifs
│ │ ├── hug.js
│ │ ├── pat.js
│ │ ├── slap.js
│ │ ├── kiss.js
│ │ └── handshake.js
│ ├── economy
│ │ ├── money.js
│ │ └── daily.js
│ └── moderation
│ │ ├── ban.js
│ │ ├── kick.js
│ │ └── createrole.js
├── apis
│ ├── Steam.js
│ ├── Brew.js
│ ├── ViaCEP.js
│ ├── Packagist.js
│ ├── SteamLadder.js
│ ├── GooglePlay.js
│ ├── NPMRegistry.js
│ ├── Owlbot.js
│ ├── LanguageLayer.js
│ ├── CurseForge.js
│ ├── MerriamWebster.js
│ ├── Chocolatey.js
│ ├── Chorus.js
│ ├── Instagram.js
│ ├── EclipsePlugins.js
│ ├── FlatHub.js
│ ├── Icecast.js
│ ├── Reddit.js
│ ├── e621.js
│ ├── SteamStore.js
│ ├── HIBP.js
│ ├── Covid19.js
│ ├── RubyGems.js
│ ├── DBL.js
│ ├── PositionStack.js
│ ├── Crowdin.js
│ ├── GoogleSearch.js
│ ├── JetBrainsPlugins.js
│ ├── Tumblr.js
│ ├── VSCodeExtensions.js
│ ├── FlightRadar.js
│ ├── BeatSaver.js
│ ├── Minecraft.js
│ ├── Genius.js
│ ├── GoogleTranslate.js
│ ├── IGDB.js
│ └── FreeFire.js
├── listeners
│ └── ExitOnWebSocketError.js
├── utils
│ ├── logger
│ │ └── PrettyStream.js
│ ├── Owoify.js
│ ├── EmojiUtils.js
│ ├── placeholders
│ │ ├── PlaceholderUtils.js
│ │ └── PlaceholderRules.js
│ ├── ConfirmationBox.js
│ ├── DiscordUtils.js
│ ├── MiscUtils.js
│ └── index.js
├── loaders
│ └── index.js
├── modules
│ ├── LanguageModule.js
│ ├── PrefixModule.js
│ └── JoinLockModule.js
├── http
│ └── api
│ │ ├── connections.js
│ │ ├── statistics.js
│ │ └── locales.js
├── controllers
│ └── BlacklistController.js
└── connections
│ └── osu.js
├── .dockerignore
├── Dockerfile
├── crowdin.yaml
├── shard.js
├── bigtitle.txt
└── index.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "standard"
3 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | open_collective: switchblade
2 |
--------------------------------------------------------------------------------
/.github/config.yml:
--------------------------------------------------------------------------------
1 | todo:
2 | keyword: "TODO:"
3 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | /src/commands/ @SwitchbladeBot/chatbot-commands
2 | * @SwitchbladeBot/chatbot-core
3 |
--------------------------------------------------------------------------------
/src/assets/png/ashwga.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/ashwga.png
--------------------------------------------------------------------------------
/src/assets/png/coins.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/coins.png
--------------------------------------------------------------------------------
/src/assets/png/petpet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/petpet.png
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | *Dockerfile*
3 | *docker-compose*
4 | node_modules
5 | .gitignore
6 | .gitmodules
7 | README.md
--------------------------------------------------------------------------------
/src/assets/fonts/Fe-Font.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Fe-Font.ttf
--------------------------------------------------------------------------------
/src/assets/png/oldPlate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/oldPlate.png
--------------------------------------------------------------------------------
/src/assets/png/quieres.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/quieres.png
--------------------------------------------------------------------------------
/src/assets/fonts/Mandatory.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Mandatory.ttf
--------------------------------------------------------------------------------
/src/assets/png/daily_clock.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/daily_clock.png
--------------------------------------------------------------------------------
/src/music/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | SwitchbladePlayerManager: require('./SwitchbladePlayerManager.js')
3 | }
4 |
--------------------------------------------------------------------------------
/src/assets/png/default_avatar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/default_avatar.png
--------------------------------------------------------------------------------
/src/assets/png/default_song.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/default_song.png
--------------------------------------------------------------------------------
/src/assets/png/placaMercosul.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/placaMercosul.png
--------------------------------------------------------------------------------
/src/assets/fonts/Comic-Sans-MS.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Comic-Sans-MS.ttf
--------------------------------------------------------------------------------
/src/assets/png/triggered_label.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/triggered_label.png
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-Black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-Black.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-Bold.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-Italic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-Light.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-Medium.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-Thin.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-Thin.ttf
--------------------------------------------------------------------------------
/src/assets/jpg/presidential_alert.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/jpg/presidential_alert.jpg
--------------------------------------------------------------------------------
/src/assets/png/backgrounds/default.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/backgrounds/default.png
--------------------------------------------------------------------------------
/src/assets/png/backgrounds/testing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/backgrounds/testing.png
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-Regular.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-SemiBold.ttf
--------------------------------------------------------------------------------
/src/assets/png/kanna_paper_template.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/kanna_paper_template.png
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16
2 | WORKDIR /usr/src/app
3 | COPY package*.json ./
4 | RUN npm install
5 | COPY . .
6 | EXPOSE 80
7 | CMD [ "npm", "start" ]
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-BlackItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-BlackItalic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-BoldItalic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-ExtraBold.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-LightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-LightItalic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-ThinItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-ThinItalic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/SFProDisplay-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/SFProDisplay-Regular.ttf
--------------------------------------------------------------------------------
/src/assets/png/backgrounds/default_gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/png/backgrounds/default_gray.png
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-MediumItalic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-ExtraBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-ExtraBoldItalic.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Montserrat-SemiBoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SwitchbladeBot/switchblade/HEAD/src/assets/fonts/Montserrat-SemiBoldItalic.ttf
--------------------------------------------------------------------------------
/src/database/mongo/repositories/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | GuildRepository: require('./GuildRepository.js'),
3 | UserRepository: require('./UserRepository.js')
4 | }
5 |
--------------------------------------------------------------------------------
/src/test/codestyle.js:
--------------------------------------------------------------------------------
1 | /* globals describe, it */
2 |
3 | describe('Code style', () => {
4 | it('should conform to standard', require('mocha-standard')).timeout(0)
5 | })
6 |
--------------------------------------------------------------------------------
/src/games/Connect4/constants.js:
--------------------------------------------------------------------------------
1 | const NUMBERS = ['1️⃣', '2️⃣', '3️⃣', '4️⃣', '5️⃣', '6️⃣', '7️⃣']
2 | const LABELS = ['⚪', '🔴', '🔵']
3 |
4 | module.exports = {
5 | NUMBERS,
6 | LABELS
7 | }
8 |
--------------------------------------------------------------------------------
/crowdin.yaml:
--------------------------------------------------------------------------------
1 | "project_identifier_env": CROWDIN_PROJECT_ID
2 | "api_key_env": CROWDIN_API_KEY
3 |
4 | files: [{
5 | "source" : "/src/locales/en-US/*.json",
6 | "translation" : "%locale%/%original_file_name%",
7 | }]
8 |
--------------------------------------------------------------------------------
/src/assets/svg/arrow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/database/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // Base structures
3 | DBWrapper: require('./DBWrapper.js'),
4 | Repository: require('./Repository.js'),
5 |
6 | // DBWrappers
7 | MongoDB: require('./mongo/MongoDB.js')
8 | }
9 |
--------------------------------------------------------------------------------
/src/structures/command/parameters/types/BooleanFlagParameter.js:
--------------------------------------------------------------------------------
1 | const Parameter = require('./Parameter.js')
2 |
3 | module.exports = class BooleanFlagParameter extends Parameter {
4 | static parse () {
5 | return true
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/structures/game/player/Player.js:
--------------------------------------------------------------------------------
1 | module.exports = class Player {
2 | constructor (game, user) {
3 | this.game = game
4 | this.user = user
5 | }
6 |
7 | toString () {
8 | return this.user.toString()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/locales/en-US/moderation.json:
--------------------------------------------------------------------------------
1 | {
2 | "joinLock": {
3 | "defaultPrivateMessage": "You've been kicked from **{{guild.name}}** because the join lock is enabled. Please try joining again later.",
4 | "kickReason": "Join lock is enabled."
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/src/structures/game/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | Player: require('./player/Player'),
3 | PlayerManager: require('./player/PlayerManager'),
4 |
5 | Game: require('./Game'),
6 | TwoPlayerGame: require('./TwoPlayerGame'),
7 | Matrix: require('./Matrix')
8 | }
9 |
--------------------------------------------------------------------------------
/src/music/structures/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | GuildPlayer: require('./GuildPlayer.js'),
3 | Song: require('./Song.js'),
4 | SongSearchResult: require('./SongSearchResult.js'),
5 | SongSource: require('./SongSource.js'),
6 | Playlist: require('./Playlist.js')
7 | }
8 |
--------------------------------------------------------------------------------
/src/commands/reddit/keanu.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Keanu extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'keanu',
7 | subreddit: 'KeanuBeingAwesome'
8 | }, client)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/commands/reddit/parrot.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Parrot extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'parrot',
7 | subreddit: 'partyparrot'
8 | }, client)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/commands/utility/info.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand } = require('../../')
2 |
3 | module.exports = class Info extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'info',
7 | authorString: 'commands:info.embedAuthor'
8 | }, client)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/shard.js:
--------------------------------------------------------------------------------
1 | const { ShardingManager } = require('discord.js')
2 | const manager = new ShardingManager('./index.js', {
3 | token: process.env.DISCORD_TOKEN,
4 | totalShards: parseInt(process.env.SHARD_COUNT) || 'auto'
5 | })
6 |
7 | manager.spawn()
8 | manager.on('shardCreate', shard => console.log(`Launching shard ${shard.id}`))
9 |
--------------------------------------------------------------------------------
/src/commands/reddit/showerthoughts.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Showerthoughts extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'showerthoughts',
7 | subreddit: 'showerthoughts'
8 | }, client)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/src/structures/command/CommandError.js:
--------------------------------------------------------------------------------
1 | module.exports = class CommandError extends Error {
2 | constructor (message, showUsage = false) {
3 | super(typeof message === 'object' ? 'EMBED_ERROR' : message)
4 | this.embed = typeof message === 'object' ? message : null
5 | this.showUsage = showUsage
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/commands/reddit/copypasta.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Copypasta extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'copypasta',
7 | category: 'memes',
8 | subreddit: 'copypasta'
9 | }, client)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/commands/reddit/tesla.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Tesla extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'tesla',
7 | aliases: ['weebmusk', 'teslaporn'],
8 | subreddit: 'TeslaPorn'
9 | }, client)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/locales/en-US/lolservers.json:
--------------------------------------------------------------------------------
1 | {
2 | "na": "North America",
3 | "euw": "Europe West",
4 | "eune": "Europe Nordic & East",
5 | "lan": "Latin America North",
6 | "las": "Latin America South",
7 | "br": "Brazil",
8 | "tr": "Turkey",
9 | "ru": "Russia",
10 | "oce": "Oceania",
11 | "jp": "Japan",
12 | "kr": "Korea"
13 | }
--------------------------------------------------------------------------------
/src/commands/misc/qrcode.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand } = require('../../')
2 |
3 | module.exports = class QRCode extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'qrcode',
7 | aliases: ['qr'],
8 | authorString: 'commands:qrcode.title'
9 | }, client)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/commands/reddit/fursuit.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Fursuit extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'fursuit',
7 | aliases: ['expensiveaf', 'furrysuit'],
8 | subreddit: 'fursuit'
9 | }, client)
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/commands/nsfw/ass.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Ass extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'ass',
7 | subreddit: 'ass',
8 | category: 'nsfw',
9 | requirements: { nsfwOnly: true }
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/commands/nsfw/boobs.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Boobs extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'boobs',
7 | subreddit: 'boobs',
8 | category: 'nsfw',
9 | requirements: { nsfwOnly: true }
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/commands/nsfw/porn.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Porn extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'porn',
7 | subreddit: 'porn',
8 | category: 'nsfw',
9 | requirements: { nsfwOnly: true }
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/commands/nsfw/pussy.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Pussy extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'pussy',
7 | subreddit: 'pussy',
8 | category: 'nsfw',
9 | requirements: { nsfwOnly: true }
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/commands/nsfw/yaoi.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Yaoi extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'yaoi',
7 | subreddit: 'yaoi',
8 | category: 'nsfw',
9 | requirements: { nsfwOnly: true }
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/commands/nsfw/yiff.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Yiff extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'yiff',
7 | subreddit: 'yiff',
8 | category: 'nsfw',
9 | requirements: { nsfwOnly: true }
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/commands/nsfw/yuri.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Yuri extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'yuri',
7 | subreddit: 'yuri',
8 | category: 'nsfw',
9 | requirements: { nsfwOnly: true }
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/bigtitle.txt:
--------------------------------------------------------------------------------
1 | _____ _ _ _ _ _ _
2 | / ____| (_| | | | | | | | | |
3 | | (_____ ___| |_ ___| |__ | |__ | | __ _ __| | ___
4 | \___ \ \ /\ / | | __/ __| '_ \| '_ \| |/ _` |/ _` |/ _ \
5 | ____) \ V V /| | || (__| | | | |_) | | (_| | (_| | __/
6 | |_____/ \_/\_/ |_|\__\___|_| |_|_.__/|_|\__,_|\__,_|\___|
7 |
--------------------------------------------------------------------------------
/src/commands/nsfw/hentai.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Hentai extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'hentai',
7 | subreddit: 'hentai',
8 | category: 'nsfw',
9 | requirements: { nsfwOnly: true }
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/apis/Steam.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const Steam = require('steamapi')
3 |
4 | module.exports = class SteamAPI extends APIWrapper {
5 | constructor () {
6 | super({
7 | name: 'steam',
8 | envVars: ['STEAM_API_KEY']
9 | })
10 | }
11 |
12 | load () {
13 | return new Steam(process.env.STEAM_API_KEY)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/commands/games/freefire.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand } = require('../../')
2 |
3 | module.exports = class FreeFire extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'freefire',
7 | aliases: ['ff'],
8 | category: 'games',
9 | authorString: 'commands:freefire.gameName'
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/commands/nsfw/porngifs.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class PornGifs extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'porngifs',
7 | subreddit: 'porngifs',
8 | category: 'nsfw',
9 | requirements: { nsfwOnly: true }
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/commands/reddit/crappydesign.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class CrappyDesign extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'crappydesign',
7 | aliases: ['cd'],
8 | category: 'memes',
9 | subreddit: 'CrappyDesign'
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/commands/reddit/softwaregore.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class SoftwareGore extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'softwaregore',
7 | aliases: ['sg'],
8 | category: 'memes',
9 | subreddit: 'softwaregore'
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/listeners/ExitOnWebSocketError.js:
--------------------------------------------------------------------------------
1 | const { EventListener } = require('../')
2 |
3 | module.exports = class ExitOnWebSocketError extends EventListener {
4 | constructor (client) {
5 | super({
6 | events: ['error']
7 | }, client)
8 | }
9 |
10 | async onError (error) {
11 | this.client.logger.fatal(error)
12 | process.exit(1)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/apis/Brew.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | module.exports = class Brew extends APIWrapper {
5 | constructor () {
6 | super({
7 | name: 'brew'
8 | })
9 | }
10 |
11 | async search (formulae) {
12 | return axios.get(`https://formulae.brew.sh/api/formula/${formulae}.json`).then(res => res.data)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/commands/nsfw/crossdressing.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Crossdressing extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'crossdressing',
7 | subreddit: 'traphentai',
8 | category: 'nsfw',
9 | requirements: { nsfwOnly: true }
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/commands/reddit/oddlysatisfying.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class OddlySatisfying extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'oddlysatisfying',
7 | aliases: ['odds'],
8 | category: 'memes',
9 | subreddit: 'oddlysatisfying'
10 | }, client)
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE/New_command.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: New command
3 | about: Create a new command
4 |
5 | ---
6 |
7 | **Command description**
8 | A clear, concise, and direct description of what the command does. (Example: `Displays someone's avatar`)
9 |
10 | **Usage**
11 | What arguments does the command take? Use <> for required and [] for optional args. (Example: `s!avatar [user]`)
--------------------------------------------------------------------------------
/src/assets/svg/brands/twitch.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/commands/reddit/aww.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Aww extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'aww',
7 | aliases: ['aw', 'cute', 'eyebleach'],
8 | category: 'memes',
9 | subreddit: 'aww',
10 | titleString: 'commands:aww.title'
11 | }, client)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/commands/reddit/birb.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Birb extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'birb',
7 | aliases: ['bird', 'borb'],
8 | category: 'memes',
9 | subreddit: 'birbs',
10 | titleString: 'commands:birb.hereIsYourBirb'
11 | }, client)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/commands/reddit/thinking.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Thinking extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'thinking',
7 | aliases: ['thonk', 'thonking', 'thonkang'],
8 | category: 'memes',
9 | subreddit: 'thinking',
10 | addTitle: false
11 | }, client)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/structures/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | APIWrapper: require('./APIWrapper.js'),
3 | Command: require('./command/Command.js'),
4 | EventListener: require('./EventListener.js'),
5 | SwitchbladeEmbed: require('./SwitchbladeEmbed.js'),
6 | Route: require('./Route.js'),
7 | Webhook: require('./Webhook.js'),
8 | Controller: require('./Controller.js'),
9 | Module: require('./Module.js')
10 | }
11 |
--------------------------------------------------------------------------------
/src/commands/bot/ping.js:
--------------------------------------------------------------------------------
1 | const { Command } = require('../../')
2 |
3 | module.exports = class Ping extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'ping',
7 | aliases: ['pang', 'peng', 'pong', 'pung'],
8 | category: 'bot'
9 | }, client)
10 | }
11 |
12 | run ({ channel }) {
13 | channel.send(`:ping_pong: \`${Math.ceil(this.client.ws.ping)}ms\``)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/commands/reddit/cat.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Cat extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'cat',
7 | aliases: ['catto', 'kitty'],
8 | category: 'general',
9 | titleString: 'commands:cat.hereIsYourCat',
10 | subreddit: 'catpictures'
11 | }, client)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/music/structures/SongSearchResult.js:
--------------------------------------------------------------------------------
1 | module.exports = class SongSearchResult {
2 | constructor (tryAgain = true) {
3 | this.tryAgain = tryAgain
4 | }
5 |
6 | async setResult (result) {
7 | this.result = await result
8 | return this
9 | }
10 |
11 | static async from (result, tryAgain = true) {
12 | const searchResult = new this(tryAgain)
13 | return searchResult.setResult(await result)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/apis/ViaCEP.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const fetch = require('node-fetch')
3 |
4 | const API_URL = 'https://viacep.com.br/ws/'
5 |
6 | module.exports = class ViaCEP extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'viacep'
10 | })
11 | }
12 |
13 | // Default
14 | searchCEP (cep) {
15 | return fetch(`${API_URL}${cep}/json`)
16 | .then(res => res.json())
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/database/mongo/schemas/GuildSchema.js:
--------------------------------------------------------------------------------
1 | const { Schema } = require('mongoose')
2 |
3 | const ModuleSchema = new Schema({
4 | active: { type: Boolean, required: true },
5 | values: {}
6 | })
7 |
8 | module.exports = new Schema({
9 | _id: String,
10 | prefix: String,
11 | language: String,
12 | joinLock: Boolean,
13 | joinLockMessage: String,
14 | modules: {
15 | type: Map,
16 | of: ModuleSchema
17 | }
18 | })
19 |
--------------------------------------------------------------------------------
/src/commands/reddit/hmmm.js:
--------------------------------------------------------------------------------
1 | const { RandomRedditPostCommand } = require('../../')
2 |
3 | module.exports = class Hmmm extends RandomRedditPostCommand {
4 | constructor (client) {
5 | super({
6 | name: 'hmmm',
7 | aliases: ['hm', 'hmm', 'hmmmm'],
8 | category: 'memes',
9 | subreddit: 'hmmm',
10 | titleString: 'hmmm',
11 | requirements: { nsfwOnly: true }
12 | }, client)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/structures/EventListener.js:
--------------------------------------------------------------------------------
1 | const Utils = require('../utils')
2 |
3 | module.exports = class EventListener {
4 | /**
5 | * @param {Object} opts
6 | * @param {string[]} opts.events
7 | * @param {Client} client
8 | */
9 | constructor (opts, client) {
10 | const options = Utils.createOptionHandler('EventListener', opts)
11 |
12 | this.events = options.required('events')
13 |
14 | this.client = client
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/structures/command/parameters/types/TimeParameter.js:
--------------------------------------------------------------------------------
1 | const Parameter = require('./Parameter')
2 | const CommandError = require('../../CommandError')
3 | const ms = require('ms')
4 |
5 | module.exports = class TimeParamenter extends Parameter {
6 | static parse (arg, { t }) {
7 | if (!arg) return
8 |
9 | const result = ms(arg)
10 | if (!result) throw new CommandError(t('errors:invalidTime'))
11 | return result
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/locales/en-US/categories.json:
--------------------------------------------------------------------------------
1 | {
2 | "anime": "Anime",
3 | "bot": "Bot",
4 | "configuration": "Configuration",
5 | "developers": "Developers",
6 | "economy": "Economy",
7 | "games": "Games",
8 | "general": "General",
9 | "government": "Government",
10 | "images": "Images",
11 | "memes": "Memes",
12 | "moderation": "Moderation",
13 | "music": "Music",
14 | "nsfw": "NSFW",
15 | "social": "Social",
16 | "utility": "Utility"
17 | }
--------------------------------------------------------------------------------
/src/commands/misc/cow.js:
--------------------------------------------------------------------------------
1 | const { Command } = require('../../')
2 | const cows = require('cows')
3 |
4 | module.exports = class Cow extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'cow'
8 | }, client)
9 | }
10 |
11 | run ({ channel }) {
12 | const cowNumber = Math.round((Math.random() * cows().length))
13 | const cow = cows()[cowNumber]
14 | channel.send(`\`\`\`${cow}\`\`\``)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/structures/command/parameters/types/GuildParameter.js:
--------------------------------------------------------------------------------
1 | const Parameter = require('./Parameter.js')
2 | const CommandError = require('../../CommandError.js')
3 |
4 | module.exports = class GuildParameter extends Parameter {
5 | static parse (arg, { t, client }) {
6 | if (!arg) return
7 | const guild = client.guilds.cache.get(arg)
8 | if (!guild) throw new CommandError(t('errors:invalidGuild'))
9 | return guild
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/music/sources/deezer/DeezerPlaylist.js:
--------------------------------------------------------------------------------
1 | const { Playlist } = require('../../structures')
2 |
3 | module.exports = class DeezerPlaylist extends Playlist {
4 | constructor (data = {}, songs = [], requestedBy) {
5 | super(data, songs, requestedBy)
6 |
7 | this.identifier = data.id
8 | this.uri = data.link
9 | this.title = data.title
10 |
11 | this.source = 'deezer'
12 | }
13 |
14 | loadInfo () {
15 | return this
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/structures/command/parameters/types/URLParameter.js:
--------------------------------------------------------------------------------
1 | const Parameter = require('./Parameter.js')
2 | const CommandError = require('../../CommandError.js')
3 | const { URL } = require('url')
4 |
5 | module.exports = class URLParameter extends Parameter {
6 | static parse (arg, { t }) {
7 | if (!arg) return
8 |
9 | try {
10 | return new URL(arg)
11 | } catch (e) {
12 | throw new CommandError(t('errors:invalidURL'))
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/commands/social/leaderboard/leaderboard.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand } = require('../../../')
2 |
3 | module.exports = class Leaderboard extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'leaderboard',
7 | aliases: ['top', 'ranking'],
8 | category: 'social',
9 | authorString: 'commands:leaderboard.title',
10 | requirements: { databaseOnly: true, canvasOnly: true }
11 | }, client)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/structures/command/parameters/types/ColorParameter.js:
--------------------------------------------------------------------------------
1 | const Parameter = require('./Parameter.js')
2 | const CommandError = require('../../CommandError.js')
3 | const Color = require('../../../../utils/Color.js')
4 |
5 | module.exports = class ColorParameter extends Parameter {
6 | static parse (arg, { t }) {
7 | const color = new Color(arg)
8 | if (!color.valid) throw new CommandError(t('errors:invalidColor'))
9 | return color
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/logger/PrettyStream.js:
--------------------------------------------------------------------------------
1 | const bunyan = require('bunyan')
2 |
3 | module.exports = class PrettyStream {
4 | write (rec) {
5 | const msg = [
6 | `[${rec.time.toISOString()}] `,
7 | `${bunyan.nameFromLevel[rec.level]}: `,
8 | rec.tag ? `[${rec.tag}] ` : '',
9 | rec.msg,
10 | rec.err ? `\n${rec.err.stack}` : ''
11 | ].join('')
12 |
13 | if (rec.level >= 50) console.error(msg)
14 | else console.log(msg)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/utils/Owoify.js:
--------------------------------------------------------------------------------
1 | const faces = ['(・`ω´・)', ';;w;;', 'owo', 'UwU', '>w<', '^w^']
2 |
3 | function Owoify (str) {
4 | return str
5 | .replace(/(?:r|l)/g, 'w')
6 | .replace(/(?:R|L)/g, 'W')
7 | .replace(/n([aeiou])/g, 'ny$1')
8 | .replace(/N([aeiou])/g, 'Ny$1')
9 | .replace(/N([AEIOU])/g, 'Ny$1')
10 | .replace(/ove/g, 'uv')
11 | .replace(/!+/g, ' ' + faces[Math.floor(Math.random() * faces.length)] + ' ')
12 | }
13 |
14 | module.exports = Owoify
15 |
--------------------------------------------------------------------------------
/src/commands/configuration/config.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand } = require('../../')
2 |
3 | module.exports = class Config extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'config',
7 | aliases: ['cfg'],
8 | category: 'configuration',
9 | authorString: 'commands:config.title',
10 | requirements: { guildOnly: true, databaseOnly: true, permissions: ['MANAGE_GUILD'] }
11 | }, client)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/music/sources/spotify/SpotifyPlaylist.js:
--------------------------------------------------------------------------------
1 | const { Playlist } = require('../../structures')
2 |
3 | module.exports = class SpotifyPlaylist extends Playlist {
4 | constructor (data = {}, songs = [], requestedBy) {
5 | super(data, songs, requestedBy)
6 |
7 | this.identifier = data.id
8 | this.uri = data.external_urls.spotify
9 | this.title = data.name
10 |
11 | this.source = 'spotify'
12 | }
13 |
14 | loadInfo () {
15 | return this
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/utils/EmojiUtils.js:
--------------------------------------------------------------------------------
1 | const Constants = require('./Constants.js')
2 |
3 | module.exports = class EmojiUtils {
4 | /**
5 | * Returns either a country's flag emoji based on its alpha-2 country code or an empty flag if no country is passed.
6 | * @param {String} [countryCode] - Alpha-2 country code
7 | */
8 | static getFlag (countryCode) {
9 | if (countryCode) return `:flag_${countryCode.toLowerCase()}:`
10 | return Constants.UNKNOWN_COUNTRY_FLAG
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/apis/Packagist.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | const API_URL = 'https://packagist.org/search.json'
5 |
6 | module.exports = class Packagist extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'packagist'
10 | })
11 | }
12 |
13 | async search (name) {
14 | return axios({
15 | params: {
16 | q: encodeURIComponent(name)
17 | },
18 | url: API_URL
19 | })
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/apis/SteamLadder.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const SteamLadder = require('steamladder')
3 |
4 | module.exports = class SteamLadderAPI extends APIWrapper {
5 | constructor () {
6 | super({
7 | name: 'steamladder',
8 | envVars: ['STEAM_LADDER_API_KEY']
9 | })
10 | this.name = 'steamladder'
11 | this.envVars = ['STEAM_LADDER_API_KEY']
12 | }
13 |
14 | load () {
15 | return new SteamLadder(process.env.STEAM_LADDER_API_KEY)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/structures/game/Game.js:
--------------------------------------------------------------------------------
1 | const PlayerManager = require('./player/PlayerManager')
2 |
3 | module.exports = class Game {
4 | constructor (options, { channel, t, client }) {
5 | this.players = new PlayerManager()
6 |
7 | this.channel = channel
8 | this.rootT = t
9 | this.t = (suffix, ...args) => t(`game:games.${this.name}.${suffix}`, ...args)
10 | this.client = client
11 |
12 | this.name = options.name
13 | this.displayName = this.t('displayName')
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/commands/memes/tipsfedora.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class TipsFedora extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'tipsfedora',
7 | category: 'memes'
8 | }, client)
9 | }
10 |
11 | run ({ author, channel }) {
12 | const embed = new SwitchbladeEmbed(author)
13 | embed.setImage('https://i.kym-cdn.com/photos/images/masonry/000/747/485/3a1.gif')
14 | channel.send(embed)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/commands/games/minecraft.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand } = require('../../')
2 |
3 | module.exports = class Minecraft extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'minecraft',
7 | aliases: ['minecraftquery', 'mc', 'mcquery'],
8 | category: 'games',
9 | authorString: 'commands:minecraft.gameName',
10 | authorImage: 'https://i.imgur.com/DBkQ0K5.png',
11 | authorURL: 'https://minecraft.net'
12 | }, client)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/database/DBWrapper.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Base DB Wrapper structure
3 | * @constructor
4 | * @param {Object} options - Options for the DB client
5 | */
6 | class DBWrapper {
7 | constructor (options = {}) {
8 | if (this.constructor === DBWrapper) throw new Error('Cannot instantiate abstract class')
9 | this.options = options
10 | }
11 |
12 | /**
13 | * Creates the DB client connection
14 | */
15 | connect () {}
16 | }
17 |
18 | DBWrapper.envVars = []
19 |
20 | module.exports = DBWrapper
21 |
--------------------------------------------------------------------------------
/src/commands/misc/covid.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand, Constants } = require('../../')
2 |
3 | module.exports = class Covid extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'covid',
7 | aliases: ['covid19', 'coronavirus'],
8 | authorString: 'commands:covid.covid',
9 | authorImage: 'https://i.imgur.com/Rnobe3k.png',
10 | authorURL: 'https://covid19.who.int/',
11 | embedColor: Constants.GENERIC_RED_COLOR
12 | }, client)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/commands/games/legendsofruneterra.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand } = require('../../')
2 |
3 | module.exports = class LegendsOfRuneterra extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'legendsofruneterra',
7 | aliases: ['lor'],
8 | category: 'games',
9 | authorString: 'commands:legendsofruneterra.gameName',
10 | authorImage: 'https://i.imgur.com/m7Bjs7i.png',
11 | authorURL: 'https://playruneterra.com'
12 | }, client)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/commands/misc/github.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand } = require('../../')
2 |
3 | module.exports = class GitHub extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'github',
7 | aliases: ['gh'],
8 | requirements: { apis: ['github'] },
9 | authorString: 'commands:github.serviceName',
10 | authorImage: 'https://i.imgur.com/gsY6oYB.png',
11 | authorURL: 'https://github.com',
12 | embedColor: '#24292e'
13 | }, client)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/apis/GooglePlay.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const gPlay = require('google-play-scraper').memoized()
3 |
4 | module.exports = class GooglePlayStore extends APIWrapper {
5 | constructor () {
6 | super({
7 | name: 'gplaystore'
8 | })
9 | }
10 |
11 | async searchApp (query) {
12 | return gPlay.search({ term: query, num: 10, fullDetail: true })
13 | }
14 |
15 | async searchDev (query) {
16 | return gPlay.developer({ devId: query, fullDetail: true, num: 10 })
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/commands/misc/spotify.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand } = require('../../')
2 |
3 | module.exports = class Spotify extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'spotify',
7 | aliases: ['sp'],
8 | requirements: { apis: ['spotify'] },
9 | authorString: 'commands:spotify.serviceName',
10 | authorImage: 'https://i.imgur.com/vw8svty.png',
11 | authorURL: 'https://spotify.com',
12 | embedColor: '#18d860'
13 | }, client)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/apis/NPMRegistry.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | const API_URL = 'https://registry.npmjs.org/-/v1/search'
5 |
6 | module.exports = class NPMRegistry extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'npmregistry'
10 | })
11 | }
12 |
13 | async search (name) {
14 | return axios({
15 | params: {
16 | text: encodeURIComponent(name),
17 | size: 10
18 | },
19 | url: API_URL
20 | })
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/commands/utility/stoptyping.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class StopTyping extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'stoptyping',
7 | aliases: ['st'],
8 | category: 'utility'
9 | }, client)
10 | }
11 |
12 | run ({ t, author, channel }) {
13 | channel.stopTyping(true)
14 | channel.send(
15 | new SwitchbladeEmbed(author).setDescription(t('commands:stoptyping.tryingToStop'))
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/commands/memes/smart.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Smart extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'smart',
7 | aliases: ['wesmart'],
8 | category: 'memes'
9 | }, client)
10 | }
11 |
12 | run ({ author, channel }) {
13 | const embed = new SwitchbladeEmbed(author)
14 | embed.setImage('https://media0.giphy.com/media/d3mlE7uhX8KFgEmY/source.gif')
15 | channel.send(embed)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/commands/bot/support.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Support extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'support',
7 | category: 'bot'
8 | }, client)
9 | }
10 |
11 | async run ({ t, channel }) {
12 | channel.send(
13 | new SwitchbladeEmbed()
14 | .setImage('https://i.imgur.com/wuuQaZu.png')
15 | .setDescription(`${this.getEmoji('discordLogo')} ${t('commands:support.clickHere')}`)
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/commands/misc/tcdne.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class TCDNE extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'tcdne',
7 | aliases: ['thiscatdoesnotexist']
8 | }, client)
9 | }
10 |
11 | run ({ t, channel, author }) {
12 | channel.send(
13 | new SwitchbladeEmbed(author)
14 | .setDescription(t('commands:tcdne.cat'))
15 | .setImage(`https://thiscatdoesnotexist.com/?q=${new Date().getTime()}`)
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/apis/Owlbot.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | const API_URL = 'https://owlbot.info/api/v4'
5 |
6 | module.exports = class OwlbotAPI extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'owlbot',
10 | envVars: ['OWLBOT_KEY']
11 | })
12 | }
13 |
14 | request (query) {
15 | return axios({
16 | url: `${API_URL}/dictionary/${encodeURIComponent(query)}`,
17 | headers: { Authorization: `Token ${process.env.OWLBOT_KEY}` }
18 | })
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/commands/misc/deezer.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand } = require('../../')
2 |
3 | module.exports = class Deezer extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'deezer',
7 | aliases: ['dz'],
8 | requirements: { apis: ['deezer'] },
9 | authorString: 'commands:deezer.serviceName',
10 | authorImage: 'https://lh3.googleusercontent.com/r55K1eQcji3QMHRKERq6zE1-csoh_MTOHiKyHTuTOblhFi_rIz06_8GN5-DHUGJOpn79',
11 | authorURL: 'https://deezer.com'
12 | }, client)
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/database/mongo/repositories/GuildRepository.js:
--------------------------------------------------------------------------------
1 | const MongoRepository = require('../MongoRepository.js')
2 | const GuildSchema = require('../schemas/GuildSchema.js')
3 |
4 | module.exports = class GuildRepository extends MongoRepository {
5 | constructor (mongoose) {
6 | super(mongoose, mongoose.model('Guild', GuildSchema))
7 | }
8 |
9 | parse (entity) {
10 | return {
11 | prefix: process.env.PREFIX,
12 | language: 'en-US',
13 | modules: new Map(),
14 | ...(super.parse(entity) || {})
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/loaders/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ListenerLoader: require('./ListenerLoader.js'),
3 | DatabaseLoader: require('./DatabaseLoader.js'),
4 | APILoader: require('./APILoader.js'),
5 | LocaleLoader: require('./LocaleLoader.js'),
6 | ControllerLoader: require('./ControllerLoader.js'),
7 | EmojiLoader: require('./EmojiLoader.js'),
8 | ModuleLoader: require('./ModuleLoader.js'),
9 | HTTPLoader: require('./HTTPLoader.js'),
10 | CommandLoader: require('./CommandLoader.js'),
11 | ConnectionLoader: require('./ConnectionLoader.js')
12 | }
13 |
--------------------------------------------------------------------------------
/src/structures/Loader.js:
--------------------------------------------------------------------------------
1 | const Utils = require('../utils')
2 |
3 | module.exports = class Loader {
4 | /**
5 | * @param {Object} opts
6 | * @param {boolean} [opts.critical]
7 | * @param {Client} client
8 | */
9 | constructor (opts, client) {
10 | const options = Utils.createOptionHandler('Loader', opts)
11 |
12 | this.critical = options.optional('critical', false)
13 | this.preLoad = options.optional('preLoad', false)
14 |
15 | this.client = client
16 | }
17 |
18 | load (client) {
19 | return true
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/commands/misc/tpdne.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class TPDNE extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'tpdne',
7 | aliases: ['thispersondoesnotexist']
8 | }, client)
9 | }
10 |
11 | run ({ t, channel, author }) {
12 | channel.send(
13 | new SwitchbladeEmbed(author)
14 | .setDescription(t('commands:tpdne.person'))
15 | .setImage(`https://thispersondoesnotexist.com/image?q=${new Date().getTime()}`)
16 | )
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/structures/command/parameters/types/MemberParameter.js:
--------------------------------------------------------------------------------
1 | const UserParameter = require('./UserParameter.js')
2 |
3 | module.exports = class MemberParameter extends UserParameter {
4 | static parseOptions (options = {}) {
5 | return {
6 | ...super.parseOptions(options),
7 | fetchUser: false
8 | }
9 | }
10 |
11 | static parse (arg, context) {
12 | if (!arg) return
13 |
14 | const { guild } = context
15 | const user = super.parse(arg, context)
16 | return guild.members.cache.get(user.id)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/apis/LanguageLayer.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | const API_URL = 'http://api.languagelayer.com'
5 |
6 | module.exports = class LanguageLayerAPI extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'languagelayer',
10 | envVars: ['LANGUAGELAYER_API_KEY']
11 | })
12 | }
13 |
14 | detectText (query) {
15 | return axios(`${API_URL}/detect`, {
16 | params: {
17 | access_key: process.env.LANGUAGELAYER_API_KEY,
18 | query
19 | }
20 | })
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/commands/bot/vote.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Vote extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'vote',
7 | category: 'bot'
8 | }, client)
9 | }
10 |
11 | async run ({ t, author, channel }) {
12 | channel.startTyping()
13 | channel.send(new SwitchbladeEmbed(author)
14 | .setDescription(t('commands:vote.howToVote.dbl', { link: `https://discordbots.org/bot/${this.client.user.id}/vote` })))
15 | .then(() => channel.stopTyping())
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/utils/placeholders/PlaceholderUtils.js:
--------------------------------------------------------------------------------
1 | const PlaceholderRules = require('./PlaceholderRules')
2 |
3 | module.exports = class PlaceholderUtils {
4 | static parse (text, context, whitelist, blacklist) {
5 | return PlaceholderRules.reduce((t, r) => {
6 | if (Array.isArray(whitelist) && !whitelist.includes(r.name)) return t
7 | if (Array.isArray(blacklist) && blacklist.includes(r.name)) return t
8 |
9 | const regex = r.regex || new RegExp(`{${r.name}}`, 'g')
10 | return t.replace(regex, r.replace.bind(null, context))
11 | }, text)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/commands/games/leagueoflegends.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand } = require('../../')
2 |
3 | module.exports = class LeagueOfLegends extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'leagueoflegends',
7 | aliases: ['lol'],
8 | category: 'games',
9 | requirements: { apis: ['lol'] },
10 | authorString: 'commands:leagueoflegends.gameName',
11 | authorImage: 'https://i.imgur.com/4dKfQZn.jpg',
12 | authorURL: 'https://leagueoflegends.com',
13 | embedColor: '#002366'
14 | }, client)
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/commands/misc/cowsay.js:
--------------------------------------------------------------------------------
1 | const { Command } = require('../../')
2 | const cowsay = require('cowsay')
3 |
4 | module.exports = class Cowsay extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'cowsay',
8 | aliases: ['cs'],
9 | category: 'general',
10 | parameters: [{
11 | type: 'string', full: true, clean: true, missingError: 'commands:cowsay.noText'
12 | }]
13 | }, client)
14 | }
15 |
16 | run ({ channel, message }, text) {
17 | channel.send(`\`\`\`${cowsay.say({ text })}\`\`\``)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/commands/misc/googleplay.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand, Constants } = require('../../')
2 |
3 | module.exports = class GooglePlay extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'googleplay',
7 | aliases: ['gplay', 'androidmarket'],
8 | requirements: { apis: ['gplaystore'] },
9 | authorString: 'commands:googleplay.serviceName',
10 | authorImage: 'https://i.imgur.com/kLdJwcj.png',
11 | authorURL: 'https://play.google.com',
12 | embedColor: Constants.GOOGLEPLAY_COLOR
13 | }, client)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/commands/misc/httpcat.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class HttpCat extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'httpcat',
7 | category: 'general',
8 | parameters: [{
9 | type: 'number',
10 | required: false
11 | }]
12 | }, client)
13 | }
14 |
15 | async run ({ t, author, channel }, statusCode = 200) {
16 | channel.send(
17 | new SwitchbladeEmbed(author)
18 | .setImage(`https://http.cat/${Math.round(statusCode)}`)
19 | )
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/music/sources/http/HTTPSong.js:
--------------------------------------------------------------------------------
1 | const { Song } = require('../../structures')
2 |
3 | module.exports = class HTTPSong extends Song {
4 | constructor (data = {}, requestedBy, Icecast) {
5 | super(data, requestedBy)
6 | this._Icecast = Icecast
7 | this.color = '#2C2F33'
8 | }
9 |
10 | async loadInfo () {
11 | const radioInfo = await this._Icecast.fetchMetadata(this.uri).catch(e => null)
12 | if (radioInfo) {
13 | this.title = radioInfo.StreamTitle || 'Unknown title'
14 | this.uri = radioInfo.StreamUrl || this.uri
15 | }
16 | return this
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/structures/game/Matrix.js:
--------------------------------------------------------------------------------
1 | module.exports = class Matrix extends Array {
2 | constructor (rows, columns) {
3 | super()
4 |
5 | this.rows = rows
6 | this.columns = columns
7 | }
8 |
9 | populate (callback = () => 0) {
10 | for (let i = 0; i < this.rows; i++) {
11 | this[i] = []
12 |
13 | for (let j = 0; j < this.columns; j++) {
14 | this[i][j] = callback(i, j)
15 | }
16 | }
17 | }
18 |
19 | get (row, column) {
20 | return this[row][column]
21 | }
22 |
23 | set (row, column, value) {
24 | this[row][column] = value
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/apis/CurseForge.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | const API_URL = 'https://addons-ecs.forgesvc.net/api/v2'
5 |
6 | module.exports = class CurseForge extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'curseforge'
10 | })
11 | }
12 |
13 | async searchAddon (gId, query) {
14 | return axios({
15 | params: {
16 | gameId: gId,
17 | pageSize: 3,
18 | searchFilter: query,
19 | sectionId: 4471,
20 | sort: 0
21 | },
22 | url: `${API_URL}/addon/search`
23 | })
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/apis/MerriamWebster.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | const API_URL = 'https://dictionaryapi.com/api/v3/references/collegiate/json'
5 |
6 | module.exports = class MerriamWebster extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'merriamwebster',
10 | envVars: ['MERRIAM_WEBSTER_API_KEY']
11 | })
12 | }
13 |
14 | async search (word) {
15 | return axios({
16 | url: `${API_URL}/${encodeURIComponent(word)}`,
17 | params: {
18 | key: process.env.MERRIAM_WEBSTER_API_KEY
19 | }
20 | })
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/apis/Chocolatey.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | module.exports = class Chocolatey extends APIWrapper {
5 | constructor () {
6 | super({
7 | name: 'chocolatey'
8 | })
9 | }
10 |
11 | async search (query) {
12 | return axios.get('https://chocolatey.org/api/v2/Search()', {
13 | params: {
14 | $filter: 'IsLatestVersion',
15 | $skip: 0,
16 | $top: 10,
17 | searchTerm: encodeURIComponent(`'${query}'`),
18 | targetFramework: "''",
19 | includePrerelease: false
20 | }
21 | })
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/commands/memes/reversetext.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../index')
2 |
3 | module.exports = class ReverseText extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'reversetext',
7 | category: 'memes',
8 | parameters: [{
9 | type: 'string', full: true, missingError: 'commands:reversetext.missingSentence'
10 | }]
11 | }, client)
12 | }
13 |
14 | async run ({ t, author, channel }, text) {
15 | channel.send(
16 | new SwitchbladeEmbed(author)
17 | .setDescription(text.split('').reverse().join(''))
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/music/sources/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | Songs: {
3 | // HTTP
4 | HTTPSong: require('./http/HTTPSong.js'),
5 |
6 | // SoundCloud
7 | SoundcloudSong: require('./soundcloud/SoundcloudSong.js'),
8 |
9 | // Twitch
10 | TwitchSong: require('./twitch/TwitchSong.js'),
11 |
12 | // YouTube
13 | YoutubePlaylist: require('./youtube/YoutubePlaylist.js'),
14 | YoutubeSong: require('./youtube/YoutubeSong.js')
15 | },
16 | Sources: {
17 | DeezerSongSource: require('./deezer/DeezerSongSource.js'),
18 | SpotifySongSource: require('./spotify/SpotifySongSource.js')
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/commands/misc/httpdog.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class HttpDog extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'httpdog',
7 | category: 'general',
8 | parameters: [{
9 | type: 'number',
10 | required: false
11 | }]
12 | }, client)
13 | }
14 |
15 | async run ({ t, author, channel }, statusCode = 200) {
16 | channel.send(
17 | new SwitchbladeEmbed(author)
18 | .setImage(`https://httpstatusdogs.com/img/${Math.round(statusCode)}.jpg`)
19 | )
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/apis/Chorus.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const fetch = require('node-fetch')
3 |
4 | const API_URL = 'https://chorus.fightthe.pw/api'
5 |
6 | module.exports = class ChorusAPI extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'chorus'
10 | })
11 | }
12 |
13 | search (query) {
14 | return this.request('/search', { query }).then(r => r.songs)
15 | }
16 |
17 | request (endpoint, queryParams = {}) {
18 | const qParams = new URLSearchParams(queryParams)
19 | return fetch(API_URL + endpoint + `?${qParams.toString()}`)
20 | .then(res => res.json())
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/commands/misc/xkcd37.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class XKCD37 extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'xkcd37',
7 | parameters: [{
8 | type: 'string', full: true
9 | }]
10 | }, client)
11 | }
12 |
13 | // Context: https://xkcd.com/37/
14 |
15 | async run ({ author, channel }, text) {
16 | const embed = new SwitchbladeEmbed(author)
17 | embed.setTitle(text.replace(/(\w+?)(?!\\)+(-ass)(\s+)(\S+?)/g, '$1$3ass-$4').replace(/\\-/g, '-'))
18 | channel.send(embed)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/music/sources/deezer/DeezerSong.js:
--------------------------------------------------------------------------------
1 | const { Song } = require('../../structures')
2 | const Constants = require('../../../utils/Constants.js')
3 |
4 | module.exports = class DeezerSong extends Song {
5 | constructor (data = {}, requestedBy, track, album = track.album) {
6 | super(data, requestedBy)
7 |
8 | this.identifier = track.id
9 | this.author = track.artist.name
10 | this.title = track.title
11 | this.uri = track.link
12 |
13 | if (album) this.artwork = album.cover_xl
14 |
15 | this.source = 'deezer'
16 | this.color = Constants.DEEZER_COLOR
17 |
18 | this.deezerTrack = track
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/apis/Instagram.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const fetch = require('node-fetch')
3 |
4 | const API_URL = 'https://instagram.com/'
5 |
6 | module.exports = class InstagramAPI extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'instagram'
10 | })
11 | }
12 |
13 | getUser (user) {
14 | return this.request(user)
15 | }
16 |
17 | request (endpoint, queryParams = {}) {
18 | const qParams = new URLSearchParams({
19 | ...queryParams,
20 | __a: 1
21 | })
22 | return fetch(API_URL + endpoint + `?${qParams.toString()}`)
23 | .then(res => res.json())
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/commands/bot/i18n.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class i18n extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'i18n',
7 | aliases: ['crowdin'],
8 | category: 'bot'
9 | }, client)
10 | }
11 |
12 | async run ({ t, channel }) {
13 | const embed = new SwitchbladeEmbed()
14 | channel.startTyping()
15 | embed
16 | .setDescription(`${this.getEmoji('crowdinLogo')} ${t('commands:i18n.translateMe')}`)
17 | .setImage('https://i.imgur.com/UVIAzg0.gif')
18 | channel.send(embed).then(() => channel.stopTyping())
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/games/Connect4/Player.js:
--------------------------------------------------------------------------------
1 | const { Player } = require('../..')
2 | const { LABELS } = require('./constants')
3 |
4 | module.exports = class Connect4Player extends Player {
5 | get label () {
6 | return LABELS[this.code]
7 | }
8 |
9 | play (column) {
10 | this.game.board.add(column, this.code)
11 | }
12 |
13 | won () {
14 | return this.game.board.hasRow(this.code) ||
15 | this.game.board.hasColumn(this.code) ||
16 | this.game.board.hasDiagonal(this.code) ||
17 | this.game.board.hasReverseDiagonal(this.code)
18 | }
19 |
20 | toString () {
21 | return `${this.label} ${this.user}`
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/commands/misc/fox.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 | const fetch = require('node-fetch')
3 |
4 | module.exports = class Fox extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'fox'
8 | }, client)
9 | }
10 |
11 | async run ({ t, author, channel }) {
12 | const embed = new SwitchbladeEmbed(author)
13 | channel.startTyping()
14 | const { image } = await fetch('https://randomfox.ca/floof/').then(res => res.json())
15 | embed.setImage(image)
16 | .setDescription(t('commands:fox.hereIsYourFox'))
17 | channel.send(embed).then(() => channel.stopTyping())
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/database/mongo/MongoDB.js:
--------------------------------------------------------------------------------
1 | const DBWrapper = require('../DBWrapper.js')
2 | const { GuildRepository, UserRepository } = require('./repositories')
3 |
4 | const mongoose = require('mongoose')
5 |
6 | class MongoDB extends DBWrapper {
7 | constructor (options = {}) {
8 | super(options)
9 | this.mongoose = mongoose
10 | }
11 |
12 | async connect () {
13 | return mongoose.connect(process.env.MONGODB_URI, this.options).then((m) => {
14 | this.guilds = new GuildRepository(m)
15 | this.users = new UserRepository(m)
16 | })
17 | }
18 | }
19 |
20 | MongoDB.envVars = ['MONGODB_URI']
21 |
22 | module.exports = MongoDB
23 |
--------------------------------------------------------------------------------
/src/apis/EclipsePlugins.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('..')
2 | const axios = require('axios')
3 | const parser = require('xml2json')
4 |
5 | const API_URL = 'https://marketplace.eclipse.org/api/p/search/apachesolr_search'
6 |
7 | module.exports = class EclipsePlugins extends APIWrapper {
8 | constructor () {
9 | super({
10 | name: 'eclipseplugins'
11 | })
12 | }
13 |
14 | async search (name) {
15 | const res = await axios({
16 | params: {
17 | page_num: 1
18 | },
19 | url: `${API_URL}/${encodeURIComponent(name)}`,
20 | method: 'POST'
21 | })
22 |
23 | return JSON.parse(parser.toJson(res.data))
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/commands/memes/bolinadegorfe.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class BolinaDeGorfe extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'bolinadegorfe',
7 | aliases: ['bolinhadegolfe', 'bolinhadegorfe', 'bolinadegolfe'],
8 | category: 'memes'
9 | }, client)
10 | }
11 |
12 | run ({ author, channel }) {
13 | // TODO: make this command only works in pt-BR
14 | const embed = new SwitchbladeEmbed(author)
15 | embed
16 | .setTitle('ooo, boliña de gorfe')
17 | .setImage('https://j.gifs.com/9QVDRP.gif')
18 | channel.send(embed)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/commands/misc/emoji.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Emoji extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'emoji',
7 | aliases: ['enlarge', 'bigemoji'],
8 | parameters: [{
9 | type: 'emoji', full: true
10 | }]
11 | }, client)
12 | }
13 |
14 | run ({ t, author, channel }, emoji) {
15 | const embed = new SwitchbladeEmbed(author)
16 | channel.startTyping()
17 | embed.setImage(emoji.url)
18 | .setDescription(t('commands:emoji.hereIsYourEmoji'))
19 | channel.send(embed).then(() => channel.stopTyping())
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for the bot
4 |
5 | ---
6 |
7 | **Is your feature request related to a problem? Please describe.**
8 |
9 |
10 | **Describe the solution you'd like**
11 |
12 |
13 | **Describe alternatives you've considered**
14 |
15 |
16 | **Additional context**
17 |
18 |
--------------------------------------------------------------------------------
/src/commands/games/osu.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand } = require('../../')
2 |
3 | module.exports = class Osu extends SubcommandListCommand {
4 | constructor (client) {
5 | super({
6 | name: 'osu',
7 | category: 'games',
8 | requirements: { apis: ['osu'] },
9 | authorString: 'commands:osu.gameName',
10 | authorImage: 'https://i.imgur.com/Ek0hnam.png',
11 | authorURL: 'https://osu.ppy.sh',
12 | embedColor: '#E7669F'
13 | }, client)
14 |
15 | this.modes = {
16 | osu: ['0', 'osu!'],
17 | taiko: ['1', 'osu!taiko'],
18 | catchthebeat: ['2', 'osu!catch'],
19 | mania: ['3', 'osu!mania']
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/commands/misc/dicksize.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Dicksize extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'dicksize',
7 | aliases: ['peepeesize']
8 | }, client)
9 | }
10 |
11 | run ({ t, author, channel }) {
12 | const embed = new SwitchbladeEmbed(author)
13 | channel.startTyping()
14 |
15 | const size = author.id.slice(-3) % 20 + 1
16 | embed
17 | .setTitle(t('commands:dicksize.yourDickSize'))
18 | .setDescription(`${size} cm\n8${'='.repeat(size)}D`)
19 | channel.send(embed).then(() => channel.stopTyping())
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/.github/auto_assign.yml:
--------------------------------------------------------------------------------
1 | # Set to true to add reviewers to pull requests
2 | addReviewers: true
3 |
4 | # Set to true to add assignees to pull requests
5 | addAssignees: false
6 |
7 | # A list of reviewers to be added to pull requests (GitHub user name)
8 | reviewers:
9 | - pedrofracassi
10 | - Doges
11 | - metehus
12 | - davipatury
13 | - dpaiv0
14 | - perronosaurio
15 | - Lireoy
16 | - LogGame-HU-DEaN
17 |
18 | # A list of keywords to be skipped the process that add reviewers if pull requests include it
19 | skipKeywords:
20 | - wip
21 |
22 | # A number of reviewers added to the pull request
23 | # Set 0 to add all the reviewers (default: 0)
24 | numberOfReviewers: 0
25 |
--------------------------------------------------------------------------------
/src/apis/FlatHub.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | const API_URL = 'https://flathub.org/api/v1/apps'
5 |
6 | module.exports = class FlatHub extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'flathub'
10 | })
11 | }
12 |
13 | async list () {
14 | const res = await axios(API_URL)
15 | return res.data
16 | }
17 |
18 | async getApp (appId) {
19 | const res = await axios({ baseURL: API_URL, url: `/${appId}` })
20 | return res.data
21 | }
22 |
23 | async search (query) {
24 | const res = await axios({ baseURL: API_URL, url: `/search/${query}` })
25 | return res.data
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/apis/Icecast.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const IcecastParser = require('icecast-parser')
3 |
4 | module.exports = class Icecast extends APIWrapper {
5 | constructor () {
6 | super({
7 | name: 'icecast'
8 | })
9 | }
10 |
11 | fetchMetadata (url) {
12 | return new Promise((resolve, reject) => {
13 | if (url.startsWith('https://')) return reject(new Error('HTTPS'))
14 | try {
15 | const station = new IcecastParser(url)
16 | station.on('metadata', resolve)
17 | station.on('error', reject)
18 | station.on('empty', reject)
19 | } catch (e) {
20 | reject(e)
21 | }
22 | })
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/apis/Reddit.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 |
3 | const Snoowrap = require('snoowrap')
4 |
5 | module.exports = class RedditAPI extends APIWrapper {
6 | constructor () {
7 | super({
8 | name: 'reddit',
9 | envVars: [
10 | 'REDDIT_CLIENT_ID',
11 | 'REDDIT_CLIENT_SECRET',
12 | 'REDDIT_REFRESH_TOKEN'
13 | ]
14 | })
15 | }
16 |
17 | load () {
18 | return new Snoowrap({
19 | userAgent: 'SwitchbladeBot http://switchblade.xyz/',
20 | clientId: process.env.REDDIT_CLIENT_ID,
21 | clientSecret: process.env.REDDIT_CLIENT_SECRET,
22 | refreshToken: process.env.REDDIT_REFRESH_TOKEN
23 | })
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/apis/e621.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const fetch = require('node-fetch')
3 |
4 | module.exports = class e621 extends APIWrapper {
5 | constructor () {
6 | super({
7 | name: 'e621'
8 | })
9 | }
10 |
11 | async searchPost (tags, eURL = 'https://e926.net') {
12 | return this.request('/posts.json', { limit: 1, tags }, eURL)
13 | }
14 |
15 | async request (endpoint, queryParams = {}, eURL) {
16 | const qParams = new URLSearchParams(queryParams)
17 | const fetched = await fetch(`${eURL}${endpoint}?${qParams.toString()}`, {
18 | headers: { 'User-Agent': 'SwitchbladeBot/1.0 xDoges' }
19 | })
20 | return fetched.json()
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/commands/misc/geekjokes.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 | const fetch = require('node-fetch')
3 |
4 | module.exports = class GeekJokes extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'geekjokes',
8 | aliases: ['geek', 'geekjoke', 'geekj']
9 | }, client)
10 | }
11 |
12 | async run ({ author, channel }, number) {
13 | const embed = new SwitchbladeEmbed(author)
14 | channel.startTyping()
15 | const body = await fetch('https://geek-jokes.sameerkumar.website/api').then(res => res.json())
16 | embed.setTitle(body)
17 | channel.send(embed).then(() => channel.stopTyping())
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/commands/bot/invite.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Invite extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'invite',
7 | category: 'bot'
8 | }, client)
9 | }
10 |
11 | async run ({ t, channel }) {
12 | const embed = new SwitchbladeEmbed()
13 | channel.startTyping()
14 | const invite = await this.client.generateInvite()
15 | embed.setThumbnail(this.client.user.displayAvatarURL({ format: 'png' }))
16 | .setDescription(`[${t('commands:invite.clickHere')}](${invite})\n${t('commands:invite.noteThat')}`)
17 | channel.send(embed).then(() => channel.stopTyping())
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/structures/command/parameters/types/BooleanParameter.js:
--------------------------------------------------------------------------------
1 | const Parameter = require('./Parameter.js')
2 | const CommandError = require('../../CommandError.js')
3 |
4 | module.exports = class BooleanParameter extends Parameter {
5 | static parseOptions (options = {}) {
6 | return {
7 | ...super.parseOptions(options),
8 | trueValues: options.trueValues || ['true', 'yes', 'on'],
9 | falseValues: options.falseValues || ['false', 'no', 'off']
10 | }
11 | }
12 |
13 | static parse (arg, { t }) {
14 | if (!this.trueValues.concat(this.falseValues).includes(arg)) throw new CommandError(t('errors:notTrueOrFalse'))
15 | return this.trueValues.includes(arg)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/structures/command/parameters/types/RoleParameter.js:
--------------------------------------------------------------------------------
1 | const Parameter = require('./Parameter.js')
2 | const CommandError = require('../../CommandError.js')
3 |
4 | const MENTION_ROLE_REGEX = /^(?:<@&?)?([0-9]{16,18})(?:>)?$/
5 |
6 | module.exports = class RoleParameter extends Parameter {
7 | static parse (arg, { t, guild }) {
8 | if (!arg) return
9 |
10 | const regexResult = MENTION_ROLE_REGEX.exec(arg)
11 | const id = regexResult && regexResult[1]
12 |
13 | const role = guild.roles.cache.get(id) || guild.roles.cache.find(r => r.name.toLowerCase().includes(arg.toLowerCase()))
14 | if (!role) throw new CommandError(t('errors:invalidRole'))
15 | return role
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/database/mongo/schemas/UserSchema.js:
--------------------------------------------------------------------------------
1 | const { Schema } = require('mongoose')
2 |
3 | // Misc
4 | const BlacklistedSchema = new Schema({
5 | reason: { type: String, required: true },
6 | blacklister: { type: String, required: true }
7 | })
8 |
9 | const UserConnection = new Schema({
10 | name: String,
11 | tokens: Object,
12 | config: Object
13 | })
14 |
15 | module.exports = new Schema({
16 | _id: String,
17 | money: Number,
18 | lastDaily: Number,
19 | globalXp: Number,
20 | personalText: String,
21 | blacklisted: BlacklistedSchema,
22 | favColor: String,
23 | rep: Number,
24 | lastRep: Number,
25 | lastDBLBonusClaim: Number,
26 | connections: [UserConnection]
27 | })
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/Bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 |
5 | ---
6 |
7 | **Bug description**
8 |
9 |
10 | **How to reproduce**
11 |
15 |
16 | **Expected behavior**
17 |
18 |
19 | **Screenshots**
20 |
21 |
22 | **Additional context**
23 |
24 |
--------------------------------------------------------------------------------
/src/modules/LanguageModule.js:
--------------------------------------------------------------------------------
1 | const { Module } = require('../')
2 |
3 | const Joi = require('joi')
4 |
5 | // Language
6 | module.exports = class LanguageModule extends Module {
7 | constructor (client) {
8 | super({
9 | name: 'language',
10 | displayName: 'Language',
11 | toggleable: false,
12 | defaultValues: { language: 'en-US' },
13 | specialInput: {
14 | language: { whitelist: Object.keys(client.i18next.store.data) }
15 | }
16 | }, client)
17 | }
18 |
19 | validateValues (entity) {
20 | return Joi.object().keys({
21 | language: Joi.string().valid(...Object.keys(this.client.i18next.store.data))
22 | }).validate(entity)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/commands/misc/lmgtfy.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class LMGTFY extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'lmgtfy',
7 | aliases: ['letmegooglethatforyou'],
8 | category: 'memes',
9 | parameters: [{
10 | type: 'string', full: true, missingError: 'commands:lmgtfy.noQuery'
11 | }]
12 | }, client)
13 | }
14 |
15 | run ({ t, channel, author }, query) {
16 | const embed = new SwitchbladeEmbed(author)
17 | embed.setDescription(t('commands:lmgtfy.search', { link: `https://lmgtfy.com/?q=${encodeURIComponent(query)}` }))
18 | channel.send(embed.setColor('#4285F4'))
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/commands/misc/qrcode/generate.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../../')
2 |
3 | module.exports = class QRCodeGenerate extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'generate',
7 | aliases: ['create', 'g'],
8 | parent: 'qrcode',
9 | parameters: [{
10 | type: 'string', full: true, missingError: 'commands:qrcode.subcommands.generate.noText'
11 | }]
12 | }, client)
13 | }
14 |
15 | async run ({ t, author, channel, language }, text) {
16 | channel.send(
17 | new SwitchbladeEmbed(author)
18 | .setImage(`https://api.qrserver.com/v1/create-qr-code/?data=${encodeURIComponent(text)}`)
19 | )
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/commands/text/vaporwave.js:
--------------------------------------------------------------------------------
1 | const { Command } = require('../../')
2 |
3 | module.exports = class Vaporwave extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'vaporwave',
7 | category: 'memes',
8 | parameters: [{
9 | type: 'string',
10 | full: true,
11 | missingError: 'commands:vaporwave.missingSentence'
12 | }]
13 | }, client)
14 | }
15 |
16 | async run ({ t, author, channel }, text) {
17 | const vaporwavefied = text.split('').map(char => {
18 | const code = char.charCodeAt(0)
19 | return code >= 33 && code <= 126 ? String.fromCharCode((code - 33) + 65281) : char
20 | }).join('')
21 | channel.send(vaporwavefied)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/commands/misc/asciify.js:
--------------------------------------------------------------------------------
1 | const { Command } = require('../../')
2 | const figlet = require('figlet')
3 |
4 | module.exports = class Asciify extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'asciify',
8 | aliases: ['bigtext'],
9 | parameters: [{
10 | type: 'string', full: true, clean: true, missingError: 'commands:asciify.noText'
11 | }]
12 | }, client)
13 | }
14 |
15 | run ({ channel, message }, text) {
16 | const bigtext = figlet.textSync(text, {
17 | font: 'Big',
18 | horizontalLayout: 'universal smushing',
19 | verticalLayout: 'universal smushing'
20 | })
21 | channel.send(`\`\`\`${bigtext}\`\`\``)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/apis/SteamStore.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | const API_URL = 'https://store.steampowered.com/api'
5 |
6 | module.exports = class SteamStore extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'steamstore'
10 | })
11 | }
12 |
13 | search (query) {
14 | return axios({
15 | url: `${API_URL}/storesearch`,
16 | params: {
17 | term: query,
18 | l: 'english',
19 | cc: 'US'
20 | }
21 | })
22 | }
23 |
24 | info (id, lang = 'english') {
25 | return axios({
26 | url: `${API_URL}/appdetails`,
27 | params: {
28 | appids: id,
29 | l: lang
30 | }
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/commands/memes/piratespeak.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 | const pirateSpeak = require('pirate-speak')
3 |
4 | module.exports = class PirateSpeak extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'piratespeak',
8 | aliases: ['ps', 'yarrspeak'],
9 | category: 'memes',
10 | parameters: [{
11 | type: 'string', full: true, clean: true, missingError: 'commands:piratespeak.missingSentence'
12 | }]
13 | }, client)
14 | }
15 |
16 | async run ({ author, channel }, text) {
17 | const embed = new SwitchbladeEmbed(author)
18 | embed.setDescription(pirateSpeak.translate(text))
19 | channel.send(embed)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/commands/music/queue/clear.js:
--------------------------------------------------------------------------------
1 | const { Command, CommandError, SwitchbladeEmbed } = require('../../../')
2 |
3 | module.exports = class QueueClear extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'clear',
7 | aliases: ['cl'],
8 | parent: 'queue'
9 | }, client)
10 | }
11 |
12 | async run ({ t, author, channel, guild }) {
13 | const guildPlayer = this.client.playerManager.players.get(guild.id)
14 | if (guildPlayer.nextSong) {
15 | guildPlayer.clearQueue()
16 | channel.send(new SwitchbladeEmbed(author)
17 | .setTitle(t(`commands:${this.tPath}.queueCleared`)))
18 | } else {
19 | throw new CommandError(t('music:noneAfterCurrent'))
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/commands/music/queue/shuffle.js:
--------------------------------------------------------------------------------
1 | const { Command, CommandError, SwitchbladeEmbed } = require('../../../')
2 |
3 | module.exports = class QueueShuffle extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'shuffle',
7 | aliases: ['sf'],
8 | parent: 'queue'
9 | }, client)
10 | }
11 |
12 | async run ({ t, author, channel, guild }) {
13 | const guildPlayer = this.client.playerManager.players.get(guild.id)
14 | if (guildPlayer.nextSong) {
15 | guildPlayer.shuffleQueue()
16 | channel.send(new SwitchbladeEmbed(author)
17 | .setTitle(t(`commands:${this.tPath}.queueShuffled`)))
18 | } else {
19 | throw new CommandError(t('music:noneAfterCurrent'))
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/commands/music/stop.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Stop extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'stop',
7 | category: 'music',
8 | requirements: { guildOnly: true, sameVoiceChannelOnly: true, guildPlaying: true, permissions: ['MANAGE_GUILD'] }
9 | }, client)
10 | }
11 |
12 | async run ({ author, channel, guild, t }) {
13 | const embed = new SwitchbladeEmbed(author)
14 | const guildPlayer = this.client.playerManager.players.get(guild.id)
15 | guildPlayer.stop(author)
16 | embed.setDescription(`${this.getEmoji('stopButton')} ${t('commands:stop.stopped')}`)
17 | channel.send(embed)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/apis/HIBP.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const fetch = require('node-fetch')
3 |
4 | const API_URL = 'https://haveibeenpwned.com/api/v2'
5 | const USER_AGENT = 'Switchblade-Discord-Bot'
6 |
7 | module.exports = class HIBP extends APIWrapper {
8 | constructor () {
9 | super({
10 | name: 'hibp'
11 | })
12 | }
13 |
14 | getBreaches (query) {
15 | return this.request(`/breachedaccount/${encodeURI(query)}`).then(u => u)
16 | }
17 |
18 | getPastes (query) {
19 | return this.request(`/pasteaccount/${encodeURI(query)}`).then(u => u)
20 | }
21 |
22 | request (endpoint) {
23 | return fetch(API_URL + endpoint, {
24 | headers: { 'User-Agent': USER_AGENT }
25 | }).then(res => res.json())
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/commands/memes/owo.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | const Owoify = require('../../utils/Owoify')
4 |
5 | module.exports = class OwO extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'owo',
9 | aliases: ['uwu', 'whatsthis', 'owoify'],
10 | category: 'memes',
11 | parameters: [{
12 | type: 'string', full: true, missingError: 'commands:owo.missingSentence'
13 | }]
14 | }, client)
15 | }
16 |
17 | async run ({ author, channel }, text) {
18 | const embed = new SwitchbladeEmbed(author)
19 | channel.startTyping()
20 | embed.setDescription(Owoify(text))
21 | channel.send(embed).then(() => channel.stopTyping())
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/structures/APIWrapper.js:
--------------------------------------------------------------------------------
1 | const Utils = require('../utils')
2 |
3 | module.exports = class APIWrapper {
4 | /**
5 | * @param {Object} opts
6 | * @param {string} opts.name
7 | * @param {string[]} [opts.envVars]
8 | */
9 | constructor (opts) {
10 | const options = Utils.createOptionHandler('APIWrapper', opts)
11 |
12 | this.name = options.required('name')
13 | this.envVars = options.optional('envVars', [])
14 | }
15 |
16 | /**
17 | * Check if the API can load
18 | * @returns {boolean} - Whether the API can load
19 | */
20 | canLoad () {
21 | return true
22 | }
23 |
24 | /**
25 | * Loads the API
26 | * @returns {APIWrapper} - The loaded API
27 | */
28 | load () {
29 | return this
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/commands/music/bassboost.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Bassboost extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'bassboost',
7 | aliases: ['bass', 'earrape'],
8 | category: 'music',
9 | requirements: { guildOnly: true, sameVoiceChannelOnly: true, guildPlaying: true }
10 | }, client)
11 | }
12 |
13 | async run ({ t, author, channel, guild }) {
14 | const embed = new SwitchbladeEmbed(author)
15 |
16 | const guildPlayer = this.client.playerManager.players.get(guild.id)
17 | guildPlayer.bassboost(!guildPlayer.bassboosted)
18 | channel.send(embed.setTitle(t(`commands:bassboost.bassboost_${guildPlayer.bassboosted}`)))
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/utils/ConfirmationBox.js:
--------------------------------------------------------------------------------
1 | const SwitchbladeEmbed = require('../structures/SwitchbladeEmbed')
2 |
3 | module.exports = async (author, channel, content) => {
4 | const msg = await channel.send(new SwitchbladeEmbed(author).setAuthor(content))
5 |
6 | await msg.react('✅')
7 | await msg.react('❌')
8 |
9 | const collector = msg.createReactionCollector((reaction, user) => (reaction.emoji.name === '✅' || reaction.emoji.name === '❌') && user.id === author.id)
10 |
11 | return new Promise((resolve) => {
12 | collector.on('collect', r => {
13 | switch (r.emoji.name) {
14 | case '✅':
15 | resolve(true)
16 | break
17 | case '❌':
18 | resolve(false)
19 | break
20 | }
21 | })
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/src/commands/misc/shiba.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | const fetch = require('node-fetch')
4 |
5 | module.exports = class Shiba extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'shiba',
9 | aliases: ['shibainu', 'doge']
10 | }, client)
11 | }
12 |
13 | async run ({ t, author, channel }) {
14 | const embed = new SwitchbladeEmbed(author)
15 | channel.startTyping()
16 | const body = await fetch('http://shibe.online/api/shibes').then(res => res.json())
17 | embed.setDescription(`${t('commands:shiba.hereIsYourShiba')} <:DoggoF:445701839564963840>`)
18 | embed.setImage(body[0])
19 | channel.send(embed).then(() => channel.stopTyping())
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/commands/music/loop.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Loop extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'loop',
7 | aliases: ['repeat'],
8 | category: 'music',
9 | requirements: { guildOnly: true, sameVoiceChannelOnly: true, guildPlaying: true }
10 | }, client)
11 | }
12 |
13 | async run ({ t, author, channel, guild }) {
14 | const embed = new SwitchbladeEmbed(author)
15 | const guildPlayer = this.client.playerManager.players.get(guild.id)
16 | const loop = !guildPlayer.looping
17 | embed.setTitle(t('music:stateChanged_loop', { context: loop ? 'on' : 'off' }))
18 | channel.send(embed).then(() => guildPlayer.loop(loop))
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/commands/misc/inspirobot.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | const fetch = require('node-fetch')
4 |
5 | module.exports = class InspiroBot extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'inspirobot',
9 | aliases: ['inspiro', 'ibot']
10 | }, client)
11 | }
12 |
13 | async run ({ t, author, channel }) {
14 | const embed = new SwitchbladeEmbed(author)
15 | channel.startTyping()
16 | const body = await fetch('http://inspirobot.me/api?generate=true').then(res => res.text())
17 | embed
18 | .setImage(body.toString('utf8'))
19 | .setDescription(t('commands:inspirobot.quote'))
20 | channel.send(embed).then(() => channel.stopTyping())
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/http/api/connections.js:
--------------------------------------------------------------------------------
1 | const { Route } = require('../..')
2 | const { Router } = require('express')
3 |
4 | module.exports = class Connections extends Route {
5 | constructor (client) {
6 | super({
7 | name: 'connections'
8 | }, client)
9 | }
10 |
11 | register (app) {
12 | const router = Router()
13 |
14 | router.get('/:connName/authURL',
15 | async (req, res) => {
16 | try {
17 | const connection = this.client.connections[req.params.connName]
18 | res.redirect(await connection.getAuthLink())
19 | } catch (e) {
20 | console.error(e)
21 | res.status(500).json({ error: 'Internal server error!' })
22 | }
23 | })
24 |
25 | app.use(this.path, router)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/music/sources/spotify/SpotifySong.js:
--------------------------------------------------------------------------------
1 | const { Song } = require('../../structures')
2 | const Constants = require('../../../utils/Constants.js')
3 |
4 | module.exports = class SpotifySong extends Song {
5 | constructor (data = {}, requestedBy, track, album = track.album) {
6 | super(data, requestedBy)
7 |
8 | this.identifier = track.id
9 | this.author = track.artists.map(a => a.name).join(', ')
10 | this.title = track.name
11 | this.uri = track.external_urls.spotify
12 |
13 | if (album) {
14 | const [cover] = album.images.sort((a, b) => b.width - a.width)
15 | this.artwork = cover.url
16 | }
17 |
18 | this.source = 'spotify'
19 | this.color = Constants.SPOTIFY_COLOR
20 |
21 | this.spotifyTrack = track
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/database/mongo/repositories/UserRepository.js:
--------------------------------------------------------------------------------
1 | const MongoRepository = require('../MongoRepository.js')
2 | const UserSchema = require('../schemas/UserSchema.js')
3 |
4 | module.exports = class UserRepository extends MongoRepository {
5 | constructor (mongoose) {
6 | super(mongoose, mongoose.model('User', UserSchema))
7 | }
8 |
9 | parse (entity) {
10 | return {
11 | money: 0,
12 | lastDaily: 0,
13 | lastRep: 0,
14 | lastDBLBonusClaim: 0,
15 | rep: 0,
16 | globalXp: 0,
17 | personalText: 'Did you know you can edit this in the future dashboard or using the personaltext command? :o',
18 | favColor: process.env.EMBED_COLOR,
19 | connections: [],
20 | ...(super.parse(entity) || {})
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/commands/developers/unblacklist.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Unblacklist extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'unblacklist',
7 | category: 'developers',
8 | hidden: true,
9 | requirements: { devOnly: true },
10 | parameters: [{
11 | type: 'user', showUsage: false, missingError: 'commands:unblacklist.missingUser'
12 | }]
13 | }, client)
14 | }
15 |
16 | async run ({ channel, author, t }, user) {
17 | const embed = new SwitchbladeEmbed(author)
18 | await this.client.controllers.developer.unblacklist(user.id)
19 | embed.setDescription(`**${t('commands:unblacklist.success', { user })}**`)
20 | channel.send(embed)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/http/api/statistics.js:
--------------------------------------------------------------------------------
1 | const { Route } = require('../../')
2 | const { Router } = require('express')
3 | const i18next = require('i18next')
4 |
5 | module.exports = class Statistics extends Route {
6 | constructor (client) {
7 | super({
8 | name: 'statistics'
9 | }, client)
10 | }
11 |
12 | register (app) {
13 | const router = Router()
14 |
15 | router.get('/', (req, res) => {
16 | res.status(200).json({
17 | serverCount: this.client.guilds.size,
18 | userCount: this.client.users.size,
19 | uptime: process.uptime() * 1000,
20 | commandCount: this.client.commands.length,
21 | languageCount: Object.keys(i18next.store.data).length
22 | })
23 | })
24 |
25 | app.use(this.path, router)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/locales/en-US/game.json:
--------------------------------------------------------------------------------
1 | {
2 | "challenge": {
3 | "title": "{{player}} has challenged you to a game of {{gameName}}",
4 | "clickToAccept": "Click the {{emoji}} reaction button below to accept.",
5 | "timeoutIn": "You have {{timeout}} seconds to accept it",
6 | "timeout": "This {{gameName}} invite has timed out."
7 | },
8 | "games": {
9 | "connect4": {
10 | "displayName": "Connect4",
11 | "loading": "Please wait, loading game...",
12 | "win": "{{player}} wins!",
13 | "tie": "It's a tie!",
14 | "yourTurn": "It's your turn, {{player}}",
15 | "currentPlayer": "Current player",
16 | "clickTimeoutsIn": "You have {{timeout}} seconds to play",
17 | "didNotPlay": "One player didn't make their move"
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/apis/Covid19.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | const API_URL = 'https://disease.sh/v3/covid-19'
5 |
6 | module.exports = class Covid19 extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'covid'
10 | })
11 | }
12 |
13 | async getCountry (country) {
14 | return this.request('countries', country)
15 | }
16 |
17 | async getWorldwide () {
18 | return this.request('all')
19 | }
20 |
21 | async getContinent (continents) {
22 | return this.request('continents', continents)
23 | }
24 |
25 | async getState (state) {
26 | return this.request('states', state)
27 | }
28 |
29 | request (endpoint, query = '') {
30 | return axios.get(encodeURI(`${API_URL}/${endpoint}/${query}`))
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/commands/misc/dog.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 | const fetch = require('node-fetch')
3 |
4 | module.exports = class Dog extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'dog',
8 | aliases: ['doggo', 'dogpics', 'randomdog'],
9 | category: 'general'
10 | }, client)
11 | }
12 |
13 | async run ({ t, author, channel }) {
14 | const embed = new SwitchbladeEmbed(author)
15 | channel.startTyping()
16 | const { message } = await fetch('https://dog.ceo/api/breeds/image/random').then(res => res.json())
17 | embed.setImage(message)
18 | .setDescription(`${t('commands:dog.hereIsYourDog')} <:DoggoF:445701839564963840>`)
19 | channel.send(embed).then(() => channel.stopTyping())
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/apis/RubyGems.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const fetch = require('node-fetch')
3 |
4 | const API_URL = 'https://rubygems.org/api/v1'
5 |
6 | module.exports = class RubyGemsAPI extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'rubygems',
10 | envVars: ['RUBYGEMS_API_KEY']
11 | })
12 | }
13 |
14 | search (query) {
15 | return this.request('/search.json', { query })
16 | }
17 |
18 | getGem (gem) {
19 | return this.request(`/gems/${gem}.json`)
20 | }
21 |
22 | request (endpoint, queryParams = {}) {
23 | const qParams = new URLSearchParams(queryParams)
24 | return fetch(API_URL + endpoint + `?${qParams.toString()}`, {
25 | headers: { Authorization: process.env.RUBYGEMS_API_KEY }
26 | }).then(res => res.json())
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/commands/misc/goat.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Goat extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'goat',
7 | aliases: ['placegoat'],
8 | category: 'general',
9 | parameters: [{
10 | type: 'number',
11 | required: false
12 | }, {
13 | type: 'number',
14 | required: false
15 | }]
16 | }, client)
17 | }
18 |
19 | async run ({ t, author, channel }, width = 500, height = 0) {
20 | channel.send(
21 | new SwitchbladeEmbed(author)
22 | .setImage(`http://placegoat.com/${Math.round(width)}/${Math.round(height)}`).setDescription(t(`commands:goat.hereIsYourGoat${height !== 0 ? '_resolution' : ''}`, { width, height }))
23 | )
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/http/api/locales.js:
--------------------------------------------------------------------------------
1 | const { Route } = require('../../')
2 | const { Router } = require('express')
3 |
4 | module.exports = class Locales extends Route {
5 | constructor (client) {
6 | super({
7 | name: 'locales'
8 | }, client)
9 | }
10 |
11 | register (app) {
12 | const router = Router()
13 |
14 | router.get('/', async (req, res) => {
15 | const language = req.query.language
16 |
17 | const languages = Object.entries(this.client.cldr.languages).map(([key, v]) => {
18 | const targetLang = v[language] || v['en-US']
19 | return { key, displayName: targetLang[0] || key }
20 | }).sort((a, b) => a.displayName.localeCompare(b.displayName))
21 |
22 | res.status(200).json({ languages })
23 | })
24 |
25 | app.use(this.path, router)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/structures/command/parameters/types/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | booleanFlag: require('./BooleanFlagParameter.js'),
3 | boolean: require('./BooleanParameter.js'),
4 | channel: require('./ChannelParameter.js'),
5 | color: require('./ColorParameter.js'),
6 | command: require('./CommandParameter.js'),
7 | emoji: require('./EmojiParameter.js'),
8 | guild: require('./GuildParameter.js'),
9 | image: require('./ImageParameter.js'),
10 | member: require('./MemberParameter.js'),
11 | number: require('./NumberParameter.js'),
12 | role: require('./RoleParameter.js'),
13 | string: require('./StringParameter.js'),
14 | url: require('./URLParameter.js'),
15 | user: require('./UserParameter.js'),
16 | time: require('./TimeParameter.js'),
17 | messageLink: require('./MessageLinkParameter.js')
18 | }
19 |
--------------------------------------------------------------------------------
/src/structures/game/player/PlayerManager.js:
--------------------------------------------------------------------------------
1 | module.exports = class PlayerManager extends Array {
2 | constructor () {
3 | super()
4 |
5 | this.currentIndex = 0
6 | }
7 |
8 | get current () {
9 | return this[this.currentIndex]
10 | }
11 |
12 | isCurrentPlayer (id) {
13 | return this.current.user.id === id
14 | }
15 |
16 | next () {
17 | this.currentIndex = (this.currentIndex + 1) % this.length
18 | }
19 |
20 | shuffle () {
21 | let currentIndex = this.length
22 |
23 | while (currentIndex !== 0) {
24 | const randomIndex = Math.floor(Math.random() * currentIndex)
25 |
26 | currentIndex -= 1
27 |
28 | const tempValue = this[currentIndex]
29 | this[currentIndex] = this[randomIndex]
30 | this[randomIndex] = tempValue
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/structures/command/parameters/types/Parameter.js:
--------------------------------------------------------------------------------
1 | const defVal = (o, k, d) => typeof o[k] === 'undefined' ? d : o[k]
2 |
3 | module.exports = class Parameter {
4 | static parseOptions (options = {}) {
5 | return {
6 | required: defVal(options, 'required', true),
7 | showUsage: defVal(options, 'showUsage', true),
8 | full: !!options.full,
9 | whitelist: options.whitelist,
10 | fullJoin: options.fullJoin,
11 | missingError: options.missingError || 'errors:generic',
12 |
13 | // Flags
14 | name: options.name,
15 | aliases: options.aliases
16 | }
17 | }
18 |
19 | static _parse (arg, options, context) {
20 | return this.parse.call(options, arg, context)
21 | }
22 |
23 | static parse (arg) {
24 | return arg
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/modules/PrefixModule.js:
--------------------------------------------------------------------------------
1 | const { Module } = require('../')
2 |
3 | const Joi = require('joi')
4 |
5 | const MIN_PREFIX_SIZE = 1
6 | const MAX_PREFIX_SIZE = 50
7 |
8 | module.exports = class PrefixModule extends Module {
9 | constructor (client) {
10 | super({
11 | name: 'prefix',
12 | displayName: 'Prefix',
13 | toggleable: false,
14 | defaultValues: {
15 | prefix: process.env.PREFIX,
16 | spacePrefix: true
17 | },
18 | specialInput: {
19 | prefix: { max: MAX_PREFIX_SIZE }
20 | }
21 | }, client)
22 | }
23 |
24 | validateValues (entity) {
25 | return Joi.object().keys({
26 | prefix: Joi.string().min(MIN_PREFIX_SIZE).max(MAX_PREFIX_SIZE).trim().truncate(),
27 | spacePrefix: Joi.boolean()
28 | }).validate(entity)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/music/sources/youtube/YoutubeSong.js:
--------------------------------------------------------------------------------
1 | const { Song } = require('../../structures')
2 | const Constants = require('../../../utils/Constants.js')
3 |
4 | module.exports = class YoutubeSong extends Song {
5 | constructor (data = {}, requestedBy, Youtube) {
6 | super(data, requestedBy)
7 | this._Youtube = Youtube
8 | this.color = Constants.YOUTUBE_COLOR
9 | }
10 |
11 | async loadInfo () {
12 | const yt = this._Youtube
13 | const video = await yt.getVideo(this.identifier)
14 | if (video) {
15 | const { viewCount, likeCount, dislikeCount, favoriteCount, commentCount } = video.statistics
16 |
17 | this.artwork = yt.getBestThumbnail(video.snippet.thumbnails).url
18 | this.richInfo = { viewCount, likeCount, dislikeCount, favoriteCount, commentCount }
19 | }
20 | return this
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/structures/Controller.js:
--------------------------------------------------------------------------------
1 | const Utils = require('../utils')
2 |
3 | module.exports = class Controller {
4 | /**
5 | * @param {Object} opts
6 | * @param {string} opts.name
7 | * @param {Controller} [opts.parent]
8 | * @param {Client} client
9 | */
10 | constructor (opts, client) {
11 | const options = Utils.createOptionHandler('Controller', opts)
12 |
13 | this.name = options.required('name')
14 | this.parentController = options.optional('parent')
15 |
16 | this.client = client
17 |
18 | this.subcontrollers = []
19 | }
20 |
21 | canLoad () {
22 | return true
23 | }
24 |
25 | load () {
26 | this.subcontrollers.forEach(subcontroller => {
27 | Object.defineProperty(this, subcontroller.name, { get: () => subcontroller })
28 | })
29 |
30 | return this
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/apis/DBL.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const fetch = require('node-fetch')
3 |
4 | const API_URL = 'https://discordbots.org/api'
5 |
6 | module.exports = class DBL extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'dbl',
10 | envVars: ['DBL_TOKEN']
11 | })
12 | }
13 |
14 | searchBots (query, maxValues) {
15 | return this.request('/bots', { search: query, limit: maxValues }).then(u => u.results)
16 | }
17 |
18 | getBot (id) {
19 | return this.request(`/bots/${id}`).then(u => u)
20 | }
21 |
22 | request (endpoint, queryParams = {}) {
23 | const qParams = new URLSearchParams(queryParams)
24 | return fetch(API_URL + endpoint + `?${qParams.toString()}`, {
25 | headers: { Authorization: process.env.DBL_TOKEN }
26 | }).then(res => res.json())
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/commands/image-manipulation/kannapaper.js:
--------------------------------------------------------------------------------
1 | const { CanvasTemplates, Command } = require('../../')
2 |
3 | const { MessageAttachment } = require('discord.js')
4 |
5 | module.exports = class KannaPaper extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'kannapaper',
9 | aliases: ['kp'],
10 | category: 'images',
11 | requirements: { canvasOnly: true },
12 | parameters: [{
13 | type: 'string', full: true, required: true, clean: true, missingError: 'commands:kannapaper.missingText'
14 | }]
15 | }, client)
16 | }
17 |
18 | async run ({ channel }, text = 'teste') {
19 | channel.startTyping()
20 | const kannaPaper = await CanvasTemplates.kannaPaper(text)
21 | channel.send(new MessageAttachment(kannaPaper, 'kanna.jpg'))
22 | channel.stopTyping()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/commands/music/skip.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Skip extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'skip',
7 | aliases: ['next'],
8 | category: 'music',
9 | requirements: { guildOnly: true, sameVoiceChannelOnly: true, guildPlaying: true }
10 | }, client)
11 | }
12 |
13 | async run ({ t, author, channel, guild }) {
14 | const embed = new SwitchbladeEmbed(author)
15 | const guildPlayer = this.client.playerManager.players.get(guild.id)
16 | const song = guildPlayer.playingSong
17 | const songName = `[${song.title}](${song.uri})`
18 | channel.send(embed.setDescription(`${this.getEmoji('stopButton')} ${t('music:wasSkipped', { songName })}`)).then(() => guildPlayer.next())
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/apis/PositionStack.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | const API_URL = 'http://api.positionstack.com/v1'
5 |
6 | module.exports = class PositionStack extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'positionstack',
10 | envVars: ['PS_ACCESS_KEY']
11 | })
12 | }
13 |
14 | async getAddress (city, options) {
15 | options = Object.assign({ query: city, limit: 1, output: 'json', timezone_module: 1 }, options)
16 | return this.request('forward', city, options)
17 | }
18 |
19 | request (endpoint, query, queryParams = '') {
20 | const qParams = new URLSearchParams(queryParams)
21 | return axios.get(encodeURI(`${API_URL}/${endpoint}?access_key=${process.env.PS_ACCESS_KEY}&${qParams.toString()}`))
22 | .then(res => res.data)
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/commands/utility/avatar.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Avatar extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'avatar',
7 | aliases: ['profilepicture', 'pfp'],
8 | category: 'utility',
9 | parameters: [{
10 | type: 'user',
11 | full: true,
12 | required: false,
13 | acceptBot: true,
14 | acceptSelf: true
15 | }]
16 | }, client)
17 | }
18 |
19 | run ({ t, author, channel }, user) {
20 | const embed = new SwitchbladeEmbed(author)
21 | user = user || author
22 | embed
23 | .setImage(user.displayAvatarURL({ dynamic: true, size: 2048 }))
24 | .setDescription(t('commands:avatar.someonesAvatar', { user: user.toString() }))
25 | channel.send(embed)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/commands/developers/welcometranslator.js:
--------------------------------------------------------------------------------
1 | const { Command } = require('../../')
2 |
3 | module.exports = class WelcomeTranslator extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'welcometranslator',
7 | category: 'developers',
8 | hidden: true,
9 | requirements: { managersOnly: true },
10 | parameters: [{
11 | type: 'user',
12 | acceptSelf: false,
13 | missingError: 'commands:welcometranslator.noMember',
14 | errors: { acceptSelf: 'commands:welcometranslator.cantWelcomeYourself' }
15 | }]
16 | }, client)
17 | }
18 |
19 | run ({ t, guild, member: author, channel }, member) {
20 | channel.startTyping()
21 | channel.send(t('commands:welcometranslator.welcomeMessage', { member: `<@${member.id}>` })).then(() => channel.stopTyping())
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/commands/memes/ashwga.js:
--------------------------------------------------------------------------------
1 | const { CanvasTemplates, Command } = require('../../')
2 |
3 | const { MessageAttachment } = require('discord.js')
4 |
5 | module.exports = class HereWeGoAgain extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'herewegoagain',
9 | aliases: ['ahshit', 'ahshitherewegoagain', 'ashwga', 'hwga'],
10 | category: 'images',
11 | requirements: { canvasOnly: true },
12 | parameters: [{
13 | type: 'image',
14 | missingError: 'commands:herewegoagain.missingImage'
15 | }]
16 | }, client)
17 | }
18 |
19 | async run ({ t, author, channel }, image) {
20 | channel.startTyping()
21 | const hwga = await CanvasTemplates.herewegoagain(image)
22 | channel.send(new MessageAttachment(hwga, 'ashwga.png')).then(() => channel.stopTyping())
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/commands/image-manipulation/petpet.js:
--------------------------------------------------------------------------------
1 | const { CanvasTemplates, Command } = require('../../')
2 |
3 | const { MessageAttachment } = require('discord.js')
4 |
5 | module.exports = class Petpet extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'petpet',
9 | aliases: ['patpat'],
10 | category: 'images',
11 | requirements: { canvasOnly: true },
12 | parameters: [{
13 | type: 'image',
14 | missingError: 'commands:petpet.missingImage',
15 | userOptions: {
16 | acceptSelf: true
17 | }
18 | }]
19 | }, client)
20 | }
21 |
22 | async run ({ channel }, image) {
23 | channel.startTyping()
24 | const petpet = await CanvasTemplates.petpet(image)
25 | channel.send(new MessageAttachment(petpet, 'petpet.gif'))
26 | channel.stopTyping()
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/commands/memes/quieres.js:
--------------------------------------------------------------------------------
1 | const { CanvasTemplates, Command } = require('../../')
2 |
3 | const { MessageAttachment } = require('discord.js')
4 |
5 | module.exports = class Quieres extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'quieres',
9 | aliases: ['bufa'],
10 | category: 'images',
11 | requirements: { canvasOnly: true },
12 | parameters: [{
13 | type: 'image',
14 | missingError: 'commands:quieres.missingImage',
15 | userOptions: {
16 | acceptSelf: true
17 | }
18 | }]
19 | }, client)
20 | }
21 |
22 | async run ({ t, author, channel }, image) {
23 | channel.startTyping()
24 | const quieres = await CanvasTemplates.quieres(image)
25 | channel.send(new MessageAttachment(quieres, 'quieres.jpg')).then(() => channel.stopTyping())
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/controllers/BlacklistController.js:
--------------------------------------------------------------------------------
1 | const { Controller } = require('../')
2 |
3 | // Developer
4 | module.exports = class DeveloperController extends Controller {
5 | constructor (client) {
6 | super({
7 | name: 'developer'
8 | }, client)
9 | }
10 |
11 | canLoad () {
12 | return !!this.client.database
13 | }
14 |
15 | get _users () {
16 | return this.client.database.users
17 | }
18 |
19 | async blacklist (_user, reason, blacklister) {
20 | await this._users.update(_user, { blacklisted: { reason, blacklister } })
21 | }
22 |
23 | async unblacklist (_user) {
24 | await this._users.update(_user, { blacklisted: null })
25 | }
26 |
27 | async blacklisted (_user, reason, blacklister) {
28 | const user = await this._users.findOne(_user, 'blacklisted')
29 | return user ? user.blacklisted : null
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/commands/anime/waifu.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 | const fetch = require('node-fetch')
3 | const waifuAPI = 'https://waifu.pics/api'
4 |
5 | module.exports = class Waifu extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'waifu',
9 | category: 'anime'
10 | }, client)
11 | }
12 |
13 | async run ({ t, author, channel }) {
14 | const embed = new SwitchbladeEmbed(author)
15 | channel.startTyping()
16 |
17 | // Send a lewd waifu if the channel is NSFW
18 | const type = channel.nsfw ? 'nsfw' : 'sfw'
19 |
20 | const { url } = await fetch(`${waifuAPI}/${type}/waifu`).then(res => res.json())
21 |
22 | embed.setImage(url)
23 | .setDescription(t('commands:waifu.hereIsYour', { context: type }))
24 |
25 | channel.send(embed).then(() => channel.stopTyping())
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/commands/image-manipulation/presidentialalert.js:
--------------------------------------------------------------------------------
1 | const { CanvasTemplates, Command } = require('../../')
2 |
3 | const { MessageAttachment } = require('discord.js')
4 |
5 | module.exports = class PresidentialAlert extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'presidentialalert',
9 | aliases: ['pa'],
10 | category: 'images',
11 | requirements: { canvasOnly: true },
12 | parameters: [{
13 | type: 'string', full: true, required: true, missingError: 'commands:presidentialalert.missingText'
14 | }]
15 | }, client)
16 | }
17 |
18 | async run ({ t, author, channel }, text) {
19 | channel.startTyping()
20 | const presidential = await CanvasTemplates.presidentialAlert(text)
21 | channel.send(new MessageAttachment(presidential, 'president.jpg'))
22 | channel.stopTyping()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/commands/misc/fliptext.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class FlipText extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'fliptext',
7 | parameters: [{
8 | type: 'string', full: true, missingError: 'commands:fliptext.noSentence'
9 | }]
10 | }, client)
11 | }
12 |
13 | run ({ author, channel }, text) {
14 | const embed = new SwitchbladeEmbed(author)
15 | const mapping = '¡"#$%⅋,)(*+\'-˙/0ƖᄅƐㄣϛ9ㄥ86:;<=>¿@∀qƆpƎℲפHIſʞ˥WNOԀQɹS┴∩ΛMX⅄Z[/]^_`ɐqɔpǝɟƃɥᴉɾʞlɯuodbɹsʇnʌʍxʎz{|}~'
16 | const offset = '!'.charCodeAt(0)
17 | embed.setTitle(
18 | text.split('')
19 | .map(c => c.charCodeAt(0) - offset)
20 | .map(c => mapping[c] || ' ')
21 | .reverse().join('')
22 | )
23 | channel.send(embed)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/commands/music/pause.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Pause extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'pause',
7 | aliases: ['resume'],
8 | category: 'music',
9 | requirements: { guildOnly: true, sameVoiceChannelOnly: true, guildPlaying: true }
10 | }, client)
11 | }
12 |
13 | async run ({ t, author, channel, guild }) {
14 | const embed = new SwitchbladeEmbed(author)
15 | const guildPlayer = this.client.playerManager.players.get(guild.id)
16 | const pause = !guildPlayer.paused
17 | embed.setTitle(`${pause ? this.getEmoji('pauseButton') : this.getEmoji('playButton')} ${t('music:stateChanged', { context: pause ? 'pause' : 'resume' })}`)
18 | channel.send(embed).then(() => guildPlayer.pause(pause))
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/music/structures/Playlist.js:
--------------------------------------------------------------------------------
1 | const { EventEmitter } = require('events')
2 | const moment = require('moment')
3 |
4 | const Song = require('./Song.js')
5 |
6 | module.exports = class Playlist extends EventEmitter {
7 | constructor (data = {}, songs = [], requestedBy) {
8 | super()
9 |
10 | this.identifier = data.identifier
11 | this.source = data.source
12 | this.requestedBy = requestedBy
13 | this.songs = songs
14 | }
15 |
16 | loadInfo () {
17 | this.songs = this.songs.map(s => new Song(s, this.requestedBy))
18 | return this
19 | }
20 |
21 | get length () {
22 | return this.songs.reduce((l, s) => l + s.length, 0)
23 | }
24 |
25 | get formattedDuration () {
26 | if (this.isStream) return ''
27 | return moment.duration(this.length).format(this.length >= 3600000 ? 'hh:mm:ss' : 'mm:ss', { trim: false })
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/apis/Crowdin.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 | const Zip = require('adm-zip')
4 |
5 | const BASE_URL = 'https://api.crowdin.net/api/project/'
6 |
7 | module.exports = class CrowdinAPI extends APIWrapper {
8 | constructor () {
9 | super({
10 | name: 'crowdin',
11 | envVars: ['CROWDIN_API_KEY', 'CROWDIN_PROJECT_ID']
12 | })
13 | }
14 |
15 | async downloadToPath (path) {
16 | const { data } = await this.request('/download/all.zip')
17 |
18 | const zip = new Zip(data)
19 | zip.extractAllTo(path, true)
20 | }
21 |
22 | async request (endpoint) {
23 | return axios({
24 | method: 'GET',
25 | url: BASE_URL + process.env.CROWDIN_PROJECT_ID + endpoint + `?key=${process.env.CROWDIN_API_KEY}`,
26 | responseType: 'arraybuffer'
27 | })
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/commands/games/coinflip.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 | const coins = {
3 | heads: 'https://i.imgur.com/yStXPCV.png',
4 | tails: 'https://i.imgur.com/kSteyPc.png'
5 | }
6 |
7 | module.exports = class Coinflip extends Command {
8 | constructor (client) {
9 | super({
10 | name: 'coinflip',
11 | aliases: ['cf'],
12 | category: 'games'
13 | }, client)
14 | }
15 |
16 | run ({ channel, author, t }) {
17 | const sides = ['heads', 'tails']
18 | const chosenSide = sides[Math.floor(Math.random() * sides.length)]
19 | const embed = new SwitchbladeEmbed(author)
20 | channel.startTyping()
21 | embed.setDescription(t('commands:coinflip.landed', { chosenSide }))
22 | .setThumbnail(coins[chosenSide])
23 | channel.send(embed).then(() => channel.stopTyping())
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/apis/GoogleSearch.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const fetch = require('node-fetch')
3 |
4 | module.exports = class CrowdinAPI extends APIWrapper {
5 | constructor () {
6 | super({
7 | name: 'gsearch',
8 | envVars: ['G_CSE_CREDENTIALS', 'G_CSE_ID']
9 | })
10 | }
11 |
12 | async search (query, useSafeSearch = true) {
13 | return fetch(`https://www.googleapis.com/customsearch/v1?q=${query}&key=${process.env.G_CSE_CREDENTIALS}&cx=${process.env.G_CSE_ID}&safe=${useSafeSearch ? 'active' : 'off'}`)
14 | .then(r => r.json())
15 | }
16 |
17 | async searchImage (query, useSafeSearch = true) {
18 | return fetch(`https://www.googleapis.com/customsearch/v1?q=${query}&key=${process.env.G_CSE_CREDENTIALS}&cx=${process.env.G_CSE_ID}&safe=${useSafeSearch ? 'active' : 'off'}&searchType=image`)
19 | .then(r => r.json())
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/utils/placeholders/PlaceholderRules.js:
--------------------------------------------------------------------------------
1 | module.exports = [
2 | // Server
3 | {
4 | name: 'server',
5 | description: 'Server\'s name',
6 | replace: ({ guild }) => guild.name
7 | },
8 | // User
9 | {
10 | name: 'username',
11 | description: 'User\'s username',
12 | replace: ({ user }) => user.username
13 | },
14 | {
15 | name: 'user',
16 | description: 'User\'s mention',
17 | replace: ({ user }) => user.toString()
18 | },
19 | {
20 | name: 'userId',
21 | description: 'User\'s ID',
22 | replace: ({ user }) => user.id
23 | },
24 | // Channel
25 | {
26 | name: 'channel',
27 | description: 'Channel\'s mention',
28 | replace: ({ channel }) => channel.toString()
29 | },
30 | {
31 | name: 'channelName',
32 | description: 'Channels\'s name',
33 | replace: ({ channel }) => channel.name
34 | }
35 | ]
36 |
--------------------------------------------------------------------------------
/src/commands/utility/restrictemoji/reset.js:
--------------------------------------------------------------------------------
1 | const { Command, CommandError, SwitchbladeEmbed } = require('../../../')
2 | module.exports = class RestrictEmojiReset extends Command {
3 | constructor (client) {
4 | super({
5 | name: 'reset',
6 | parent: 'restrictemoji',
7 | parameters: [{
8 | type: 'emoji',
9 | sameGuildOnly: true
10 | }]
11 | }, client)
12 | }
13 |
14 | async run ({ t, author, channel, guild }, emoji) {
15 | channel.startTyping()
16 | try {
17 | await emoji.roles.set([])
18 | channel.send(
19 | new SwitchbladeEmbed(author)
20 | .setTitle(t('commands:restrictemoji.subcommands.reset.reset', { emoji: emoji.name }))
21 | ).then(() => channel.stopTyping())
22 | } catch (e) {
23 | channel.stopTyping()
24 | throw new CommandError(t('errors:generic'))
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/apis/JetBrainsPlugins.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('..')
2 | const axios = require('axios')
3 |
4 | const API_URL = 'https://plugins.jetbrains.com/api'
5 |
6 | module.exports = class JetBrainsPlugins extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'jetbrainsplugins'
10 | })
11 | }
12 |
13 | async search (name) {
14 | return axios({
15 | params: {
16 | excludeTags: 'theme',
17 | max: 10,
18 | offset: 0,
19 | search: encodeURIComponent(name)
20 | },
21 | url: `${API_URL}/searchPlugins`
22 | })
23 | }
24 |
25 | async getPluginInfo (id) {
26 | const res = await axios(`${API_URL}/plugins/${id}`)
27 | return res.data
28 | }
29 |
30 | async getPluginVersion (id) {
31 | const res = await axios(`${API_URL}/plugins/${id}/updates?channel=&size=1`)
32 | return res.data
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/utils/DiscordUtils.js:
--------------------------------------------------------------------------------
1 | module.exports = class DiscordUtils {
2 | /**
3 | * @param {String} userID - The id of the user who should have permission checked.
4 | * @param {Object} channel - The channel from the message.
5 | * @param {Array} permissions - An Array with discord permissions.
6 | * @param {Function} t - The translate function.
7 | * @param {String} blameWho - Who to blame if the permission is missing.
8 | * @returns {String}
9 | */
10 | static ensurePermissions (userID, channel, permissions, t, blameWho) {
11 | for (let i = 0; i < permissions.length; i++) {
12 | const permission = permissions[i]
13 | if (!channel.permissionsFor(userID).has(permission)) {
14 | return t(blameWho === 'bot' ? 'errors:iDontHavePermission' : 'errors:youDontHavePermissionToRead', { permission })
15 | }
16 | }
17 |
18 | return null
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/commands/misc/lorem.js:
--------------------------------------------------------------------------------
1 | const { SwitchbladeEmbed, Command } = require('../../')
2 | const { LoremIpsum } = require('lorem-ipsum')
3 |
4 | module.exports = class Lorem extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'lorem',
8 | parameters: [{
9 | type: 'number',
10 | missingError: 'commands:lorem.noNumber',
11 | required: false,
12 | min: 1,
13 | max: 10
14 | }]
15 | },
16 | client)
17 |
18 | this.lorem = new LoremIpsum()
19 | }
20 |
21 | async run ({ channel, t }, paragraphs = 2) {
22 | let loremString = this.lorem.generateParagraphs(paragraphs)
23 |
24 | if (loremString.length >= 4096) {
25 | loremString = loremString.slice(0, 4095) + '…'
26 | }
27 |
28 | const embed = new SwitchbladeEmbed()
29 | .setDescription(loremString)
30 |
31 | channel.send(embed)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/apis/Tumblr.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const fetch = require('node-fetch')
3 |
4 | const API_URL = 'https://api.tumblr.com/v2'
5 |
6 | module.exports = class TumblrAPI extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'tumblr',
10 | envVars: ['TUMBLR_API_KEY']
11 | })
12 | }
13 |
14 | /**
15 | * Get an array of 20 first posts with images
16 | * @param blog {String} The tumblr blog url
17 | * @param params {Object}
18 | */
19 | getPhotoPosts (blog, params = {}) {
20 | return this.request(`/blog/${blog}/posts/photo`, params)
21 | }
22 |
23 | // Default
24 | request (endpoint, queryParams = {}) {
25 | queryParams.api_key = process.env.TUMBLR_API_KEY
26 | const qParams = new URLSearchParams(queryParams)
27 | return fetch(API_URL + endpoint + `?${qParams.toString()}`)
28 | .then(res => res.json())
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/commands/gifs/hug.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | const fetch = require('node-fetch')
4 |
5 | module.exports = class Hug extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'hug',
9 | category: 'images',
10 | parameters: [{
11 | type: 'user', acceptBot: true, acceptSelf: false, missingError: 'commands:hug.noMention'
12 | }]
13 | }, client)
14 | }
15 |
16 | async run ({ t, channel, author }, user) {
17 | const body = await fetch('https://nekos.life/api/v2/img/hug').then(res => res.json())
18 | const embed = new SwitchbladeEmbed(author)
19 | channel.startTyping()
20 | embed.setImage(body.url)
21 | .setDescription(t('commands:hug.success', { hugger: author.toString(), hugged: user.toString() }))
22 | channel.send(embed).then(() => channel.stopTyping())
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/commands/gifs/pat.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | const fetch = require('node-fetch')
4 |
5 | module.exports = class Pat extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'pat',
9 | category: 'images',
10 | parameters: [{
11 | type: 'user', acceptBot: true, acceptSelf: false, missingError: 'commands:pat.noMention'
12 | }]
13 | }, client)
14 | }
15 |
16 | async run ({ t, channel, author }, user) {
17 | const body = await fetch('https://nekos.life/api/v2/img/pat').then(res => res.json())
18 | const embed = new SwitchbladeEmbed(author)
19 | channel.startTyping()
20 | embed.setImage(body.url)
21 | .setDescription(t('commands:pat.success', { _author: author.toString(), pat: user.toString() }))
22 | channel.send(embed).then(() => channel.stopTyping())
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/commands/misc/8ball.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed, Constants } = require('../../')
2 |
3 | module.exports = class EightBall extends Command {
4 | constructor (client) {
5 | super({
6 | name: '8ball',
7 | aliases: ['eightball', '8b', 'magicball', '8-ball'],
8 | parameters: [{
9 | type: 'string', full: true, missingError: 'commands:8ball.noQuestion'
10 | }]
11 | }, client)
12 | }
13 |
14 | run ({ t, author, channel }, question) {
15 | const embed = new SwitchbladeEmbed(author)
16 | channel.startTyping()
17 | const answerCount = 19
18 | const result = Math.floor((Math.random() * answerCount))
19 | embed.setColor(Constants.EIGHTBALL_COLOR)
20 | .setDescription(`:grey_question: ${question}\n:8ball: ${t(`commands:8ball.answers.${result}`)}`)
21 | channel.send(embed).then(() => channel.stopTyping())
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/commands/anime/nekogif.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 | const fetch = require('node-fetch')
3 | const nekoAPI = 'https://nekos.life/api/v2/img/'
4 |
5 | module.exports = class NekoGif extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'nekogif',
9 | category: 'anime'
10 | }, client)
11 | }
12 |
13 | async run ({ t, author, channel }) {
14 | const embed = new SwitchbladeEmbed(author)
15 | channel.startTyping()
16 |
17 | // Send a lewd neko if the channel is NSFW
18 | const endpoint = channel.nsfw ? 'nsfw_neko_gif' : 'ngif'
19 |
20 | const { url } = await fetch(nekoAPI + endpoint).then(res => res.json())
21 |
22 | embed.setImage(url)
23 | .setDescription(t('commands:nekogif.hereIsYour', { context: endpoint }))
24 |
25 | channel.send(embed).then(() => channel.stopTyping())
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/commands/gifs/slap.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | const fetch = require('node-fetch')
4 |
5 | module.exports = class Slap extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'slap',
9 | category: 'images',
10 | parameters: [{
11 | type: 'user', acceptBot: true, acceptSelf: false, missingError: 'commands:slap.noMention'
12 | }]
13 | }, client)
14 | }
15 |
16 | async run ({ t, channel, author }, user) {
17 | const body = await fetch('https://nekos.life/api/v2/img/slap').then(res => res.json())
18 | const embed = new SwitchbladeEmbed(author)
19 | channel.startTyping()
20 | embed.setImage(body.url)
21 | .setDescription(t('commands:slap.success', { _author: author.toString(), slapped: user.toString() }))
22 | channel.send(embed).then(() => channel.stopTyping())
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/apis/VSCodeExtensions.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | const API_URL = 'https://marketplace.visualstudio.com/_apis/public/gallery/extensionquery'
5 |
6 | module.exports = class VSCodeExtensions extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'vscodeextensions'
10 | })
11 | }
12 |
13 | async search (name) {
14 | return axios({
15 | data: {
16 | filters: [{
17 | criteria: [{
18 | filterType: 10,
19 | value: name
20 | }]
21 | }],
22 | flags: 0x2 | 0x4 | 0x100
23 | },
24 | headers: {
25 | accept: 'application/json; api-version=3.0-preview',
26 | 'accept-encoding': 'gzip',
27 | 'content-type': 'application/json; api-version=3.0-preview.1'
28 | },
29 | url: API_URL,
30 | method: 'POST'
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/commands/anime/neko.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 | const fetch = require('node-fetch')
3 | const nekoAPI = 'https://waifu.pics/api'
4 |
5 | module.exports = class Neko extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'neko',
9 | aliases: ['nekogirl'],
10 | category: 'anime'
11 | }, client)
12 | }
13 |
14 | async run ({ t, author, channel }) {
15 | const embed = new SwitchbladeEmbed(author)
16 | channel.startTyping()
17 |
18 | // Send a lewd neko if the channel is NSFW
19 | const type = channel.nsfw ? 'nsfw' : 'sfw'
20 |
21 | const { url } = await fetch(`${nekoAPI}/${type}/neko`).then(res => res.json())
22 |
23 | embed.setImage(url)
24 | .setDescription(t('commands:neko.hereIsYour', { context: type }))
25 |
26 | channel.send(embed).then(() => channel.stopTyping())
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/commands/image-manipulation/triggered.js:
--------------------------------------------------------------------------------
1 | const { CanvasTemplates, Command } = require('../../')
2 |
3 | const { MessageAttachment } = require('discord.js')
4 |
5 | module.exports = class Triggered extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'triggered',
9 | aliases: ['trigger', 'puto'],
10 | category: 'images',
11 | requirements: { canvasOnly: true },
12 | parameters: [{
13 | type: 'image',
14 | missingError: 'commands:morejpeg.missingImage',
15 | userOptions: {
16 | acceptSelf: true
17 | }
18 | }]
19 | }, client)
20 | }
21 |
22 | async run ({ t, author, channel }, image) {
23 | channel.startTyping()
24 | const triggered = await CanvasTemplates.triggered(image)
25 | channel.send(new MessageAttachment(triggered, 'triggered.gif')).then(() => channel.stopTyping())
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const Sentry = require('@sentry/node')
2 | Sentry.init({ dsn: process.env.SENTRY_DSN })
3 |
4 | const { readFileSync } = require('fs')
5 |
6 | require('moment')
7 | require('moment-duration-format')
8 |
9 | // Initialize Canvas
10 | let canvasLoaded = false
11 | try {
12 | require('canvas')
13 | require('./src/utils/CanvasUtils.js').initializeHelpers()
14 | canvasLoaded = true
15 | } catch (e) {}
16 |
17 | // Initialize client
18 | const CLIENT_OPTIONS = {
19 | fetchAllMembers: false,
20 | enableEveryone: false,
21 | canvasLoaded
22 | }
23 |
24 | console.log(readFileSync('bigtitle.txt', 'utf8').toString())
25 |
26 | const Switchblade = require('./src/Switchblade.js')
27 | const client = new Switchblade(CLIENT_OPTIONS)
28 |
29 | client.on('debug', (...args) => client.logger.debug(...args))
30 | client.on('rateLimit', (...args) => client.logger.info({ tag: 'rateLimit' }, ...args))
31 |
32 | client.initialize()
33 |
--------------------------------------------------------------------------------
/src/apis/FlightRadar.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | const ID_API_URL = 'https://www.flightradar24.com/v1/search/web/find'
5 | const AIRCRAFT_API_URL = 'https://data-live.flightradar24.com/clickhandler/?version=1.5'
6 |
7 | module.exports = class FlightRadar extends APIWrapper {
8 | constructor () {
9 | super({
10 | name: 'flightradar'
11 | })
12 | }
13 |
14 | async findId (flightNum) {
15 | return axios.get(ID_API_URL, {
16 | params: {
17 | query: flightNum,
18 | limit: 1
19 | }
20 | }).then(res => res.data)
21 | }
22 |
23 | async searchAircraft (flightNum) {
24 | const { results } = await this.findId(flightNum)
25 | if (!results?.[0].id) return undefined
26 | return axios.get(AIRCRAFT_API_URL, {
27 | params: {
28 | flight: results[0].id
29 | }
30 | }).then(res => res.data)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/commands/utility/math.js:
--------------------------------------------------------------------------------
1 | const { Command, CommandError, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Math extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'math',
7 | category: 'utility',
8 | parameters: [{
9 | type: 'string',
10 | full: true,
11 | missingError: 'commands:math.needMathExpression'
12 | }]
13 | }, client)
14 | }
15 |
16 | async run ({ t, author, channel }, expression) {
17 | const embed = new SwitchbladeEmbed(author)
18 | try {
19 | const data = await this.client.apis.mathematics.calculate(expression).then(json => JSON.parse(json))
20 | embed.setTitle(t('commands:math.result', { result: data._data || data }))
21 | } catch (error) {
22 | throw new CommandError(t('commands:math.invalidMathExpression'), true)
23 | }
24 | channel.send(embed)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/commands/anime/kemonomimi.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 | const fetch = require('node-fetch')
3 | const nekoAPI = 'https://nekos.life/api/v2/img/'
4 |
5 | module.exports = class Kemonomimi extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'kemonomimi',
9 | category: 'anime'
10 | }, client)
11 | }
12 |
13 | async run ({ t, author, channel }) {
14 | const embed = new SwitchbladeEmbed(author)
15 | channel.startTyping()
16 |
17 | // Send a lewd kemonomimi if the channel is NSFW
18 | const endpoint = channel.nsfw ? 'lewdkemo' : 'kemonomimi'
19 |
20 | const { url } = await fetch(nekoAPI + endpoint).then(res => res.json())
21 |
22 | embed.setImage(url)
23 | .setDescription(t('commands:kemonomimi.hereIsYour', { context: endpoint }))
24 |
25 | channel.send(embed).then(() => channel.stopTyping())
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/connections/osu.js:
--------------------------------------------------------------------------------
1 | const { Connection } = require('../')
2 |
3 | module.exports = class Osu extends Connection {
4 | constructor (client) {
5 | super({
6 | name: 'osu'
7 | }, client)
8 | }
9 |
10 | async getAuthLink () {
11 | return `https://osu.ppy.sh/oauth/authorize?response_type=code&client_id=${process.env.OSU_CLIENT_ID}&redirect_uri=${this.authCallbackURL}&state=ok&scope=identify`
12 | }
13 |
14 | async callback (req) {
15 | const accessToken = await this.client.apis.osu.getAccessToken(req.query.code)
16 | return {
17 | token: accessToken.access_token
18 | }
19 | }
20 |
21 | async getAccountInfo ({ token }) {
22 | const account = await this.client.apis.osu.getAuthenticatedUserInfo(token)
23 | return {
24 | user: account.username,
25 | url: `https://osu.ppy.sh/u/${account.id}`,
26 | avatar: account.avatar_url,
27 | id: account.id
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/commands/games/connect4.js:
--------------------------------------------------------------------------------
1 | const { Command } = require('../..')
2 | const Connect4 = require('../../games/Connect4')
3 |
4 | module.exports = class Connect4Command extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'connect4',
8 | aliases: [
9 | 'connect-four',
10 | 'four-up',
11 | 'plot-four',
12 | 'four-in-a-row',
13 | 'four-in-a-line',
14 | 'drop-four',
15 | 'gravitrips'
16 | ],
17 | category: 'games',
18 | requirements: {
19 | guildOnly: true,
20 | botPermissions: ['ADD_REACTIONS']
21 | },
22 | parameters: [{
23 | type: 'user',
24 | acceptSelf: false,
25 | missingError: 'commands:connect4.missingUser'
26 | }]
27 | }, client)
28 | }
29 |
30 | async run (context, opponent) {
31 | const game = new Connect4(context, opponent)
32 |
33 | await game.start()
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/commands/anime/kitsune.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 | const fetch = require('node-fetch')
3 | const nekoAPI = 'https://nekos.life/api/v2/img/'
4 |
5 | module.exports = class Kitsune extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'kitsune',
9 | aliases: ['foxgirl'],
10 | category: 'anime'
11 | }, client)
12 | }
13 |
14 | async run ({ t, author, channel }) {
15 | const embed = new SwitchbladeEmbed(author)
16 | channel.startTyping()
17 |
18 | // Send a lewd kitsune if the channel is NSFW
19 | const endpoint = channel.nsfw ? 'lewdk' : 'fox_girl'
20 |
21 | const { url } = await fetch(nekoAPI + endpoint).then(res => res.json())
22 |
23 | embed.setImage(url)
24 | .setDescription(t('commands:kitsune.hereIsYour', { context: endpoint }))
25 |
26 | channel.send(embed).then(() => channel.stopTyping())
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/commands/gifs/kiss.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | const fetch = require('node-fetch')
4 |
5 | module.exports = class Kiss extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'kiss',
9 | aliases: ['beijo', 'beijar'],
10 | category: 'images',
11 | parameters: [{
12 | type: 'user', acceptBot: true, acceptSelf: false, missingError: 'commands:kiss.noMention'
13 | }]
14 | }, client)
15 | }
16 |
17 | async run ({ t, channel, author }, user) {
18 | const body = await fetch('https://nekos.life/api/v2/img/kiss').then(res => res.json())
19 | const embed = new SwitchbladeEmbed(author)
20 | channel.startTyping()
21 | embed.setImage(body.url)
22 | .setDescription(t('commands:kiss.success', { kisser: author.toString(), kissed: user.toString() }))
23 | channel.send(embed).then(() => channel.stopTyping())
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/apis/BeatSaver.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const fetch = require('node-fetch')
3 |
4 | const API_URL = 'https://beatsaver.com/api'
5 |
6 | module.exports = class BeatSaverAPI extends APIWrapper {
7 | constructor () {
8 | super({
9 | name: 'beatsaver'
10 | })
11 | }
12 |
13 | getMapDetails (key) {
14 | return this.request(`/maps/detail/${key}`)
15 | }
16 |
17 | searchMaps (q) {
18 | return this.request('/search/text/0', { q, automapper: 1 }).then(r => r.docs)
19 | }
20 |
21 | request (endpoint, queryParams = {}) {
22 | const qParams = new URLSearchParams(queryParams)
23 | return fetch(API_URL + endpoint + `?${qParams.toString()}`, {
24 | headers: {
25 | 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36',
26 | accept: 'application/json'
27 | }
28 | }).then(res => res.json())
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/commands/developers/blacklist.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class BlacklistCommand extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'blacklist',
7 | category: 'developers',
8 | hidden: true,
9 | requirements: { devOnly: true },
10 | parameters: [{
11 | type: 'user', acceptDeveloper: false, missingError: 'commands:blacklist.missingUser'
12 | }, {
13 | type: 'string', full: true, missingError: 'commands:blacklist.missingReason'
14 | }]
15 | }, client)
16 | }
17 |
18 | async run ({ channel, author, t }, user, reason) {
19 | const embed = new SwitchbladeEmbed(author)
20 | await this.client.controllers.developer.blacklist(user.id, reason, author.id)
21 | embed
22 | .setTitle(t('commands:blacklist.successTitle'))
23 | .setDescription(`${user} - \`${reason}\``)
24 | channel.send(embed)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/commands/memes/clapify.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed, Constants } = require('../../index')
2 |
3 | const emoji = '\uD83D\uDC4F'
4 | const CLAPIFY_LIMIT = 128
5 |
6 | module.exports = class Clapify extends Command {
7 | constructor (client) {
8 | super({
9 | name: 'clapify',
10 | category: 'memes',
11 | parameters: [{
12 | type: 'string', full: true, missingError: 'commands:clapify.missingSentence'
13 | }]
14 | }, client)
15 | }
16 |
17 | async run ({ t, author, channel }, text) {
18 | const embed = new SwitchbladeEmbed(author)
19 | if (text.length >= CLAPIFY_LIMIT) {
20 | embed
21 | .setTitle(t('commands:clapify.tooLongText', { limit: CLAPIFY_LIMIT }))
22 | .setColor(Constants.ERROR_COLOR)
23 | } else {
24 | embed.setTitle(`${emoji} ${text.toUpperCase().split(' ').join(` ${emoji} `)} ${emoji}`)
25 | }
26 | channel.send(embed)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/commands/misc/hibp.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | const types = ['breach', 'paste', 'b', 'p']
4 |
5 | module.exports = class HIBP extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'hibp',
9 | aliases: ['haveibeenpwned'],
10 | requirements: { apis: ['hibp'] },
11 | parameters: [{
12 | type: 'string',
13 | full: false,
14 | whitelist: types,
15 | missingError: ({ t, prefix }) => {
16 | return new SwitchbladeEmbed().setTitle(t('commons:search.noType'))
17 | .setDescription([
18 | this.usage(t, prefix),
19 | '',
20 | `__**${t('commons:search.types')}:**__`,
21 | `\`${['breach', 'paste'].join('`, `')}\``
22 | ].join('\n'))
23 | }
24 | }]
25 | }, client)
26 |
27 | this.HIBP_LOGO = 'https://haveibeenpwned.com/Content/Images/SocialLogo.png'
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/commands/utility/restrictemoji/add.js:
--------------------------------------------------------------------------------
1 | const { Command, CommandError, SwitchbladeEmbed } = require('../../../')
2 |
3 | module.exports = class RestrictEmojiAdd extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'add',
7 | parent: 'restrictemoji',
8 | parameters: [{
9 | type: 'emoji',
10 | sameGuildOnly: true
11 | }, {
12 | type: 'role',
13 | full: true
14 | }]
15 | }, client)
16 | }
17 |
18 | async run ({ t, author, channel, guild }, emoji, role) {
19 | channel.startTyping()
20 | try {
21 | await emoji.roles.add(role)
22 | channel.send(
23 | new SwitchbladeEmbed(author)
24 | .setTitle(t('commands:restrictemoji.subcommands.add.canUse', { role: role.name, emoji: emoji.name }))
25 | ).then(() => channel.stopTyping())
26 | } catch (e) {
27 | channel.stopTyping()
28 | throw new CommandError(t('errors:generic'))
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/commands/utility/restrictemoji.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class RestrictEmoji extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'restrictemoji',
7 | category: 'utility',
8 | requirements: {
9 | guildOnly: true,
10 | botPermissions: ['MANAGE_EMOJIS'],
11 | permissions: ['MANAGE_EMOJIS']
12 | }
13 | }, client)
14 | }
15 |
16 | run ({ t, author, prefix, alias, channel }) {
17 | const embed = new SwitchbladeEmbed(author)
18 | channel.startTyping()
19 | embed.setDescription([
20 | t('commands:restrictemoji.addRole', { command: `${prefix}${alias || this.name}` }),
21 | t('commands:restrictemoji.removeRole', { command: `${prefix}${alias || this.name}` }),
22 | t('commands:restrictemoji.reset', { command: `${prefix}${alias || this.name}` })
23 | ].join('\n'))
24 | channel.send(embed).then(() => channel.stopTyping())
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/commands/utility/restrictemoji/remove.js:
--------------------------------------------------------------------------------
1 | const { Command, CommandError, SwitchbladeEmbed } = require('../../../')
2 |
3 | module.exports = class RestrictEmojiRemove extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'remove',
7 | parent: 'restrictemoji',
8 | parameters: [{
9 | type: 'emoji',
10 | sameGuildOnly: true
11 | }, {
12 | type: 'role',
13 | full: true
14 | }]
15 | }, client)
16 | }
17 |
18 | async run ({ t, author, channel, guild }, emoji, role) {
19 | channel.startTyping()
20 | try {
21 | await emoji.roles.remove(role)
22 | channel.send(
23 | new SwitchbladeEmbed(author)
24 | .setTitle(t('commands:restrictemoji.subcommands.remove.cantUse', { role: role.name, emoji: emoji.name }))
25 | ).then(() => channel.stopTyping())
26 | } catch (e) {
27 | channel.stopTyping()
28 | throw new CommandError(t('errors:generic'))
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/structures/SwitchbladeEmbed.js:
--------------------------------------------------------------------------------
1 | const { MessageEmbed } = require('discord.js')
2 |
3 | /**
4 | * A MessageEmbed with the default fields already filled
5 | * @constructor
6 | * @param {User} [user] - The user that executed the command that resulted in this embed
7 | * @param {object} [data] - Data to set in the rich embed
8 | */
9 | module.exports = class SwitchbladeEmbed extends MessageEmbed {
10 | constructor (user, data = {}) {
11 | super(data)
12 | this.setColor(process.env.EMBED_COLOR).setTimestamp()
13 | if (user) this.setFooter(user.tag)
14 | }
15 |
16 | /**
17 | * Sets the description of this embed based on an array of arrays of strings
18 | * @param {Array} Array containing arrays (blocks) of and strings
19 | * @returns {SwitchbladeEmbed}
20 | */
21 | setDescriptionFromBlockArray (blocks) {
22 | this.description = blocks.map(lines => lines.filter(l => !!l).join('\n')).filter(b => !!b.length).join('\n\n')
23 | return this
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/apis/Minecraft.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const axios = require('axios')
3 |
4 | module.exports = class Minecraft extends APIWrapper {
5 | constructor () {
6 | super({
7 | name: 'minecraft'
8 | })
9 | }
10 |
11 | async nameToUUID (name) {
12 | const { data } = await axios(`https://api.mojang.com/users/profiles/minecraft/${name}`)
13 | return data.id ? { uuid: data.id, name: data.name } : null
14 | }
15 |
16 | async uuidToName (uuid) {
17 | const { data } = await axios(`https://sessionserver.mojang.com/session/minecraft/profile/${uuid}`)
18 | return data.id ? { uuid: data.id, name: data.name } : null
19 | }
20 |
21 | async getPreviousNames (uuid) {
22 | const { data } = await axios(`https://api.mojang.com/user/profiles/${uuid}/names`)
23 | return data
24 | }
25 |
26 | async getServer (host, port) {
27 | const { data } = await axios(`https://mcapi.us/server/status?ip=${host}&port=${port}`)
28 | return data
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/assets/svg/brands/mixer.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
--------------------------------------------------------------------------------
/src/commands/developers/reloadlocales.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed, Constants } = require('../../')
2 |
3 | module.exports = class reloadlocales extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'reloadlocales',
7 | category: 'developers',
8 | hidden: true,
9 | requirements: { managersOnly: true }
10 | }, client)
11 | }
12 |
13 | async run ({ t, channel, author }) {
14 | channel.startTyping()
15 | const embed = new SwitchbladeEmbed(author)
16 | try {
17 | this.client.downloadAndInitializeLocales('src/locales').then(() => {
18 | embed
19 | .setTitle(t('commands:reloadlocales:reloaded'))
20 | channel.send(embed).then(() => channel.stopTyping())
21 | })
22 | } catch (e) {
23 | embed
24 | .setColor(Constants.ERROR_COLOR)
25 | .setTitle(t('errors:generic'))
26 | channel.send(embed).then(() => channel.stopTyping())
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | test:
7 | name: Tests
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v2
11 |
12 | - name: Install Node v16
13 | uses: actions/setup-node@v2
14 | with:
15 | cache: npm
16 | node-version: 16
17 |
18 | - name: Install dependencies
19 | run: npm ci
20 |
21 | - name: Run Tests
22 | run: npm run test
23 |
24 | build:
25 | name: Docker
26 | runs-on: ubuntu-latest
27 |
28 | needs: test
29 | if: github.event_name == 'push'
30 |
31 | steps:
32 | - uses: actions/checkout@v2
33 |
34 | - name: Build and publish to registry
35 | uses: docker/build-push-action@v1
36 | with:
37 | username: ${{ secrets.DOCKER_USERNAME }}
38 | password: ${{ secrets.DOCKER_PASSWORD }}
39 | repository: switchbladebot/switchblade-legacy
40 | tag_with_ref: true
41 |
--------------------------------------------------------------------------------
/src/commands/configuration/prefix.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed, Constants } = require('../../')
2 |
3 | module.exports = class ConfigPrefix extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'prefix',
7 | parent: 'config',
8 | parameters: [{
9 | type: 'string',
10 | full: true,
11 | required: false,
12 | maxLength: 50,
13 | missingError: 'commands:config.subcommands.prefix.noPrefix'
14 | }]
15 | }, client)
16 | }
17 |
18 | async run ({ t, author, channel, guild }, prefix = process.env.PREFIX) {
19 | const embed = new SwitchbladeEmbed(author)
20 |
21 | try {
22 | await this.client.modules.prefix.updateValues(guild.id, { prefix })
23 | embed.setTitle(t('commands:config.subcommands.prefix.changedSuccessfully', { prefix }))
24 | } catch (e) {
25 | embed.setColor(Constants.ERROR_COLOR)
26 | .setTitle(t('errors:generic'))
27 | }
28 |
29 | channel.send(embed)
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/commands/misc/imageoftheday.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | const Parser = require('rss-parser')
4 | const parser = new Parser()
5 |
6 | module.exports = class ImageOfTheDay extends Command {
7 | constructor (client) {
8 | super({
9 | name: 'imageoftheday',
10 | aliases: ['iotd']
11 | }, client)
12 | }
13 |
14 | async run ({ t, author, channel }) {
15 | channel.startTyping()
16 | const embed = new SwitchbladeEmbed(author)
17 | const feed = await parser.parseURL('https://www.nasa.gov/rss/dyn/lg_image_of_the_day.rss')
18 | const item = feed.items[0]
19 | embed
20 | .setAuthor(t('commands:imageoftheday.embedAuthor'), 'https://i.imgur.com/bHmqqHL.jpg')
21 | .setTitle(item.title)
22 | .setURL(item.link)
23 | .setImage(item.enclosure.url)
24 | .setDescription(item.content)
25 | .setColor(0x0b3d91)
26 | channel.send(embed).then(() => channel.stopTyping())
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/commands/misc/lastfm.js:
--------------------------------------------------------------------------------
1 | const { SubcommandListCommand } = require('../../')
2 |
3 | // Formatting url for embeds
4 | const formatUrl = name => name.replace(/\)/g, '%29').replace(/\(/g, '%28').replace(/_/g, '%25')
5 |
6 | // Regex to change the Read More from last.fm bio
7 | const READ_MORE_REGEX = /Read more on Last.fm<\/a>/g
8 |
9 | module.exports = class LastFM extends SubcommandListCommand {
10 | constructor (client) {
11 | super({
12 | name: 'lastfm',
13 | aliases: ['lfm'],
14 | requirements: { apis: ['lastfm'] },
15 | authorString: 'commands:lastfm.serviceName',
16 | authorImage: 'https://c7.uihere.com/files/934/662/527/last-fm-logo-computer-icons-music-recommender-system-icon-free-image-lastfm-thumb.jpg',
17 | authorURL: 'https://last.fm',
18 | embedColor: '#df2703'
19 | }, client)
20 |
21 | this.formatUrl = formatUrl
22 | this.READ_MORE_REGEX = READ_MORE_REGEX
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/utils/MiscUtils.js:
--------------------------------------------------------------------------------
1 | const moment = require('moment')
2 | const Intl = require('intl')
3 | Intl.__disableRegExpRestore()
4 |
5 | module.exports = class MiscUtils {
6 | static findArrayDuplicates (arr) {
7 | return arr.filter((value, index) => {
8 | return arr.indexOf(value) !== index
9 | })
10 | }
11 |
12 | static formatNumber (value, language) {
13 | const formatter = new Intl.NumberFormat(language)
14 | return formatter.format(value)
15 | }
16 |
17 | static formatDuration (duration) {
18 | return moment.duration(duration).format('hh:mm:ss', { stopTrim: 'm' })
19 | }
20 |
21 | static capitalizeFirstLetter (string, everyWord = false) {
22 | const capitalizeWord = w => w.charAt(0).toUpperCase() + w.slice(1)
23 | if (everyWord) return string.split(' ').map(capitalizeWord).join(' ')
24 | return capitalizeWord(string)
25 | }
26 |
27 | static isEqual (obj1, obj2) {
28 | return Object.entries(obj1).toString() === Object.entries(obj2).toString()
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | /**
3 | * @param {string} structure
4 | * @param {Object} structureOptions
5 | * @param {Object} [options]
6 | * @param {boolean} [options.optionalOptions]
7 | */
8 | createOptionHandler (structureName, structureOptions, options = {}) {
9 | if (!options.optionalOptions && typeof options === 'undefined') {
10 | throw new Error(`The options of structure "${structureName}" is required.`)
11 | }
12 |
13 | return ({
14 | optional (name, defaultValue = null) {
15 | const value = structureOptions[name]
16 |
17 | return typeof value === 'undefined'
18 | ? defaultValue
19 | : value
20 | },
21 |
22 | required (name) {
23 | const value = structureOptions[name]
24 |
25 | if (typeof value === 'undefined') {
26 | throw new Error(`The option "${name}" of structure "${structureName}" is required.`)
27 | }
28 | return value
29 | }
30 | })
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/commands/misc/image.js:
--------------------------------------------------------------------------------
1 | const { Command, CommandError } = require('../../')
2 |
3 | module.exports = class ImageSearchCommand extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'image',
7 | aliases: ['googleimage', 'gogleimage', 'imagesearch', 'images', 'gimages'],
8 | requirements: { apis: ['gsearch'] },
9 | category: 'general',
10 | parameters: [{
11 | type: 'string', required: true, full: true, missingError: 'commands:image.noQuery'
12 | }]
13 | }, client)
14 | }
15 |
16 | async run ({ t, channel }, query) {
17 | try {
18 | const image = await this.client.apis.gsearch.searchImage(query, channel.nsfw)
19 | if (image.items) {
20 | return channel.send(image.items[0].link)
21 | } else {
22 | throw new CommandError(t('commons:search.noResults'))
23 | }
24 | } catch (err) {
25 | if (err instanceof CommandError) throw err
26 | throw new CommandError(t('errors:generic'))
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/commands/economy/money.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Money extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'money',
7 | aliases: ['balance', 'bal'],
8 | category: 'economy',
9 | requirements: { guildOnly: true, databaseOnly: true },
10 | parameters: [{
11 | type: 'user', full: true, required: false
12 | }]
13 | }, client)
14 | }
15 |
16 | async run ({ t, author, channel }, user = author) {
17 | channel.startTyping()
18 |
19 | const embed = new SwitchbladeEmbed(author)
20 | const money = await this.client.controllers.economy.balance(user.id)
21 | if (author.id === user.id) {
22 | embed.setDescription(t('commands:money.youHave', { count: money }))
23 | } else {
24 | embed.setDescription(t('commands:money.someoneHas', { count: money, user }))
25 | }
26 |
27 | channel.send(embed).then(() => channel.stopTyping())
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/assets/svg/brands/deezer.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/commands/social/leaderboard/reputation.js:
--------------------------------------------------------------------------------
1 | const { CanvasTemplates, Command, Constants } = require('../../../')
2 | const { MessageAttachment } = require('discord.js')
3 |
4 | module.exports = class ReputationLeaderboard extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'reputation',
8 | aliases: ['rep'],
9 | parent: 'leaderboard'
10 | }, client)
11 | }
12 |
13 | async run ({ t, author, channel }) {
14 | channel.startTyping()
15 |
16 | const top = await this.client.controllers.social.leaderboard('rep')
17 | const leaderboard = await CanvasTemplates.leaderboard({ t }, top, {
18 | icon: Constants.REPUTATION_SVG,
19 | iconWidth: 48,
20 | iconHeight: 48,
21 | title: t(`commands:${this.tPath}.title`).toUpperCase(),
22 | valueFunction: (u) => t('commons:reputationWithCount', { count: Math.round(u.rep) })
23 | })
24 |
25 | channel.send(new MessageAttachment(leaderboard, 'leaderboard.jpg')).then(() => channel.stopTyping())
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/commands/social/profile.js:
--------------------------------------------------------------------------------
1 | const { CanvasTemplates, Command, PermissionUtils } = require('../../')
2 | const { MessageAttachment } = require('discord.js')
3 |
4 | module.exports = class Profile extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'profile',
8 | category: 'social',
9 | requirements: { databaseOnly: true, canvasOnly: true },
10 | parameters: [{
11 | type: 'user',
12 | full: true,
13 | required: false,
14 | acceptSelf: true
15 | }]
16 | }, client)
17 | }
18 |
19 | async run ({ t, author, channel }, user = author) {
20 | channel.startTyping()
21 | const userDocument = await this.client.controllers.social.retrieveProfile(user.id)
22 | const role = PermissionUtils.specialRole(this.client, user)
23 | const profile = await CanvasTemplates.profile({ t }, user, userDocument, role)
24 | channel.send(new MessageAttachment(profile, 'profile.jpg')).then(() => channel.stopTyping())
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/structures/Route.js:
--------------------------------------------------------------------------------
1 | const Utils = require('../utils')
2 |
3 | module.exports = class Route {
4 | /**
5 | * @param {Object} opts
6 | * @param {string} opts.name
7 | * @param {Route} [opts.parent]
8 | * @param {Client} client
9 | */
10 | constructor (opts, client) {
11 | const options = Utils.createOptionHandler('Route', opts)
12 |
13 | this.name = options.required('name')
14 | this.parentRoute = options.optional('parent')
15 |
16 | this.client = client
17 |
18 | this.subRoutes = null
19 | this.requirements = null
20 | }
21 |
22 | get path () {
23 | return `${this.parentRoute ? '' : '/api'}${this.parentRoute ? this.parentRoute.path : ''}/${this.name}`
24 | }
25 |
26 | _register (app) {
27 | if (this.subRoutes) {
28 | this.subRoutes.forEach(route => {
29 | route._register(app)
30 | })
31 | }
32 |
33 | this.register(app)
34 | }
35 |
36 | /**
37 | * Registers express Router with routes information
38 | */
39 | register (app) {}
40 | }
41 |
--------------------------------------------------------------------------------
/src/commands/social/leaderboard/money.js:
--------------------------------------------------------------------------------
1 | const { CanvasTemplates, Command, Constants } = require('../../../')
2 | const { MessageAttachment } = require('discord.js')
3 |
4 | module.exports = class MoneyLeaderboard extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'money',
8 | aliases: ['balance', 'switchcoins'],
9 | parent: 'leaderboard'
10 | }, client)
11 | }
12 |
13 | async run ({ t, author, channel }) {
14 | channel.startTyping()
15 |
16 | const top = await this.client.controllers.social.leaderboard('money')
17 | const leaderboard = await CanvasTemplates.leaderboard({ t }, top, {
18 | icon: Constants.COINS_SVG,
19 | iconWidth: 48,
20 | iconHeight: 48,
21 | title: t(`commands:${this.tPath}.title`).toUpperCase(),
22 | valueFunction: (u) => t('commons:currencyWithCount_plural', { count: Math.round(u.money) })
23 | })
24 |
25 | channel.send(new MessageAttachment(leaderboard, 'leaderboard.jpg')).then(() => channel.stopTyping())
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/commands/misc/numberfacts.js:
--------------------------------------------------------------------------------
1 | const { Command, CommandError, SwitchbladeEmbed } = require('../../')
2 |
3 | const fetch = require('node-fetch')
4 |
5 | module.exports = class NumberFacts extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'numberfacts',
9 | aliases: ['number', 'numfacts', 'numf'],
10 | parameters: [{
11 | type: 'number', min: 0, missingError: 'commands:numberfacts.validNumber'
12 | }]
13 | }, client)
14 | }
15 |
16 | async run ({ t, author, channel }, number) {
17 | const embed = new SwitchbladeEmbed(author)
18 | channel.startTyping()
19 | try {
20 | const body = await fetch(`http://numbersapi.com/${number}/trivia`).then(res => res.text())
21 | embed.setTitle(body)
22 | channel.send(embed).then(() => channel.stopTyping())
23 | } catch (e) {
24 | channel.stopTyping()
25 | console.error(e)
26 | throw new CommandError(t('commands:numberfacts.anErrorOcurred'))
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/commands/misc/qrcode/read.js:
--------------------------------------------------------------------------------
1 | const { Command, CommandError, SwitchbladeEmbed } = require('../../../')
2 | const fetch = require('node-fetch')
3 |
4 | module.exports = class QRCodeRead extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'read',
8 | aliases: ['r'],
9 | parent: 'qrcode',
10 | parameters: [{
11 | type: 'image',
12 | authorAvatar: false,
13 | url: true,
14 | missingError: 'commands:qrcode.subcommands.read.noImage'
15 | }]
16 | }, client)
17 | }
18 |
19 | async run ({ t, channel, author, message }, image) {
20 | const body = await fetch(`http://api.qrserver.com/v1/read-qr-code/?fileurl=${encodeURIComponent(image)}`).then(res => res.json())
21 | if (body[0].symbol[0].data !== null) {
22 | channel.send(
23 | new SwitchbladeEmbed(author)
24 | .setDescription(body[0].symbol[0].data)
25 | )
26 | } else {
27 | throw new CommandError(t('commands:qrcode.subcommands.read.unknownImage'))
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/apis/Genius.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const fetch = require('node-fetch')
3 | const cheerio = require('cheerio')
4 |
5 | const API_URL = 'https://api.genius.com'
6 |
7 | module.exports = class GeniusAPI extends APIWrapper {
8 | constructor () {
9 | super({
10 | name: 'genius',
11 | envVars: ['GENIUS_API']
12 | })
13 | }
14 |
15 | // Find a track
16 | findTrack (q) {
17 | return this.request('/search', { q })
18 | }
19 |
20 | // Load lyrics from the html
21 | loadLyrics (url) {
22 | return fetch(url).then(async r => {
23 | const _ = await r.text()
24 | const $ = cheerio.load(_)
25 | return $('.lyrics') ? $('.lyrics').text().trim() : null
26 | })
27 | }
28 |
29 | // Default
30 | request (endpoint, queryParams = {}) {
31 | const qParams = new URLSearchParams(queryParams)
32 | return fetch(API_URL + endpoint + `?${qParams.toString()}`, {
33 | headers: { Authorization: `Bearer ${process.env.GENIUS_API}` }
34 | }).then(res => res.json())
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/apis/GoogleTranslate.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const fetch = require('node-fetch')
3 |
4 | module.exports = class GoogleTranslate extends APIWrapper {
5 | constructor () {
6 | super({
7 | name: 'gtranslate'
8 | })
9 | }
10 |
11 | /**
12 | * Translates a text from a language to another.
13 | * @param {string} from
14 | * @param {string} to
15 | * @param {string} text
16 | * @returns {Promise<{original: string, from: string, translated: string, to: string}>}
17 | */
18 | async translateText (from, to, text) {
19 | const params = {
20 | sl: from,
21 | tl: to,
22 | q: text
23 | }
24 |
25 | const URLqueryParams = new URLSearchParams(params)
26 | const res = await fetch('https://translate.googleapis.com/translate_a/single?client=gtx&dt=t' + `&${URLqueryParams.toString()}`)
27 | .then(res => res.json())
28 |
29 | return {
30 | translated: res[0][0][0],
31 | original: res[0][0][1],
32 | from: res[2],
33 | to: to
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/structures/Webhook.js:
--------------------------------------------------------------------------------
1 | const Utils = require('../utils')
2 |
3 | module.exports = class Webhook {
4 | /**
5 | * @param {Object} opts
6 | * @param {string} opts.name
7 | * @param {Webhook} [opts.parent]
8 | * @param {Client} client
9 | */
10 | constructor (opts, client) {
11 | const options = Utils.createOptionHandler('Webhook', opts)
12 |
13 | this.name = options.required('name')
14 | this.parentWebhook = options.optional('parent')
15 |
16 | this.client = client
17 |
18 | this.subWebhooks = null
19 | this.requirements = null
20 | }
21 |
22 | get path () {
23 | return `${this.parentRoute ? '' : '/webhooks'}${this.parentRoute ? this.parentRoute.path : ''}/${this.name}`
24 | }
25 |
26 | _register (app) {
27 | if (this.subWebhooks) {
28 | this.subWebhooks.forEach(webhook => {
29 | webhook._register(app)
30 | })
31 | }
32 |
33 | this.register(app)
34 | }
35 |
36 | /**
37 | * Registers express Router with webhooks information
38 | */
39 | register (app) {}
40 | }
41 |
--------------------------------------------------------------------------------
/src/commands/utility/guildicon.js:
--------------------------------------------------------------------------------
1 | const { Command, CommandError, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class GuildIcon extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'guildicon',
7 | aliases: ['gicon', 'sicon', 'srvicn', 'servericon'],
8 | category: 'utility',
9 | requirements: { guildOnly: true },
10 | parameters: [{
11 | type: 'guild',
12 | full: true,
13 | required: false
14 | }]
15 | }, client)
16 | }
17 |
18 | run ({ t, author, channel }, guild = channel.guild) {
19 | const embed = new SwitchbladeEmbed(author)
20 | channel.startTyping()
21 | guild = guild || channel.guild
22 | if (guild.iconURL) {
23 | embed.setImage(guild.iconURL({ format: 'png', dynamic: true }))
24 | .setDescription(t('commands:guildicon.iconDescription', { guild: guild.name }))
25 | } else {
26 | throw new CommandError(t('commands:guildicon.noIcon'))
27 | }
28 | channel.send(embed).then(() => channel.stopTyping())
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/music/sources/youtube/YoutubePlaylist.js:
--------------------------------------------------------------------------------
1 | const { Playlist } = require('../../structures')
2 | const YoutubeSong = require('./YoutubeSong.js')
3 |
4 | const PLAYLIST_URI = 'https://www.youtube.com/playlist?list='
5 |
6 | module.exports = class YoutubePlaylist extends Playlist {
7 | constructor (data = {}, songs = [], requestedBy, Youtube) {
8 | super(data, songs, requestedBy)
9 |
10 | this.uri = PLAYLIST_URI + this.identifier
11 | this._Youtube = Youtube
12 | }
13 |
14 | async loadInfo () {
15 | const yt = this._Youtube
16 | // Load songs
17 | this.songs = await Promise.all(this.songs.map(s => new YoutubeSong(s, this.requestedBy, yt).loadInfo()))
18 |
19 | // Load playlist
20 | const playlist = await yt.getPlaylist(this.identifier)
21 | if (playlist) {
22 | this.title = playlist.snippet.title
23 | this.description = playlist.snippet.description
24 | this.artwork = yt.getBestThumbnail(playlist.snippet.thumbnails).url
25 | } else {
26 | return null
27 | }
28 | return this
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/modules/JoinLockModule.js:
--------------------------------------------------------------------------------
1 | const { Module, PlaceholderUtils, PlaceholderRules } = require('../')
2 |
3 | const Joi = require('joi')
4 |
5 | const MAX_MESSAGE_SIZE = 250
6 | const PLACEHOLDER_BLACKLIST = ['channel', 'channelName']
7 |
8 | module.exports = class JoinLockModule extends Module {
9 | constructor (client) {
10 | super({
11 | name: 'joinLock',
12 | displayName: 'Join Lock',
13 | defaultState: false,
14 | defaultValues: { message: '' },
15 | specialInput: {
16 | message: { max: MAX_MESSAGE_SIZE },
17 | placeholders: PlaceholderRules.filter(r => !PLACEHOLDER_BLACKLIST.includes(r.name))
18 | }
19 | }, client)
20 | }
21 |
22 | parseMessage (message, member) {
23 | return PlaceholderUtils.parse(message, {
24 | guild: member.guild,
25 | user: member.user
26 | }, null, PLACEHOLDER_BLACKLIST)
27 | }
28 |
29 | validateValues (entity) {
30 | return Joi.object().keys({
31 | message: Joi.string().max(MAX_MESSAGE_SIZE).allow('').optional().trim()
32 | }).validate(entity)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/apis/IGDB.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 |
3 | const fetch = require('node-fetch')
4 |
5 | const API_URL = 'https://api-v3.igdb.com'
6 |
7 | module.exports = class IGDB extends APIWrapper {
8 | constructor () {
9 | super({
10 | name: 'igdb',
11 | envVars: ['IGDB_API_KEY']
12 | })
13 | }
14 |
15 | searchGame (gameName) {
16 | return this.request('/games', `fields name,url; search: "${gameName}";`)
17 | }
18 |
19 | getGameById (gameId) {
20 | return this.request('/games', `fields name,cover.url,genres.name,platforms.name,release_dates.date,release_dates.platform.name,release_dates.region,age_ratings.*,alternative_names.name,dlcs.*,expansions.*,involved_companies.company.name,popularity,similar_games.*,total_rating,total_rating_count,url,summary; where id = ${gameId};`).then(g => g[0])
21 | }
22 |
23 | request (endpoint, data) {
24 | return fetch(API_URL + endpoint, {
25 | method: 'POST',
26 | headers: {
27 | 'user-key': process.env.IGDB_API_KEY
28 | },
29 | body: data
30 | }).then(res => res.json())
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/commands/utility/hastebin.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | const fetch = require('node-fetch')
4 |
5 | const EscapeMarkdown = (text) => text.replace(/(\*|~+|`)/g, '')
6 |
7 | const baseURL = 'https://hastebin.com'
8 |
9 | module.exports = class Hastebin extends Command {
10 | constructor (client) {
11 | super({
12 | name: 'hastebin',
13 | aliases: ['haste'],
14 | category: 'utility',
15 | parameters: [{
16 | type: 'string',
17 | full: true,
18 | missingError: 'commands:hastebin.missingCode'
19 | }]
20 | }, client)
21 | }
22 |
23 | async run ({ t, author, channel, message }, code) {
24 | const embed = new SwitchbladeEmbed()
25 | const { key } = await fetch(`${baseURL}/documents`, {
26 | method: 'POST',
27 | headers: { 'Content-Type': 'application/json' },
28 | body: EscapeMarkdown(code)
29 | }).then(res => res.json())
30 |
31 | embed
32 | .setAuthor(t('commands:hastebin.hereIsYourURL'))
33 | .setDescription(`${baseURL}/${key}`)
34 | channel.send(embed)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/apis/FreeFire.js:
--------------------------------------------------------------------------------
1 | const { APIWrapper } = require('../')
2 | const fetch = require('node-fetch')
3 |
4 | const REQUEST_URL = 'https://ffstaticdata.switchblade.xyz'
5 |
6 | const weaponData = {}
7 |
8 | module.exports = class FreeFire extends APIWrapper {
9 | constructor () {
10 | super({
11 | name: 'freefire'
12 | })
13 | this.languages = []
14 | }
15 |
16 | load () {
17 | this.loadLanguages()
18 | return this
19 | }
20 |
21 | async loadLanguages () {
22 | const { locales } = await this.request('/metadata.json')
23 | this.languages = locales
24 | }
25 |
26 | selectLanguage (language = 'en_US') {
27 | const normalizedLanguage = language.slice(0, 2)
28 | return this.languages.find(l => l.toLowerCase() === normalizedLanguage) || 'en'
29 | }
30 |
31 | async getWeaponData (lang) {
32 | const language = this.selectLanguage(lang)
33 | return weaponData[language] || (weaponData[language] = await this.request(`/${language}/weapons.json`))
34 | }
35 |
36 | request (endpoint) {
37 | return fetch(REQUEST_URL + endpoint).then(res => res.json())
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/commands/developers/clientvalues.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-eval */
2 |
3 | const { Command } = require('../../')
4 |
5 | module.exports = class ClientValues extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'clientvalues',
9 | aliases: ['cv'],
10 | category: 'developers',
11 | hidden: true,
12 | requirements: { devOnly: true },
13 | parameters: [{
14 | type: 'string', full: true, missingError: 'errors:missingParameters', showUsage: false
15 | }]
16 | }, client)
17 | }
18 |
19 | async run ({ channel, message }, path) {
20 | try {
21 | const values = await this.client.shard.fetchClientValues(path)
22 | channel.send(values.map((value, shard) => `**Shard ${shard}**\n\`\`\`${this.clean(value)}\`\`\``).join('\n'))
23 | } catch (e) {
24 | channel.send('`ERROR` ```xl\n' + this.clean(e) + '\n```')
25 | }
26 | }
27 |
28 | clean (text) {
29 | const blankSpace = String.fromCharCode(8203)
30 | return typeof text === 'string' ? text.replace(/`/g, '`' + blankSpace).replace(/@/g, '@' + blankSpace) : text
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/commands/social/favcolor.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed, CommandError } = require('../../')
2 |
3 | module.exports = class FavColor extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'favcolor',
7 | aliases: ['favoritecolor', 'sethex', 'setcolor'],
8 | category: 'social',
9 | requirements: { databaseOnly: true },
10 | parameters: [{
11 | type: 'color',
12 | full: true,
13 | missingError: 'errors:invalidColor'
14 | }]
15 | }, client)
16 | }
17 |
18 | async run ({ t, author, channel, userDocument }, color) {
19 | const hexcode = color.rgb(true)
20 | channel.startTyping()
21 |
22 | try {
23 | const embed = new SwitchbladeEmbed(author)
24 | await this.client.controllers.social.setFavoriteColor(author.id, hexcode)
25 | embed.setColor(hexcode)
26 | .setTitle(t('commands:favcolor.changedSuccessfully', { hexcode }))
27 | channel.send(embed).then(() => channel.stopTyping())
28 | } catch (e) {
29 | throw new CommandError(t('errors:generic'))
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/commands/misc/google.js:
--------------------------------------------------------------------------------
1 | const { SearchCommand, Constants } = require('../../')
2 | const Turndown = require('turndown')
3 | const turndownService = new Turndown()
4 |
5 | module.exports = class GoogleCommand extends SearchCommand {
6 | constructor (client) {
7 | super({
8 | name: 'google',
9 | aliases: ['gogle', 'googl'],
10 | requirements: { apis: ['gsearch'] },
11 | embedColor: Constants.GOOGLE_COLOR,
12 | embedLogoURL: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/1200px-Google_%22G%22_Logo.svg.png'
13 | }, client)
14 | }
15 |
16 | async search ({ channel }, query) {
17 | const { items } = await this.client.apis.gsearch.search(query, channel.nsfw)
18 | return items
19 | }
20 |
21 | searchResultFormatter ({ htmlTitle, link }) {
22 | let title = turndownService.turndown(htmlTitle
23 | .replace(/\]/g, '\\\\]')
24 | .replace(/\[/g, '\\\\['))
25 | title = link.length > 120 ? title : `[${title}](${link})`
26 | return `${title}`
27 | }
28 |
29 | async handleResult ({ channel }, { link }) {
30 | channel.send(link)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/commands/utility/deleteemoji.js:
--------------------------------------------------------------------------------
1 | const { Command, CommandError, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class DeleteEmoji extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'deleteemoji',
7 | aliases: ['delemoji'],
8 | category: 'utility',
9 | requirements: { guildOnly: true, permissions: ['MANAGE_EMOJIS'], botPermissions: ['MANAGE_EMOJIS'] },
10 | parameters: [{
11 | type: 'emoji',
12 | sameGuildOnly: true,
13 | missingError: 'commands:deleteemoji.noEmoji'
14 | }]
15 | }, client)
16 | }
17 |
18 | async run ({ t, channel, author, guild }, emoji) {
19 | const embed = new SwitchbladeEmbed(author)
20 | channel.startTyping()
21 |
22 | try {
23 | await emoji.delete()
24 |
25 | embed.setDescription(t('commands:deleteemoji.deleted', { emoji }))
26 | .setThumbnail(emoji.url)
27 |
28 | channel.send(embed).then(() => channel.stopTyping())
29 | } catch (e) {
30 | channel.stopTyping()
31 | throw new CommandError(`${t('commands:deleteemoji.error')}\n${e.toString()}`)
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/commands/bot/shards.js:
--------------------------------------------------------------------------------
1 | const { Command, MiscUtils } = require('../../')
2 | const AsciiTable = require('ascii-table')
3 |
4 | module.exports = class Shards extends Command {
5 | constructor (client) {
6 | super({
7 | name: 'shards',
8 | category: 'bot'
9 | }, client)
10 | }
11 |
12 | async run ({ channel }) {
13 | const table = new AsciiTable()
14 | .setHeading('Shard', 'Servers', 'Cached Users', 'Ping')
15 | .setAlign(0, AsciiTable.CENTER)
16 | .setAlign(1, AsciiTable.CENTER)
17 | .setAlign(2, AsciiTable.CENTER)
18 | .setAlign(3, AsciiTable.CENTER)
19 | .removeBorder()
20 | const guildCount = await this.client.shard.fetchClientValues('guilds.cache.size')
21 | const users = await this.client.shard.fetchClientValues('users.cache.size')
22 | const ping = await this.client.shard.fetchClientValues('ws.ping')
23 | guildCount.forEach((count, shardId) => {
24 | table.addRow(shardId, MiscUtils.formatNumber(count), MiscUtils.formatNumber(users[shardId]), `${MiscUtils.formatNumber(ping[shardId])}ms`)
25 | })
26 | channel.send(`\`\`\`${table.toString()}\`\`\``)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/commands/gifs/handshake.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | const handshakeArray = [
4 | 'https://i.gifer.com/C9M7.gif',
5 | 'https://media1.tenor.com/images/314a2f7c3647ec0b9ba4100f8e35dc2e/tenor.gif?itemid=12270042',
6 | 'https://i.giphy.com/media/lNjOEfoKIplcc/200_d.gif'
7 | ]
8 |
9 | module.exports = class Handshake extends Command {
10 | constructor (client) {
11 | super({
12 | name: 'handshake',
13 | aliases: ['hs', 'hands'],
14 | category: 'images',
15 | parameters: [{
16 | type: 'user', acceptBot: true, acceptSelf: false, missingError: 'commands:handshake.noMention'
17 | }]
18 | }, client)
19 | }
20 |
21 | run ({ t, channel, author }, user) {
22 | const handshakeImg = handshakeArray[Math.floor(Math.random() * handshakeArray.length)]
23 | const embed = new SwitchbladeEmbed(author)
24 | channel.startTyping()
25 | embed.setImage(handshakeImg)
26 | .setDescription(t('commands:handshake.success', { handshaker: author.toString(), handshaked: user.toString() }))
27 | channel.send(embed).then(() => channel.stopTyping())
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/commands/text/emojify.js:
--------------------------------------------------------------------------------
1 | const { Command } = require('../../')
2 |
3 | const specialCodes = {
4 | 0: ':zero:',
5 | 1: ':one:',
6 | 2: ':two:',
7 | 3: ':three:',
8 | 4: ':four:',
9 | 5: ':five:',
10 | 6: ':six:',
11 | 7: ':seven:',
12 | 8: ':eight:',
13 | 9: ':nine:',
14 | '#': ':hash:',
15 | '*': ':asterisk:',
16 | '?': ':grey_question:',
17 | '!': ':grey_exclamation:',
18 | ' ': ' '
19 | }
20 |
21 | module.exports = class Emojify extends Command {
22 | constructor (client) {
23 | super({
24 | name: 'emojify',
25 | category: 'memes',
26 | parameters: [{
27 | type: 'string',
28 | full: true,
29 | missingError: 'commands:emojify.missingSentence'
30 | }]
31 | }, client)
32 | }
33 |
34 | async run ({ t, author, channel }, text) {
35 | const emojified = text.toLowerCase().split('').map(letter => {
36 | if (/[a-z]/g.test(letter)) {
37 | return `:regional_indicator_${letter}: `
38 | } else if (specialCodes[letter]) {
39 | return `${specialCodes[letter]} `
40 | }
41 | return letter
42 | }).join('')
43 | channel.send(emojified)
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/commands/developers/whyblacklisted.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed, Constants } = require('../../index')
2 |
3 | module.exports = class WhyBlacklisted extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'whyblacklisted',
7 | category: 'developers',
8 | hidden: true,
9 | requirements: { devOnly: true },
10 | parameters: [{
11 | type: 'user', showUsage: false, missingError: 'commands:whyblacklisted.missingUser'
12 | }]
13 | }, client)
14 | }
15 |
16 | async run ({ channel, author, t }, user) {
17 | const embed = new SwitchbladeEmbed(author)
18 | const info = await this.client.controllers.developer.blacklisted(user.id)
19 | if (info) {
20 | const text = { user, blacklister: `<@${info.blacklister}>` }
21 | embed.setDescription(
22 | [
23 | `**${t('commands:whyblacklisted.reasonTitle', text)}**`,
24 | `\`${info.reason}\``
25 | ].join('\n')
26 | )
27 | } else {
28 | embed
29 | .setColor(Constants.ERROR_COLOR)
30 | .setTitle(t('commands:whyblacklisted.notBlacklisted'))
31 | }
32 | channel.send(embed)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/locales/en-US/permissions.json:
--------------------------------------------------------------------------------
1 | {
2 | "ADMINISTRATOR": "Administrator",
3 | "CREATE_INSTANT_INVITE": "Create Instant Invite",
4 | "KICK_MEMBERS": "Kick Members",
5 | "BAN_MEMBERS": "Ban Members",
6 | "MANAGE_CHANNELS": "Manage Channels",
7 | "MANAGE_GUILD": "Manage Server",
8 | "ADD_REACTIONS": "Add Reactions",
9 | "VIEW_AUDIT_LOG": "View Audit Log",
10 | "VIEW_CHANNEL": "Read Messages",
11 | "SEND_MESSAGES": "Send Messages",
12 | "SEND_TTS_MESSAGES": "Send TTS Messages",
13 | "MANAGE_MESSAGES": "Manage Messages",
14 | "EMBED_LINKS": "Embed Links",
15 | "ATTACH_FILES": "Attach Files",
16 | "READ_MESSAGE_HISTORY": "Read Message History",
17 | "MENTION_EVERYONE": "Mention Everyone",
18 | "USE_EXTERNAL_EMOJIS": "Use External Emojis",
19 | "CONNECT": "Connect",
20 | "SPEAK": "Speak",
21 | "MUTE_MEMBERS": "Mute Members",
22 | "DEAFEN_MEMBERS": "Deafen Members",
23 | "MOVE_MEMBERS": "Move Members",
24 | "USE_VAD": "Use Voice Activity",
25 | "CHANGE_NICKNAME": "Change Nickname",
26 | "MANAGE_NICKNAMES": "Manage Nicknames",
27 | "MANAGE_ROLES": "Manage Roles",
28 | "MANAGE_WEBHOOKS": "Manage Webhooks",
29 | "MANAGE_EMOJIS": "Manage Emojis"
30 | }
--------------------------------------------------------------------------------
/src/commands/moderation/ban.js:
--------------------------------------------------------------------------------
1 | const { Command, Constants, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Ban extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'ban',
7 | aliases: ['banir'],
8 | category: 'moderation',
9 | requirements: { guildOnly: true, botPermissions: ['BAN_MEMBERS'], permissions: ['BAN_MEMBERS'] },
10 | parameters: [{
11 | type: 'member', acceptBot: true, missingError: 'commands:ban.missingUser'
12 | }, {
13 | type: 'string', full: true, missingError: 'commands:ban.missingReason'
14 | }]
15 | }, client)
16 | }
17 |
18 | async run ({ channel, guild, author, t }, member, reason) {
19 | const embed = new SwitchbladeEmbed(author)
20 | await member.ban({ days: 7, reason }).then(bannedMember => {
21 | embed
22 | .setTitle(t('commands:ban.successTitle'))
23 | .setDescription(`${bannedMember} - \`${reason}\``)
24 | }).catch(err => {
25 | embed
26 | .setColor(Constants.ERROR_COLOR)
27 | .setTitle(t('commands:ban.cantBan'))
28 | .setDescription(`\`${err}\``)
29 | })
30 | channel.send(embed)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/commands/moderation/kick.js:
--------------------------------------------------------------------------------
1 | const { Command, Constants, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class Kick extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'kick',
7 | aliases: ['expulsar'],
8 | category: 'moderation',
9 | requirements: { guildOnly: true, botPermissions: ['KICK_MEMBERS'], permissions: ['KICK_MEMBERS'] },
10 | parameters: [{
11 | type: 'member', acceptBot: true, missingError: 'commands:kick.missingUser'
12 | }, {
13 | type: 'string', full: true, missingError: 'commands:kick.missingReason'
14 | }]
15 | }, client)
16 | }
17 |
18 | async run ({ channel, guild, author, t }, member, reason) {
19 | const embed = new SwitchbladeEmbed(author)
20 | await member.kick(reason).then(kickedMember => {
21 | embed
22 | .setTitle(t('commands:kick.successTitle'))
23 | .setDescription(`${kickedMember} - \`${reason}\``)
24 | }).catch(err => {
25 | embed
26 | .setColor(Constants.ERROR_COLOR)
27 | .setTitle(t('commands:kick.cantKick'))
28 | .setDescription(`\`${err}\``)
29 | })
30 | channel.send(embed)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/commands/misc/time.js:
--------------------------------------------------------------------------------
1 | const { Command, CommandError, SwitchbladeEmbed } = require('../../')
2 |
3 | const moment = require('moment-timezone')
4 |
5 | module.exports = class Time extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'time',
9 | aliases: ['currenttime'],
10 | requirements: { apis: ['positionstack'] },
11 | parameters: [{
12 | type: 'string', full: true, missingError: 'commands:time.noZone'
13 | }]
14 | }, client)
15 | }
16 |
17 | async run ({ t, author, channel, language }, address) {
18 | const embed = new SwitchbladeEmbed(author)
19 | channel.startTyping()
20 | moment.locale(language)
21 |
22 | const place = await this.client.apis.positionstack.getAddress(address)
23 | if (!place.data[0]) {
24 | throw new CommandError(t('commands:time.notFound'))
25 | }
26 |
27 | const { name } = place.data[0].timezone_module
28 | const time = moment.tz(name).format('LLLL (z)')
29 |
30 | embed
31 | .setTitle(t('commands:time.currentTime', { timezone: place.data[0].label }))
32 | .setDescription(time)
33 | channel.send(embed).then(() => channel.stopTyping())
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/commands/misc/uigradient.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed, CanvasTemplates } = require('../../')
2 | const { MessageAttachment } = require('discord.js')
3 | const fetch = require('node-fetch')
4 |
5 | module.exports = class UIGradient extends Command {
6 | constructor (client) {
7 | super({
8 | name: 'uigradient',
9 | aliases: ['rg', 'randomgradient']
10 | }, client)
11 | }
12 |
13 | async run ({ t, author, channel }) {
14 | const embed = new SwitchbladeEmbed(author)
15 | channel.startTyping()
16 |
17 | const body = await fetch('https://cdn.jsdelivr.net/gh/ghosh/uiGradients/gradients.json').then(res => res.json())
18 | const { name, colors } = body[Math.floor(Math.random() * body.length)]
19 |
20 | const gradient = CanvasTemplates.gradient(colors, 300, 100)
21 |
22 | embed.setTitle(name)
23 | .setURL(`https://uigradients.com/#${name.replace(/\s+/g, '')}`)
24 | .setColor(colors[0])
25 | .setImage('attachment://gradient.png')
26 | .setDescription(`\`${colors.join('`, `')}\``)
27 | .attachFiles(new MessageAttachment(gradient, 'gradient.png'))
28 | channel.send(embed).then(() => channel.stopTyping())
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/commands/misc/whatlanguage.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed } = require('../../')
2 |
3 | module.exports = class WhatLanguage extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'whatlanguage',
7 | requirements: { apis: ['languagelayer'] },
8 | parameters: [{
9 | type: 'string', full: true, missingError: 'commands:whatlanguage.noText'
10 | }]
11 | }, client)
12 | }
13 |
14 | async run ({ t, author, channel }, query) {
15 | channel.startTyping()
16 | const { data } = await this.client.apis.languagelayer.detectText(query)
17 | const embed = new SwitchbladeEmbed(author)
18 | .setTitle(t('commands:whatlanguage.embedTitle'))
19 | .setDescription(t('commands:whatlanguage.embedDes'))
20 | .addField(t('commands:whatlanguage.fieldLanguage'), `:flag_${data.results[0].language_code}: ${data.results[0].language_name}`, true)
21 | .addField(t('commands:whatlanguage.fieldProbability'), `${Math.round(data.results[0].probability)}%`, true)
22 | .addField(t('commands:whatlanguage.fieldPercentage'), `${data.results[0].percentage}%`, true)
23 | channel.send(embed).then(() => channel.stopTyping())
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/structures/command/parameters/types/NumberParameter.js:
--------------------------------------------------------------------------------
1 | const Parameter = require('./Parameter.js')
2 | const CommandError = require('../../CommandError.js')
3 |
4 | const isNull = (n) => n === null || n === undefined || isNaN(n)
5 |
6 | module.exports = class NumberParameter extends Parameter {
7 | static parseOptions (options = {}) {
8 | return {
9 | ...super.parseOptions(options),
10 | min: Number(options.min),
11 | max: Number(options.max),
12 | forceMin: !!options.forceMin,
13 | forceMax: !!options.forceMax
14 | }
15 | }
16 |
17 | static parse (arg, { t }) {
18 | if (!arg) return
19 |
20 | let nmb = Number(arg.replace(/%/g, ''))
21 | if (isNull(nmb)) throw new CommandError(t('errors:invalidNumber'), this.showUsage)
22 | if (!isNull(this.min) && nmb < this.min) {
23 | if (!this.forceMin) throw new CommandError(t('errors:needBiggerNumber', { number: this.min }))
24 | nmb = this.min
25 | }
26 | if (!isNull(this.max) && nmb > this.max) {
27 | if (!this.forceMax) throw new CommandError(t('errors:needSmallerNumber', { number: this.max }))
28 | nmb = this.max
29 | }
30 |
31 | return nmb
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/commands/utility/isitup.js:
--------------------------------------------------------------------------------
1 | const { Command, CommandError, SwitchbladeEmbed } = require('../../')
2 | const fetch = require('node-fetch')
3 |
4 | const PROTOCOL_REGEX = /^[a-zA-Z]+:\/\//
5 | const PATH_REGEX = /(\/(.+)?)/g
6 |
7 | module.exports = class IsItUp extends Command {
8 | constructor (client) {
9 | super({
10 | name: 'isitup',
11 | category: 'utility',
12 | parameters: [{
13 | type: 'string',
14 | full: true,
15 | missingError: 'commands:isitup.noWebsite'
16 | }]
17 | }, client)
18 | }
19 |
20 | async run ({ t, author, channel }, url) {
21 | url = url.replace(PROTOCOL_REGEX, '').replace(PATH_REGEX, '')
22 | const embed = new SwitchbladeEmbed(author)
23 | channel.startTyping()
24 | const body = await fetch(`https://isitup.org/${url}.json`).then(res => res.json())
25 | if (body.response_code) {
26 | body.response_time *= 1000
27 | embed.setTitle(t('commands:isitup.isUp'))
28 | .setDescription(t('commands:isitup.details', { body }))
29 | } else {
30 | throw new CommandError(t('commands:isitup.isDown'))
31 | }
32 | channel.send(embed).then(() => channel.stopTyping())
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/commands/economy/daily.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed, Constants } = require('../../')
2 |
3 | module.exports = class Daily extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'daily',
7 | category: 'economy',
8 | requirements: { databaseOnly: true }
9 | }, client)
10 | }
11 |
12 | async run ({ t, author, channel }) {
13 | const embed = new SwitchbladeEmbed(author)
14 | channel.startTyping()
15 |
16 | try {
17 | const { collectedMoney } = await this.client.controllers.economy.bonus.claimDaily(author.id)
18 | embed.setDescription(t('commands:daily.claimedSuccessfully', { count: collectedMoney }))
19 | } catch (e) {
20 | embed.setColor(Constants.ERROR_COLOR)
21 | switch (e.message) {
22 | case 'ALREADY_CLAIMED':
23 | embed.setTitle(t('commands:daily.alreadyClaimedTitle'))
24 | .setDescription(t('commands:daily.alreadyClaimedDescription', { time: e.formattedCooldown }))
25 | break
26 | default:
27 | embed.setTitle(t('errors:generic'))
28 | }
29 | }
30 |
31 | channel.send(embed).then(() => channel.stopTyping())
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/commands/moderation/createrole.js:
--------------------------------------------------------------------------------
1 | const { Command, SwitchbladeEmbed, Color, CommandError } = require('../../')
2 |
3 | module.exports = class CreateRole extends Command {
4 | constructor (client) {
5 | super({
6 | name: 'createrole',
7 | category: 'moderation',
8 | requirements: { guildOnly: true, botPermissions: ['MANAGE_ROLES'], permissions: ['MANAGE_ROLES'] },
9 | parameters: [
10 | { type: 'color', required: false },
11 | { type: 'string', full: true, missingError: 'commands:createrole.noParams', required: true }
12 | ]
13 | }, client)
14 | }
15 |
16 | async run ({ channel, guild, author, t }, color = new Color('#ffffff'), name) {
17 | const hexcode = color.rgb(true)
18 | const embed = new SwitchbladeEmbed(author)
19 |
20 | try {
21 | await guild.roles.create({ data: { name, color: hexcode } })
22 | embed
23 | .setTitle(t('commands:createrole.successTitle'))
24 | .setDescription(t('commands:createrole.successMessage', { name }))
25 | .setColor(hexcode)
26 | } catch (err) {
27 | throw new CommandError(t('commands:createrole.errorTitle'))
28 | }
29 |
30 | channel.send(embed)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/structures/Connection.js:
--------------------------------------------------------------------------------
1 | const Utils = require('../utils')
2 |
3 | module.exports = class Connection {
4 | /**
5 | * @param {Object} opts
6 | * @param {string} opts.name
7 | * @param {Client} client
8 | */
9 | constructor (opts, client) {
10 | const options = Utils.createOptionHandler('Connection', opts)
11 |
12 | this.name = options.required('name')
13 |
14 | this.client = client
15 | }
16 |
17 | canLoad () {
18 | return !!(this.client.database && process.env.DASHBOARD_URL)
19 | }
20 |
21 | load () {
22 | return this
23 | }
24 |
25 | get _users () {
26 | return this.client.database.users
27 | }
28 |
29 | get configPattern () {
30 | return {}
31 | }
32 |
33 | checkConfig ([key, value]) {
34 | if (!this.configPattern[key]) return true
35 | return this.configPattern[key](value)
36 | }
37 |
38 | get authCallbackURL () {
39 | return `${process.env.DASHBOARD_URL}/connections/${this.name}/callback/`
40 | }
41 |
42 | async callbackHandler (req) {
43 | const tokens = await this.callback(req)
44 | const connect = await this.client.controllers.connection.connect(req.userId, this, tokens)
45 | return connect
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/structures/command/parameters/types/EmojiParameter.js:
--------------------------------------------------------------------------------
1 | const Parameter = require('./Parameter.js')
2 | const CommandError = require('../../CommandError.js')
3 |
4 | const EMOJI_REGEX = /^<(a)?:(\w+):(\d{16,18})>$/
5 |
6 | module.exports = class EmojiParameter extends Parameter {
7 | static parseOptions (options = {}) {
8 | return {
9 | ...super.parseOptions(options),
10 | sameGuildOnly: !!options.sameGuildOnly
11 | }
12 | }
13 |
14 | static parse (arg, { t, client, guild }) {
15 | const regexResult = EMOJI_REGEX.exec(arg)
16 | if (regexResult) {
17 | const [,,, id] = regexResult
18 | const emoji = client.emojis.cache.get(id)
19 | if (!emoji) throw new CommandError(t('errors:invalidEmoji'), this.showUsage)
20 | if (this.sameGuildOnly && emoji.guild.id !== guild.id) throw new CommandError(t('errors:emojiNotFromSameGuild'))
21 | return emoji
22 | }
23 |
24 | const emoji = (this.sameGuildOnly ? guild : client).emojis.cache.find(e => e.name === arg)
25 | if (!emoji) throw new CommandError(t('errors:invalidEmoji'), this.showUsage)
26 | return emoji
27 | }
28 | }
29 |
30 | module.exports.EMOJI_REGEX = EMOJI_REGEX
31 |
--------------------------------------------------------------------------------