├── .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 | Glitch_White_RGB -------------------------------------------------------------------------------- /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 | 5 | 8 | 11 | 14 | 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 | Icons8 -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------