├── .eslintignore ├── .eslintrc.js ├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── ISSUE_TEMPLATE │ ├── report-a-bug.md │ ├── request-for-enhancement.md │ └── request-for-feature.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .nsprc ├── .travis.yml ├── .vscode └── settings.json ├── Commands ├── .eslintrc.js ├── PM │ ├── _base.js │ ├── afk.js │ ├── config.js │ ├── giveaway.js │ ├── help.js │ ├── join.js │ ├── poll.js │ ├── profile.js │ ├── remindme.js │ ├── say.js │ └── servernick.js ├── Private │ ├── giveaway.js │ ├── index.js │ ├── poll.js │ └── say.js ├── Public │ ├── 8ball.js │ ├── _base.js │ ├── about.js │ ├── afk.js │ ├── alert.js │ ├── anime.js │ ├── appstore.js │ ├── archive.js │ ├── avatar.js │ ├── ban.js │ ├── calc.js │ ├── cat.js │ ├── catfact.js │ ├── choose.js │ ├── convert.js │ ├── cool.js │ ├── count.js │ ├── countdown.js │ ├── disable.js │ ├── dog.js │ ├── dogfact.js │ ├── e621.js │ ├── emoji.js │ ├── emotes.js │ ├── enable.js │ ├── expand.js │ ├── fortune.js │ ├── games.js │ ├── gif.js │ ├── giveaway.js │ ├── help.js │ ├── imgur.js │ ├── info.js │ ├── invite.js │ ├── joke.js │ ├── kick.js │ ├── list.js │ ├── lottery.js │ ├── messages.js │ ├── modlog.js │ ├── mute.js │ ├── nick.js │ ├── nuke.js │ ├── numfact.js │ ├── ping.js │ ├── points.js │ ├── poll.js │ ├── prefix.js │ ├── quiet.js │ ├── ranks.js │ ├── reason.js │ ├── reddit.js │ ├── remindme.js │ ├── roleinfo.js │ ├── roll.js │ ├── room.js │ ├── rss.js │ ├── safebooru.js │ ├── say.js │ ├── shorten.js │ ├── stats.js │ ├── streamers.js │ ├── strike.js │ ├── strikes.js │ ├── tag.js │ ├── time.js │ ├── translate.js │ ├── trivia.js │ ├── twitter.js │ ├── unban.js │ ├── unmute.js │ ├── urban.js │ ├── weather.js │ ├── wiki.js │ ├── wolfram.js │ ├── xkcd.js │ ├── year.js │ └── youtube.js └── Shared │ ├── _base.js │ ├── debug.js │ ├── eval.js │ └── reload.js ├── Configurations ├── auth.template.js ├── commands.js ├── config.template.js ├── config.template.json ├── events.js ├── filter.json ├── ranks.json ├── rss_feeds.json ├── status_messages.json ├── tag_reactions.json ├── tags.json └── trivia.json ├── Database ├── Cursor.js ├── Document.js ├── Driver.js ├── Model.js ├── Query.js ├── Schema.js └── Schemas │ ├── blogSchema.js │ ├── gallerySchema.js │ ├── serverChannelsSchema.js │ ├── serverConfigSchema.js │ ├── serverGallerySchema.js │ ├── serverGamesSchema.js │ ├── serverMembersSchema.js │ ├── serverModlogSchema.js │ ├── serverSchema.js │ ├── trafficSchema.js │ ├── userSchema.js │ └── wikiSchema.js ├── GAwesomeBot.js ├── Internals ├── Boot.js ├── Client.js ├── Constants.js ├── Errors │ ├── GABError.js │ ├── Messages.js │ └── index.js ├── Events │ ├── BaseEvent.js │ ├── EventHandler.js │ ├── channelCreate │ │ └── .gitkeep │ ├── channelDelete │ │ └── GAB.ChannelDelete.js │ ├── channelPinsUpdate │ │ └── .gitkeep │ ├── channelUpdate │ │ └── .gitkeep │ ├── debug │ │ └── .gitkeep │ ├── disconnect │ │ └── .gitkeep │ ├── emojiCreate │ │ └── .gitkeep │ ├── emojiDelete │ │ └── .gitkeep │ ├── emojiUpdate │ │ └── .gitkeep │ ├── error │ │ └── .gitkeep │ ├── guildAvailable │ │ └── .gitkeep │ ├── guildBanAdd │ │ ├── .gitkeep │ │ └── GAB.GuildBanAdd.js │ ├── guildBanRemove │ │ ├── .gitkeep │ │ └── GAB.GuildBanRemove.js │ ├── guildCreate │ │ └── GAB.GuildCreate.js │ ├── guildDelete │ │ └── GAB.GuildDelete.js │ ├── guildMemberAdd │ │ └── GAB.GuildMemberAdd.js │ ├── guildMemberAvailable │ │ └── .gitkeep │ ├── guildMemberRemove │ │ ├── .gitkeep │ │ └── GAB.GuildMemberRemove.js │ ├── guildMemberSpeaking │ │ └── .gitkeep │ ├── guildMemberUpdate │ │ ├── .gitkeep │ │ └── GAB.GuildMemberUpdate.js │ ├── guildMembersChunk │ │ └── .gitkeep │ ├── guildUnavailable │ │ └── GAB.GuildUnavailable.js │ ├── guildUpdate │ │ ├── .gitkeep │ │ └── GAB.GuildUpdate.js │ ├── message │ │ ├── GAB.AFKHandler.js │ │ ├── GAB.MessageCreate.js │ │ ├── GAB.SharedCommandMessageHandler.js │ │ ├── GAB.SpamHandler.js │ │ ├── GAB.UsernameHandler.js │ │ └── GAB.VoteHandler.js │ ├── messageDelete │ │ ├── .gitkeep │ │ └── GAB.MessageDelete.js │ ├── messageDeleteBulk │ │ └── .gitkeep │ ├── messageReactionAdd │ │ └── .gitkeep │ ├── messageReactionRemove │ │ └── .gitkeep │ ├── messageReactionRemoveAll │ │ └── .gitkeep │ ├── messageUpdate │ │ ├── GAB.MessageCommandUpdateHandler.js │ │ └── GAB.MessageUpdate.js │ ├── presenceUpdate │ │ ├── .gitkeep │ │ └── GAB.PresenceUpdate.js │ ├── rateLimit │ │ └── .gitkeep │ ├── ready │ │ └── GAB.Ready.js │ ├── resumed │ │ └── .gitkeep │ ├── roleCreate │ │ └── .gitkeep │ ├── roleDelete │ │ ├── .gitkeep │ │ └── GAB.RoleDelete.js │ ├── roleUpdate │ │ └── .gitkeep │ ├── userUpdate │ │ ├── .gitkeep │ │ ├── GAB.UpdateUsername.js │ │ └── GAB.UserUpdate.js │ ├── voiceStateUpdate │ │ ├── .gitkeep │ │ └── GAB.VoiceStateUpdate.js │ └── warn │ │ └── .gitkeep ├── ExtendableBase.js ├── Extendables │ ├── Postable.js │ └── Readable.js ├── Extensions │ ├── API │ │ ├── .eslintrc.js │ │ ├── Modules │ │ │ ├── Client.js │ │ │ └── Extension.js │ │ ├── Sandbox.js │ │ ├── Structures │ │ │ ├── Channel.js │ │ │ ├── Embed.js │ │ │ ├── Emoji.js │ │ │ ├── Guild.js │ │ │ ├── Member.js │ │ │ ├── Message.js │ │ │ └── User.js │ │ ├── Utils │ │ │ ├── ScopeManager.js │ │ │ └── Utils.js │ │ └── index.js │ ├── EventsHandler.js │ ├── ExtensionManager.js │ └── index.js ├── IPC.js ├── Logger.js ├── NPMScripts │ └── WarnPreInstallWindows.js ├── README.md ├── ShardUtil.js ├── Sharder.js ├── Worker.js ├── WorkerManager.js └── index.js ├── LICENSE ├── Modules ├── ConversionHandler.js ├── Emoji │ ├── Emoji.js │ ├── EmojiUtil.js │ ├── PrepareEndFrames.js │ └── PrepareFrames.js ├── Encryption.js ├── ExtensionRunner.js ├── GAwesomeClient.js ├── GetGuild.js ├── Giphy.js ├── Giveaways.js ├── Imgur.js ├── Lotteries.js ├── ManageCommands.js ├── MarkdownTable.js ├── MessageUtils │ ├── DurationParser.js │ ├── PaginatedEmbed.js │ ├── Parser.js │ ├── ReactionMenus │ │ ├── BaseMenu.js │ │ ├── HelpMenu.js │ │ ├── ReactionBasedMenu.js │ │ └── index.js │ ├── ReminderParser.js │ └── index.js ├── MicrosoftTranslate.js ├── Migration.js ├── ModLog.js ├── NewServer.js ├── Polls.js ├── PostShardedData.js ├── PostTotalData.js ├── README.md ├── RandomAnimals.js ├── String.js ├── Temp.js ├── Timeouts │ ├── Base.js │ ├── Interval.js │ ├── Timeout.js │ └── index.js ├── Traffic.js ├── Trivia.js ├── Utils │ ├── ClearServerStats.js │ ├── FileExists.js │ ├── FilterChecker.js │ ├── Gag.js │ ├── GetFlagForRegion.js │ ├── GetValue.js │ ├── GitHubGist.js │ ├── GlobalDefines.js │ ├── IsURL.js │ ├── MessageOfTheDay.js │ ├── ObjectDefines.js │ ├── RSS.js │ ├── RegExpMaker.js │ ├── RemoveFormatting.js │ ├── SearchiTunes.js │ ├── SetCountdown.js │ ├── SetReminder.js │ ├── Stopwatch.js │ ├── StreamChecker.js │ ├── StreamerUtils.js │ ├── StreamingRSS.js │ ├── StructureExtender.js │ ├── TitlecasePermissions.js │ └── index.js ├── VoiceStatsCollector.js ├── Voicetext.js └── index.js ├── README.md ├── Temp └── .gitkeep ├── Web ├── WebServer.js ├── controllers │ ├── activity.js │ ├── api.js │ ├── auth.js │ ├── blog.js │ ├── dashboard │ │ ├── administration.js │ │ ├── commands.js │ │ ├── delete.js │ │ ├── index.js │ │ ├── other.js │ │ └── stats.js │ ├── debug.js │ ├── donate.js │ ├── extensions.js │ ├── index.js │ ├── landing.js │ ├── maintainer.js │ └── wiki.js ├── helpers.js ├── middleware │ ├── auth.js │ └── index.js ├── parsers.js ├── public │ ├── css │ │ ├── auto-complete.css │ │ ├── bulma-7.css │ │ ├── codemirror-monokai.css │ │ ├── codemirror.css │ │ ├── custom-07aed24a0d.min.css │ │ ├── custom.css │ │ ├── extensions.css │ │ └── simplemde.min.css │ ├── fonts │ │ ├── discord-font-1.eot │ │ ├── discord-font-1.svg │ │ ├── discord-font-1.ttf │ │ ├── discord-font-1.woff │ │ └── icon-discord.css │ ├── img │ │ ├── NEO.png │ │ ├── NEO.webp │ │ ├── admin-console.png │ │ ├── admin-console.webp │ │ ├── bitquote.png │ │ ├── bitquote.webp │ │ ├── demo-activity-old.gif │ │ ├── discord-icon.png │ │ ├── discord-icon.webp │ │ ├── discord-logo.png │ │ ├── discord-logo.webp │ │ ├── gilbert.png │ │ ├── gilbert.webp │ │ ├── header-bg-taco.jpg │ │ ├── header-bg-taco.webp │ │ ├── header-bg.jpg │ │ ├── header-bg.webp │ │ ├── hilbert.png │ │ ├── hilbert.webp │ │ ├── icon.png │ │ ├── icon.webp │ │ ├── ksham.jpg │ │ ├── ksham.webp │ │ ├── media.png │ │ ├── media.webp │ │ ├── mistmurk.png │ │ ├── mistmurk.webp │ │ ├── profile.png │ │ ├── profile.webp │ │ ├── transparent.png │ │ ├── transparent.webp │ │ ├── vlad.png │ │ └── vlad.webp │ └── js │ │ ├── .eslintrc.js │ │ ├── AutoComplete.min.js │ │ ├── FileSaver.min.js │ │ ├── app-c20cb23e57.min.js │ │ ├── app.js │ │ ├── bulma.js │ │ ├── codemirror.min.js │ │ ├── jquery-2.2.0.min.js │ │ ├── showdown.min.js │ │ ├── simplemde.min.js │ │ ├── socket.io-1.4.5.min.js │ │ ├── terminal.min.js │ │ └── update.js ├── routes │ ├── Route.js │ ├── api.js │ ├── dashboard.js │ ├── debug.js │ ├── index.js │ └── maintainer.js └── views │ ├── pages │ ├── 404.ejs │ ├── 503.ejs │ ├── activity.ejs │ ├── admin-admins.ejs │ ├── admin-api-keys.ejs │ ├── admin-auto-translation.ejs │ ├── admin-blocked.ejs │ ├── admin-command-list.ejs │ ├── admin-command-options.ejs │ ├── admin-extension-builder.ejs │ ├── admin-extensions.ejs │ ├── admin-filters.ejs │ ├── admin-gawesome-points.ejs │ ├── admin-logs.ejs │ ├── admin-message-of-the-day.ejs │ ├── admin-messages.ejs │ ├── admin-moderation.ejs │ ├── admin-muted.ejs │ ├── admin-name-display.ejs │ ├── admin-ongoing-activities.ejs │ ├── admin-overview.ejs │ ├── admin-public-data.ejs │ ├── admin-ranks.ejs │ ├── admin-roles.ejs │ ├── admin-rss-feeds.ejs │ ├── admin-stats-collection.ejs │ ├── admin-status-messages.ejs │ ├── admin-streamers.ejs │ ├── admin-strikes.ejs │ ├── admin-tag-reaction.ejs │ ├── admin-tags.ejs │ ├── admin-trivia-sets.ejs │ ├── admin-voicetext-channels.ejs │ ├── blog.ejs │ ├── dashboard.ejs │ ├── donate.ejs │ ├── error.ejs │ ├── extension-installer.ejs │ ├── extensions.ejs │ ├── landing.ejs │ ├── maintainer-big-message.ejs │ ├── maintainer-blocklist.ejs │ ├── maintainer-bot-user.ejs │ ├── maintainer-eval.ejs │ ├── maintainer-homepage.ejs │ ├── maintainer-injection.ejs │ ├── maintainer-logs.ejs │ ├── maintainer-maintainers.ejs │ ├── maintainer-server-list.ejs │ ├── maintainer-shards.ejs │ ├── maintainer-version.ejs │ ├── maintainer-wiki-contributors.ejs │ ├── maintainer.ejs │ ├── paperwork.ejs │ └── wiki.ejs │ └── partials │ ├── admin-command-item.ejs │ ├── admin-menu.ejs │ ├── admin-sudo.ejs │ ├── adspace.ejs │ ├── blog-article.ejs │ ├── blog-composer.ejs │ ├── dashboard-header.ejs │ ├── dashboard-socket-updates.ejs │ ├── extension-builder.ejs │ ├── extension-gallery.ejs │ ├── footer.ejs │ ├── form-buttons-bar.ejs │ ├── form-buttons.ejs │ ├── head-build.ejs │ ├── head.ejs │ ├── header.ejs │ ├── maintainer-menu.ejs │ ├── menu-toggle.ejs │ ├── my-extensions.ejs │ ├── no-results.ejs │ ├── scroll-top-button.ejs │ ├── servers-section.ejs │ ├── users-section.ejs │ ├── wiki-edit-history.ejs │ ├── wiki-editor.ejs │ └── wiki-search-results.ejs ├── extensions └── .gitignore ├── logs └── .gitignore ├── master.js ├── package-lock.json ├── package.json ├── travis ├── build.sh ├── pushback.sh └── test.sh └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | *.min.js 2 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code ownership file 2 | * @GilbertGobbels 3 | 4 | /.github/ @KingDGrizzle 5 | /Configurations/ @GilbertGobbels @KingDGrizzle 6 | /Commands/ @KingDGrizzle 7 | /Database/ @GilbertGobbels 8 | /Internals/Events/ @KingDGrizzle 9 | /Internals/ @GilbertGobbels @KingDGrizzle 10 | /Modules/ @KingDGrizzle @GilbertGobbels 11 | /Web/ @GilbertGobbels 12 | 13 | /Internals/Logger.js @GilbertGobbels 14 | /Internals/Sharder.js @GilbertGobbels 15 | /Internals/IPC.js @GilbertGobbels 16 | /Modules/GAwesomeClient.js @GilbertGobbels 17 | /master.js @GilbertGobbels 18 | /package.json @KingDGrizzle 19 | /.eslintrc.js @KingDGrizzle 20 | /LICENSE @GilbertGobbels 21 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | **This repository's issues section is only for bug tracking, code contribution, questions about the inner workings of GAB/design decisions or other code-related purposes. For help on how to use GAB, please visit [our Discord Server](https://discord.gg/UPJ2xt6) instead.** 4 | 5 | GAwesomeBot is open source, as such, anyone can clone, fork, and host their own instance of GAB. Before you do so, please make sure you're up-to-date on [our license](https://github.com/GilbertGobbels/GAwesomeBot/blob/development/LICENSE) and its terms. If you want to contribute to GAB's development, you can help us track down bugs and reporting them [here](https://github.com/GilbertGobbels/GAwesomeBot/issues). If you want to contribute to the codebase, make sure you follow [our ESLint rules](https://github.com/GilbertGobbels/GAwesomeBot/blob/development/.eslintrc.json), your Pull Request must not contain any ESLint errors, or it will not be merged. When naming your commits, please use the "\ \" format. To find out which gitmoji should be used when, go [here](https://gitmoji.carloscuesta.me/). 6 | 7 | *Pro Tip: Using an editor that has ESLint syntax checking is super useful when working on GAB!* 8 | 9 | ## Setup 10 | To get ready to edit GAwesomeBot's code, do the following: 11 | 12 | 1. Fork & clone the repository, and make sure you're on the **development** branch 13 | 2. Run `npm install` 14 | 3. Start coding, making sure to document changes using JSDoc accordingly. 15 | 4. Run the bot, and test that your changes work. 16 | 5. [Submit a pull request](https://github.com/GilbertGobbels/GAwesomeBot/compare) 17 | 18 | After an initial review, your issue/PR will either be closed, or added [here](https://github.com/GilbertGobbels/GAwesomeBot/projects/1). 19 | 20 | Happy coding, GAwesomeUsers! 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | **Please describe the problem you are having in as much detail as possible:** 8 | 9 | 10 | **Include some reproduction steps:** 11 | 12 | 13 | **Further details:** 14 | 15 | - [ ] The problem happens on the official GAwesomeBot 16 | - [ ] The problem occurs on a self-host 17 | - Node.JS version (If applicable): 18 | - Operating System (If applicable): 19 | - GAwesomeBot version AND branch: 20 | - Priority this issue should have – please be realistic and elaborate if possible: 21 | - [ ] Low 22 | - [ ] Medium 23 | - [ ] High 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/report-a-bug.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Report a Bug 3 | about: Alert us of a problem with GAwesomeBot 4 | 5 | --- 6 | 7 | 12 | 13 | 16 | 17 | **Please describe the problem you are having in as much detail as possible:** 18 | 19 | 20 | **Include some reproduction steps (How can we cause the bug to happen?):** 21 | 22 | 23 | **Further details:** 24 | 25 | 28 | 29 | - [ ] The problem happens on the official GAwesomeBot 30 | - [ ] The problem occurs on a self-host 31 | - Node.JS version (If applicable): 32 | - Operating System (If applicable): 33 | - GAwesomeBot version AND branch: 34 | - Priority this issue should have – please be realistic and elaborate if possible: 35 | - [ ] Low (Only partially affects the usage of a feature or command) 36 | - [ ] Medium (Affects the full usage of a feature or command) 37 | - [ ] High (Prevents the full usage of a feature or command) 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/request-for-enhancement.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Request for Enhancement 3 | about: Suggest enhancements or improvements to existing features. 4 | 5 | --- 6 | 7 | 12 | 13 | 16 | 17 | **Describe the existing feature's behavior you wish to see enhanced:** 18 | 19 | 20 | **Describe your suggestion to enhance the feature's behavior described above:** 21 | 22 | 23 | **Explain why your suggestion would enhance this feature for all or a select group of GAB users, and does not degrade the feature for any other GAB users:** 24 | 25 | 28 | 29 | - GAwesomeBot version AND branch: 30 | - [ ] This enhancement is backwards compatible and does not require a change of user behavior 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/request-for-feature.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Request for Feature 3 | about: Suggest a new Feature or command. 4 | 5 | --- 6 | 7 | 12 | 13 | 16 | 17 | **Describe the new feature or command you wish to see in GAB:** 18 | 19 | 20 | **Explain why your new feature or command would improve GAB for all its users:** 21 | 22 | 23 | **Describe all current features and/or commands that would be modified by your new feature or command:** 24 | 25 | 26 | **Anything else you wish to say:** 27 | 28 | 31 | 32 | - GAwesomeBot version AND branch: 33 | - [ ] This feature is backwards compatible and does not require a change of user behavior 34 | - [ ] This feature only modifies GAB for self-hosters 35 | - [ ] This feature is possible to recreate in a GAB Extension 36 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **Please describe the changes this PR makes and why it should be merged:** 2 | 3 | **What does this PR do:** 4 | - [ ] This PR changes internal functions, modules and/or utilities 5 | - [ ] This PR modifies the Extension API 6 | - [ ] This PR modifies commands 7 | - [ ] This PR changes metadata for commands (such as usage), updated in `commands.js` 8 | - [ ] This PR modifies Web processing and/or content 9 | - [ ] This PR modifies static/ejs content 10 | - [ ] This PR modifies request processing (controllers & middleware) 11 | - [ ] This PR modifies paths to existing resources (routes) 12 | - [ ] This PR **only** includes non-code changes, like changes to default configurations, README, typos, etc. 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Configurations/config.json 2 | Configurations/config.js 3 | Configurations/auth.js 4 | node_modules/ 5 | .idea/ 6 | package-cache*.json 7 | 8 | Temp/*.json 9 | -------------------------------------------------------------------------------- /.nsprc: -------------------------------------------------------------------------------- 1 | { 2 | "exceptions": [ 3 | "https://nodesecurity.io/advisories/57", 4 | "https://nodesecurity.io/advisories/309", 5 | "https://nodesecurity.io/advisories/77", 6 | "https://nodesecurity.io/advisories/28", 7 | "https://nodesecurity.io/advisories/29" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | cache: 5 | directories: 6 | - "$HOME/.npm" 7 | install: npm ci 8 | jobs: 9 | include: 10 | - stage: test 11 | script: bash ./travis/test.sh 12 | - stage: build 13 | if: type = push 14 | script: bash ./travis/build.sh 15 | after_success: 16 | - wget https://raw.githubusercontent.com/KingDGrizzle/travis-ci-discord-webhook/master/send.sh 17 | - chmod +x send.sh 18 | - ./send.sh success $WEBHOOK_URL 19 | after_failure: 20 | - wget https://raw.githubusercontent.com/KingDGrizzle/travis-ci-discord-webhook/master/send.sh 21 | - chmod +x send.sh 22 | - ./send.sh failure $WEBHOOK_URL 23 | dist: trusty 24 | sudo: true 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.eol": "\n" 3 | } -------------------------------------------------------------------------------- /Commands/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: "../.eslintrc.js", 3 | rules: { 4 | "max-len": "off", 5 | "no-unused-vars": "off" 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /Commands/PM/_base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a full documented base file for PM commands 3 | * Use this as a base for coding 4 | */ 5 | module.exports = async (main, msg, commandData) => { 6 | /** 7 | * @param {Discord.Message} msg The raw message 8 | * Suffix is present in the msg object 9 | * UserDocument is in the message author property 10 | */ 11 | /** 12 | * @type {Object} 13 | * @param commandData Object containing the command name and usage. 14 | * Use `client.getPMCommandMetadata(commandData.name)` for other things 15 | */ 16 | /** 17 | * @type {Object} 18 | * @param main Object containing the most important things 19 | * Feel free to deconstruct it using { Value } 20 | * @property {Discord.Client} client The client object 21 | * @property {Object} configJS The config js object 22 | * @property {Object} Utils Util object 23 | * @property {Object} utils Util object 24 | * @property {Object} Constants Constants Object 25 | * configJSON is in the global 26 | */ 27 | }; 28 | -------------------------------------------------------------------------------- /Commands/PM/afk.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ Constants: { Colors } }, msg, commandData) => { 2 | if (msg.suffix) { 3 | if (msg.suffix === ".") { 4 | msg.author.userDocument.query.set("afk_message", null); 5 | msg.send({ 6 | embed: { 7 | color: Colors.GREEN, 8 | title: `Welcome back! 🎊`, 9 | description: `I removed your global AFK message.`, 10 | footer: { 11 | text: `You can set a new one by running "${commandData.name} "`, 12 | }, 13 | }, 14 | }); 15 | } else { 16 | msg.author.userDocument.query.set("afk_message", msg.suffix); 17 | msg.send({ 18 | embed: { 19 | color: Colors.GREEN, 20 | description: `Alright, I'll show that when someone mentions you on a server. 👌`, 21 | footer: { 22 | text: `Use "${commandData.name} ." to remove it`, 23 | }, 24 | }, 25 | }); 26 | } 27 | await msg.author.userDocument.save().catch(err => { 28 | logger.verbose(`Failed to save user document for AFK message >.>\n`, err); 29 | }); 30 | } else if (msg.author.userDocument.afk_message) { 31 | msg.send({ 32 | embed: { 33 | color: Colors.BLUE, 34 | title: `Your current global AFK message is:`, 35 | description: `${msg.author.userDocument.afk_message}`, 36 | footer: { 37 | text: `Use "${commandData.name} " to change it or "${commandData.name} ." to remove it.`, 38 | }, 39 | }, 40 | }); 41 | } else { 42 | msg.send({ 43 | embed: { 44 | color: Colors.LIGHT_RED, 45 | description: `You don't have a global AFK message set right now! ⌨️`, 46 | footer: { 47 | text: `You can set one by running "${commandData.name} "`, 48 | }, 49 | }, 50 | }); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /Commands/PM/config.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ configJS, Constants: { Colors } }, msg, commandData) => { 2 | msg.reply({ 3 | embed: { 4 | color: Colors.RED, 5 | title: `This command is deprecated!`, 6 | description: `You can visit the dashboard by going [here](${configJS.hostingURL}dashboard)`, 7 | }, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /Commands/PM/help.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ client, configJS, Constants: { Colors } }, msg, commandData) => { 2 | const info = client.getPMCommandList() 3 | .map(command => `${command} ${client.getPMCommandMetadata(command).usage}`).sort(); 4 | msg.reply({ 5 | embed: { 6 | color: Colors.BLUE, 7 | title: `Here are the PM commands you can use ~~--~~ Learn more by clicking here 📘`, 8 | url: `${configJS.hostingURL}wiki`, 9 | description: `\`\`\`${info.join("\n")}\`\`\``, 10 | }, 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /Commands/PM/join.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ client, configJS, Constants: { Colors } }, msg, commandData) => { 2 | msg.reply({ 3 | embed: { 4 | color: Colors.SUCCESS, 5 | title: `Thank you for choosing ${client.user.username}! 😊`, 6 | description: `Click [here](${configJS.oauthLink.format({ id: client.user.id })}) to invite me to your server!`, 7 | }, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /Commands/PM/poll.js: -------------------------------------------------------------------------------- 1 | module.exports = async (main, msg, commandData) => { 2 | if (msg.suffix && msg.suffix.includes("|")) { 3 | const params = msg.suffix.split("|"); 4 | const svrid = params[0].trim(); 5 | const chname = params[1].trim(); 6 | if (svrid && chname) { 7 | const initMsg = await msg.channel.send({ 8 | embed: { 9 | color: 0x3669FA, 10 | author: { 11 | name: main.client.user.username, 12 | icon_url: main.client.user.displayAvatarURL(), 13 | url: "https://github.com/GilbertGobbels/GAwesomeBot", 14 | }, 15 | description: "⌛ Preparing Poll...", 16 | footer: { 17 | text: "Please wait; we're working hard behind the scenes!", 18 | }, 19 | }, 20 | }); 21 | const relayRes = await main.client.relayCommand("poll", { str: svrid, usrid: msg.author.id }, { initMsg: initMsg.id, usrid: msg.author.id, svrid, chname }); 22 | let errMsg = "An unknown Error occurred"; 23 | if (relayRes === "none") errMsg = "The requested server was not found. Double check for typo's!"; 24 | if (relayRes === "multi") errMsg = "Multiple servers were found. Set a unique server nick or use server ID instead of name."; 25 | if (relayRes !== true) { 26 | initMsg.edit({ 27 | embed: { 28 | author: { 29 | name: main.client.user.username, 30 | icon_url: main.client.user.displayAvatarURL(), 31 | url: "https://github.com/GilbertGobbels/GAwesomeBot", 32 | }, 33 | description: "Something went wrong while fetching server data!", 34 | color: 0xFF0000, 35 | footer: { 36 | text: errMsg, 37 | }, 38 | }, 39 | }); 40 | } 41 | return; 42 | } 43 | } 44 | logger.silly(`Invalid parameters \`${msg.suffix}\` provided for ${commandData.name}`, { usrid: msg.author.id }); 45 | msg.send({ 46 | embed: { 47 | author: { 48 | name: main.client.user.username, 49 | icon_url: main.client.user.displayAvatarURL(), 50 | url: "https://github.com/GilbertGobbels/GAwesomeBot", 51 | }, 52 | color: 0xFFFF00, 53 | description: `🗯 Correct usage is: \`${commandData.name} ${commandData.usage}\``, 54 | }, 55 | }); 56 | }; 57 | -------------------------------------------------------------------------------- /Commands/PM/remindme.js: -------------------------------------------------------------------------------- 1 | const remind = require("../../Modules/MessageUtils/ReminderParser"); 2 | const moment = require("moment"); 3 | 4 | module.exports = async ({ client, Constants: { Colors, Text } }, msg, commandData) => { 5 | const { userDocument } = msg.author; 6 | if (msg.suffix) { 7 | const result = await remind(client, userDocument, userDocument.query, msg.suffix); 8 | if (result === "ERR") { 9 | msg.sendInvalidUsage(commandData); 10 | } else { 11 | msg.send({ 12 | embed: { 13 | color: Colors.SUCCESS, 14 | description: `Alright, I'll remind you ${moment.duration(result).humanize(true)} ⏰`, 15 | }, 16 | }); 17 | } 18 | } else { 19 | const fields = []; 20 | userDocument.reminders.forEach(reminderDocument => { 21 | fields.push({ 22 | name: `__${reminderDocument.name}__`, 23 | value: `${moment(reminderDocument.expiry_timestamp).fromNow()}`, 24 | inline: true, 25 | }); 26 | }); 27 | if (userDocument.reminders.length === 0) { 28 | msg.send({ 29 | embed: { 30 | color: Colors.SOFT_ERR, 31 | title: "No reminders set 😴", 32 | description: `You don't have any reminders set yet; use \`${commandData.name} ${commandData.usage}\` to set one.`, 33 | }, 34 | }); 35 | } else { 36 | msg.send({ 37 | embed: { 38 | color: Colors.INFO, 39 | title: `Your reminders:`, 40 | fields, 41 | }, 42 | }); 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /Commands/PM/say.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ client, Constants: { Colors, Text } }, msg, commandData) => { 2 | if (msg.suffix && msg.suffix.includes("|")) { 3 | const params = msg.suffix.split("|"); 4 | const svrname = params[0].trim(); 5 | const chname = params[1].trim(); 6 | if (svrname && chname) { 7 | const initMsg = await msg.send({ 8 | embed: { 9 | color: 0x3669FA, 10 | author: { 11 | name: client.user.username, 12 | icon_url: client.user.displayAvatarURL(), 13 | url: "https://github.com/GilbertGobbels/GAwesomeBot", 14 | }, 15 | description: "⌛ Fetching Data...", 16 | footer: { 17 | text: "Get excited!", 18 | }, 19 | }, 20 | }); 21 | const relay = () => client.relayCommand("say", { str: svrname, usrid: msg.author.id }, { initMsg: initMsg.id, usrid: msg.author.id, svrname, chname }); 22 | setTimeout(async () => { 23 | const relayRes = await relay(); 24 | let errMsg = "An unknown Error occurred"; 25 | if (relayRes === "none") errMsg = "The requested server was not found. Double check for typo's!"; 26 | if (relayRes === "multi") errMsg = "Multiple servers were found. Set a unique server nick or use server ID instead of name."; 27 | if (relayRes !== true) { 28 | initMsg.edit({ 29 | embed: { 30 | author: { 31 | name: client.user.username, 32 | icon_url: client.user.displayAvatarURL(), 33 | url: "https://github.com/GilbertGobbels/GAwesomeBot", 34 | }, 35 | description: "Something went wrong while fetching server data!", 36 | color: Colors.ERROR, 37 | footer: { 38 | text: errMsg, 39 | }, 40 | }, 41 | }); 42 | } 43 | }, 200); 44 | return; 45 | } 46 | } 47 | logger.silly(`Invalid parameters \`${msg.suffix}\` provided for ${commandData.name}`, { usrid: msg.author.id }); 48 | msg.sendInvalidUsage(commandData); 49 | }; 50 | -------------------------------------------------------------------------------- /Commands/Private/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | poll: require("./poll.js"), 3 | giveaway: require("./giveaway.js"), 4 | say: require("./say.js"), 5 | }; 6 | -------------------------------------------------------------------------------- /Commands/Public/8ball.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 8ball! 3 | */ 4 | module.exports = async (main, documents, msg, commandData) => { 5 | const { Utils: { PromiseWait }, Constants: { Colors, EightBall: { WaitTimes, Answers } } } = main; 6 | if (msg.suffix) { 7 | await msg.send({ 8 | embed: { 9 | color: Colors.BLUE, 10 | description: `Asking the 🎱 your question..`, 11 | footer: { 12 | text: `Please wait...`, 13 | }, 14 | }, 15 | }); 16 | await PromiseWait(WaitTimes[Math.floor(Math.random() * WaitTimes.length)]); 17 | const randomChoice = Answers[Math.floor(Math.random() * Answers.length)]; 18 | msg.send({ 19 | embed: { 20 | description: `Our 🎱 replied with:\`\`\`css\n${randomChoice.answer}\`\`\``, 21 | color: randomChoice.color, 22 | }, 23 | }); 24 | } else { 25 | logger.verbose(`No suffix was provided for the "${commandData.name}" command`, { svrid: msg.guild.id, chid: msg.channel.id, usrid: msg.author.id }); 26 | msg.send({ 27 | embed: { 28 | color: Colors.RED, 29 | description: `You tell me... 😜`, 30 | footer: { 31 | text: `Psst. You need to ask the 8ball a question, ya'know?`, 32 | }, 33 | }, 34 | }); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /Commands/Public/_base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a full documented base file for Public commands 3 | * Use this as a base for coding 4 | */ 5 | module.exports = async (main, documents, msg, commandData) => { 6 | /** 7 | * @param {Discord.Message} msg The message object 8 | * Suffix is present in the msg object 9 | * @type {Object} 10 | * @param commandData Object containing the command name, usage and description. 11 | * Use `client.getPMCommandMetadata(commandData.name)` for other things 12 | */ 13 | /** 14 | * @type {Object} 15 | * @param documents Object containing all documents you need. 16 | * Available documents: 17 | * * serverDocument 18 | * * serverQueryDocument 19 | * * channelDocument 20 | * * channelQueryDocument 21 | * * memberDocument 22 | * * memberQueryDocument 23 | * * userDocument 24 | */ 25 | /** 26 | * @type {Object} 27 | * @param main Object containing the most important things 28 | * Feel free to deconstruct it using { Value } 29 | * @property {Discord.Client} client The client object 30 | * @property {Object} configJS The config js object 31 | * @property {Object} Utils Util object 32 | * @property {Object} utils Util object 33 | * @property {Object} Constants Constants Object 34 | * configJSON is in the global 35 | */ 36 | }; 37 | -------------------------------------------------------------------------------- /Commands/Public/about.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ client, configJS, Constants: { Colors } }, documents, msg, commandData) => { 2 | const description = [ 3 | `Hello! I'm **${client.user.username}**, running **GAwesomeBot ${configJSON.version} on branch ${configJSON.branch}**, the best Discord Bot! 🐬`, 4 | `Created by Gilbert and [the GAwesomeDevs](${configJS.hostingURL}#team)! ❤`, 5 | `Built using [Node.js](https://nodejs.org/en/) and [Discord.js](https://discord.js.org/#/)`, 6 | ].join("\n"); 7 | const fields = []; 8 | configJS.hostingURL && fields.push({ 9 | name: `Want to learn more?`, 10 | value: `Click [here](${configJS.hostingURL})`, 11 | inline: true, 12 | }); 13 | configJS.discordLink && fields.push({ 14 | name: `Need some help?`, 15 | value: `Join our [Discord Server](${configJS.discordLink})`, 16 | inline: true, 17 | }); 18 | msg.send({ 19 | embed: { 20 | color: Colors.LIGHT_GREEN, 21 | fields: fields.length ? fields : [], 22 | description, 23 | footer: { 24 | text: `Use "${msg.guild.commandPrefix}help" to list all commands that you can use in this server`, 25 | }, 26 | }, 27 | }); 28 | }; 29 | -------------------------------------------------------------------------------- /Commands/Public/afk.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ Constants: { Colors } }, { memberDocument, memberQueryDocument }, msg, commandData) => { 2 | if (msg.suffix) { 3 | if (msg.suffix === ".") { 4 | memberQueryDocument.set("afk_message", null); 5 | msg.send({ 6 | embed: { 7 | color: Colors.GREEN, 8 | title: `Welcome back! 🎊`, 9 | description: `I removed your AFK message in this server.`, 10 | footer: { 11 | text: `You can set a new one by using "${msg.guild.commandPrefix}${commandData.name} "`, 12 | }, 13 | }, 14 | }); 15 | } else { 16 | memberQueryDocument.set("afk_message", msg.suffix); 17 | msg.send({ 18 | embed: { 19 | color: Colors.GREEN, 20 | description: `Alright, I will now show that message when you are mentioned in chat. 👌`, 21 | footer: { 22 | text: `Use "${msg.guild.commandPrefix}${commandData.name} ." to remove it`, 23 | }, 24 | }, 25 | }); 26 | } 27 | } else if (memberDocument.afk_message) { 28 | msg.send({ 29 | embed: { 30 | color: Colors.BLUE, 31 | title: `Your current AFK message is:`, 32 | description: `${memberDocument.afk_message}`, 33 | footer: { 34 | text: `Use "${msg.guild.commandPrefix}${commandData.name} " to change it or "${msg.guild.commandPrefix}${commandData.name} ." to remove it.`, 35 | }, 36 | }, 37 | }); 38 | } else { 39 | msg.send({ 40 | embed: { 41 | color: Colors.LIGHT_RED, 42 | description: `You don't have an AFK message set right now! ⌨`, 43 | footer: { 44 | text: `You can set one by using "${msg.guild.commandPrefix}${commandData.name} "`, 45 | }, 46 | }, 47 | }); 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /Commands/Public/alert.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ client, Constants: { Colors } }, { serverDocument }, msg, commandData) => { 2 | if (!msg.suffix) { 3 | msg.send({ 4 | embed: { 5 | color: Colors.RED, 6 | description: `You need to provide a message for this alert!`, 7 | }, 8 | }); 9 | } else { 10 | const embedObj = { 11 | embed: { 12 | color: Colors.LIGHT_ORANGE, 13 | title: `Alert from ${msg.author.tag}`, 14 | description: `In #${msg.channel.name} (${msg.channel}) on **${msg.guild}**\n\`\`\`\n${msg.suffix}\`\`\``, 15 | thumbnail: { 16 | url: msg.author.displayAvatarURL(), 17 | }, 18 | }, 19 | }; 20 | client.messageBotAdmins(msg.guild, serverDocument, embedObj); 21 | msg.send({ 22 | embed: { 23 | color: Colors.LIGHT_GREEN, 24 | description: `I've alerted the admins about it! ⚠️`, 25 | }, 26 | }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /Commands/Public/appstore.js: -------------------------------------------------------------------------------- 1 | const iTunes = require("../../Modules/Utils/SearchiTunes"); 2 | const ArgParser = require("../../Modules/MessageUtils/Parser"); 3 | 4 | module.exports = async ({ Constants: { Colors } }, documents, msg, commandData) => { 5 | if (msg.suffix) { 6 | const apps = ArgParser.parseQuoteArgs(msg.suffix, ","); 7 | const results = []; 8 | for (const app of apps) { 9 | let res; 10 | try { 11 | [res] = await iTunes({ entity: "software", country: "US", term: app, limit: 1 }); 12 | } catch (err) { 13 | logger.verbose(`Couldn't find any Apple app called "${app}"`, { svrid: msg.guild.id, chid: msg.channel.id, usrid: msg.author.id }); 14 | results.push({ 15 | embed: { 16 | color: 0xFF0000, 17 | description: `There were no results for the app called \`${app}\`. ❌`, 18 | footer: { 19 | text: `You should try searching for the app again, making sure you spelt it right.`, 20 | }, 21 | }, 22 | }); 23 | } 24 | if (res) { 25 | results.push({ 26 | embed: { 27 | color: 0x00FF00, 28 | author: { 29 | name: `By ${res.artistName}`, 30 | url: res.sellerUrl ? res.sellerUrl : "", 31 | }, 32 | thumbnail: { 33 | url: res.artworkUrl100 ? res.artworkUrl100 : "", 34 | }, 35 | title: `__${res.trackCensoredName}__`, 36 | description: `A quick summary: \`\`\`\n${res.description.split("\n")[0]}\`\`\``, 37 | url: `${res.trackViewUrl}`, 38 | footer: { 39 | text: `Rated ${res.averageUserRating} ⭐ | ${res.formattedPrice.toLowerCase() === "free" ? "This app is free" : `The price of the app is ${res.formattedPrice}`}`, 40 | }, 41 | }, 42 | }); 43 | } 44 | } 45 | for (const msgObj of results) { 46 | msg.channel.send(msgObj).catch(err => logger.debug(`Failed to send appstore result.`, {}, err)); 47 | } 48 | } else { 49 | msg.send({ 50 | embed: { 51 | color: Colors.LIGHT_RED, 52 | description: `[What app would you like to find today...?](https://www.apple.com/itunes/charts/free-apps/) 🤔`, 53 | }, 54 | }); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /Commands/Public/avatar.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ client, Constants: { Colors } }, { serverDocument }, msg, commandData) => { 2 | const handleMissingMember = () => { 3 | logger.verbose(`Couldn't find any user or the user doesn't exist so "${commandData.name}" command can't be ran`, { svrid: msg.guild.id, chid: msg.channel.id, usrid: msg.author.id }); 4 | msg.send({ 5 | embed: { 6 | color: Colors.LIGHT_ORANGE, 7 | title: `I don't know who that is!`, 8 | description: `You can admire my beautiful face instead! 💖`, 9 | image: { 10 | url: client.user.displayAvatarURL({ size: 512 }), 11 | }, 12 | }, 13 | }); 14 | }; 15 | 16 | const sendAvatarImage = (m, isUserOnly) => { 17 | msg.send({ 18 | embed: { 19 | title: `@__${isUserOnly ? m.tag : client.getName(serverDocument, m)}__'s Avatar`, 20 | color: Colors.BLUE, 21 | image: { 22 | url: isUserOnly ? m.displayAvatarURL({ size: 512 }) : m.user.displayAvatarURL({ size: 512 }), 23 | }, 24 | }, 25 | }); 26 | }; 27 | 28 | let { member } = msg; 29 | let fetchedUsr = false; 30 | if (msg.suffix && msg.suffix !== "me" && !/^\d+$/.test(msg.suffix.trim())) { 31 | member = client.memberSearch(msg.suffix.trim(), msg.guild); 32 | } else if (msg.suffix && /^\d+$/.test(msg.suffix.trim())) { 33 | member = client.users.fetch(msg.suffix.trim(), true); 34 | fetchedUsr = true; 35 | } 36 | if (member) { 37 | if (member instanceof Promise) { 38 | try { 39 | member = await member; 40 | } catch (err) { 41 | return handleMissingMember(); 42 | } 43 | if (member && ((!fetchedUsr && member.user) || (fetchedUsr && member))) sendAvatarImage(member, fetchedUsr); 44 | } else { 45 | sendAvatarImage(member, false); 46 | } 47 | } else { 48 | handleMissingMember(); 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /Commands/Public/cat.js: -------------------------------------------------------------------------------- 1 | const { RandomAnimals } = require("../../Modules/"); 2 | 3 | module.exports = async ({ Constants: { Colors } }, documents, msg, commandData) => { 4 | await msg.send({ 5 | embed: { 6 | color: Colors.INFO, 7 | title: `We're getting you a cute cat picture 😺`, 8 | description: `Please stand by...`, 9 | }, 10 | }); 11 | try { 12 | const cat = await RandomAnimals.cat(); 13 | if (cat) { 14 | msg.send({ 15 | embed: { 16 | color: Colors.LIGHT_GREEN, 17 | title: `Here's your adorale cat picture! Meow! 🐱`, 18 | image: { 19 | url: cat, 20 | }, 21 | }, 22 | }); 23 | } 24 | } catch (err) { 25 | return msg.send({ 26 | embed: { 27 | color: Colors.SOFT_ERR, 28 | title: `Meow... 😿`, 29 | description: `I failed to fetch a cat picture...`, 30 | footer: { 31 | text: `Pwease try again...`, 32 | }, 33 | }, 34 | }); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /Commands/Public/catfact.js: -------------------------------------------------------------------------------- 1 | const { get } = require("snekfetch"); 2 | const PaginatedEmbed = require("../../Modules/MessageUtils/PaginatedEmbed"); 3 | 4 | module.exports = async ({ Constants: { Colors, Text, APIs } }, { serverDocument }, msg, commandData) => { 5 | let number = msg.suffix; 6 | if (number < 1) number = serverDocument.config.command_fetch_properties.default_count; 7 | else if (number > serverDocument.config.command_fetch_properties.max_count) number = serverDocument.config.command_fetch_properties.max_count; 8 | else if (isNaN(number)) number = serverDocument.config.command_fetch_properties.default_count; 9 | else number = parseInt(number); 10 | 11 | const { body, statusCode, statusText } = await get(APIs.CATFACT(number)); 12 | if (statusCode === 200 && body && body.data.length) { 13 | const descriptions = []; 14 | body.data.forEach(d => { 15 | descriptions.push(d.fact); 16 | }); 17 | const menu = new PaginatedEmbed(msg, { 18 | color: Colors.RESPONSE, 19 | title: `Cat fact {currentPage} out of {totalPages}`, 20 | footer: ``, 21 | }, { 22 | descriptions, 23 | }); 24 | await menu.init(); 25 | } else { 26 | logger.verbose(`Failed to fetch cat facts...`, { svrid: msg.guild.id, chid: msg.channel.id, usrid: msg.author.id, statusCode, err: statusText }); 27 | msg.send({ 28 | embed: { 29 | color: Colors.SOFT_ERR, 30 | title: Text.ERROR_TITLE(), 31 | description: `I was unable to fetch your purrfect cat facts...`, 32 | }, 33 | }); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /Commands/Public/choose.js: -------------------------------------------------------------------------------- 1 | const ArgParser = require("../../Modules/MessageUtils/Parser"); 2 | 3 | module.exports = async ({ Constants: { Colors, Text } }, documents, msg, commandData) => { 4 | if (msg.suffix) { 5 | const choices = ArgParser.parseQuoteArgs(msg.suffix, msg.suffix.includes("|") ? "|" : " "); 6 | msg.send({ 7 | embed: { 8 | color: Colors.RESPONSE, 9 | title: `I choose:`, 10 | description: `\`\`\`css\n${choices.random.trim()}\`\`\``, 11 | footer: { 12 | text: `I chose this out of ${choices.length} option${choices.length === 1 ? "" : "s"}!`, 13 | }, 14 | }, 15 | }); 16 | } else { 17 | logger.verbose(`No options given for "${commandData.name}" command!`, { svrid: msg.guild.id, chid: msg.channel.id, usrid: msg.author.id }); 18 | msg.sendInvalidUsage(commandData); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /Commands/Public/disable.js: -------------------------------------------------------------------------------- 1 | const ManageCommands = require("../../Modules/ManageCommands"); 2 | 3 | module.exports = async (main, documents, msg, commandData) => { 4 | const disableCommand = new ManageCommands(main, documents, msg, commandData); 5 | if (!msg.suffix) { 6 | await disableCommand.listDisabled(); 7 | } else if (disableCommand.parse("DISABLE")) { 8 | await disableCommand.executeDisable(); 9 | } else { 10 | await disableCommand.listDisabled(); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /Commands/Public/dog.js: -------------------------------------------------------------------------------- 1 | const { RandomAnimals } = require("../../Modules/"); 2 | 3 | module.exports = async ({ Constants: { Colors } }, documents, msg, commandData) => { 4 | await msg.send({ 5 | embed: { 6 | color: Colors.INFO, 7 | title: `We're getting you a cute dog picture 🐶`, 8 | description: `Please stand by...`, 9 | }, 10 | }); 11 | try { 12 | const dog = await RandomAnimals.dog(); 13 | if (dog) { 14 | msg.send({ 15 | embed: { 16 | color: Colors.LIGHT_GREEN, 17 | title: `Here's your adorale dog picture! Woof! 🐶`, 18 | image: { 19 | url: dog, 20 | }, 21 | }, 22 | }); 23 | } 24 | } catch (err) { 25 | return msg.send({ 26 | embed: { 27 | color: Colors.SOFT_ERR, 28 | description: `I failed to fetch a dog picture...`, 29 | footer: { 30 | text: `Pwease try again...`, 31 | }, 32 | }, 33 | }); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /Commands/Public/dogfact.js: -------------------------------------------------------------------------------- 1 | const { get } = require("snekfetch"); 2 | const PaginatedEmbed = require("../../Modules/MessageUtils/PaginatedEmbed"); 3 | 4 | module.exports = async ({ Constants: { Colors, Text, APIs } }, { serverDocument }, msg, commandData) => { 5 | let number = msg.suffix; 6 | if (number < 1) number = serverDocument.config.command_fetch_properties.default_count; 7 | else if (number > serverDocument.config.command_fetch_properties.max_count) number = serverDocument.config.command_fetch_properties.max_count; 8 | else if (isNaN(number)) number = serverDocument.config.command_fetch_properties.default_count; 9 | else number = parseInt(number); 10 | 11 | const { body, statusCode, statusText } = await get(APIs.DOGFACT(number)); 12 | if (statusCode === 200 && body && body.facts.length) { 13 | const descriptions = body.facts; 14 | const menu = new PaginatedEmbed(msg, { 15 | color: Colors.RESPONSE, 16 | title: `Dog fact {currentPage} out of {totalPages}`, 17 | footer: ``, 18 | }, { 19 | descriptions, 20 | }); 21 | await menu.init(); 22 | } else { 23 | logger.verbose(`Failed to fetch dog facts...`, { svrid: msg.guild.id, chid: msg.channel.id, usrid: msg.author.id, statusCode, err: statusText }); 24 | msg.send({ 25 | embed: { 26 | color: Colors.SOFT_ERR, 27 | title: Text.ERROR_TITLE(), 28 | description: `I was unable to fetch your perfect dog facts...`, 29 | }, 30 | }); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /Commands/Public/emoji.js: -------------------------------------------------------------------------------- 1 | const { MessageAttachment } = require("discord.js"); 2 | 3 | module.exports = async ({ client, Constants: { Colors, Text, WorkerTypes } }, documents, msg, commandData) => { 4 | if (msg.suffix) { 5 | const m = await msg.channel.send({ 6 | embed: { 7 | color: Colors.INFO, 8 | description: `We're processing your input.`, 9 | footer: { 10 | text: `This might take a while.`, 11 | }, 12 | }, 13 | }); 14 | const emojis = msg.suffix.replace(/[\r\n]/g, " ").split(/\s+/).trimAll(); 15 | const { buffer, animated } = await client.workerManager.getValueFromWorker(WorkerTypes.EMOJI, { data: emojis }); 16 | await m.delete(); 17 | await msg.channel.send({ 18 | embed: { 19 | files: [new MessageAttachment(buffer, `jumbo.${animated ? "gif" : "png"}`)], 20 | color: Colors.SUCCESS, 21 | image: { 22 | url: `attachment://jumbo.${animated ? "gif" : "png"}`, 23 | }, 24 | description: animated ? [ 25 | `**Please note**`, 26 | `This image was automatically generated. Thereby, it has some caveats:`, 27 | `\t- It might show some emojis too fast / slow depending on their original framerate (we render them in 50fps)`, 28 | `\t- Certain emojis might cut off.`, 29 | ].join("\n") : "", 30 | }, 31 | }); 32 | } else { 33 | logger.verbose(`Emoji(s) not provided for "${commandData.name}" command`, { svrid: msg.guild.id, chid: msg.channel.id, usrid: msg.author.id }); 34 | msg.sendInvalidUsage(commandData, "What would you like to jumbo today? 🤔"); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /Commands/Public/enable.js: -------------------------------------------------------------------------------- 1 | const ManageCommands = require("../../Modules/ManageCommands"); 2 | 3 | module.exports = async (main, documents, msg, commandData) => { 4 | const enableCommand = new ManageCommands(main, documents, msg, commandData); 5 | if (!msg.suffix) { 6 | await enableCommand.listEnabled(); 7 | } else if (enableCommand.parse("ENABLE")) { 8 | await enableCommand.executeEnable(); 9 | } else { 10 | await enableCommand.listEnabled(); 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /Commands/Public/fortune.js: -------------------------------------------------------------------------------- 1 | const { get } = require("snekfetch"); 2 | const leven = require("fast-levenshtein"); 3 | 4 | module.exports = async ({ Constants: { Colors, APIs, FortuneCategories, Text } }, documents, msg, commandData) => { 5 | let ENDPOINT = APIs.FORTUNE(); 6 | if (msg.suffix) { 7 | let choice = null; 8 | if (FortuneCategories.includes(msg.suffix.toLowerCase().trim())) { 9 | choice = msg.suffix.toLowerCase().trim(); 10 | } else { 11 | choice = FortuneCategories.find(category => leven.get(category, msg.suffix.toLowerCase().trim()) < 3); 12 | } 13 | if (choice) { 14 | ENDPOINT = APIs.FORTUNE(choice); 15 | } else { 16 | return msg.send({ 17 | embed: { 18 | color: Colors.INVALID, 19 | title: `That doesn't seem like a valid category...`, 20 | description: `Here are the valid categories:\n\n${FortuneCategories.map(cat => `» **${cat}**\n`).join("")}`, 21 | }, 22 | }); 23 | } 24 | } 25 | await msg.send({ 26 | embed: { 27 | color: Colors.INFO, 28 | description: `We're preparing your fortune cookie`, 29 | footer: { 30 | text: `Please stand by...`, 31 | }, 32 | }, 33 | }); 34 | try { 35 | const res = await get(ENDPOINT).set("Accept", "application/json"); 36 | msg.send({ 37 | embed: { 38 | color: Colors.SUCCESS, 39 | title: `Here is your fortune:`, 40 | description: `${res.body.fortune}`, 41 | }, 42 | }); 43 | } catch (err) { 44 | return msg.send({ 45 | embed: { 46 | color: Colors.SOFT_ERR, 47 | title: Text.ERROR_TITLE(), 48 | description: `I received this error:\`\`\`js\n${err.body}\`\`\``, 49 | }, 50 | }); 51 | } 52 | }; 53 | -------------------------------------------------------------------------------- /Commands/Public/games.js: -------------------------------------------------------------------------------- 1 | const moment = require("moment"); 2 | 3 | module.exports = async ({ Constants: { Colors } }, { serverDocument }, msg, commandData) => { 4 | const sortedGames = serverDocument.games.sort((a, b) => b.time_played - a.time_played); 5 | const totalTime = sortedGames.reduce((a, b) => (a.time_played || a) + b.time_played, 0) * 5; 6 | const totalGames = sortedGames.length; 7 | const description = sortedGames 8 | .slice(0, 8) 9 | .map(game => { 10 | const timePlayed = game.time_played * 5; 11 | const timeStringSplit = moment.duration(timePlayed, "minutes").humanize().split(" "); 12 | return [ 13 | `» **${game._id}** «`, 14 | `\t**${timeStringSplit[0]}** ${timeStringSplit[1]}`, 15 | ].join("\n"); 16 | }); 17 | if (description.length) { 18 | msg.send({ 19 | embed: { 20 | color: Colors.SUCCESS, 21 | title: `Here ${description.length === 1 ? "is" : "are"} the ${description.length === 1 ? "only game" : `top ${description.length} games`} played in this server this week!`, 22 | description: description.join("\n\n"), 23 | footer: { 24 | text: `There were ${totalGames} games played this week with a combined playtime of ${moment.duration(totalTime, "minutes").humanize()}!`, 25 | }, 26 | }, 27 | }); 28 | } else { 29 | msg.send({ 30 | embed: { 31 | color: Colors.INFO, 32 | title: `There's nothing to see here! 🎮`, 33 | description: `You should start playing some games, I know you want to!`, 34 | }, 35 | }); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /Commands/Public/gif.js: -------------------------------------------------------------------------------- 1 | const { Giphy } = require("../../Modules"); 2 | 3 | module.exports = async ({ Constants: { Colors, Text } }, { serverDocument: { config: { moderation: { isEnabled: moderationIsEnabled, filters: { nsfw_filter } } } } }, msg, commandData) => { 4 | if (msg.suffix) { 5 | await msg.send({ 6 | embed: { 7 | color: Colors.INFO, 8 | title: `Searching for a gif...`, 9 | description: `Please stand by!`, 10 | }, 11 | }); 12 | try { 13 | const data = await Giphy(msg.suffix, moderationIsEnabled && nsfw_filter.isEnabled && !nsfw_filter.disabled_channel_ids.includes(msg.channel.id) && !msg.channel.nsfw ? "pg-13" : "r"); 14 | msg.send({ 15 | embed: { 16 | color: Colors.RESPONSE, 17 | image: { 18 | url: data.image_url, 19 | }, 20 | description: `Here is your \`${msg.suffix}\` GIF.\nClick [here](${data.image_url}) for the direct URL`, 21 | footer: { 22 | text: `Powered by GIPHY!`, 23 | }, 24 | }, 25 | }); 26 | } catch (err) { 27 | switch (err.code) { 28 | case "NO_GIPHY_RESULT": { 29 | logger.verbose(`No GIFs found for "${msg.suffix}" search`, { svrid: msg.guild.id, chid: msg.channel.id, usrid: msg.author.id }); 30 | return msg.send({ 31 | embed: { 32 | color: Colors.SOFT_ERR, 33 | description: `The internet has ran out of gifs or I was unable to find one! (╯°□°)╯︵ ┻━┻`, 34 | }, 35 | }); 36 | } 37 | } 38 | return msg.send({ 39 | embed: { 40 | color: Colors.SOFT_ERR, 41 | description: `I received this error:\`\`\`js\n${err.message || err}\`\`\``, 42 | }, 43 | }); 44 | } 45 | } else { 46 | msg.sendInvalidUsage(commandData, "Forgot something...? 🤔"); 47 | } 48 | }; 49 | -------------------------------------------------------------------------------- /Commands/Public/invite.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ Constants: { Text } }, documents, msg, commandData) => msg.send(Text.INVITE(msg.client)); 2 | -------------------------------------------------------------------------------- /Commands/Public/joke.js: -------------------------------------------------------------------------------- 1 | const fetch = require("chainfetch"); 2 | 3 | module.exports = async ({ Constants: { Colors, APIs, UserAgent } }, documents, msg, commandData) => { 4 | await msg.send({ 5 | embed: { 6 | color: Colors.INFO, 7 | title: `Loading times are the biggest joke of all`, 8 | description: `We're getting you a better one...`, 9 | }, 10 | }); 11 | 12 | try { 13 | const body = await fetch.get(APIs.JOKE()).set({ Accept: "application/json", "User-Agent": UserAgent }).toJSON() 14 | .onlyBody(); 15 | 16 | if (body.status === 200 && body.joke && typeof body.joke === "string") { 17 | msg.send({ 18 | embed: { 19 | color: Colors.RESPONSE, 20 | description: body.joke.length < 2048 ? body.joke : `${body.joke.substring(0, 2045)}...`, 21 | }, 22 | }); 23 | } 24 | } catch (err) { 25 | logger.debug("Failed to fetch joke for the joke command.", { svrid: msg.guild.id }, err); 26 | msg.send({ 27 | embed: { 28 | color: Colors.SOFT_ERR, 29 | description: "Failed to fetch a joke...", 30 | footer: { 31 | text: "Failure is funny too, right?", 32 | }, 33 | }, 34 | }); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /Commands/Public/numfact.js: -------------------------------------------------------------------------------- 1 | const fetch = require("chainfetch"); 2 | 3 | module.exports = async ({ Constants: { Colors, APIs, UserAgent, Text } }, documents, msg, commandData) => { 4 | const number = msg.suffix || "random"; 5 | if (msg.suffix && isNaN(msg.suffix)) { 6 | return msg.sendInvalidUsage(commandData, "That isn't a valid number!"); 7 | } 8 | 9 | await msg.send(Text.THIRD_PARTY_FETCH(`We're fetching your ${number} fact`)); 10 | 11 | try { 12 | const body = await fetch.get(APIs.NUMFACT(number)).set("User-Agent", UserAgent).onlyBody(); 13 | 14 | if (body && typeof body === "string") { 15 | msg.send({ 16 | embed: { 17 | color: Colors.RESPONSE, 18 | description: body.length < 2048 ? body : `${body.substring(0, 2045)}...`, 19 | }, 20 | }); 21 | } 22 | } catch (err) { 23 | logger.debug("Failed to fetch number fact for the numfact command.", { svrid: msg.guild.id }, err); 24 | msg.send({ 25 | embed: { 26 | color: Colors.SOFT_ERR, 27 | description: "Failed to fetch a number fact...", 28 | footer: { 29 | text: "In hindsight, dividing by zero was a mistake.", 30 | }, 31 | }, 32 | }); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /Commands/Public/ping.js: -------------------------------------------------------------------------------- 1 | const { Stopwatch } = require("../../Modules/Utils/"); 2 | 3 | module.exports = async ({ client, Constants: { Colors } }, documents, msg, commandData) => { 4 | const timer = new Stopwatch(); 5 | await msg.send({ 6 | embed: { 7 | color: Colors.INFO, 8 | title: "Getting the ping 🏓", 9 | description: "Please stand by...", 10 | }, 11 | }); 12 | const sendPing = timer.duration; 13 | timer.stop(); 14 | msg.send({ 15 | embed: { 16 | color: Colors.LIGHT_GREEN, 17 | title: "Pong! 🏓", 18 | description: `Sending this message took **${Math.round(sendPing / 2)}**ms. The average heartbeat ping is **${Math.floor(client.ws.ping)}**ms`, 19 | footer: { 20 | text: `This server is on shard ${client.shardID}.`, 21 | }, 22 | }, 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /Commands/Public/prefix.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ Constants: { Colors } }, { serverQueryDocument }, msg, commandData) => { 2 | if (msg.suffix) { 3 | let { suffix } = msg; 4 | if (msg.suffix.startsWith(`"`) && msg.suffix.endsWith(`"`)) suffix = msg.suffix.slice(1, msg.suffix.length - 1); 5 | if (suffix.length > 25) { 6 | return msg.send({ 7 | embed: { 8 | color: Colors.INVALID, 9 | description: `That prefix is too long, don't ya think? 🐳`, 10 | }, 11 | }); 12 | } 13 | serverQueryDocument.set("config.command_prefix", suffix); 14 | return msg.send({ 15 | embed: { 16 | color: Colors.SUCCESS, 17 | title: `Got it! 🐬`, 18 | description: `The new prefix for this server is \`${suffix}\``, 19 | }, 20 | }); 21 | } 22 | return msg.send({ 23 | embed: { 24 | color: Colors.INFO, 25 | title: "I'm sure you already know this...", 26 | description: `The command prefix in this server is \`${msg.guild.commandPrefix}\``, 27 | }, 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /Commands/Public/quiet.js: -------------------------------------------------------------------------------- 1 | const moment = require("moment"); 2 | const parseDuration = require("parse-duration"); 3 | 4 | module.exports = async ({ Constants: { Colors }, client }, { serverDocument, serverQueryDocument, channelQueryDocument }, msg, commandData) => { 5 | let responseString = ""; 6 | if (msg.suffix && msg.suffix.toLowerCase().trim() === "all") { 7 | responseString = " in all channels"; 8 | Object.values(serverDocument.channels).forEach(channelDocument => { 9 | serverQueryDocument.set(`channels.${channelDocument._id}.bot_enabled`, false); 10 | }); 11 | } else if (msg.suffix && parseDuration(msg.suffix) > 0) { 12 | const time = parseDuration(msg.suffix); 13 | if (time > 3600000) { 14 | return msg.send({ 15 | embed: { 16 | color: Colors.INVALID, 17 | description: "I can't miss you guys for that long!", 18 | footer: { 19 | text: "Try a shorter duration or no duration at all for an indefinitely long period", 20 | }, 21 | }, 22 | }); 23 | } 24 | responseString = ` for ${moment.duration(time).humanize()}`; 25 | channelQueryDocument.set("bot_enabled", false); 26 | client.setTimeout(() => { 27 | channelQueryDocument.set("bot_enabled", true); 28 | serverDocument.save().catch(err => { 29 | logger.warn("Failed to save server data for automatically starting the bot in a channel.", { svrid: msg.guild.id }, err); 30 | }); 31 | }, time); 32 | } else { 33 | channelQueryDocument.set("bot_enabled", false); 34 | } 35 | msg.send({ 36 | embed: { 37 | color: Colors.SUCCESS, 38 | description: `Ok, I'll be quiet${responseString} 😶`, 39 | }, 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /Commands/Public/reason.js: -------------------------------------------------------------------------------- 1 | const ModLog = require("../../Modules/ModLog"); 2 | 3 | module.exports = async ({ Constants: { Colors, Text } }, { serverDocument }, msg, commandData) => { 4 | const args = msg.suffix ? msg.suffix.split(" ") : []; 5 | if (args.length >= 2) { 6 | const result = await ModLog.update(msg.guild, args[0].trim(), { 7 | reason: msg.suffix.substring(msg.suffix.indexOf(" ") + 1).trim(), 8 | }); 9 | if (isNaN(result)) { 10 | switch (result.code) { 11 | case "INVALID_MODLOG_CHANNEL": 12 | case "MISSING_MODLOG_CHANNEL": 13 | case "MODLOG_ENTRY_NOT_FOUND": 14 | msg.send({ 15 | embed: { 16 | color: Colors.SOFT_ERR, 17 | description: result.message, 18 | }, 19 | }); 20 | break; 21 | default: 22 | throw result; 23 | } 24 | } else { 25 | msg.send({ 26 | embed: { 27 | color: Colors.SUCCESS, 28 | description: "Reason updated ✅", 29 | }, 30 | }); 31 | } 32 | } else { 33 | msg.send({ 34 | embed: { 35 | color: Colors.INVALID, 36 | description: Text.INVALID_USAGE(commandData, msg.guild.commandPrefix), 37 | }, 38 | }); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /Commands/Public/remindme.js: -------------------------------------------------------------------------------- 1 | const remind = require("../../Modules/MessageUtils/ReminderParser"); 2 | const moment = require("moment"); 3 | 4 | module.exports = async ({ client, Constants: { Colors, Text } }, { userDocument }, msg, commandData) => { 5 | if (msg.suffix) { 6 | const result = await remind(client, userDocument, userDocument.query, msg.suffix); 7 | if (result === "ERR") { 8 | msg.sendInvalidUsage(commandData); 9 | } else { 10 | msg.send({ 11 | embed: { 12 | color: Colors.SUCCESS, 13 | description: `Alright, I'll remind you ${moment.duration(result).humanize(true)} ⏰`, 14 | }, 15 | }); 16 | } 17 | } else if (userDocument.reminders.length === 0) { 18 | msg.send({ 19 | embed: { 20 | color: Colors.SOFT_ERR, 21 | title: "No reminders set 😴", 22 | description: `You don't have any reminders set yet; use \`${commandData.name} ${commandData.usage}\` to set one.`, 23 | }, 24 | }); 25 | } else { 26 | const fields = userDocument.reminders.map(reminderDocument => ({ 27 | name: `__${reminderDocument.name}__`, 28 | value: `${moment(reminderDocument.expiry_timestamp).fromNow()}`, 29 | inline: true, 30 | })); 31 | msg.send({ 32 | embed: { 33 | color: Colors.INFO, 34 | title: `Your reminders:`, 35 | fields: fields.splice(0, 25), 36 | }, 37 | }); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /Commands/Public/roll.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ Constants: { Colors, Text } }, documents, msg, commandData) => { 2 | let min = 1; 3 | let max = 6; 4 | if (msg.suffix) { 5 | const choices = msg.suffix.split(" "); 6 | if (choices.length === 1) { 7 | max = +choices[0]; 8 | } else if (choices.length > 1) { 9 | min = +choices[0]; 10 | max = +choices[1]; 11 | } 12 | } 13 | if (!isFinite(min) || !isFinite(max)) { 14 | return msg.sendInvalidUsage(commandData, "Those aren't valid numbers! 🔢"); 15 | } 16 | if (min > max) [min, max] = [max, min]; 17 | const randNum = Math.floor(Math.random() * (max - min + 1)) + min; 18 | msg.send({ 19 | embed: { 20 | color: Colors.SUCCESS, 21 | description: `You rolled a ${randNum}! 🎲`, 22 | }, 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /Commands/Public/say.js: -------------------------------------------------------------------------------- 1 | module.exports = async ({ Constants: { Colors } }, documents, msg) => { 2 | msg.send({ 3 | embed: { 4 | color: Colors.SUCCESS, 5 | description: `${msg.suffix && msg.suffix !== "" ? msg.suffix : "🙊"}`, 6 | }, 7 | disableEveryone: true, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /Commands/Public/streamers.js: -------------------------------------------------------------------------------- 1 | const PaginatedEmbed = require("../../Modules/MessageUtils/PaginatedEmbed"); 2 | const isStreaming = require("../../Modules/Utils/StreamerUtils"); 3 | 4 | module.exports = async ({ Constants: { Colors, Text }, client }, { serverDocument }, msg, commandData) => { 5 | if (serverDocument.config.streamers_data.length) { 6 | const descriptions = []; 7 | const thumbnails = []; 8 | const titles = []; 9 | const footers = []; 10 | const colors = []; 11 | await Promise.all(serverDocument.config.streamers_data.map(async streamer => { 12 | const streamerData = await isStreaming(streamer.type, streamer._id); 13 | if (!streamerData) return; 14 | descriptions.push(`**${streamerData.name}** is streaming **${streamerData.game}**, [watch their stream now](${streamerData.url})!`); 15 | thumbnails.push(streamerData.preview); 16 | titles.push(`${streamerData.name} is live!`); 17 | footers.push(`${streamerData.type} Streamer • `); 18 | colors.push(streamerData.type === "YouTube" ? Colors.YOUTUBE : Colors.TWITCH); 19 | })); 20 | if (descriptions.length) { 21 | await new PaginatedEmbed(msg, { 22 | footer: "{footer}Streamer {currentPage} out of {totalPages}", 23 | }, { 24 | descriptions, 25 | thumbnails, 26 | titles, 27 | footers, 28 | colors, 29 | }).init(); 30 | } else if (serverDocument.config.streamers_data.length === 1) { 31 | msg.send({ 32 | embed: { 33 | color: Colors.SOFT_ERR, 34 | description: "The 1 streamer added to this server isn't live right now. 😞", 35 | }, 36 | }); 37 | } else { 38 | msg.send({ 39 | embed: { 40 | color: Colors.SOFT_ERR, 41 | description: `None of the ${serverDocument.config.streamers_data.length} streamers added to this server are live right now. 😞`, 42 | }, 43 | }); 44 | } 45 | } else { 46 | msg.send({ 47 | embed: { 48 | color: Colors.SOFT_ERR, 49 | description: "I'm not tracking any streamers yet!\nA Server Admin can add a streamer to track on the dashboard! 🌐", 50 | }, 51 | }); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /Commands/Public/time.js: -------------------------------------------------------------------------------- 1 | const moment = require("moment-timezone"); 2 | 3 | module.exports = async ({ Constants: { APIs, Colors, Text }, client }, { serverDocument }, msg, commandData) => { 4 | if (!msg.suffix) { 5 | await msg.send({ 6 | embed: { 7 | color: Colors.INVALID, 8 | description: `I'm not entirely sure where you are, but for me it's **${moment().format("HH:mm")}** 😊`, 9 | footer: { 10 | text: `By the way, you can lookup the time in a specific timezone by using \`${msg.guild.commandPrefix}time \`!`, 11 | }, 12 | }, 13 | }); 14 | } else { 15 | const zone = moment.tz.zone(msg.suffix); 16 | if (!zone) { 17 | await msg.send({ 18 | embed: { 19 | color: Colors.SOFT_ERR, 20 | description: "You're ahead of me, I don't know that timezone! 😵", 21 | }, 22 | }); 23 | } else { 24 | await msg.send({ 25 | embed: { 26 | color: Colors.RESPONSE, 27 | description: `It's currently **${moment().tz(msg.suffix).format("HH:mm")}** for **${msg.suffix}**. ⏰`, 28 | }, 29 | }); 30 | } 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /Commands/Public/translate.js: -------------------------------------------------------------------------------- 1 | const ArgParser = require("./../../Modules/MessageUtils/Parser"); 2 | const mstranslate = require("./../../Modules/MicrosoftTranslate"); 3 | 4 | module.exports = ({ Constants: { Colors, Text }, client }, { serverDocument }, msg, commandData) => { 5 | const args = ArgParser.parseQuoteArgs(msg.suffix || "", " "); 6 | if (args.length < 3) { 7 | return msg.sendInvalidUsage(commandData); 8 | } 9 | 10 | let source, target, text; 11 | // to 12 | if (args[1] === "to") { 13 | [source, , target] = args; 14 | text = args.splice(3).join(" ").trim(); 15 | // 16 | } else { 17 | [source, target] = args; 18 | text = args.splice(2).join(" ").trim(); 19 | } 20 | 21 | const sendTranslation = (from, to, res) => msg.send({ 22 | embed: { 23 | color: Colors.RESPONSE, 24 | title: `Your ${from} text in ${to}:`, 25 | description: `\`\`\`${res}\`\`\``, 26 | footer: { 27 | text: `Translated using Microsoft Translator. The translated text might not be 100% accurate!`, 28 | }, 29 | }, 30 | }); 31 | const onFail = (err, src) => { 32 | msg.send({ 33 | embed: { 34 | color: Colors.SOFT_ERR, 35 | description: `Something went wrong while trying to ${src === null ? "detect your text's language" : `translate your ${src} text`}! 😵`, 36 | }, 37 | }); 38 | if (err) logger.debug(`Failed to ${src === null ? "auto-detect language" : "translate text"} for ${commandData.name} command.`, { svrid: msg.guild.id, chid: msg.channel.id, msgid: msg.id }, err); 39 | }; 40 | 41 | const translateText = (from, to, input) => { 42 | mstranslate.translate({ text: input, from, to }, (err, res) => { 43 | if (err || !res) { 44 | onFail(err, from); 45 | } else { 46 | sendTranslation(from, to, res); 47 | } 48 | }); 49 | }; 50 | 51 | if (source === "?") { 52 | mstranslate.detect({ text }, (err, res) => { 53 | if (err || !res || res === "") { 54 | onFail(err, null); 55 | } else { 56 | translateText(res, target, text); 57 | } 58 | }); 59 | } else { 60 | translateText(source, target, text); 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /Commands/Public/wiki.js: -------------------------------------------------------------------------------- 1 | const wiki = require("wikijs").default(); 2 | 3 | module.exports = async ({ Constants: { Colors } }, documents, msg, commandData) => { 4 | await msg.send({ 5 | embed: { 6 | color: Colors.INFO, 7 | title: `Searching Wikipedia just for you ⌛`, 8 | description: `Please stand by...`, 9 | }, 10 | }); 11 | let result; 12 | if (!msg.suffix) { 13 | const random = await wiki.random(1); 14 | result = await wiki.page(random[0]); 15 | } else { 16 | const search = await wiki.search(msg.suffix, 1); 17 | if (!search.results.length) { 18 | return msg.send({ 19 | embed: { 20 | color: Colors.SOFT_ERR, 21 | title: "What was that again? 📚🤓", 22 | description: "Even Wikipedia doesn't seem to know what you're talking about.", 23 | footer: { 24 | text: "Check for typos or try searching for something else!", 25 | }, 26 | }, 27 | }); 28 | } 29 | result = await wiki.page(search.results[0]); 30 | } 31 | let description = await result.summary(); 32 | if (description.length < 100) { 33 | // 100 is a bit short so load the full description in that case 34 | description = await result.content(); 35 | } 36 | if (description.length > 1950) { 37 | description = `${description.substring(0, 1950)}...\nArticle is too long, click [**here**](${result.raw.fullurl}) to read more!`; 38 | } 39 | // Sometimes wikijs crashes when attempting to grab a main image. If it works, great. If not, too bad. 40 | const mainImage = await result.mainImage().catch(() => null); 41 | msg.send({ 42 | embed: { 43 | color: Colors.RESPONSE, 44 | title: result.raw.title, 45 | url: result.raw.fullurl, 46 | description, 47 | image: { 48 | url: mainImage, 49 | }, 50 | }, 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /Commands/Public/wolfram.js: -------------------------------------------------------------------------------- 1 | const fetch = require("chainfetch"); 2 | const { tokens: { wolframAppID } } = require("../../Configurations/auth"); 3 | 4 | module.exports = async ({ Constants: { Colors, APIs, UserAgent, Text } }, documents, msg, commandData) => { 5 | if (!msg.suffix) { 6 | return msg.sendInvalidUsage(commandData); 7 | } 8 | 9 | const response = await msg.send(Text.THIRD_PARTY_FETCH("Fetching data from Wolfram|Alpha")); 10 | 11 | const query = encodeURIComponent(msg.suffix); 12 | const body = (await fetch.get(APIs.WOLFRAM(wolframAppID, query)).set("User-Agent", UserAgent).onlyBody()).queryresult; 13 | if (body.success && body.pods.length) { 14 | const fields = body.pods.map(pod => ({ 15 | name: pod.title, 16 | value: pod.subpods[0].plaintext && pod.subpods[0].plaintext !== "" ? pod.subpods[0].plaintext : `[Image](${pod.subpods[0].img.src})`, 17 | })); 18 | 19 | response.edit({ 20 | embed: { 21 | color: Colors.RESPONSE, 22 | title: "Here's your result from Wolfram|Alpha!", 23 | fields, 24 | }, 25 | }); 26 | } else if (!body.error && body.numpods === 0) { 27 | response.edit({ 28 | embed: { 29 | color: Colors.SOFT_ERR, 30 | description: "Wolfram|Alpha has nothing 💡", 31 | footer: { 32 | text: "Try searching for something else!", 33 | }, 34 | }, 35 | }); 36 | } else { 37 | logger.debug("Error occurred at Wolfram|Alpha!", { svrid: msg.guild.id, query: query, err: body.error }); 38 | response.edit({ 39 | embed: { 40 | color: Colors.SOFT_ERR, 41 | description: "Unfortunately, I didn't get anything back from Wolfram|Alpha 😔", 42 | }, 43 | }); 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /Commands/Public/xkcd.js: -------------------------------------------------------------------------------- 1 | const fetch = require("chainfetch"); 2 | 3 | module.exports = async ({ Constants: { Colors, Text, APIs, UserAgent } }, documents, msg) => { 4 | const comicData = await fetch.get(APIs.XKCD(msg.suffix ? msg.suffix : undefined)).set({ "User-Agent": UserAgent, Accept: "application/json" }).onlyBody() 5 | .catch(() => null); 6 | 7 | if (!comicData) { 8 | msg.send({ 9 | embed: { 10 | color: Colors.SOFT_ERR, 11 | description: msg.suffix ? `Doh! An XKCD comic with ID ${msg.suffix} wasn't found.` : `Doh! The current XKCD comic couldn't be fetched.`, 12 | }, 13 | }); 14 | } else { 15 | msg.send({ 16 | embed: { 17 | color: Colors.RESPONSE, 18 | title: comicData.title, 19 | image: { 20 | url: comicData.img || comicData.alt, 21 | }, 22 | footer: { 23 | text: msg.suffix ? `#${comicData.num}` : `Latest XKCD Comic | #${comicData.num}`, 24 | }, 25 | timestamp: new Date(`${comicData.year}-${comicData.month}-${comicData.day}`), 26 | }, 27 | }); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /Commands/Public/year.js: -------------------------------------------------------------------------------- 1 | const moment = require("moment"); 2 | 3 | module.exports = async ({ Constants: { Colors } }, documents, msg, commandData) => { 4 | const now = new Date(); 5 | const next = new Date(now); 6 | next.setFullYear(now.getFullYear() + 1); 7 | next.setHours(0, 0, 0, 0); 8 | next.setMonth(0, 1); 9 | const duration = next - now; 10 | const seconds = Math.floor((duration / 1000) % 60); 11 | const minutes = Math.floor((duration / 1000 / 60) % 60); 12 | const hours = Math.floor((duration / (1000 * 60 * 60)) % 24); 13 | const days = Math.floor(duration / (1000 * 60 * 60 * 24)); 14 | return msg.send({ 15 | embed: { 16 | color: Colors.INFO, 17 | title: "Woo! Prepare the party poppers!", 18 | description: `There are **${days} days**, **${hours} hours**, **${minutes} minutes** and **${seconds} seconds** until **${next.getFullYear()}**! 🎆`, 19 | footer: { 20 | text: `Or, in short, ${moment.duration(next - now).humanize()}.`, 21 | }, 22 | }, 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /Commands/Shared/_base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Shared commands are the most basic command-like structure ever. 3 | * They are NOT expected to have any of the params the main commands have. 4 | * They should only ever expect the main object, the message and the command data! 5 | * Anything else is at the maintainers choice when creating commands. 6 | * Hell, you can make a custom shared command that runs under specific settings! 7 | */ 8 | module.exports = async (main, msg, commandData) => { 9 | /** 10 | * @param {Discord.Message} msg The raw message 11 | * Suffix is present in the msg object 12 | */ 13 | /** 14 | * @type {Object} 15 | * @param commandData Object containing the command name and usage. 16 | * Use `client.getPMCommandMetadata(commandData.name)` for other things 17 | */ 18 | /** 19 | * @type {Object} 20 | * @param main Object containing the most important things 21 | * Feel free to deconstruct it using { Value } 22 | * @property {Discord.Client} client The client object 23 | * @property {Object} configJS The config js object 24 | * configJSON is in the global 25 | */ 26 | }; 27 | -------------------------------------------------------------------------------- /Configurations/auth.template.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | discord: { 3 | clientID: "", 4 | clientSecret: "", 5 | clientToken: "", 6 | }, 7 | tokens: { 8 | discordBotsOrg: "", 9 | discordList: "", 10 | discordBots: "", 11 | giphyAPI: "", 12 | googleCSEID: "", 13 | googleAPI: "", 14 | imgurClientID: "", 15 | microsoftTranslation: "", 16 | twitchClientID: "", 17 | wolframAppID: "", 18 | openExchangeRatesKey: "", 19 | omdbAPI: "", 20 | gistKey: "", 21 | openWeatherMap: "", 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /Configurations/config.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "sudoMaintainers": [], 3 | "maintainers": [], 4 | "wikiContributors": [], 5 | "userBlocklist": [], 6 | "guildBlocklist": [], 7 | "activityBlocklist": [], 8 | "activity": { 9 | "name": "default", 10 | "type": "PLAYING", 11 | "twitchURL": "" 12 | }, 13 | "status": "online", 14 | "headerImage": "header-bg.jpg", 15 | "homepageMessageHTML": "", 16 | "pmForward": false, 17 | "version": "5.0.0", 18 | "branch": "development", 19 | "perms": { 20 | "eval": 0, 21 | "sudo": 2, 22 | "management": 2, 23 | "administration": 1, 24 | "shutdown": 2 25 | }, 26 | "injection": { 27 | "headScript": "", 28 | "pageScript": "" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Configurations/ranks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "Pro Lurker", 4 | "max_score": 5 5 | }, 6 | { 7 | "_id": "Learning Lurker", 8 | "max_score": 10 9 | }, 10 | { 11 | "_id": "Bad Lurker", 12 | "max_score": 20 13 | }, 14 | { 15 | "_id": "Mostly Inactive", 16 | "max_score": 40 17 | }, 18 | { 19 | "_id": "In Between", 20 | "max_score": 75 21 | }, 22 | { 23 | "_id": "Kinda Active", 24 | "max_score": 100 25 | }, 26 | { 27 | "_id": "Pretty Active", 28 | "max_score": 200 29 | }, 30 | { 31 | "_id": "Super Active", 32 | "max_score": 400 33 | }, 34 | { 35 | "_id": "Pro Member", 36 | "max_score": 750 37 | }, 38 | { 39 | "_id": "Almost a Spammer", 40 | "max_score": 1000 41 | } 42 | ] -------------------------------------------------------------------------------- /Configurations/rss_feeds.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "gnews", 4 | "url": "https://news.google.com/news?ned=us&topic=h&output=rss", 5 | "streaming": { 6 | "isEnabled": false, 7 | "enabled_channel_ids": [] 8 | } 9 | } 10 | ] -------------------------------------------------------------------------------- /Configurations/status_messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "new_member_message": [ 3 | "@user, welcome to our little corner of hell!", 4 | "@user has joined the server.", 5 | "@user, you're gonna have a jolly good time here!", 6 | "@user is new here.", 7 | "@user is here, everybody!", 8 | "@user sends his/her regards.", 9 | "@user, welcome to the server!", 10 | "@user is our next victim...", 11 | "Hello @user!", 12 | "Please welcome our newest member, @user" 13 | ], 14 | "member_online_message": [ 15 | "@user is now online!", 16 | "Welcome back @user!" 17 | ], 18 | "member_offline_message": [ 19 | "@user is gone (for now)", 20 | "@user has gone offline." 21 | ], 22 | "member_removed_message": [ 23 | "@user has left us :slight_frown:", 24 | "Goodbye @user", 25 | "@user Come back!", 26 | "@user is gone *cries*", 27 | "@user has left the server", 28 | "RIP @user", 29 | "Uh-oh, @user went away", 30 | "Please convince @user to come back!" 31 | ], 32 | "member_banned_message": [ 33 | "@user has been banned" 34 | ], 35 | "member_unbanned_message": [ 36 | "@user has been unbanned" 37 | ] 38 | } -------------------------------------------------------------------------------- /Configurations/tag_reactions.json: -------------------------------------------------------------------------------- 1 | [ 2 | "@user you called?", 3 | "Yo @user wassup" 4 | ] -------------------------------------------------------------------------------- /Configurations/tags.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "_id": "shrug", 4 | "content": "¯\\_(ツ)_/¯" 5 | }, 6 | { 7 | "_id": "lenny", 8 | "content": "( ͡° ͜ʖ ͡°)" 9 | }, 10 | { 11 | "_id": "raiseyourdongers", 12 | "content": "ヽ༼ຈل͜ຈ༽ノ Raise Your Dongers ヽ༼ຈل͜ຈ༽ノ" 13 | }, 14 | { 15 | "_id": "praisehelix", 16 | "content": "つ ◕_◕ ༽つ PRAISE HELIX༼つ ◕_◕ ༽つ" 17 | }, 18 | { 19 | "_id": "goodshit", 20 | "content": "👌👀👌👀👌👀👌👀👌👀 good shit go౦ԁ sHit👌 thats ✔ some good👌👌shit right👌👌there👌👌👌 right✔there ✔✔if i do ƽaү so my self 💯 i say so 💯 thats what im talking about right there right there (chorus: ʳᶦᵍʰᵗ ᵗʰᵉʳᵉ) mMMMMᎷМ💯 👌👌 👌НO0ОଠOOOOOОଠଠOoooᵒᵒᵒᵒᵒᵒᵒᵒᵒ👌 👌👌 👌 💯 👌 👀 👀 👀 👌👌Good shit" 21 | }, 22 | { 23 | "_id": "creepylenny", 24 | "content": "┬┴┬┴┤ ͜ʖ ͡°) ├┬┴┬┴" 25 | }, 26 | { 27 | "_id": "kawaii", 28 | "content": "(ノ◕ヮ◕)ノ*:・゚✧" 29 | }, 30 | { 31 | "_id": "yeeaah", 32 | "content": "(•_•) ( •_•)>⌐■-■ (⌐■_■)" 33 | }, 34 | { 35 | "_id": "lod", 36 | "content": "ಠ_ಠ" 37 | }, 38 | { 39 | "_id": "orly", 40 | "content": "﴾͡๏̯͡๏﴿ O'RLY?" 41 | }, 42 | { 43 | "_id": "ayy", 44 | "content": "(☞゚∀゚)☞" 45 | }, 46 | { 47 | "_id": "gib", 48 | "content": "༼ つ ◕_◕ ༽つ" 49 | }, 50 | { 51 | "_id": "kawaiidog", 52 | "content": "(ᵔᴥᵔ)" 53 | }, 54 | { 55 | "_id": "fite", 56 | "content": "(ง'̀-'́)ง" 57 | }, 58 | { 59 | "_id": "kawaiimeh", 60 | "content": " ╮ (. ❛ ᴗ ❛.) ╭" 61 | }, 62 | { 63 | "_id": "evilsmiley", 64 | "content": "“ψ(`∇´)ψ" 65 | }, 66 | { 67 | "_id": "rip", 68 | "content": "(✖╭╮✖)" 69 | }, 70 | { 71 | "_id": "wink", 72 | "content": "ಠ‿↼" 73 | } 74 | ] 75 | -------------------------------------------------------------------------------- /Database/Cursor.js: -------------------------------------------------------------------------------- 1 | const Document = require("./Document"); 2 | 3 | /** 4 | * A GADriver Cursor wrapper for the MongoDB Cursor 5 | * @class 6 | */ 7 | class Cursor { 8 | /** 9 | * Create a new Cursor 10 | * @param {Cursor} cursor 11 | * @param {Model} model 12 | */ 13 | constructor (cursor, model) { 14 | this._model = model; 15 | this._client = this._model._client; 16 | this._cursor = cursor; 17 | } 18 | 19 | skip (val) { 20 | this._cursor.skip(val); 21 | return this; 22 | } 23 | 24 | limit (val) { 25 | this._cursor.limit(val); 26 | return this; 27 | } 28 | 29 | sort (val) { 30 | this._cursor.sort(val); 31 | return this; 32 | } 33 | 34 | async exec () { 35 | const rawArray = await this._cursor.toArray(); 36 | 37 | return rawArray.map(obj => new Document(obj, this._model)); 38 | } 39 | } 40 | 41 | module.exports = Cursor; 42 | -------------------------------------------------------------------------------- /Database/Driver.js: -------------------------------------------------------------------------------- 1 | /* eslint node/exports-style: ["error", "exports"] */ 2 | const { MongoClient } = require("mongodb"); 3 | 4 | const Model = require("./Model"); 5 | const { addToGlobal } = require("../Modules/Utils/GlobalDefines.js"); 6 | 7 | /** 8 | * Prepares models, creates and connects a client to MongoDB 9 | * @param {object} config A set of MongoDB config options 10 | * @returns {Promise} 11 | */ 12 | exports.initialize = async config => { 13 | const mongoClient = new MongoClient(config.URL, config.options); 14 | await mongoClient.connect(); 15 | const db = mongoClient.db(config.db); 16 | const [ 17 | Servers, 18 | Users, 19 | Gallery, 20 | Blog, 21 | Wiki, 22 | Traffic, 23 | ] = [ 24 | new Model(db, "servers", require("./Schemas/serverSchema")), 25 | new Model(db, "users", require("./Schemas/userSchema")), 26 | new Model(db, "gallery", require("./Schemas/gallerySchema")), 27 | new Model(db, "blog", require("./Schemas/blogSchema")), 28 | new Model(db, "wiki", require("./Schemas/wikiSchema")), 29 | new Model(db, "traffic", require("./Schemas/trafficSchema")), 30 | ]; 31 | addToGlobal("Servers", Servers); 32 | addToGlobal("Users", Users); 33 | addToGlobal("Gallery", Gallery); 34 | addToGlobal("Blog", Blog); 35 | addToGlobal("Wiki", Wiki); 36 | addToGlobal("Client", db); 37 | addToGlobal("Database", { 38 | Servers, servers: Servers, 39 | Users, users: Users, 40 | Gallery, gallery: Gallery, 41 | Blog, blog: Blog, 42 | Wiki, wiki: Wiki, 43 | Traffic, traffic: Traffic, 44 | client: db, 45 | mongoClient, 46 | }); 47 | return global.Database.client; 48 | }; 49 | 50 | exports.get = exports.getConnection = () => global.Database; 51 | -------------------------------------------------------------------------------- /Database/Schemas/blogSchema.js: -------------------------------------------------------------------------------- 1 | const Schema = require("../Schema"); 2 | 3 | /* 4 | * Schema for Blog Entries 5 | */ 6 | module.exports = new Schema({ 7 | title: { 8 | type: String, 9 | minlength: 10, 10 | maxlength: 100, 11 | required: true, 12 | }, 13 | author_id: { 14 | type: String, 15 | required: true, 16 | }, 17 | category: { 18 | type: String, 19 | enum: [ 20 | "Development", 21 | "Announcement", 22 | "New Stuff", 23 | "Tutorial", 24 | "Random", 25 | ], 26 | required: true, 27 | }, 28 | published_timestamp: { 29 | type: Date, 30 | default: Date.now, 31 | }, 32 | content: { 33 | type: String, 34 | required: true, 35 | }, 36 | reactions: [new Schema({ 37 | _id: { 38 | type: String, 39 | required: true, 40 | }, 41 | value: { 42 | type: Number, 43 | min: -1, 44 | max: 1, 45 | }, 46 | })], 47 | }); 48 | -------------------------------------------------------------------------------- /Database/Schemas/serverGallerySchema.js: -------------------------------------------------------------------------------- 1 | const Schema = require("../Schema"); 2 | 3 | module.exports = new Schema({ 4 | // Stringified ObjectID 5 | _id: { 6 | type: String, 7 | required: true, 8 | }, 9 | version: Number, 10 | key: { 11 | type: String, 12 | minlength: 2, 13 | maxlength: 25, 14 | }, 15 | keywords: [String], 16 | case_sensitive: Boolean, 17 | interval: { 18 | type: Number, 19 | min: 300000, 20 | max: 86400000, 21 | }, 22 | disabled_channel_ids: [String], 23 | admin_level: { 24 | type: Number, 25 | min: 0, 26 | max: 4, 27 | }, 28 | store: Schema.Mixed, 29 | status: new Schema({ 30 | code: { 31 | type: Number, 32 | enum: [0, 1, 2], 33 | default: 0, 34 | }, 35 | description: String, 36 | }), 37 | }); 38 | -------------------------------------------------------------------------------- /Database/Schemas/serverGamesSchema.js: -------------------------------------------------------------------------------- 1 | const Schema = require("../Schema"); 2 | 3 | // Server's game data (time played and game playing) 4 | module.exports = new Schema({ 5 | _id: { 6 | type: String, 7 | required: true, 8 | }, 9 | time_played: { 10 | type: Number, 11 | default: 0, 12 | min: 0, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /Database/Schemas/serverMembersSchema.js: -------------------------------------------------------------------------------- 1 | const { ObjectID } = require("mongodb"); 2 | const Schema = require("../Schema"); 3 | 4 | module.exports = Schema.Map({ 5 | _id: { 6 | type: String, 7 | required: true, 8 | }, 9 | messages: { 10 | type: Number, 11 | default: 0, 12 | min: 0, 13 | }, 14 | voice: { 15 | type: Number, 16 | default: 0, 17 | min: 0, 18 | }, 19 | rank: { 20 | type: String, 21 | default: "No Rank", 22 | }, 23 | rank_score: { 24 | type: Number, 25 | default: 0, 26 | min: 0, 27 | }, 28 | afk_message: String, 29 | last_active: Date, 30 | cannotAutokick: { 31 | type: Boolean, 32 | default: false, 33 | }, 34 | strikes: [new Schema({ 35 | _id: { 36 | type: String, 37 | default: () => new ObjectID().toString(), 38 | }, 39 | admin: { 40 | type: String, 41 | required: true, 42 | }, 43 | reason: { 44 | type: String, 45 | required: true, 46 | maxlength: 2000, 47 | }, 48 | timestamp: { 49 | type: Date, 50 | default: Date.now, 51 | }, 52 | modlog_entry: { 53 | type: Number, 54 | }, 55 | })], 56 | profile_fields: Schema.Mixed, 57 | muted: [new Schema({ 58 | _id: { 59 | type: String, 60 | required: true, 61 | }, 62 | since: { 63 | type: Date, 64 | default: Date.now, 65 | }, 66 | })], 67 | }); 68 | -------------------------------------------------------------------------------- /Database/Schemas/serverModlogSchema.js: -------------------------------------------------------------------------------- 1 | const Schema = require("../Schema"); 2 | 3 | module.exports = new Schema({ 4 | isEnabled: { 5 | type: Boolean, 6 | default: false, 7 | }, 8 | channel_id: String, 9 | current_id: { 10 | type: Number, 11 | default: 0, 12 | }, 13 | entries: [new Schema({ 14 | _id: { 15 | // Based off current_id 16 | type: Number, 17 | required: true, 18 | }, 19 | timestamp: { 20 | type: Date, 21 | default: Date.now, 22 | }, 23 | type: { 24 | type: String, 25 | enum: [ 26 | "Add Role", 27 | "Ban", 28 | "Block", 29 | "Kick", 30 | "Mute", 31 | "Other", 32 | "Remove Role", 33 | "Softban", 34 | "Unban", 35 | "Unmute", 36 | "Strike", 37 | "Temp Ban", 38 | "Temp Mute", 39 | "Delete Role", 40 | "Modify Role", 41 | "Create Role", 42 | ], 43 | required: true, 44 | }, 45 | affected_user: { 46 | // User Id of the affected user 47 | type: String, 48 | // We're getting modlogs without affected_user (role deletion is an example) 49 | // So, /shrug 50 | // required: true, 51 | }, 52 | creator: { 53 | // User ID of the issuer 54 | type: String, 55 | required: true, 56 | }, 57 | message_id: { 58 | // Message ID of the modlog entry 59 | type: String, 60 | required: true, 61 | }, 62 | reason: { 63 | type: String, 64 | maxlength: 1500, 65 | }, 66 | isValid: { 67 | type: Boolean, 68 | default: true, 69 | }, 70 | canEdit: { 71 | type: Boolean, 72 | default: true, 73 | }, 74 | })], 75 | }); 76 | -------------------------------------------------------------------------------- /Database/Schemas/serverSchema.js: -------------------------------------------------------------------------------- 1 | const Schema = require("../Schema"); 2 | 3 | // Server Schema 4 | module.exports = new Schema({ 5 | _id: { 6 | type: String, 7 | required: true, 8 | }, 9 | added_timestamp: { 10 | type: Date, 11 | default: Date.now, 12 | }, 13 | config: require("./serverConfigSchema.js"), 14 | extensions: [require("./serverGallerySchema.js")], 15 | members: require("./serverMembersSchema.js"), 16 | games: [require("./serverGamesSchema.js")], 17 | channels: require("./serverChannelsSchema.js"), 18 | command_usage: { 19 | type: Object, 20 | default: {}, 21 | }, 22 | messages_today: { 23 | type: Number, 24 | default: 0, 25 | }, 26 | stats_timestamp: { 27 | type: Date, 28 | default: Date.now, 29 | }, 30 | voice_data: [new Schema({ 31 | _id: { 32 | type: String, 33 | required: true, 34 | }, 35 | started_timestamp: { 36 | type: Date, 37 | required: true, 38 | }, 39 | })], 40 | logs: [new Schema({ 41 | timestamp: { 42 | type: Date, 43 | required: false, 44 | default: Date.now, 45 | }, 46 | level: { 47 | type: String, 48 | required: true, 49 | }, 50 | content: { 51 | type: String, 52 | required: true, 53 | }, 54 | userid: { 55 | type: String, 56 | required: false, 57 | }, 58 | channelid: { 59 | type: String, 60 | required: false, 61 | }, 62 | }, { _id: false })], 63 | modlog: require("./serverModlogSchema.js"), 64 | }); 65 | -------------------------------------------------------------------------------- /Database/Schemas/trafficSchema.js: -------------------------------------------------------------------------------- 1 | const Schema = require("../Schema"); 2 | 3 | // Traffic Schema 4 | module.exports = new Schema({ 5 | _id: { 6 | type: Number, 7 | default: Date.now, 8 | }, 9 | pageViews: { 10 | type: Number, 11 | required: true, 12 | }, 13 | authViews: { 14 | type: Number, 15 | required: true, 16 | }, 17 | uniqueUsers: { 18 | type: Number, 19 | required: true, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /Database/Schemas/userSchema.js: -------------------------------------------------------------------------------- 1 | const Schema = require("../Schema"); 2 | 3 | // User data (past names, profile fields, etc) 4 | module.exports = new Schema({ 5 | _id: { 6 | type: String, 7 | required: true, 8 | }, 9 | past_names: [String], 10 | points: { 11 | type: Number, 12 | default: 1, 13 | }, 14 | afk_message: String, 15 | server_nicks: [new Schema({ 16 | _id: { 17 | type: String, 18 | required: true, 19 | lowercase: true, 20 | }, 21 | server_id: { 22 | type: String, 23 | required: true, 24 | }, 25 | })], 26 | reminders: [new Schema({ 27 | _id: { 28 | type: String, 29 | required: true, 30 | }, 31 | name: { 32 | type: String, 33 | required: true, 34 | }, 35 | expiry_timestamp: { 36 | type: Number, 37 | required: true, 38 | }, 39 | })], 40 | location: String, 41 | weatherunit: String, 42 | last_seen: Date, 43 | profile_fields: Schema.Mixed, 44 | profile_background_image: { 45 | type: String, 46 | default: `http://i.imgur.com/8UIlbtg.jpg`, 47 | }, 48 | isProfilePublic: { 49 | type: Boolean, 50 | default: true, 51 | }, 52 | upvoted_gallery_extensions: [String], 53 | username: String, 54 | }); 55 | -------------------------------------------------------------------------------- /Database/Schemas/wikiSchema.js: -------------------------------------------------------------------------------- 1 | const Schema = require("../Schema"); 2 | 3 | // Schema for wiki entries 4 | module.exports = new Schema({ 5 | _id: { 6 | type: String, 7 | minlength: 3, 8 | maxlength: 100, 9 | required: true, 10 | }, 11 | content: { 12 | type: String, 13 | required: true, 14 | }, 15 | reactions: [new Schema({ 16 | _id: { 17 | type: String, 18 | required: true, 19 | }, 20 | value: { 21 | type: Number, 22 | enum: [-1, 1], 23 | }, 24 | })], 25 | updates: [new Schema({ 26 | _id: { 27 | type: String, 28 | required: true, 29 | }, 30 | timestamp: { 31 | type: Date, 32 | default: Date.now, 33 | }, 34 | diff: { 35 | type: String, 36 | }, 37 | })], 38 | }); 39 | -------------------------------------------------------------------------------- /Internals/Errors/GABError.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Heavily inspired from Discord.js's Error code, which in turn is inspired from Node's `internal/errors` module 3 | */ 4 | const kCode = Symbol("code"); 5 | const messages = new Map(); 6 | 7 | /** 8 | * Extend an error of some sort into a GABError 9 | */ 10 | 11 | const makeGABError = base => class GABError extends base { 12 | constructor (key, meta, ...args) { 13 | super(message(key, args)); 14 | this[kCode] = key; 15 | this._meta = meta || {}; 16 | if (Error.captureStackTrace) Error.captureStackTrace(this, GABError); 17 | } 18 | 19 | get name () { 20 | return `${super.name} [${this[kCode]}]`; 21 | } 22 | 23 | get code () { 24 | return this[kCode]; 25 | } 26 | }; 27 | 28 | /** 29 | * Formats the message into an error. 30 | * @param {String} key Error key 31 | * @param {*[]} args Arguments to pass for util format or as function args 32 | * @returns {String} The formatted string 33 | */ 34 | function message (key, args) { 35 | if (typeof key !== "string") throw new Error("Error message key must be a string"); 36 | const msg = messages.get(key); 37 | if (!msg) throw new Error(`An invalid error message key was used: ${key}.`); 38 | if (typeof msg === "function") return msg(...args); 39 | if (args === undefined || args.length === 0) return msg; 40 | args.unshift(msg); 41 | return String(...args); 42 | } 43 | 44 | module.exports = { 45 | register: (sym, val) => messages.set(sym, typeof val === "function" ? val : String(val)), 46 | Error: makeGABError(Error), 47 | TypeError: makeGABError(TypeError), 48 | RangeError: makeGABError(RangeError), 49 | }; 50 | -------------------------------------------------------------------------------- /Internals/Errors/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./GABError"); 2 | module.exports.Messages = require("./Messages"); 3 | -------------------------------------------------------------------------------- /Internals/Events/BaseEvent.js: -------------------------------------------------------------------------------- 1 | const { Error } = require("../Errors/"); 2 | 3 | class BaseEvent { 4 | /** 5 | * Base class for all events. 6 | * @param {Client} client 7 | * @param {Object} configJS 8 | */ 9 | constructor (client, configJS) { 10 | this.client = client; 11 | this.configJS = configJS; 12 | this.configJSON = configJSON; 13 | } 14 | 15 | /** 16 | * Public handler for events 17 | * @param {?Object} [values] The values that the event emitted 18 | */ 19 | async handle () { 20 | throw new Error("NO_HANDLE", {}, this.constructor.name); 21 | } 22 | 23 | /** 24 | * Call this function to handle events if the requirement is set 25 | * @param {?*[]} [args] Params to hand over to the requirement check, the prerequisite, and to the event 26 | * @private 27 | */ 28 | async _handle (...args) { 29 | if (this.requirements(...args)) { 30 | await this.prerequisite(...args); 31 | return this.handle(...args); 32 | } 33 | } 34 | 35 | /** 36 | * Simple logic for checking if the event should run or not. 37 | * @param {?Object} [values] The values object that the event emitted 38 | * @returns {Boolean} 39 | */ 40 | requirements () { 41 | return true; 42 | } 43 | 44 | /** 45 | * Simple function that prepares everything that the event may need (like documents) 46 | */ 47 | async prerequisite () { } // eslint-disable-line no-empty-function 48 | } 49 | 50 | module.exports = BaseEvent; 51 | -------------------------------------------------------------------------------- /Internals/Events/channelCreate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/channelCreate/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/channelPinsUpdate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/channelPinsUpdate/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/channelUpdate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/channelUpdate/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/debug/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/debug/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/disconnect/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/disconnect/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/emojiCreate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/emojiCreate/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/emojiDelete/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/emojiDelete/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/emojiUpdate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/emojiUpdate/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/error/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/error/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/guildAvailable/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/guildAvailable/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/guildBanAdd/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/guildBanAdd/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/guildBanAdd/GAB.GuildBanAdd.js: -------------------------------------------------------------------------------- 1 | const BaseEvent = require("../BaseEvent"); 2 | const { StatusMessages } = require("../../Constants"); 3 | 4 | class GuildBanAdd extends BaseEvent { 5 | async handle (guild, user) { 6 | const serverDocument = await Servers.findOne(guild.id); 7 | if (!serverDocument) { 8 | return logger.debug("Failed to find server data for GuildBanAdd.", { svrid: guild.id, usrid: user.id }); 9 | } 10 | 11 | if (serverDocument.config.moderation.isEnabled && serverDocument.config.moderation.status_messages.member_banned_message.isEnabled) { 12 | logger.verbose(`Member '${user.tag}' banned from guild '${guild}'.`, { svrid: guild.id, usrid: user.id }); 13 | const channel = guild.channels.get(serverDocument.config.moderation.status_messages.member_banned_message.channel_id); 14 | if (channel) { 15 | const channelDocument = serverDocument.channels[channel.id]; 16 | if (!channelDocument || channelDocument.bot_enabled) { 17 | const { messages } = serverDocument.config.moderation.status_messages.member_banned_message; 18 | const message = messages[Math.floor(Math.random() * messages.length)]; 19 | if (!message) return; 20 | channel.send({ 21 | embed: StatusMessages.GUILD_BAN_ADD(message, user), 22 | }).catch(err => { 23 | logger.debug(`Failed to send StatusMessage for GUILD_BAN_ADD.`, { svrid: guild.id, chid: channel.id }, err); 24 | }); 25 | } 26 | } 27 | } 28 | } 29 | } 30 | 31 | module.exports = GuildBanAdd; 32 | -------------------------------------------------------------------------------- /Internals/Events/guildBanRemove/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/guildBanRemove/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/guildBanRemove/GAB.GuildBanRemove.js: -------------------------------------------------------------------------------- 1 | const BaseEvent = require("../BaseEvent"); 2 | const { StatusMessages } = require("../../Constants"); 3 | 4 | class GuildBanAdd extends BaseEvent { 5 | async handle (guild, user) { 6 | const serverDocument = await Servers.findOne(guild.id); 7 | if (!serverDocument) { 8 | return logger.debug("Failed to find server data for GuildBanRemove", { svrid: guild.id, usrid: user.id }); 9 | } 10 | 11 | if (serverDocument.config.moderation.isEnabled && serverDocument.config.moderation.status_messages.member_unbanned_message.isEnabled) { 12 | logger.verbose(`Member '${user.tag}' unbanned from guild '${guild}'`, { svrid: guild.id, usrid: user.id }); 13 | const channel = guild.channels.get(serverDocument.config.moderation.status_messages.member_unbanned_message.channel_id); 14 | if (channel) { 15 | const channelDocument = serverDocument.channels[channel.id]; 16 | if (!channelDocument || channelDocument.bot_enabled) { 17 | const { messages } = serverDocument.config.moderation.status_messages.member_unbanned_message; 18 | const message = messages[Math.floor(Math.random() * messages.length)]; 19 | if (!message) return; 20 | channel.send({ 21 | embed: StatusMessages.GUILD_BAN_REMOVE(message, user), 22 | }).catch(err => { 23 | logger.debug(`Failed to send StatusMessage for GUILD_BAN_REMOVE.`, { svrid: guild.id, chid: channel.id }, err); 24 | }); 25 | } 26 | } 27 | } 28 | } 29 | } 30 | 31 | module.exports = GuildBanAdd; 32 | -------------------------------------------------------------------------------- /Internals/Events/guildCreate/GAB.GuildCreate.js: -------------------------------------------------------------------------------- 1 | const BaseEvent = require("../BaseEvent.js"); 2 | const { NewServer: getNewServerData, PostShardedData } = require("../../../Modules/"); 3 | const { LoggingLevels } = require("../../Constants"); 4 | 5 | class GuildCreate extends BaseEvent { 6 | async handle (guild) { 7 | if (this.configJSON.guildBlocklist.includes(guild.id)) { 8 | logger.info(`Left "${guild}" due to it being blocklisted!`, { guild: guild.id }); 9 | guild.leave(); 10 | } else { 11 | this.client.IPC.send("sendAllGuilds", {}); 12 | await Promise.all([guild.members.fetch(), PostShardedData(this.client)]); 13 | let serverDocument, shouldMakeDocument = false; 14 | try { 15 | serverDocument = await Servers.findOne(guild.id); 16 | } catch (err) { 17 | shouldMakeDocument = true; 18 | } 19 | if (serverDocument) { 20 | logger.info(`Rejoined server ${guild}`, { svrid: guild.id }); 21 | this.client.logMessage(serverDocument, LoggingLevels.INFO, "I've been re-added to your server! (^-^)"); 22 | } else if (shouldMakeDocument || !serverDocument) { 23 | logger.info(`Joined server ${guild}`, { svrid: guild.id }); 24 | try { 25 | const newServerDocument = await getNewServerData(this.client, guild, Servers.new({ _id: guild.id })); 26 | await newServerDocument.save(); 27 | } catch (err) { 28 | logger.warn(`Failed to create a new server document for new server >.>`, { svrid: guild.id }, err); 29 | } 30 | this.client.logMessage(await Servers.findOne(guild.id), LoggingLevels.INFO, "I've been added to your server! (^-^)"); 31 | } 32 | } 33 | } 34 | } 35 | 36 | module.exports = GuildCreate; 37 | -------------------------------------------------------------------------------- /Internals/Events/guildDelete/GAB.GuildDelete.js: -------------------------------------------------------------------------------- 1 | const BaseEvent = require("../BaseEvent"); 2 | const { PostShardedData } = require("../../../Modules/"); 3 | 4 | class GuildDelete extends BaseEvent { 5 | async handle (guild) { 6 | this.client.IPC.send("sendAllGuilds"); 7 | await PostShardedData(this.client); 8 | logger.info(`Left server ${guild} :(`, { svrid: guild.id }); 9 | } 10 | } 11 | 12 | module.exports = GuildDelete; 13 | -------------------------------------------------------------------------------- /Internals/Events/guildMemberAvailable/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/guildMemberAvailable/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/guildMemberRemove/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/guildMemberRemove/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/guildMemberSpeaking/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/guildMemberSpeaking/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/guildMemberUpdate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/guildMemberUpdate/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/guildMembersChunk/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/guildMembersChunk/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/guildUnavailable/GAB.GuildUnavailable.js: -------------------------------------------------------------------------------- 1 | const BaseEvent = require("../BaseEvent"); 2 | 3 | class GuildUnavailable extends BaseEvent { 4 | async handle (guild) { 5 | logger.warn(`Guild "${guild.name}" is unavailable!`, { svrid: guild.id }); 6 | } 7 | } 8 | 9 | module.exports = GuildUnavailable; 10 | -------------------------------------------------------------------------------- /Internals/Events/guildUpdate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/guildUpdate/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/message/GAB.SharedCommandMessageHandler.js: -------------------------------------------------------------------------------- 1 | const BaseEvent = require("../BaseEvent"); 2 | const { Constants } = require("../../index"); 3 | const { Colors } = Constants; 4 | 5 | class MaintainerMessageCreate extends BaseEvent { 6 | requirements (msg) { 7 | if (!msg.channel.postable || msg.type !== "DEFAULT") return false; 8 | if (configJSON.userBlocklist.includes(msg.author.id)) return false; 9 | if (configJSON.sudoMaintainers.includes(msg.author.id) || 10 | configJSON.maintainers.includes(msg.author.id)) { 11 | return true; 12 | } 13 | return false; 14 | } 15 | 16 | async handle (msg) { 17 | if (msg.command) { 18 | const command = this.client.getSharedCommand(msg.command); 19 | if (command) { 20 | if (await this.client.canRunSharedCommand(msg.command, msg.author)) { 21 | try { 22 | await command({ 23 | client: this.client, 24 | configJS: this.configJS, 25 | Constants, 26 | }, msg, { 27 | name: this.client.getSharedCommandName(msg.command), 28 | usage: this.client.getSharedCommandMetadata(msg.command).usage, 29 | }); 30 | } catch (err) { 31 | logger.warn(`Failed to process shared command "${msg.command}"`, { usrid: msg.author.id }, err); 32 | msg.send({ 33 | embed: { 34 | color: Colors.ERROR, 35 | title: `Something went wrong! 😱`, 36 | description: `**Error Message**: \`\`\`js\n${err.stack}\`\`\``, 37 | footer: { 38 | text: `You should report this on GitHub so we can fix it!`, 39 | }, 40 | }, 41 | }); 42 | } 43 | } else { 44 | msg.send({ 45 | embed: { 46 | color: Colors.MISSING_PERMS, 47 | title: `Sorry, you're not authorized to run this!`, 48 | description: `You are unable to run the \`${msg.command}\` command, because you're lacking permissions! 😦`, 49 | }, 50 | }); 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | module.exports = MaintainerMessageCreate; 58 | -------------------------------------------------------------------------------- /Internals/Events/message/GAB.UsernameHandler.js: -------------------------------------------------------------------------------- 1 | const BaseEvent = require("../BaseEvent"); 2 | 3 | /** 4 | * Username updates per message 5 | */ 6 | class UsernameHandler extends BaseEvent { 7 | requirements (msg) { 8 | return !msg.author.bot && msg.type === "DEFAULT"; 9 | } 10 | 11 | async prerequisite (msg) { 12 | this.userDocument = await Users.findOne(msg.author.id); 13 | } 14 | 15 | async handle (msg) { 16 | if (this.userDocument && this.userDocument.username !== msg.author.tag) { 17 | this.userDocument.query.set("username", msg.author.tag); 18 | await this.userDocument.save(); 19 | } 20 | } 21 | } 22 | 23 | module.exports = UsernameHandler; 24 | -------------------------------------------------------------------------------- /Internals/Events/messageDelete/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/messageDelete/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/messageDeleteBulk/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/messageDeleteBulk/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/messageReactionAdd/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/messageReactionAdd/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/messageReactionRemove/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/messageReactionRemove/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/messageReactionRemoveAll/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/messageReactionRemoveAll/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/messageUpdate/GAB.MessageCommandUpdateHandler.js: -------------------------------------------------------------------------------- 1 | const BaseEvent = require("../BaseEvent"); 2 | 3 | module.exports = class extends BaseEvent { 4 | requirements (old, msg) { 5 | if (old.content !== msg.content) return true; 6 | } 7 | 8 | async handle (old, msg) { 9 | this.client.emit("message", msg); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /Internals/Events/presenceUpdate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/presenceUpdate/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/rateLimit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/rateLimit/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/resumed/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/resumed/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/roleCreate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/roleCreate/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/roleDelete/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/roleDelete/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/roleDelete/GAB.RoleDelete.js: -------------------------------------------------------------------------------- 1 | const BaseEvent = require("../BaseEvent.js"); 2 | 3 | class RoleDelete extends BaseEvent { 4 | async handle (role) { 5 | const serverDocument = await Servers.findOne(role.guild.id); 6 | if (!serverDocument) { 7 | return logger.debug("Failed to find server data for role deletion", { svrid: role.guild.id, roleid: role.id }); 8 | } 9 | const serverQueryDocument = serverDocument.query; 10 | 11 | let updated = false; 12 | 13 | const adminQueryDocument = serverQueryDocument.clone.id("config.admins", role.id); 14 | if (adminQueryDocument.val) { 15 | updated = true; 16 | adminQueryDocument.remove(); 17 | } 18 | 19 | if (serverDocument.config.custom_roles.includes(role.id)) { 20 | updated = true; 21 | serverQueryDocument.pull("config.custom_roles", role.id); 22 | } 23 | 24 | for (const filter in serverDocument.config.moderation.filters) { 25 | if (serverDocument.config.moderation.filters[filter].violator_role_id === role.id) { 26 | updated = true; 27 | serverQueryDocument.remove(`config.moderation.filters.${filter}.violator_role_id`); 28 | } 29 | } 30 | 31 | if (serverDocument.config.moderation.new_member_roles.includes(role.id)) { 32 | updated = true; 33 | serverQueryDocument.pull("config.moderation.new_member_roles", role.id); 34 | } 35 | 36 | serverDocument.config.ranks_list.forEach(rankDocument => { 37 | if (rankDocument.role_id === role.id) { 38 | updated = true; 39 | serverQueryDocument.clone.id("config.ranks_list", role.id).remove("role_id"); 40 | } 41 | }); 42 | 43 | if (updated) { 44 | serverDocument.save().catch(err => { 45 | logger.warn("Failed to save server data for role deletion", { svrid: role.guild.id, roleid: role.id }, err); 46 | }); 47 | } 48 | } 49 | } 50 | 51 | module.exports = RoleDelete; 52 | -------------------------------------------------------------------------------- /Internals/Events/roleUpdate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/roleUpdate/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/userUpdate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/userUpdate/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/userUpdate/GAB.UpdateUsername.js: -------------------------------------------------------------------------------- 1 | const BaseEvent = require("../BaseEvent"); 2 | 3 | class UsernameUpdater extends BaseEvent { 4 | requirements (oldUser, newUser) { 5 | return oldUser.id !== this.client.user.id && !oldUser.bot && !this.configJSON.userBlocklist.includes(oldUser.id) && oldUser.tag !== newUser.tag; 6 | } 7 | 8 | async handle (oldUser, newUser) { 9 | let userDocument = await Users.findOne(oldUser.id); 10 | if (!userDocument) userDocument = await Users.new({ _id: oldUser.id }); 11 | userDocument.query.set("username", newUser.tag); 12 | if (userDocument.past_names && !userDocument.past_names.includes(oldUser.username)) userDocument.query.push("past_names", oldUser.username); 13 | userDocument.save().catch(err => { 14 | logger.debug(`Failed to save userDocument ${oldUser.tag} for UpdateUsername.`, { usrid: oldUser.id }, err); 15 | }); 16 | } 17 | } 18 | 19 | module.exports = UsernameUpdater; 20 | -------------------------------------------------------------------------------- /Internals/Events/userUpdate/GAB.UserUpdate.js: -------------------------------------------------------------------------------- 1 | const BaseEvent = require("../BaseEvent"); 2 | const { StatusMessages } = require("../../Constants"); 3 | 4 | class UserUpdate extends BaseEvent { 5 | requirements (oldUser, newUser) { 6 | return !newUser.bot; 7 | } 8 | 9 | async handle (oldUser, newUser) { 10 | this.client.guilds.forEach(guild => { 11 | if (guild.members.has(newUser.id)) this.sendStatusMessages(guild, oldUser, newUser).catch(() => null); 12 | }); 13 | } 14 | 15 | async sendStatusMessages (guild, oldUser, newUser) { 16 | const serverDocument = await Servers.findOne(guild.id); 17 | if (!serverDocument || !serverDocument.config.moderation.isEnabled) return; 18 | const member = guild.members.get(newUser.id); 19 | 20 | if (oldUser.username !== newUser.username && serverDocument.config.moderation.status_messages.member_username_updated_message.isEnabled) { 21 | const channel = guild.channels.get(serverDocument.config.moderation.status_messages.member_username_updated_message.channel_id); 22 | if (channel) { 23 | channel.send({ 24 | embed: StatusMessages.USER_USERNAME_UPDATED(this.client, serverDocument, oldUser, member), 25 | disableEveryone: true, 26 | }).catch(err => { 27 | logger.debug(`Failed to send StatusMessage for USER_USERNAME_UPDATES.`, { svrid: guild.id, chid: channel.id }, err); 28 | }); 29 | } 30 | } 31 | 32 | if (oldUser.avatar !== newUser.avatar && serverDocument.config.moderation.status_messages.member_avatar_updated_message.isEnabled) { 33 | const channel = guild.channels.get(serverDocument.config.moderation.status_messages.member_avatar_updated_message.channel_id); 34 | if (channel) { 35 | channel.send({ 36 | embed: StatusMessages.USER_AVATAR_UPDATED(this.client, serverDocument, oldUser, member), 37 | disableEveryone: true, 38 | }).catch(err => { 39 | logger.debug(`Failed to send StatusMessage for USER_AVATAR_UPDATED.`, { svrid: guild.id, chid: channel.id }, err); 40 | }); 41 | } 42 | } 43 | } 44 | } 45 | 46 | module.exports = UserUpdate; 47 | -------------------------------------------------------------------------------- /Internals/Events/voiceStateUpdate/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/voiceStateUpdate/.gitkeep -------------------------------------------------------------------------------- /Internals/Events/warn/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Internals/Events/warn/.gitkeep -------------------------------------------------------------------------------- /Internals/ExtendableBase.js: -------------------------------------------------------------------------------- 1 | module.exports = class ExtendableBase { 2 | constructor (appliesTo, name) { 3 | this.appliesTo = appliesTo; 4 | this.name = name; 5 | 6 | this.target = require("discord.js"); 7 | 8 | this.enabled = false; 9 | } 10 | 11 | extend () { 12 | // Overwrite in your class 13 | } 14 | 15 | enable () { 16 | this.enabled = true; 17 | for (const structure of this.appliesTo) Object.defineProperty(this.target[structure].prototype, this.name, Object.getOwnPropertyDescriptor(this.constructor.prototype, "extend")); 18 | return this; 19 | } 20 | 21 | disable () { 22 | this.enabled = false; 23 | for (const structure of this.appliesTo) delete this.target[structure].prototype[this.name]; 24 | return this; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /Internals/Extendables/Postable.js: -------------------------------------------------------------------------------- 1 | const Extendable = require("../ExtendableBase"); 2 | 3 | module.exports = class extends Extendable { 4 | constructor () { 5 | super(["DMChannel", "TextChannel"], "postable"); 6 | } 7 | 8 | get extend () { 9 | return !this.guild || (this.readable && this.permissionsFor(this.guild.me).has("SEND_MESSAGES")); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /Internals/Extendables/Readable.js: -------------------------------------------------------------------------------- 1 | const Extendable = require("../ExtendableBase"); 2 | 3 | module.exports = class extends Extendable { 4 | constructor () { 5 | super(["DMChannel", "TextChannel"], "readable"); 6 | } 7 | 8 | get extend () { 9 | return !this.guild || this.permissionsFor(this.guild.me).has("VIEW_CHANNEL"); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /Internals/Extensions/API/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: "../../../.eslintrc.js", 3 | rules: { 4 | "valid-jsdoc": ["error", { 5 | "requireReturn": false, 6 | "requireReturnDescription": false, 7 | "prefer": { 8 | "return": "returns", 9 | "arg": "param" 10 | }, 11 | "preferType": { 12 | "string": "String", 13 | "number": "Number", 14 | "boolean": "Boolean", 15 | "symbol": "Symbol", 16 | "object": "Object", 17 | "function": "Function", 18 | "array": "Array", 19 | "date": "Date", 20 | "error": "Error", 21 | "null": "void" 22 | } 23 | }], 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /Internals/Extensions/API/Modules/Client.js: -------------------------------------------------------------------------------- 1 | const API = require("../index"); 2 | const privProps = new WeakMap(); 3 | 4 | 5 | /** 6 | * Sandboxed Discord.js client 7 | */ 8 | module.exports = class Client { 9 | /** 10 | * Create a Discord.js client sandboxed for extension execution 11 | * @param {GABClient} bot The raw bot instance 12 | * @param {Discord.Guild} server The raw guild this client should be instantiated for 13 | * @param {Document} serverDocument The server document for the guild 14 | * @param {Object} extensionDocument The extension's DB document 15 | * @param {Object} scopes The scopes this extension has been granted by the guild 16 | */ 17 | constructor (bot, server, serverDocument, extensionDocument, scopes) { 18 | // Private values 19 | privProps.set(this, { bot, server, serverDocument, extensionDocument, scopes }); 20 | 21 | // Extension values 22 | /** 23 | * The client's Discord user. 24 | * @type {API.User} 25 | */ 26 | this.user = new API.User(bot.user); 27 | 28 | /** 29 | * The amount of ms that have passed since this shard entered the READY state. 30 | * @type {number} 31 | */ 32 | this.uptime = bot.uptime; 33 | 34 | /** 35 | * The ID for the current shard. 36 | * @type {string} 37 | */ 38 | this.shard = bot.shardID; 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /Internals/Extensions/API/Structures/Channel.js: -------------------------------------------------------------------------------- 1 | const { Scopes } = require("../../../Constants"); 2 | const ScopeManager = require("../Utils/ScopeManager"); 3 | const privProps = new WeakMap(); 4 | 5 | /** 6 | * Represents a message on Discord. 7 | * @memberof API 8 | */ 9 | class Channel { 10 | constructor (API, client, channel, scopes) { 11 | privProps.set(this, { API, client, channel, scopes }); 12 | 13 | /** 14 | * A UNIX Timestamp of the creation of this channel. 15 | * @type {Number} 16 | */ 17 | this.createdTimestamp = channel.createdTimestamp; 18 | } 19 | /** 20 | * The Date this message was created. 21 | * @type {?Date} 22 | * @readonly 23 | */ 24 | get createdAt () { 25 | return this.createdTimestamp ? new Date(this.createdTimestamp) : null; 26 | } 27 | 28 | /** 29 | * Whether the extension can delete this channel. 30 | * @type {Boolean} 31 | * @readonly 32 | */ 33 | get deletable () { 34 | return !!(privProps.get(this).channel.deletable && ScopeManager.check(privProps.get(this).scopes, Scopes.channels_manage.scope)); 35 | } 36 | } 37 | 38 | module.exports = Channel; 39 | -------------------------------------------------------------------------------- /Internals/Extensions/API/Structures/Emoji.js: -------------------------------------------------------------------------------- 1 | module.exports = class Emoji { 2 | 3 | }; 4 | -------------------------------------------------------------------------------- /Internals/Extensions/API/Structures/Guild.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a Discord Guild 3 | */ 4 | module.exports = class Guild { 5 | 6 | }; 7 | -------------------------------------------------------------------------------- /Internals/Extensions/API/Structures/Member.js: -------------------------------------------------------------------------------- 1 | class Member { 2 | 3 | } 4 | 5 | module.exports = Member; 6 | -------------------------------------------------------------------------------- /Internals/Extensions/API/Structures/User.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents an extension user 3 | */ 4 | class User { 5 | 6 | } 7 | 8 | module.exports = User; 9 | -------------------------------------------------------------------------------- /Internals/Extensions/API/Utils/ScopeManager.js: -------------------------------------------------------------------------------- 1 | const { 2 | Errors: { 3 | Error: GABError, 4 | }, 5 | } = require("../../../index"); 6 | 7 | module.exports = class ScopeManager { 8 | /** 9 | * Checks if the extension has sufficient scopes to execute a function. 10 | * @param {Array} scopes - The scopes an extension has 11 | * @param {String} scope - The scope to check 12 | * @returns {Boolean} True if the extension can successfully execute any functions that requires the given scope 13 | */ 14 | static check (scopes, scope) { 15 | if (!scopes.includes(scope)) throw new GABError("MISSING_SCOPES"); 16 | return true; 17 | } 18 | 19 | /** 20 | * Sets a value protected by scopes on the target object 21 | * @param {Object} object - The target object receiving the protected value 22 | * @param {String} key - The key the value is assigned to on the target object 23 | * @param {*} value - The value to protect and set on the target object 24 | * @param {Array} scopes - A list of scopes the extension has access to 25 | * @param {String} scope - The scope required to access the protected value 26 | */ 27 | static setProtectedValue (object, key, value, scopes, scope) { 28 | Object.defineProperty(object, key, { 29 | get: () => { 30 | ScopeManager.check(scopes, scope); 31 | return value; 32 | }, 33 | }); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /Internals/Extensions/API/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The GAB Extension Code API. 3 | * @namespace API 4 | */ 5 | module.exports = { 6 | Client: require("./Modules/Client"), 7 | Message: require("./Structures/Message"), 8 | User: require("./Structures/User"), 9 | Guild: require("./Structures/Guild"), 10 | Channel: require("./Structures/Channel"), 11 | Member: require("./Structures/Member"), 12 | Emoji: require("./Structures/Emoji"), 13 | Embed: require("./Structures/Embed"), 14 | 15 | Extension: require("./Modules/Extension"), 16 | ScopeManager: require("./Utils/ScopeManager"), 17 | }; 18 | -------------------------------------------------------------------------------- /Internals/Extensions/EventsHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Class for handling ExtensionManager events 3 | * @class 4 | */ 5 | class EventsHandler { 6 | /** 7 | * Construct a new EventsHandler and hook it into the provided ExtensionManager 8 | * @param {ExtensionManager} manager The ExtensionManager to hook into and listen to for events 9 | * @param {string[]} events A list of events to listen for 10 | */ 11 | constructor (manager, events) { 12 | this.manager = manager; 13 | this.events = [...events, "message"]; 14 | 15 | this.events.forEach(event => { 16 | this.manager.on(event, this._handlerWrapper(this[event] || this.defaultHandler)); 17 | }); 18 | } 19 | 20 | defaultHandler () { 21 | return null; 22 | } 23 | 24 | _handlerWrapper (func) { 25 | return async (...args) => { 26 | try { 27 | await func(...args); 28 | } catch (err) { 29 | logger.error("An exception occurred while trying to run an extension.", {}, err); 30 | } 31 | }; 32 | } 33 | } 34 | 35 | module.exports = EventsHandler; 36 | -------------------------------------------------------------------------------- /Internals/Extensions/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./ExtensionManager"); 2 | -------------------------------------------------------------------------------- /Internals/IPC.js: -------------------------------------------------------------------------------- 1 | class SharderIPC { 2 | constructor (sharder, logger) { 3 | this.sharder = sharder; 4 | this.logger = logger; 5 | this.onEvents = new Map(); 6 | this.onceEvents = new Map(); 7 | } 8 | 9 | send (subject, payload, shard, timeout) { 10 | try { 11 | this.logger.silly("Sending message to shard", { subject: subject, payload: payload, shard: shard }); 12 | 13 | if (shard === "*") { 14 | return this.sharder.broadcast(subject, payload, timeout); 15 | } else { 16 | shard = this.sharder.shards.get(shard); 17 | if (!shard) shard = this.sharder.shards.get(0); 18 | return shard.send(subject, payload, timeout); 19 | } 20 | } catch (err) { 21 | this.logger.warn("Failed to send message to shard :C\n", { subject: subject, payload: payload, shard: shard }, err); 22 | } 23 | } 24 | 25 | on (event, callback) { 26 | this.onEvents.set(event, callback); 27 | } 28 | 29 | once (event, callback) { 30 | this.onceEvents.set(event, callback); 31 | } 32 | 33 | forward (event, prop = "guild") { 34 | this.onEvents.set(event, async (msg, callback) => { 35 | const target = prop === "this" ? msg : msg[prop]; 36 | const ID = target === "*" ? target : this.sharder.IPC.shard(target); 37 | if (this.sharder.shards.has(ID)) return callback(await this.send(event, msg, ID)); 38 | return callback({}); 39 | }); 40 | } 41 | 42 | shard (guildID) { 43 | try { 44 | // We have to avoid BigInt syntax because it's too flashy and experimental for eslint. 45 | // eslint-disable-next-line no-undef,no-bitwise 46 | return Math.abs(Number((BigInt(guildID) >> BigInt(22)) % BigInt(this.sharder.count))); 47 | } catch (_) { 48 | return undefined; 49 | } 50 | } 51 | } 52 | 53 | module.exports = SharderIPC; 54 | -------------------------------------------------------------------------------- /Internals/NPMScripts/WarnPreInstallWindows.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-useless-concat, no-console */ 2 | if (process.platform === "win32") { 3 | const text = [ 4 | "\u001b[31m" + "[Warning]" + "\u001b[39m" + " You are running Windows!", 5 | "You may encounter issues while installing GAwesomeBot's Dependencies.", 6 | "", 7 | "If you do, please run \"\u001b[32m\u001b[4m" + "npm run windows-install" + "\u001b[24m\u001b[39m\"" + " in an Administrator Command Prompt!", 8 | "If you are still encountering issues, please submit the logs over at\n\"\u001b[36m\u001b[4m" + "https://github.com/GilbertGobbels/GAwesomeBot" + "\u001b[24m\u001b[39m\"", 9 | ]; 10 | console.log(text.join("\n")); 11 | } 12 | -------------------------------------------------------------------------------- /Internals/README.md: -------------------------------------------------------------------------------- 1 | # GAwesomeBot Internals 2 | 3 | These files are crucial to the core functioning of GAB, you can call them the "engine" of GAB, if you will. 4 | 5 | #### When do I modify Internals? 6 | It is strongly recommended **to not edit any Internal files** for non-development purposes. 7 | If you are strictly seeking customization, you should check out the files in the `/Configurations` folder instead. 8 | *It is possible to edit the Constants.js file to modify some GAB behavior, but make sure you understand what you're doing first*. 9 | 10 | #### How do I decide what files are Internals? 11 | If a file; 12 | * Is crucial to *all* functioning of GAB, or 13 | * Is crucial to the functioning of another Internal file, or 14 | * Extends and/or modifies the behavior of another Internal file, or 15 | * Extends and utilises the Node Process, or 16 | * Provides features to the Self Hoster and/or Developer that do not function stand-alone, 17 | 18 | it is considered an Internal file. 19 | Files that do not fit any of the above descriptions may still be considered an Internal file if no other folder serves as a better fit. 20 | Some of the above functionality may be written in top-level files such as master.js or GAwesomeBot.js 21 | -------------------------------------------------------------------------------- /Internals/ShardUtil.js: -------------------------------------------------------------------------------- 1 | // Polyfill for client.shard 2 | module.exports = class ShardUtil { 3 | constructor (client) { 4 | this.client = client; 5 | } 6 | 7 | get id () { 8 | return Number(this.client.shardID); 9 | } 10 | 11 | get count () { 12 | return Number(process.env.SHARD_COUNT); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /Internals/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Boot: require("./Boot"), 3 | Logger: require("./Logger"), 4 | Constants: require("./Constants"), 5 | Errors: require("./Errors/"), 6 | EventHandler: require("./Events/EventHandler"), 7 | Sharder: require("./Sharder"), 8 | SharderIPC: require("./IPC"), 9 | ShardUtil: require("./ShardUtil"), 10 | WorkerManager: require("./WorkerManager"), 11 | }; 12 | -------------------------------------------------------------------------------- /Modules/Emoji/PrepareFrames.js: -------------------------------------------------------------------------------- 1 | /* 2 | MIT License 3 | 4 | Copyright (c) TTtie 2018 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | */ 24 | // Code is slightly modified from the source. See license above. 25 | 26 | const { GifCodec, GifFrame, BitmapImage } = require("gifwrap"); 27 | const Jimp = require("jimp"); 28 | const { AUTO } = Jimp; 29 | 30 | const codec = new GifCodec(); 31 | 32 | module.exports = async (result, buffer) => { 33 | let frames = []; 34 | let gif; 35 | if (Buffer.isBuffer(buffer)) { 36 | gif = await codec.decodeGif(buffer); 37 | ({ frames } = gif); 38 | } else if (buffer instanceof GifFrame) { 39 | frames = [buffer]; 40 | } 41 | frames.map(async frame => { 42 | const image = new Jimp(frame.bitmap.width, frame.bitmap.height); 43 | const bImage = new BitmapImage(frame); 44 | image.bitmap = bImage.bitmap; 45 | image.resize(result.type === "unicode" ? 72 : 128, AUTO); 46 | return new GifFrame(bImage); 47 | }); 48 | return { 49 | frames: await Promise.all(frames), 50 | gif, 51 | }; 52 | }; 53 | -------------------------------------------------------------------------------- /Modules/Encryption.js: -------------------------------------------------------------------------------- 1 | const { encryptionPassword, encryptionIv } = require("../Configurations/config"); 2 | const { discord: { clientID } } = require("../Configurations/auth"); 3 | const { createCipheriv, createDecipheriv, pbkdf2Sync } = require("crypto"); 4 | 5 | const pass = Buffer.from(`${clientID}:${encryptionPassword}`).toString("base64"); 6 | 7 | let password; 8 | 9 | module.exports = class EncryptionManager { 10 | constructor (client) { 11 | this.client = client; 12 | client.fetchApplication().then(data => { 13 | password = pbkdf2Sync(pass, data.owner.id, 100000, 16, "sha512").toString("hex"); 14 | }).catch(); 15 | } 16 | 17 | encrypt (data) { 18 | const cipher = createCipheriv("aes256", password, encryptionIv); 19 | let encrypted = cipher.update(data, "utf8", "hex"); 20 | encrypted += cipher.final("hex"); 21 | return encrypted; 22 | } 23 | 24 | decrypt (data) { 25 | const decipher = createDecipheriv("aes256", password, encryptionIv); 26 | let decrypted = decipher.update(data, "hex", "utf8"); 27 | decrypted += decipher.final("utf8"); 28 | return decrypted; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /Modules/ExtensionRunner.js: -------------------------------------------------------------------------------- 1 | const { VM } = require("vm2"); 2 | const fs = require("fs-nextra"); 3 | 4 | // Run an extension (command or keyword) in the sandbox 5 | /* eslint-disable max-len, no-unused-vars*/ 6 | module.exports = async (bot, server, serverDocument, channel, extensionDocument, msg, suffix, keywordMatch) => { 7 | let extensionCode; 8 | try { 9 | extensionCode = await fs.readFile(`${__dirname}/../extensions/${extensionDocument.code_id}.gabext`, "utf8"); 10 | } catch (err) { 11 | logger.warn(`Failed to load the extension code for ${extensionDocument.type} extension "${extensionDocument.name}"`, { svrid: server.id, extid: extensionDocument._id }, err); 12 | } 13 | if (extensionCode) { 14 | try { 15 | const vm = new VM({ 16 | timeout: extensionDocument.timeout, 17 | sandbox: {}, 18 | }); 19 | vm.run(extensionCode); 20 | } catch (err) { 21 | logger.warn(`Failed to run ${extensionDocument.type} extension "${extensionDocument.name}"`, { svrid: server.id, chid: channel.id, extid: extensionDocument._id }, err); 22 | } 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /Modules/Giphy.js: -------------------------------------------------------------------------------- 1 | const { get } = require("snekfetch"); 2 | const { tokens: { giphyAPI } } = require("../Configurations/auth"); 3 | const { APIs } = require("../Internals/Constants"); 4 | const { GABError } = require("../Internals/Errors"); 5 | 6 | /** 7 | * Fetches a random gif from Giphy 8 | * @param {String} query 9 | * @param {?String} [nsfw="pg-13"] 10 | * @returns {Promise} Giphy API return 11 | */ 12 | module.exports = async (query, nsfw = "pg-13") => { 13 | if (!query) throw new GABError("MISSING_GIPHY_QUERY"); 14 | const res = await get(APIs.GIPHY(giphyAPI, query, nsfw)); 15 | if (res.statusCode === 200 && res.body && res.body.data) return res.body.data; 16 | else throw new GABError("NO_GIPHY_RESULT"); 17 | }; 18 | -------------------------------------------------------------------------------- /Modules/Imgur.js: -------------------------------------------------------------------------------- 1 | const request = require("snekfetch"); 2 | 3 | const apiURL = "https://api.imgur.com/3/"; 4 | 5 | module.exports = class Imgur { 6 | constructor (clientID) { 7 | this.clientID = clientID; 8 | } 9 | 10 | async uploadUrl (url, albumID) { 11 | const form = { 12 | type: "URL", 13 | image: url, 14 | }; 15 | if (albumID) { 16 | form.album = albumID; 17 | } 18 | const res = await this._createRequest("post", "image", form); 19 | return { 20 | status: res.statusCode, 21 | body: res.body, 22 | data: res.body.data, 23 | raw: res, 24 | }; 25 | } 26 | 27 | async createAlbum () { 28 | const res = await this._createRequest("post", "album"); 29 | return { 30 | status: res.statusCode, 31 | body: res.body, 32 | data: res.body.data, 33 | raw: res, 34 | }; 35 | } 36 | 37 | async getCredits () { 38 | const res = await this._createRequest("get", "credits"); 39 | return { 40 | status: res.statusCode, 41 | body: res.body, 42 | data: res.body.data, 43 | raw: res, 44 | }; 45 | } 46 | 47 | _createRequest (method, path, form) { 48 | const req = request[method](`${apiURL}${path}`) 49 | .set({ 50 | Authorization: `Client-ID ${this.clientID}`, 51 | }); 52 | for (const [key, value] of Object.entries(form)) { 53 | req.attach(key, value); 54 | } 55 | return req; 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /Modules/MessageUtils/DurationParser.js: -------------------------------------------------------------------------------- 1 | const parseDuration = require("parse-duration"); 2 | 3 | // Parses durations 4 | module.exports = async string => { 5 | let time, event; 6 | const args = string.split("|").trimAll(); 7 | if (args.length === 2) { 8 | // Easy peasy lemon sqeezy 9 | [event, time] = args; 10 | } else { 11 | // Parse with assumption to "command to ... in ..." 12 | time = string.substring(string.toLowerCase().lastIndexOf(" in ") + 4); 13 | event = string.indexOf("to ") === 0 ? string.substring(3, string.toLowerCase.lastIndexOf(" in ")) : string.substring(0, string.toLowerCase().lastIndexOf(" in ")); 14 | } 15 | const actualTime = parseDuration(time); 16 | if (actualTime > 0 && event) { 17 | return { 18 | time: actualTime, 19 | event, 20 | }; 21 | } else { 22 | return { 23 | time: null, 24 | event, 25 | error: "ERR", 26 | }; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /Modules/MessageUtils/Parser.js: -------------------------------------------------------------------------------- 1 | const { Error } = require("../../Internals/Errors/"); 2 | 3 | module.exports = class Parser { 4 | constructor () { 5 | throw new Error("STATIC_CLASS", {}, this.constructor.name); 6 | } 7 | 8 | /** 9 | * Parses arguments and quoted arguments 10 | * Heavily inspired from Klasa 11 | * @param {String} content The message suffix 12 | */ 13 | static parseQuoteArgs (content, delim = " ") { 14 | if (delim === "") return [content]; 15 | 16 | const args = []; 17 | let current = ""; 18 | let open = false; 19 | 20 | for (let i = 0; i < content.length; i++) { 21 | if (!open && content.slice(i, i + delim.length) === delim) { 22 | if (current !== "") args.push(current); 23 | current = ""; 24 | i += delim.length - 1; 25 | continue; 26 | } 27 | if (content[i] === '"' && content[i - 1] !== "\\") { 28 | open = !open; 29 | if (current !== "") args.push(current); 30 | current = ""; 31 | continue; 32 | } 33 | current += content[i]; 34 | } 35 | if (current !== "") args.push(current); 36 | 37 | return args.length === 1 && args[0] === "" ? [] : args.filter(a => a !== delim && a !== " "); 38 | } 39 | }; 40 | -------------------------------------------------------------------------------- /Modules/MessageUtils/ReactionMenus/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ReactionBasedMenu: require("./ReactionBasedMenu"), 3 | }; 4 | -------------------------------------------------------------------------------- /Modules/MessageUtils/ReminderParser.js: -------------------------------------------------------------------------------- 1 | const { SetReminder } = require("../Utils/"); 2 | const parseDuration = require("parse-duration"); 3 | const { ObjectID } = require("mongodb"); 4 | 5 | // Set a reminder from a remindme command suffix 6 | module.exports = async (client, userDocument, userQueryDocument, str) => { 7 | let timestr, remind; 8 | const args = str.split("|").trimAll(); 9 | if (args.length === 2) { 10 | // Easy parse 11 | [remind, timestr] = args; 12 | } else { 13 | // Parse with assumption "remind me to ... in ..." 14 | timestr = str.substring(str.toLowerCase().lastIndexOf(" in ") + 4); 15 | remind = str.indexOf("to ") === 0 ? str.substring(3, str.toLowerCase().lastIndexOf(" in ")) : str.substring(0, str.toLowerCase().lastIndexOf(" in ")); 16 | } 17 | const time = parseDuration(timestr); 18 | if (time > 0 && remind) { 19 | userQueryDocument.push("reminders", { 20 | _id: ObjectID().toString(), 21 | name: remind, 22 | expiry_timestamp: Date.now() + time, 23 | }); 24 | await SetReminder(client, userDocument, userDocument.reminders[userDocument.reminders.length - 1]); 25 | return time; 26 | } else { 27 | return "ERR"; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /Modules/MessageUtils/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ArgumentParser: require("./Parser"), 3 | DurationParser: require("./DurationParser"), 4 | PaginatedEmbed: require("./PaginatedEmbed"), 5 | ReminderParser: require("./ReminderParser"), 6 | }; 7 | -------------------------------------------------------------------------------- /Modules/MicrosoftTranslate.js: -------------------------------------------------------------------------------- 1 | const auth = require("../Configurations/auth.js"); 2 | const MSTranslator = require("mstranslator"); 3 | 4 | module.exports = new MSTranslator({ 5 | api_key: auth.tokens.microsoftTranslation, 6 | }, true); 7 | -------------------------------------------------------------------------------- /Modules/PostShardedData.js: -------------------------------------------------------------------------------- 1 | const snekfetch = require("snekfetch"); 2 | const { tokens } = require("../Configurations/auth.js"); 3 | 4 | module.exports = async client => { 5 | // Send server amount to DBots per shard 6 | if (tokens.discordBots) { 7 | let res; 8 | try { 9 | res = await snekfetch.post(`https://bots.discord.pw/api/bots/${client.user.id}/stats`).set({ 10 | Accept: "application/json", 11 | "Content-Type": "application/json", 12 | Authorization: tokens.discordBots, 13 | }).send({ 14 | shard_id: Number(process.env.SHARDS), 15 | shard_total: Number(process.env.SHARD_COUNT), 16 | server_count: client.guilds.size, 17 | }); 18 | } catch (err) { 19 | logger.warn(`Failed to post to DiscordBots...`, {}, err); 20 | } 21 | if (res && res.statusCode === 200) { 22 | logger.info(`Succesfully POSTed to bots.discord.pw`); 23 | } else { 24 | logger.warn(`Failed to POST to bots.discord.pw`, { statusCode: res.statusCode }); 25 | } 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /Modules/PostTotalData.js: -------------------------------------------------------------------------------- 1 | const { tokens } = require("../Configurations/auth.js"); 2 | const snekfetch = require("snekfetch"); 3 | 4 | module.exports = async client => { 5 | const totalAmount = await client.guilds.totalCount; 6 | if (tokens.discordList) { 7 | let res; 8 | try { 9 | res = await snekfetch.post(`https://bots.discordlist.net/api`).set("Content-Type", "application/x-www-form-urlencoded").send({ 10 | token: tokens.discordList, 11 | servers: totalAmount, 12 | }); 13 | } catch (err) { 14 | logger.warn(`Failed to POST to Discordlist.net >~<`, {}, err); 15 | } 16 | if (res && res.statusCode === 200) { 17 | logger.info(`Succesfully POSTed to Discordlist.net`); 18 | } else { 19 | logger.warn(`Failed to POST to Discordlist.net`, { statusCode: res.statusCode }); 20 | } 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /Modules/README.md: -------------------------------------------------------------------------------- 1 | # GAwesomeBot Modules 2 | 3 | GAwesomeBot Modules are snippets of codes that either act as utilities to aid in development, or as stand-alone features that perform actions on their own. 4 | 5 | #### When do I modify Modules? 6 | A Module should only be edited if you wish to change the functionality of said Module. In general, **Utils should not be edited** for non-development purposes. 7 | If you are strictly seeking customization, you should check out the files in the `/Configurations` folder instead. 8 | 9 | #### How do I decide what files are Modules? 10 | If a file contains *one* function, class, and/or namespace containing only functions about the same feature, a file would classify as a Module. 11 | If a GAB feature is too large for one Module, a folder may be created to contain multiple files for that Module instead. 12 | If you simply wish to store a snippet of code that is often used, it should be a Util. 13 | Files that do not fit any of the above descriptions may still be considered a Module if no other folder serves as a better fit. 14 | Some of the above functionality may be written in top-level files such as master.js or GAwesomeBot.js 15 | -------------------------------------------------------------------------------- /Modules/RandomAnimals.js: -------------------------------------------------------------------------------- 1 | const { Error } = require("../Internals/Errors/"); 2 | const { get } = require("snekfetch"); 3 | 4 | const CAT = `https://aws.random.cat/meow`; 5 | const DOG = `https://random.dog/woof`; 6 | 7 | module.exports = class RandomAnimals { 8 | constructor () { 9 | throw new Error("STATIC_CLASS", {}, this.constructor.name); 10 | } 11 | 12 | static async cat () { 13 | let res; 14 | try { 15 | res = await get(CAT); 16 | } catch (err) { 17 | throw err; 18 | } 19 | return res.body.file; 20 | } 21 | 22 | static async dog () { 23 | let res; 24 | try { 25 | res = await get(DOG); 26 | } catch (err) { 27 | throw err; 28 | } 29 | if (res.raw.toString().includes(".mp4")) { 30 | return RandomAnimals.dog(); 31 | } else { 32 | return `https://random.dog/${res.raw.toString()}`; 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /Modules/Timeouts/Base.js: -------------------------------------------------------------------------------- 1 | module.exports = class Base { 2 | constructor (listener, after, key, ...args) { 3 | this.listener = listener; 4 | this.after = after; 5 | this.unreffed = false; 6 | this.args = args; 7 | /** 8 | * Maximum value of a timer, minus 1 9 | * Also known as 2^31 - 1 10 | * @type {Number} 11 | */ 12 | this.MAX = 2147483647; 13 | this.timeout = null; 14 | 15 | this.startedAt = Date.now(); 16 | 17 | /** 18 | * Special identifier for this timeout 19 | */ 20 | this.specialIdentifier = key; 21 | } 22 | 23 | unref () { 24 | if (!this.unreffed) { 25 | this.unreffed = true; 26 | this.timeout.unref(); 27 | } 28 | } 29 | 30 | ref () { 31 | if (this.unreffed) { 32 | this.unreffed = false; 33 | this.timeout.ref(); 34 | } 35 | } 36 | 37 | close () { 38 | clearTimeout(this.timeout); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /Modules/Timeouts/Interval.js: -------------------------------------------------------------------------------- 1 | const Base = require("./Base"); 2 | 3 | /** 4 | * An Interval that can repeat with a timeout thats larger than 24.8 days 5 | * @extends Base 6 | */ 7 | module.exports = class Interval extends Base { 8 | constructor (listener, after, ...args) { 9 | super(listener, after, ...args); 10 | this.timeLeft = this.after; 11 | this.start(); 12 | } 13 | 14 | start () { 15 | if (this.timeLeft <= this.MAX) { 16 | this.timeout = setTimeout(() => { 17 | this.listener(...this.args); 18 | this.timeLeft = this.after; 19 | this.startedAt = Date.now(); 20 | this.start(); 21 | }, this.timeLeft); 22 | } else { 23 | this.timeout = setTimeout(() => { 24 | this.timeLeft -= this.MAX; 25 | this.start(); 26 | }, this.MAX); 27 | } 28 | if (this.unreffed) { 29 | this.timeout.unref(); 30 | } 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /Modules/Timeouts/Timeout.js: -------------------------------------------------------------------------------- 1 | const Base = require("./Base"); 2 | 3 | /** 4 | * A Timeout that can wait for longer than 24.8 days before running. 5 | * Use {@link Interval} for Intervals 6 | * @extends Base 7 | */ 8 | module.exports = class Timeout extends Base { 9 | constructor (listener, after, ...args) { 10 | super(listener, after, ...args); 11 | this.start(); 12 | } 13 | 14 | start () { 15 | if (this.after <= this.MAX) { 16 | this.timeout = setTimeout(this.listener, this.after, ...this.args); 17 | } else { 18 | this.timeout = setTimeout(() => { 19 | this.after -= this.MAX; 20 | this.start(); 21 | }, this.MAX); 22 | } 23 | 24 | if (this.unreffed) { 25 | this.timeout.unref(); 26 | } 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /Modules/Timeouts/index.js: -------------------------------------------------------------------------------- 1 | /* eslint node/exports-style: ["error", "exports"] */ 2 | 3 | const Timeout = require("./Timeout"); 4 | const Interval = require("./Interval"); 5 | 6 | exports.setTimeout = (listener, after, key, ...args) => new Timeout(listener, after, key, ...args); 7 | 8 | exports.setInterval = (listener, after, key, ...args) => new Interval(listener, after, key, ...args); 9 | 10 | exports.clearTimeout = exports.clearInterval = timer => { 11 | if (timer && (timer instanceof Timeout || timer instanceof Interval)) timer.close(); 12 | }; 13 | 14 | exports.Timeout = Timeout; 15 | exports.Interval = Interval; 16 | -------------------------------------------------------------------------------- /Modules/Utils/FileExists.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | 3 | module.exports = filePath => fs.promises.access(filePath).then(() => true).catch(() => false); 4 | -------------------------------------------------------------------------------- /Modules/Utils/FilterChecker.js: -------------------------------------------------------------------------------- 1 | const nsfwWords = require("../../Configurations/filter.json"); 2 | 3 | // Check if a message contains a filtered string, and whether filtering is enabled 4 | /* eslint-disable max-len */ 5 | module.exports = (serverDocument, channel, string, isNsfw, isCustom, nsfwOverride) => { 6 | if (serverDocument.config.moderation.isEnabled) { 7 | if (isNsfw && serverDocument.config.moderation.filters.nsfw_filter.isEnabled && !serverDocument.config.moderation.filters.nsfw_filter.disabled_channel_ids.includes(channel.id)) { 8 | if (nsfwOverride) return true; 9 | 10 | for (let i = 0; i < nsfwWords.length; i++) { 11 | if (` ${string} `.toLowerCase().includes(` ${nsfwWords[i]} `)) { 12 | return true; 13 | } 14 | } 15 | } else if (isCustom && serverDocument.config.moderation.filters.custom_filter.isEnabled && !serverDocument.config.moderation.filters.custom_filter.disabled_channel_ids.includes(channel.id)) { 16 | for (let i = 0; i < serverDocument.config.moderation.filters.custom_filter.keywords.length; i++) { 17 | if (` ${string} `.toLowerCase().includes(` ${serverDocument.config.moderation.filters.custom_filter.keywords[i]} `)) { 18 | return true; 19 | } 20 | } 21 | } 22 | } 23 | return false; 24 | }; 25 | -------------------------------------------------------------------------------- /Modules/Utils/Gag.js: -------------------------------------------------------------------------------- 1 | /* Gag; gar without all the gagging that comes with trying to implement it in GAB 2 | * An edited version of ethanent/gar 3 | * This code may be (partially) licensed under the LICENSE file present in the ethanent/gar GitHub repo 4 | */ 5 | 6 | module.exports = sargs => { 7 | const props = {}; 8 | const lones = []; 9 | 10 | const convertIfApplicable = value => value.toString().toLowerCase() === "true" ? true : value.toString().toLowerCase() === "false" ? false : value; 11 | const removeStartHyphens = value => value.replace(/^-+/g, ""); 12 | 13 | for (let i = 0; i < sargs.length; i++) { 14 | const equalsIndex = sargs[i].indexOf("="); 15 | const isNextRefProp = sargs[i].charAt(0) === "-" && sargs.length - 1 >= i + 1 && sargs[i + 1].indexOf("=") === -1 && sargs[i + 1].charAt(0) !== "-"; 16 | const argName = equalsIndex === -1 ? removeStartHyphens(sargs[i]) : removeStartHyphens(sargs[i].slice(0, equalsIndex)); 17 | 18 | if (equalsIndex !== -1) { 19 | props[argName] = convertIfApplicable(sargs[i].slice(equalsIndex + 1)); 20 | } else if (isNextRefProp) { 21 | props[argName] = convertIfApplicable(sargs[i + 1]); 22 | i++; 23 | } else if (sargs[i].charAt(0) === "-") { 24 | if (sargs[i].charAt(1) === "-") { 25 | props[argName] = true; 26 | } else { 27 | for (let b = 0; b < argName.length; b++) { 28 | props[argName.charAt(b)] = true; 29 | } 30 | } 31 | } else { 32 | lones.push(convertIfApplicable(argName)); 33 | } 34 | } 35 | 36 | return Object.assign(props, { 37 | _: lones, 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /Modules/Utils/GetFlagForRegion.js: -------------------------------------------------------------------------------- 1 | module.exports = region => { 2 | if (!region) { 3 | return ":x:"; 4 | } 5 | if (region.startsWith("vip-")) { 6 | region = region.substring(4); 7 | } 8 | switch (region) { 9 | case "amsterdam": 10 | return ":flag_nl:"; 11 | case "brazil": 12 | return ":flag_br:"; 13 | case "frankfurt": 14 | return ":flag_de:"; 15 | case "hongkong": 16 | return ":flag_hk:"; 17 | case "japan": 18 | return ":flag_jp:"; 19 | case "london": 20 | return ":flag_gb:"; 21 | case "russia": 22 | return ":flag_ru:"; 23 | case "singapore": 24 | return ":flag_sg:"; 25 | case "sydney": 26 | return ":flag_au:"; 27 | } 28 | if (region.startsWith("eu-")) { 29 | return ":flag_eu:"; 30 | } 31 | if (region.startsWith("us-")) { 32 | return ":flag_us:"; 33 | } 34 | return ":interrobang:"; 35 | }; 36 | -------------------------------------------------------------------------------- /Modules/Utils/GetValue.js: -------------------------------------------------------------------------------- 1 | const Discord = require("discord.js"); 2 | const ProcessAsPromised = require("process-as-promised"); 3 | const IPC = new ProcessAsPromised(); 4 | 5 | module.exports = (client, val, merge, func) => { 6 | try { 7 | let code = `this.${val}`; 8 | if (func) { 9 | code = `${func}(this.${val})`; 10 | } 11 | return new Promise((resolve, reject) => { 12 | IPC.send("eval", code).then(msg => { 13 | resolve(msg); 14 | }).catch(err => reject(err)); 15 | }).then(res => { 16 | switch (merge) { 17 | case "int": 18 | return res.reduce((prev, value) => prev + value, 0); 19 | case "obj": 20 | return res.reduce((prev, value) => Object.assign(prev, value), {}); 21 | case "arr": 22 | return res.reduce((prev, value) => prev.concat(value), []); 23 | case "map": { 24 | const ress = res.map(value => new Discord.Collection(value)); 25 | return ress.reduce((prev, value) => prev.concat(value), new Discord.Collection()); 26 | } 27 | default: 28 | return res; 29 | } 30 | }).catch(err => { 31 | logger.warn("An error occurred while fetching shard data!", {}, err); 32 | }); 33 | } catch (err) { 34 | logger.warn("An error occurred while fetching shard data!", {}, err); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /Modules/Utils/GlobalDefines.js: -------------------------------------------------------------------------------- 1 | module.exports.addToGlobal = (name, val) => { 2 | global[name] = val; 3 | }; 4 | -------------------------------------------------------------------------------- /Modules/Utils/IsURL.js: -------------------------------------------------------------------------------- 1 | const pattern = [ 2 | `^(https?:\\/\\/)`, 3 | `((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|`, 4 | `((\\d{1,3}\\.){3}\\d{1,3}))`, 5 | `(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*`, 6 | `(\\?[;&a-z\\d%_.~+=-]*)?`, 7 | `(\\#[-a-z\\d_]*)?$`, 8 | ].join(""); 9 | const regex = new RegExp(pattern, "i"); 10 | 11 | module.exports = url => regex.test(url); 12 | -------------------------------------------------------------------------------- /Modules/Utils/RSS.js: -------------------------------------------------------------------------------- 1 | const Parser = require("rss-parser"); 2 | const parser = new Parser({ timeout: 2000 }); 3 | 4 | 5 | /** 6 | * Fetch RSS entries 7 | * @param {string} url The url of the feed 8 | * @param {number} num The number of RSS articles to fetch 9 | * @returns {?array} 10 | */ 11 | module.exports = (url, num) => new Promise((resolve, reject) => { 12 | parser.parseURL(url).then(articles => resolve(articles && articles.items ? articles.items.slice(0, num) : [])).catch(err => { 13 | logger.debug(`Feed at URL ${url} did not respond with valid RSS.`, { url }, err); 14 | reject("invalid"); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /Modules/Utils/RegExpMaker.js: -------------------------------------------------------------------------------- 1 | module.exports = class RegExpMaker { 2 | constructor (array) { 3 | if (typeof array !== "string") { 4 | this.array = array; 5 | } else { 6 | this.array = array.split(" "); 7 | } 8 | } 9 | make (type = "g") { 10 | for (let i = 0; i < this.array.length; i++) { 11 | if (this.array[i] === "") { 12 | this.array.splice(i, 1); 13 | i--; 14 | } else if (this.array[i] !== "") { 15 | this.array[i] = this.array[i].escapeRegex(); 16 | } 17 | } 18 | return new RegExp(`${this.array.join("|")}`, type); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /Modules/Utils/RemoveFormatting.js: -------------------------------------------------------------------------------- 1 | const removeMd = require("remove-markdown"); 2 | 3 | module.exports = str => { 4 | str = removeMd(str) 5 | .replace(/_/g, "\\_") 6 | .replace(/\*/g, "\\*") 7 | .replace(/`/g, "\\`") 8 | .replace(/@(everyone|here|\d{17,18}>)/g, "@\u200b$1"); 9 | if (str.startsWith("everyone") || str.startsWith("here")) { 10 | str = `\u200b${str}`; 11 | } 12 | return str; 13 | }; 14 | -------------------------------------------------------------------------------- /Modules/Utils/SearchiTunes.js: -------------------------------------------------------------------------------- 1 | const request = require("snekfetch"); 2 | const { UserAgent } = require("../../Internals/Constants"); 3 | 4 | const idKeys = [ 5 | "amgAlbumId", 6 | "amgArtistId", 7 | "amgVideoId", 8 | "id", 9 | "isbn", 10 | "upc", 11 | ]; 12 | 13 | const keyInObj = obj => { 14 | for (let i = 0; i < idKeys.length; i++) { 15 | if (obj[idKeys[i]]) return true; 16 | } 17 | return false; 18 | }; 19 | 20 | module.exports = async params => { 21 | params = params || {}; 22 | params.version = 2; 23 | let res; 24 | let firstRes = false; 25 | let URL = "https://itunes.apple.com/search"; 26 | 27 | if (keyInObj(params)) { 28 | URL = "https://itunes.apple.com/lookup"; 29 | firstRes = true; 30 | } 31 | 32 | try { 33 | res = await request.get(URL).query(params).set({ 34 | Accept: "application/json", 35 | "Content-Type": "application/json", 36 | "User-Agent": UserAgent, 37 | }); 38 | res.body = JSON.parse(res.body); 39 | } catch (err) { 40 | throw err; 41 | } 42 | 43 | let ret; 44 | 45 | if (res.body && res.body.results) { 46 | if (!res.body.results.length) { 47 | throw new Error(`No results!`); 48 | } else if (firstRes) { 49 | [ret] = res.body.results; 50 | } else { 51 | ret = res.body.results; 52 | } 53 | } 54 | return ret; 55 | }; 56 | -------------------------------------------------------------------------------- /Modules/Utils/SetCountdown.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set countdown for a server 3 | * @param client The client instance 4 | * @param {Document} serverDocument The full server document 5 | * @param {Document} countdownDocument The countdown document 6 | */ 7 | /* eslint-disable require-await*/ 8 | module.exports = async (client, serverDocument, countdownDocument) => { 9 | const svr = client.guilds.get(serverDocument._id); 10 | if (svr) { 11 | const ch = svr.channels.get(countdownDocument.channel_id); 12 | if (ch) { 13 | client.setTimeout(async () => { 14 | try { 15 | await ch.send({ 16 | embed: { 17 | color: 0x3669FA, 18 | description: `3...2...1... **${countdownDocument._id}**`, 19 | }, 20 | }); 21 | logger.verbose(`Countdown "${countdownDocument._id}" expired.`, { svrid: svr.id, chid: ch.id }); 22 | } catch (err) { 23 | logger.debug(`Failed to send countdown in server.`, { svrid: svr.id, chid: ch.id }, err); 24 | } 25 | try { 26 | const newServerDocument = await Servers.findOne(serverDocument._id); 27 | const newCountdownQueryDocument = newServerDocument.query.id("config.countdown_data", countdownDocument._id); 28 | if (newCountdownQueryDocument.val && newCountdownQueryDocument.val._id) newCountdownQueryDocument.remove(); 29 | await newServerDocument.save(); 30 | } catch (err) { 31 | logger.debug("Failed to save server data for countdown expiry", { svrid: svr.id }, err); 32 | } 33 | }, countdownDocument.expiry_timestamp - Date.now()); 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /Modules/Utils/SetReminder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set a reminder for a user 3 | * @param client The client instance 4 | * @param {Document} userDocument The full user document 5 | * @param {Document} reminderDocument The reminder document 6 | */ 7 | module.exports = async (client, userDocument, reminderDocument) => { 8 | let usr; 9 | try { 10 | usr = client.users.get(userDocument._id); 11 | } catch (err) { 12 | usr = await client.users.fetch(userDocument._id, true); 13 | } 14 | if (!usr) usr = await client.users.fetch(userDocument._id, true); 15 | if (usr) { 16 | client.setTimeout(async () => { 17 | const newUserDocument = await Users.findOne(userDocument._id); 18 | const newReminderQueryDocument = newUserDocument.query.id("reminders", reminderDocument._id); 19 | const newReminderDocument = newReminderQueryDocument.val; 20 | try { 21 | await usr.send({ 22 | embed: { 23 | color: 0x3669FA, 24 | title: `Hey, here's the reminder you set!`, 25 | description: `${newReminderDocument.name}`, 26 | }, 27 | }); 28 | newReminderQueryDocument.remove(); 29 | await newUserDocument.save(); 30 | logger.verbose(`Reminded user of "${newReminderDocument.name}"`, { usrid: newUserDocument._id }); 31 | } catch (err) { 32 | logger.debug(`Failed to remind user of "${newReminderDocument.name}"`, { usrid: newUserDocument._id }, err); 33 | } 34 | }, reminderDocument.expiry_timestamp - Date.now()); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /Modules/Utils/Stopwatch.js: -------------------------------------------------------------------------------- 1 | const { performance } = require("perf_hooks"); 2 | 3 | module.exports = class Stopwatch { 4 | constructor () { 5 | this._start = performance.now(); 6 | 7 | this._end = null; 8 | } 9 | 10 | get running () { 11 | return Boolean(!this._end); 12 | } 13 | 14 | get duration () { 15 | return this._end ? this._end - this._start : performance.now() - this._start; 16 | } 17 | 18 | get friendlyDuration () { 19 | const time = this.duration; 20 | if (time >= 1000) return `${(time / 1000).toFixed(2)}s`; 21 | if (time >= 1) return `${time.toFixed(2)}ms`; 22 | return `${(time * 1000).toFixed(2)}μs`; 23 | } 24 | 25 | toString () { 26 | return this.friendlyDuration; 27 | } 28 | 29 | restart () { 30 | this._start = performance.now(); 31 | this._end = this._start; 32 | return this; 33 | } 34 | 35 | start () { 36 | if (!this.running) { 37 | this._start = performance.now() - this.duration; 38 | this._end = null; 39 | } 40 | return this; 41 | } 42 | 43 | stop () { 44 | if (this.running) this._end = performance.now(); 45 | return this; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /Modules/Utils/StreamChecker.js: -------------------------------------------------------------------------------- 1 | const isStreaming = require("./StreamerUtils"); 2 | const { Templates: { StreamingTemplate }, LoggingLevels } = require("../../Internals/Constants"); 3 | 4 | // Checks if a user is streaming on Twitch, YouTube Gaming, and posts message in server channel if necessary 5 | module.exports = async (client, server, serverDocument, streamerDocument) => { 6 | try { 7 | const streamerQueryDocument = serverDocument.query.id("config.streamers_data", streamerDocument._id); 8 | 9 | const data = await isStreaming(streamerDocument.type, streamerDocument._id); 10 | 11 | if (data && !streamerDocument.live_state) { 12 | logger.verbose(`Streamer "${streamerDocument._id}" started streaming`, { svrid: server.id }); 13 | streamerQueryDocument.set("live_state", true); 14 | 15 | const channel = streamerDocument.channel_id ? server.channels.get(streamerDocument.channel_id) : null; 16 | if (channel) { 17 | const channelDocument = serverDocument.channels[channel.id]; 18 | if (!channelDocument || channelDocument.bot_enabled) { 19 | await channel.send(StreamingTemplate(data)); 20 | } 21 | } 22 | } else if (!data) { 23 | streamerQueryDocument.set("live_state", false); 24 | } 25 | 26 | // Save serverDocument if necessary 27 | await serverDocument.save().catch(err => { 28 | logger.warn(`Failed to save data for streamer "${streamerDocument._id}"`, { svrid: server.id }, err); 29 | }); 30 | } catch (err) { 31 | logger.debug(`An error occurred while checking streamer status -_-`, { svrid: server.id, streamer: streamerDocument._id }, err); 32 | client.logMessage(serverDocument, LoggingLevels.WARN, `Failed to fetch streamer ${streamerDocument._id}, they might not be configured correctly!`, streamerDocument.channel_id); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /Modules/Utils/StreamerUtils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable max-len */ 2 | const auth = require("../../Configurations/auth.js"); 3 | const snekfetch = require("snekfetch"); 4 | 5 | const isStreamingTwitch = async username => { 6 | const { body: { stream }, statusCode } = await snekfetch.get(`https://api.twitch.tv/kraken/streams/${username}?client_id=${auth.tokens.twitchClientID}`); 7 | if (statusCode === 200 && stream) { 8 | return { 9 | name: stream.channel.display_name, 10 | type: "Twitch", 11 | game: stream.game, 12 | url: stream.channel.url, 13 | streamerImage: stream.channel.logo, 14 | preview: stream.preview.large, 15 | }; 16 | } 17 | }; 18 | 19 | const isStreamingYoutube = async channel => { 20 | const { body: { items }, statusCode } = await snekfetch.get(`https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=${channel}&type=video&eventType=live&key=${auth.tokens.googleAPI}`); 21 | const item = items[0]; 22 | if (statusCode === 200 && item && item.snippet.liveBroadcastContent === "live") { 23 | return { 24 | name: item.snippet.channelTitle, 25 | type: "YouTube", 26 | game: item.snippet.title, 27 | url: `https://youtube.com/watch?v=${item.id.videoId}`, 28 | preview: item.snippet.thumbnails.high.url, 29 | }; 30 | } 31 | }; 32 | 33 | module.exports = async (type, username) => { 34 | username = encodeURIComponent(username); 35 | switch (type) { 36 | case "twitch": { 37 | return isStreamingTwitch(username); 38 | } 39 | case "ytg": { 40 | return isStreamingYoutube(username); 41 | } 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /Modules/Utils/TitlecasePermissions.js: -------------------------------------------------------------------------------- 1 | module.exports = str => str.replace(/\w+(_\w+)*/g, match => match.split("_").map(v => `${v.charAt(0)}${v.substr(1).toLowerCase()}`).join(" ")); 2 | -------------------------------------------------------------------------------- /Modules/Utils/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ClearServerStats: require("./ClearServerStats.js"), 3 | FileExists: require("./FileExists.js"), 4 | FilterChecker: require("./FilterChecker.js"), 5 | Gag: require("./Gag.js"), 6 | GetFlagForRegion: require("./GetFlagForRegion.js"), 7 | GetValue: require("./GetValue.js"), 8 | Gist: require("./GitHubGist.js"), 9 | GlobalDefines: require("./GlobalDefines.js"), 10 | IsURL: require("./IsURL.js"), 11 | MessageOfTheDay: require("./MessageOfTheDay.js"), 12 | ObjectDefines: require("./ObjectDefines.js"), 13 | PromiseWait: waitFor => new Promise(resolve => setTimeout(resolve, waitFor)), 14 | RankScoreCalculator: (messages, voice) => messages + voice, 15 | RegExpMaker: require("./RegExpMaker.js"), 16 | RemoveFormatting: require("./RemoveFormatting.js"), 17 | RSS: require("./RSS.js"), 18 | SearchiTunes: require("./SearchiTunes.js"), 19 | SetCountdown: require("./SetCountdown.js"), 20 | SetReminder: require("./SetReminder.js"), 21 | Stopwatch: require("./Stopwatch"), 22 | StreamChecker: require("./StreamChecker.js"), 23 | StreamerUtils: require("./StreamerUtils.js"), 24 | StreamingRSS: require("./StreamingRSS.js"), 25 | StructureExtender: require("./StructureExtender.js"), 26 | TitlecasePermissions: require("./TitlecasePermissions.js"), 27 | }; 28 | -------------------------------------------------------------------------------- /Modules/Voicetext.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | addMember: async (guild, voiceChannel, member) => { 3 | let channel = guild.channels.find(ch => ch.topic && ch.topic.includes(`gab:voicetext:${voiceChannel.id}`)); 4 | if (!channel || channel.type !== "text") { 5 | channel = await guild.channels.create(`${voiceChannel.name.replaceAll(" ", "").toLowerCase().replace(/[^-_a-z0-9]/ig, "")}-voicetext`, { 6 | topic: `The GAB voicetext channel for **${voiceChannel.name}**.\n\nNote: Modifying the following line will desync this channel from its voice counterpart.\ngab:voicetext:${voiceChannel.id}`, 7 | parent: voiceChannel.parent, 8 | permissionOverwrites: [{ id: guild.id, deny: "VIEW_CHANNEL", type: "role" }], 9 | reason: `Voicetext Management`, 10 | }); 11 | } 12 | await channel.createOverwrite(member.id, { 13 | VIEW_CHANNEL: true, 14 | }, "Voicetext Management"); 15 | }, 16 | removeMember: async (guild, voiceChannel, member) => { 17 | const channel = guild.channels.find(ch => ch.topic && ch.topic.includes(`gab:voicetext:${voiceChannel.id}`)); 18 | if (channel && channel.type === "text") { 19 | await channel.createOverwrite(member.id, { 20 | VIEW_CHANNEL: null, 21 | }, "Voicetext Management"); 22 | } 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /Modules/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | GAwesomeClient: require("./GAwesomeClient.js"), 3 | 4 | ConversionHandler: require("./ConversionHandler.js"), 5 | Encryption: require("./Encryption"), 6 | ExtensionRunner: require("./ExtensionRunner.js"), 7 | getGuild: require("./GetGuild.js"), 8 | Giphy: require("./Giphy"), 9 | Giveaways: require("./Giveaways.js"), 10 | Imgur: require("./Imgur"), 11 | Lotteries: require("./Lotteries.js"), 12 | MarkdownTable: require("./MarkdownTable.js"), 13 | MessageUtils: require("./MessageUtils/index"), 14 | MicrosoftTranslate: require("./MicrosoftTranslate.js"), 15 | ModLog: require("./ModLog.js"), 16 | NewServer: require("./NewServer.js"), 17 | Polls: require("./Polls.js"), 18 | PostShardedData: require("./PostShardedData.js"), 19 | PostTotalData: require("./PostTotalData.js"), 20 | RandomAnimals: require("./RandomAnimals.js"), 21 | StringJS: require("./String"), 22 | Temp: require("./Temp"), 23 | Traffic: require("./Traffic.js"), 24 | Trivia: require("./Trivia.js"), 25 | Utils: require("./Utils/index"), 26 | VoiceStatsCollector: require("./VoiceStatsCollector"), 27 | Voicetext: require("./Voicetext"), 28 | }; 29 | -------------------------------------------------------------------------------- /Temp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Temp/.gitkeep -------------------------------------------------------------------------------- /Web/controllers/auth.js: -------------------------------------------------------------------------------- 1 | const discordOAuthScopes = ["identify", "guilds", "email"]; 2 | const { renderError } = require("../helpers"); 3 | 4 | const controllers = module.exports; 5 | 6 | // Builders 7 | controllers.buildLoginController = router => router.app.passport.authenticate("discord", { 8 | scope: discordOAuthScopes, 9 | }); 10 | 11 | // Controllers 12 | controllers.logout = (req, res) => { 13 | req.logout(); 14 | res.redirect("/activity"); 15 | }; 16 | 17 | controllers.authenticate = (req, res) => { 18 | if (configJSON.userBlocklist.indexOf(req.user.id) > -1 || req.user.verified === false) { 19 | req.session.destroy(err => { 20 | if (!err) renderError(res, "Your Discord account must have a verified email.", "Hah! Thought you were close, didn'tcha?"); 21 | else renderError(res, "Failed to destroy your session."); 22 | }); 23 | } else { 24 | res.redirect("/dashboard"); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /Web/controllers/dashboard/delete.js: -------------------------------------------------------------------------------- 1 | module.exports = pullEndpointKey => async (req, res) => { 2 | switch (pullEndpointKey) { 3 | case "muted": { 4 | const memberDocument = req.svr.document.members[req.params.id]; 5 | if (!memberDocument || !req.svr.memberList.includes(req.params.id)) return res.sendStatus(404); 6 | for (const memberMutedDocument of memberDocument.muted) { 7 | req.app.client.IPC.send("unmuteMember", { guild: req.svr.id, channel: memberMutedDocument._id, member: req.params.id }); 8 | } 9 | req.svr.queryDocument.id("members", req.params.id).set("muted", []); 10 | break; 11 | } 12 | case "tags": 13 | req.svr.queryDocument.pull("config.tags.list", req.params.id); 14 | break; 15 | case "extensions": { 16 | req.svr.queryDocument.pull("extensions", req.params.id); 17 | break; 18 | } 19 | default: 20 | req.svr.queryDocument.pull(`config.${pullEndpointKey}`, req.params.id); 21 | } 22 | require("../../helpers").saveAdminConsoleOptions(req, res, true); 23 | }; 24 | -------------------------------------------------------------------------------- /Web/controllers/debug.js: -------------------------------------------------------------------------------- 1 | const controllers = module.exports; 2 | 3 | controllers["503"] = (req, res) => res.status(503).render("pages/503.ejs"); 4 | 5 | controllers["404"] = (req, res) => res.status(404).render("pages/404.ejs"); 6 | -------------------------------------------------------------------------------- /Web/controllers/donate.js: -------------------------------------------------------------------------------- 1 | module.exports = (req, { res }) => { 2 | res.setConfigData({ 3 | charities: configJS.donateCharities, 4 | donateSubtitle: configJS.donateSubtitle, 5 | }); 6 | 7 | res.setPageData("page", "donate.ejs"); 8 | 9 | res.render(); 10 | }; 11 | -------------------------------------------------------------------------------- /Web/controllers/index.js: -------------------------------------------------------------------------------- 1 | const { renderError } = require("../helpers"); 2 | 3 | const controllers = module.exports; 4 | 5 | controllers.landing = require("./landing"); 6 | controllers.activity = require("./activity"); 7 | controllers.extensions = require("./extensions"); 8 | controllers.wiki = require("./wiki"); 9 | controllers.blog = require("./blog"); 10 | controllers.donate = require("./donate"); 11 | 12 | controllers.dashboard = require("./dashboard"); 13 | controllers.console = require("./maintainer"); 14 | 15 | controllers.auth = require("./auth"); 16 | controllers.api = require("./api"); 17 | controllers.debug = require("./debug"); 18 | 19 | controllers.headerImage = (req, res) => { 20 | let { headerImage } = configJSON; 21 | if (req.get("Accept") && req.get("Accept").indexOf("image/webp") > -1) headerImage = `${headerImage.substring(0, headerImage.lastIndexOf("."))}.webp`; 22 | res.sendFile(require("path").resolve(`${__dirname}/../public/img/${headerImage}`), err => { 23 | if (err) logger.debug("It looks like your headerImage value is invalid!", {}, err); 24 | }); 25 | }; 26 | 27 | controllers.paperwork = (req, { res }) => { 28 | res.render("pages/paperwork.ejs"); 29 | }; 30 | 31 | controllers.error = (req, res, next) => { 32 | if (req.query.err === "discord") renderError(res, "The Discord OAuth flow could not be completed."); 33 | else if (req.query.err === "json") renderError(res, "That doesn't look like a valid trivia set to me!"); 34 | else if (req.debugMode) renderError(res, "I AM ERROR"); 35 | else return next(); 36 | }; 37 | 38 | controllers.add = (req, res) => res.redirect(global.configJS.oauthLink.format({ id: req.app.client.user.id })); 39 | -------------------------------------------------------------------------------- /Web/controllers/landing.js: -------------------------------------------------------------------------------- 1 | const moment = require("moment"); 2 | const { getRoundedUptime } = require("../helpers"); 3 | 4 | module.exports = async (req, { res }) => { 5 | const uptime = process.uptime(); 6 | const guildSize = await req.app.client.guilds.totalCount; 7 | const userSize = await req.app.client.users.totalCount; 8 | 9 | res.setPageData({ 10 | page: req.debugMode ? "newLanding.ejs" : "landing.ejs", 11 | bannerMessage: configJSON.homepageMessageHTML, 12 | rawServerCount: guildSize, 13 | roundedServerCount: Math.floor(guildSize / 100) * 100, 14 | rawUserCount: `${Math.floor(userSize / 1000)}K`, 15 | rawUptime: moment.duration(uptime, "seconds").humanize(), 16 | roundedUptime: getRoundedUptime(uptime), 17 | }); 18 | 19 | res.render(); 20 | }; 21 | -------------------------------------------------------------------------------- /Web/public/css/auto-complete.css: -------------------------------------------------------------------------------- 1 | .autocomplete-suggestions { 2 | text-align: left; cursor: default; border-top: 0; background: #fff; box-shadow: 0 2px 3px rgba(17, 17, 17, 0.1), 0 0 0 1px rgba(17, 17, 17, 0.1); 3 | 4 | /* core styles should not be changed */ 5 | position: absolute; display: none; z-index: 9999; max-height: 254px; overflow: hidden; overflow-y: auto; box-sizing: border-box; 6 | } 7 | .autocomplete-suggestion { position: relative; padding: 0 .6em; line-height: 23px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: 1.02em; color: #333; } 8 | .autocomplete-suggestion b { font-weight: normal; color: #009688; } 9 | .autocomplete-suggestion.selected { background: #009688; color: #f5f7fa; } 10 | .autocomplete-suggestion.selected b { font-weight: bold; color: #f5f7fa; } 11 | -------------------------------------------------------------------------------- /Web/public/css/codemirror-monokai.css: -------------------------------------------------------------------------------- 1 | /* Based on Sublime Text's Monokai theme */ 2 | 3 | .cm-s-monokai.CodeMirror { background: #272822; color: #f8f8f2; } 4 | .cm-s-monokai div.CodeMirror-selected { background: #49483E; } 5 | .cm-s-monokai .CodeMirror-line::selection, .cm-s-monokai .CodeMirror-line > span::selection, .cm-s-monokai .CodeMirror-line > span > span::selection { background: rgba(73, 72, 62, .99); } 6 | .cm-s-monokai .CodeMirror-line::-moz-selection, .cm-s-monokai .CodeMirror-line > span::-moz-selection, .cm-s-monokai .CodeMirror-line > span > span::-moz-selection { background: rgba(73, 72, 62, .99); } 7 | .cm-s-monokai .CodeMirror-gutters { background: #272822; border-right: 0px; } 8 | .cm-s-monokai .CodeMirror-guttermarker { color: white; } 9 | .cm-s-monokai .CodeMirror-guttermarker-subtle { color: #d0d0d0; } 10 | .cm-s-monokai .CodeMirror-linenumber { color: #d0d0d0; } 11 | .cm-s-monokai .CodeMirror-cursor { border-left: 1px solid #f8f8f0; } 12 | 13 | .cm-s-monokai span.cm-comment { color: #75715e; } 14 | .cm-s-monokai span.cm-atom { color: #ae81ff; } 15 | .cm-s-monokai span.cm-number { color: #ae81ff; } 16 | 17 | .cm-s-monokai span.cm-property, .cm-s-monokai span.cm-attribute { color: #a6e22e; } 18 | .cm-s-monokai span.cm-keyword { color: #f92672; } 19 | .cm-s-monokai span.cm-builtin { color: #66d9ef; } 20 | .cm-s-monokai span.cm-string { color: #e6db74; } 21 | 22 | .cm-s-monokai span.cm-variable { color: #f8f8f2; } 23 | .cm-s-monokai span.cm-variable-2 { color: #9effff; } 24 | .cm-s-monokai span.cm-variable-3 { color: #66d9ef; } 25 | .cm-s-monokai span.cm-def { color: #fd971f; } 26 | .cm-s-monokai span.cm-bracket { color: #f8f8f2; } 27 | .cm-s-monokai span.cm-tag { color: #f92672; } 28 | .cm-s-monokai span.cm-header { color: #ae81ff; } 29 | .cm-s-monokai span.cm-link { color: #ae81ff; } 30 | .cm-s-monokai span.cm-error { background: #f92672; color: #f8f8f0; } 31 | 32 | .cm-s-monokai .CodeMirror-activeline-background { background: #373831; } 33 | .cm-s-monokai .CodeMirror-matchingbracket { 34 | text-decoration: underline; 35 | color: white !important; 36 | } 37 | -------------------------------------------------------------------------------- /Web/public/fonts/discord-font-1.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/fonts/discord-font-1.eot -------------------------------------------------------------------------------- /Web/public/fonts/discord-font-1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Generated by Fontastic.me 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Web/public/fonts/discord-font-1.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/fonts/discord-font-1.ttf -------------------------------------------------------------------------------- /Web/public/fonts/discord-font-1.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/fonts/discord-font-1.woff -------------------------------------------------------------------------------- /Web/public/fonts/icon-discord.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | @font-face { 4 | font-family: "discord-font-1"; 5 | src:url("discord-font-1.eot"); 6 | src:url("discord-font-1.eot?#iefix") format("embedded-opentype"), 7 | url("discord-font-1.woff") format("woff"), 8 | url("discord-font-1.ttf") format("truetype"), 9 | url("discord-font-1.svg#discord-font-1") format("svg"); 10 | font-weight: normal; 11 | font-style: normal; 12 | 13 | } 14 | 15 | [data-icon]:before { 16 | font-family: "discord-font-1" !important; 17 | content: attr(data-icon); 18 | font-style: normal !important; 19 | font-weight: normal !important; 20 | font-variant: normal !important; 21 | text-transform: none !important; 22 | speak: none; 23 | line-height: 1; 24 | -webkit-font-smoothing: antialiased; 25 | -moz-osx-font-smoothing: grayscale; 26 | } 27 | 28 | [class^="icon-"]:before, 29 | [class*=" icon-"]:before { 30 | font-family: "discord-font-1" !important; 31 | font-style: normal !important; 32 | font-weight: normal !important; 33 | font-variant: normal !important; 34 | text-transform: none !important; 35 | speak: none; 36 | line-height: 1; 37 | -webkit-font-smoothing: antialiased; 38 | -moz-osx-font-smoothing: grayscale; 39 | } 40 | 41 | .icon-discord:before { 42 | content: "\61"; 43 | } 44 | -------------------------------------------------------------------------------- /Web/public/img/NEO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/NEO.png -------------------------------------------------------------------------------- /Web/public/img/NEO.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/NEO.webp -------------------------------------------------------------------------------- /Web/public/img/admin-console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/admin-console.png -------------------------------------------------------------------------------- /Web/public/img/admin-console.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/admin-console.webp -------------------------------------------------------------------------------- /Web/public/img/bitquote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/bitquote.png -------------------------------------------------------------------------------- /Web/public/img/bitquote.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/bitquote.webp -------------------------------------------------------------------------------- /Web/public/img/demo-activity-old.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/demo-activity-old.gif -------------------------------------------------------------------------------- /Web/public/img/discord-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/discord-icon.png -------------------------------------------------------------------------------- /Web/public/img/discord-icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/discord-icon.webp -------------------------------------------------------------------------------- /Web/public/img/discord-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/discord-logo.png -------------------------------------------------------------------------------- /Web/public/img/discord-logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/discord-logo.webp -------------------------------------------------------------------------------- /Web/public/img/gilbert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/gilbert.png -------------------------------------------------------------------------------- /Web/public/img/gilbert.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/gilbert.webp -------------------------------------------------------------------------------- /Web/public/img/header-bg-taco.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/header-bg-taco.jpg -------------------------------------------------------------------------------- /Web/public/img/header-bg-taco.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/header-bg-taco.webp -------------------------------------------------------------------------------- /Web/public/img/header-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/header-bg.jpg -------------------------------------------------------------------------------- /Web/public/img/header-bg.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/header-bg.webp -------------------------------------------------------------------------------- /Web/public/img/hilbert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/hilbert.png -------------------------------------------------------------------------------- /Web/public/img/hilbert.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/hilbert.webp -------------------------------------------------------------------------------- /Web/public/img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/icon.png -------------------------------------------------------------------------------- /Web/public/img/icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/icon.webp -------------------------------------------------------------------------------- /Web/public/img/ksham.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/ksham.jpg -------------------------------------------------------------------------------- /Web/public/img/ksham.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/ksham.webp -------------------------------------------------------------------------------- /Web/public/img/media.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/media.png -------------------------------------------------------------------------------- /Web/public/img/media.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/media.webp -------------------------------------------------------------------------------- /Web/public/img/mistmurk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/mistmurk.png -------------------------------------------------------------------------------- /Web/public/img/mistmurk.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/mistmurk.webp -------------------------------------------------------------------------------- /Web/public/img/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/profile.png -------------------------------------------------------------------------------- /Web/public/img/profile.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/profile.webp -------------------------------------------------------------------------------- /Web/public/img/transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/transparent.png -------------------------------------------------------------------------------- /Web/public/img/transparent.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/transparent.webp -------------------------------------------------------------------------------- /Web/public/img/vlad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/vlad.png -------------------------------------------------------------------------------- /Web/public/img/vlad.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GAwesomeBot/bot/010b1cd4cb62eef8e907f69a36c1752c5cb0bae8/Web/public/img/vlad.webp -------------------------------------------------------------------------------- /Web/public/js/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: "../../../.eslintrc.js", 3 | env: { 4 | browser: true, 5 | jquery: true, 6 | }, 7 | globals: { 8 | GAwesomeUtil: true, 9 | GAwesomeData: true, 10 | GAwesomePaths: true, 11 | Turbolinks: true, 12 | cm: true, 13 | CodeMirror: true, 14 | AutoComplete: true, 15 | NProgress: true, 16 | io: true, 17 | bulma: true, 18 | md5: true, 19 | saveAs: true, 20 | swal: true, 21 | showdown: true, 22 | SimpleMDE: true, 23 | }, 24 | rules: { 25 | "no-console": "off", 26 | "no-invalid-this": "off", 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /Web/routes/api.js: -------------------------------------------------------------------------------- 1 | const RateLimit = require("express-rate-limit"); 2 | 3 | const { setupResource } = require("../helpers"); 4 | const middleware = require("../middleware"); 5 | const controllers = require("../controllers"); 6 | 7 | // GAwesomeBot Data API 8 | module.exports = router => { 9 | // Configure RateLimit 10 | router.use("/api/", new RateLimit({ 11 | windowMs: 3600000, 12 | max: 150, 13 | delayMs: 0, 14 | })); 15 | 16 | setupResource(router, "/", [], controllers.api.status, "get", "public"); 17 | setupResource(router, "/servers", [], controllers.api.servers, "get", "public"); 18 | setupResource(router, "/servers/:svrid/channels", [], controllers.api.servers.channels, "get", "authorization"); 19 | setupResource(router, "/list/servers", [], controllers.api.servers.list, "get", "public"); 20 | setupResource(router, "/list/users", [middleware.authorizeGuildAccess], controllers.api.users.list, "get", "public"); 21 | setupResource(router, "/users", [], controllers.api.users, "get", "public"); 22 | setupResource(router, "/extensions", [], controllers.api.extensions, "get", "public"); 23 | setupResource(router, "/*", [], (req, res) => res.sendStatus(404), "all", "public"); 24 | }; 25 | -------------------------------------------------------------------------------- /Web/routes/debug.js: -------------------------------------------------------------------------------- 1 | const { setupPage } = require("../helpers"); 2 | const controllers = require("../controllers"); 3 | 4 | module.exports = router => { 5 | setupPage(router, "/503", [], controllers.debug["503"]); 6 | }; 7 | -------------------------------------------------------------------------------- /Web/routes/maintainer.js: -------------------------------------------------------------------------------- 1 | const { setupConsolePage, setupRedirection } = require("../helpers"); 2 | const controllers = require("../controllers"); 3 | 4 | module.exports = router => { 5 | setupRedirection(router, "/", "/maintainer"); 6 | setupConsolePage(router, "/maintainer", "maintainer", [], controllers.console.maintainer); 7 | 8 | // Server Settings 9 | setupConsolePage(router, "/servers/server-list", "maintainer", [], controllers.console.servers.list); 10 | setupConsolePage(router, "/servers/big-message", "maintainer", [], controllers.console.servers.bigmessage); 11 | 12 | // Global Settings 13 | setupConsolePage(router, "/global-options/blocklist", "administration", [], controllers.console.options.blocklist); 14 | setupConsolePage(router, "/global-options/bot-user", "administration", [], controllers.console.options.bot); 15 | setupConsolePage(router, "/global-options/homepage", "administration", [], controllers.console.options.homepage); 16 | setupConsolePage(router, "/global-options/wiki-contributors", "administration", [], controllers.console.options.contributors); 17 | 18 | // Management Settings 19 | setupConsolePage(router, "/management/maintainers", "management", [], controllers.console.management.maintainers); 20 | setupConsolePage(router, "/management/shards", "management", [], controllers.console.management.shards); 21 | setupConsolePage(router, "/management/injection", "management", [], controllers.console.management.injection); 22 | setupConsolePage(router, "/management/version", "management", [], controllers.console.management.version); 23 | setupConsolePage(router, "/management/eval", "eval", [], controllers.console.management.eval); 24 | setupConsolePage(router, "/management/logs", "management", [], controllers.console.management.logs); 25 | }; 26 | -------------------------------------------------------------------------------- /Web/views/pages/404.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 404 - GAwesomeBot 5 | <% include ../partials/head %> 6 | 7 | 8 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Web/views/pages/503.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 503 - GAwesomeBot 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Web/views/pages/admin-extension-builder.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= pageData.extensionData.name ? (pageData.extensionData.name + " - ") : "" %><%= serverData.name %> Extension Builder - GAwesomeBot Admin Console 5 | <% include ../partials/head %> 6 | 7 | 8 | <% include ../partials/dashboard-header %> 9 | <% include ../partials/dashboard-socket-updates %> 10 | 11 |
12 |
13 |
14 | 17 |
18 |

19 | Extension Builder 20 |

21 | <% const isGallery = false; %> 22 | <% include ../partials/extension-builder %> 23 |
24 |
25 |
26 |
27 | 28 | <% include ../partials/footer %> 29 | <% include ../partials/scroll-top-button %> 30 | 31 | 32 | -------------------------------------------------------------------------------- /Web/views/pages/admin-gawesome-points.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= serverData.name %> GAwesomePoints - GAwesomeBot Admin Console 5 | <% include ../partials/head %> 6 | 7 | 8 | <% include ../partials/dashboard-header %> 9 | <% include ../partials/dashboard-socket-updates %> 10 | 11 |
12 |
13 |
14 | 17 |
18 |

19 | GAwesomePoints 20 |

21 |
22 |
23 | Points work much like karma on Reddit, but can be disabled per-server and there's no downvoting. You can get GAwesomePoints by being upvoted or gilded, winning the lottery, or being one of the top 5 most active members on the server each week. Learn more on the wiki. 24 |
25 |
26 |
27 |
28 | <% var commandData = configData.commands.points; commandData.name = "points"; commandData.description = pageData.commandDescriptions.points; commandData.category = pageData.commandCategories.points; %> 29 | <% include ../partials/admin-command-item %> 30 | <% commandData = configData.commands.lottery; commandData.name = "lottery"; commandData.description = pageData.commandDescriptions.lottery; commandData.category = pageData.commandCategories.lottery; %> 31 | <% include ../partials/admin-command-item %> 32 |
33 |
34 | <% var formButtonsUnsaved = false; %> 35 | <% include ../partials/form-buttons %> 36 | <% include ../partials/form-buttons-bar %> 37 |
38 |
39 |
40 |
41 |
42 | 43 | <% include ../partials/footer %> 44 | <% include ../partials/scroll-top-button %> 45 | 46 | 47 | -------------------------------------------------------------------------------- /Web/views/pages/error.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Uh-oh! - GAwesomeBot 5 | <% include ../partials/head %> 6 | 7 | 8 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Web/views/pages/maintainer-big-message.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | BigMessage - <%= serverData.name %> Maintainer Console 5 | <% include ../partials/head %> 6 | 7 | 8 | <% include ../partials/dashboard-header %> 9 | 10 |
11 |
12 |
13 | 16 |
17 |

18 | BigMessage 19 |

20 |
21 |
22 |
23 | BigMessage is for really important announcements. This message is sent to every single server the bot is on.
Use it only if it's absolutely necessary. 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | This message will be sent to all <%= pageData.serverCount %> server<%= pageData.serverCount === 1 ? "" : "s" %>, so make sure it's important! 32 |
33 |
34 |
35 | <% var formButtonsUnsaved = false; %> 36 | <% include ../partials/form-buttons %> 37 | <% include ../partials/form-buttons-bar %> 38 |
39 |
40 |
41 |
42 |
43 | 44 | <% include ../partials/footer %> 45 | <% include ../partials/scroll-top-button %> 46 | 47 | 48 | -------------------------------------------------------------------------------- /Web/views/pages/maintainer-logs.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Logs - <%= serverData.name %> Maintainer Console 5 | <% include ../partials/head %> 6 | 7 | 8 | <% include ../partials/dashboard-header %> 9 | <% include ../partials/dashboard-socket-updates %> 10 | 11 |
12 |
13 |
14 | 17 |
18 |

19 | Logs 20 |

21 |
22 |
23 |
24 | These logs are streamed directly from GAB's log files, detailing all events that your bot encounters. Verbosity is based on the consoleLevel value in config.js. 25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 |
35 | 47 | <% include ../partials/footer %> 48 | <% include ../partials/scroll-top-button %> 49 | 50 | 51 | -------------------------------------------------------------------------------- /Web/views/partials/admin-sudo.ejs: -------------------------------------------------------------------------------- 1 | <% if (sudo) { %> 2 | 3 | 4 | 5 | 6 | Sudo Mode 7 | 8 | 23 | <% } %> -------------------------------------------------------------------------------- /Web/views/partials/adspace.ejs: -------------------------------------------------------------------------------- 1 | <% if (officialMode && adsense.isEnabled) { %> 2 | <%# Ads are enabled by maintainer and adsPreference cookie, but may still not be displayed on client due to, for example, adblocking software %> 3 |

4 | Advertisement - Hide ads 5 |

6 | <%# Your ad here! %> 7 |
8 | <% } else { %> 9 | <%# Ads are disabled by maintainer or adsPreference %> 10 | <% } %> 11 | -------------------------------------------------------------------------------- /Web/views/partials/dashboard-header.ejs: -------------------------------------------------------------------------------- 1 | 36 | -------------------------------------------------------------------------------- /Web/views/partials/dashboard-socket-updates.ejs: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /Web/views/partials/footer.ejs: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /Web/views/partials/form-buttons-bar.ejs: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /Web/views/partials/form-buttons.ejs: -------------------------------------------------------------------------------- 1 |
2 | 8 |    9 | 10 | <%= formButtonsUnsaved ? "Discard" : "Reset" %> 11 | 12 | 13 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /Web/views/partials/head-build.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Web/views/partials/menu-toggle.ejs: -------------------------------------------------------------------------------- 1 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Menu 20 | 21 | 22 | -------------------------------------------------------------------------------- /Web/views/partials/no-results.ejs: -------------------------------------------------------------------------------- 1 | <% if (isSearchQuery) { %> 2 |
3 |
4 |
5 |

6 | Wow, this place is empty 7 | 8 | 9 | 10 |

11 | <% if (activeSearchQuery) { %> 12 |

13 | 14 | 15 | 16 | 17 | Reset search 18 | 19 |

20 | <% } else { %> 21 |

22 | Nothing in <%= mode %> 23 |

24 | <% } %> 25 |
26 |
27 |
28 | <% } else { %> 29 |
"> 30 |
31 |

32 | 33 | 34 | 35 | Nothing to see here 36 |

37 |
38 |
39 | <%- description %> 40 |
41 |
42 | <% } %> 43 | -------------------------------------------------------------------------------- /Web/views/partials/scroll-top-button.ejs: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 |
-------------------------------------------------------------------------------- /Web/views/partials/wiki-edit-history.ejs: -------------------------------------------------------------------------------- 1 | 14 | 15 | <% pageData.updates.forEach((revision, i) => { %> 16 |
17 |
18 | <%= revision.responsibleUser.id %> 19 |
20 |
21 |

@<%= revision.responsibleUser.name %>

22 |

<%= revision.relativeTimestamp %>

23 | 26 |
27 | 34 |
35 | <% }); %> 36 | -------------------------------------------------------------------------------- /Web/views/partials/wiki-editor.ejs: -------------------------------------------------------------------------------- 1 |
/edit" method="post"> 2 |
3 | 4 |

5 | > 6 | <%- pageData.title ? "Wiki Articles can't be renamed. Download this Article's content, and upload it to a new Article if you wish to switch names." : "Name of the page. Make it short and avoid special characters."%> 7 |

8 |
9 |
10 | 14 | Editor by SimpleMDE 15 |

16 | 17 |

18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 | 26 | Download 27 | 28 | 35 |
36 | <% var formButtonsUnsaved = false; %> 37 | <% include ../partials/form-buttons %> 38 | <% include ../partials/form-buttons-bar %> 39 |
40 | -------------------------------------------------------------------------------- /Web/views/partials/wiki-search-results.ejs: -------------------------------------------------------------------------------- 1 | <% if (pageData.searchResults.length > 0) { %> 2 |
3 | <% pageData.searchResults.forEach(searchResult => { %> 4 |
5 | 6 | <%= searchResult.title %> 7 | 8 |

9 |
10 | <%- searchResult.matchText %> 11 |
12 |
13 | <% }); %> 14 |
15 | <% } else { %> 16 | <% const resetUrl = "/wiki/Home"; %> 17 | <% const activeSearchQuery = pageData.activeSearchQuery; %> 18 | <%- include("../partials/no-results", { isSearchQuery: true, activeSearchQuery, resetUrl, mode: pageData.mode }) %> 19 | <% } %> 20 | -------------------------------------------------------------------------------- /extensions/.gitignore: -------------------------------------------------------------------------------- 1 | *.gabext 2 | -------------------------------------------------------------------------------- /logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /travis/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -a files=("Web/public/css/custom.css" "Web/public/js/app.js") 4 | declare -a minified_files=("Web/public/css/customHASH.min.css" "Web/public/js/appHASH.min.js") 5 | declare js_tag="" 6 | declare css_tag="" 7 | head_draft="" 8 | 9 | write_head_file() { 10 | echo -e $head_draft > Web/views/partials/head-build.ejs 11 | } 12 | 13 | remove_old_file() { 14 | local file_name=${1##*/} 15 | find Web/public -name ${file_name/HASH/"-*"} -delete 16 | } 17 | 18 | minify_file() { 19 | if [ "${1##*.}" = "js" ] 20 | then 21 | uglifyjs -c -m --comments /GPL/ -- $1 > ${2/HASH/""} 22 | else 23 | cleancss $1 > ${2/HASH/""} 24 | fi 25 | } 26 | 27 | set_hash() { 28 | local original_file_name=${1/HASH/""} 29 | local checksum=$(md5sum $original_file_name) 30 | local file_hash="-${checksum:0:10}" 31 | mv $original_file_name ${1/HASH/$file_hash} 32 | echo ${1/HASH/$file_hash} 33 | } 34 | 35 | append_head_draft () { 36 | local file_name=${1##*/} 37 | if [ "${file_name##*.}" = "js" ] 38 | then 39 | head_draft="${head_draft}${js_tag/FILENAME/$file_name}\n" 40 | else 41 | head_draft="${head_draft}${css_tag/FILENAME/$file_name}\n" 42 | fi 43 | } 44 | 45 | for index in "${!files[@]}"; do 46 | remove_old_file ${minified_files[$index]} 47 | minify_file ${files[$index]} ${minified_files[$index]} 48 | append_head_draft $(set_hash ${minified_files[$index]}) 49 | done 50 | 51 | write_head_file 52 | 53 | bash travis/pushback.sh -------------------------------------------------------------------------------- /travis/pushback.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | git config --global user.email "gawesomebot@kingdgrizzle.ml" 4 | git config --global user.name "GAwesomeBot" 5 | 6 | git checkout indev-4.0.2 7 | git add -A 8 | git commit -m ":rocket: :package: Minify source files" -m "Build: $TRAVIS_COMMIT ($TRAVIS_BUILD_NUMBER)" 9 | 10 | git push https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG} $TRAVIS_BRANCH -------------------------------------------------------------------------------- /travis/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # For revert branches, do nothing 5 | if [[ "$TRAVIS_BRANCH" == revert-* ]]; then 6 | echo -e "\e[36m\e[1mTest triggered for reversion branch \"${TRAVIS_BRANCH}\" - doing nothing." 7 | exit 0 8 | fi 9 | 10 | # For PRs 11 | if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then 12 | echo -e "\e[36m\e[1mTest triggered for PR #${TRAVIS_PULL_REQUEST}." 13 | fi 14 | 15 | # Figure out the source of the test 16 | if [ -n "$TRAVIS_TAG" ]; then 17 | echo -e "\e[36m\e[1mTest triggered for tag \"${TRAVIS_TAG}\"." 18 | else 19 | echo -e "\e[36m\e[1mTest triggered for branch \"${TRAVIS_BRANCH}\"." 20 | fi 21 | 22 | # Run the tests 23 | npm test --------------------------------------------------------------------------------