├── .all-contributorsrc ├── .dockerignore ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github ├── CONTRIBUTING.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PRIVACY_POLICY.md ├── TERMS_OF_SERVICE.md ├── pull_request_template.md └── workflows │ └── lint.yml ├── .gitignore ├── .vscode └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── assets ├── images │ ├── animals │ │ └── cat.png │ ├── f.png │ ├── github │ │ └── steve-readme.png │ ├── help_embed_thumbnail.png │ ├── john_screech.png │ ├── pfp │ │ ├── steve_cute_gray.png │ │ ├── steve_cute_green.png │ │ ├── steve_face_darkblue.png │ │ ├── steve_fire.png │ │ └── steve_peace.png │ ├── playlist.png │ ├── stats_embed_thumbnail.png │ └── steve_dab.png └── sounds │ └── plau.mp3 ├── babel.config.js ├── config.example.ts ├── jest.config.ts ├── package-lock.json ├── package.json ├── src ├── Steve.ts ├── arguments │ ├── date.ts │ ├── rolename.ts │ ├── timespan.ts │ └── username.ts ├── commands │ ├── Bot Info │ │ ├── help.ts │ │ ├── info.ts │ │ ├── ping.ts │ │ └── stats.ts │ ├── Developer │ │ └── yarn.ts │ ├── Fun │ │ ├── Animals │ │ │ ├── cat.ts │ │ │ ├── dog.ts │ │ │ └── fox.ts │ │ ├── Games │ │ │ └── rockpaperscissors.ts │ │ ├── RNG │ │ │ ├── 8ball.ts │ │ │ ├── choose.ts │ │ │ ├── dftba.ts │ │ │ ├── rate.ts │ │ │ └── roll.ts │ │ ├── audino.ts │ │ ├── f.ts │ │ └── xkcd.ts │ ├── Member Info │ │ ├── avatar.ts │ │ └── whois.ts │ ├── Miscellaneous │ │ ├── discordstatus.ts │ │ ├── lyrics.ts │ │ └── shared.ts │ ├── Moderation │ │ ├── Channel Modding │ │ │ ├── lock.ts │ │ │ ├── purge.ts │ │ │ ├── slowmode.ts │ │ │ └── unlock.ts │ │ ├── Member Modding │ │ │ ├── ban.ts │ │ │ ├── deafen.ts │ │ │ ├── kick.ts │ │ │ ├── mute.ts │ │ │ ├── nickname.ts │ │ │ ├── role.ts │ │ │ ├── unban.ts │ │ │ ├── undeafen.ts │ │ │ └── unmute.ts │ │ └── Utilities │ │ │ ├── clearrole.ts │ │ │ ├── mentionable.ts │ │ │ └── permissions.ts │ ├── Productivity │ │ ├── pomodoro.ts │ │ └── remind.ts │ ├── Role Aliases │ │ └── rolealias.ts │ ├── Self-Assign │ │ ├── addassignablerole.ts │ │ ├── assign.ts │ │ └── removeassignablerole.ts │ ├── Server Info │ │ ├── roleinfo.ts │ │ └── serverinfo.ts │ ├── Server Settings │ │ ├── Ignored Channels │ │ │ ├── ignorechannel.ts │ │ │ ├── showignoredchannels.ts │ │ │ └── unignorechannel.ts │ │ ├── Ignored Roles │ │ │ ├── ignorerole.ts │ │ │ ├── showignoredroles.ts │ │ │ └── unignorerole.ts │ │ ├── Log Events │ │ │ ├── togglechannelcreate.ts │ │ │ ├── togglechanneldelete.ts │ │ │ ├── togglechannelupdate.ts │ │ │ ├── toggleemojicreate.ts │ │ │ ├── toggleemojidelete.ts │ │ │ ├── toggleemojiupdate.ts │ │ │ ├── toggleguildbanadd.ts │ │ │ ├── toggleguildbanremove.ts │ │ │ ├── toggleguildmemberadd.ts │ │ │ ├── toggleguildmemberremove.ts │ │ │ ├── toggleguildmemberupdate.ts │ │ │ ├── toggleinvitecreate.ts │ │ │ ├── toggleinvitedelete.ts │ │ │ ├── togglemessagedelete.ts │ │ │ ├── togglemessagedeletebulk.ts │ │ │ ├── togglerolecreate.ts │ │ │ ├── toggleroledelete.ts │ │ │ └── toggleroleupdate.ts │ │ ├── conf.ts │ │ ├── manageassignableroles.ts │ │ ├── managedisabledcommands.ts │ │ ├── managerestrictedroles.ts │ │ ├── managewordblacklist.ts │ │ ├── setadministratorrole.ts │ │ ├── setbandeletedays.ts │ │ ├── setdeafenedrole.ts │ │ ├── setmemberlog.ts │ │ ├── setmoderatorrole.ts │ │ ├── setmutedrole.ts │ │ ├── setreminderchannel.ts │ │ ├── setserverlog.ts │ │ ├── settrustedrole.ts │ │ ├── toggledeletepinmessages.ts │ │ └── toggletrustedrolerequirement.ts │ ├── Snippets │ │ └── snippet.ts │ ├── Space │ │ └── spacepic.ts │ ├── System │ │ ├── Developer Tools │ │ │ ├── load.ts │ │ │ ├── reboot.ts │ │ │ ├── transfer.ts │ │ │ └── unload.ts │ │ ├── blacklist.ts │ │ ├── disable.ts │ │ ├── enable.ts │ │ ├── eval.ts │ │ ├── feedback.ts │ │ ├── invite.ts │ │ └── support.ts │ ├── Unit Conversions │ │ ├── lengthconvert.ts │ │ ├── massconvert.ts │ │ └── tempconvert.ts │ └── User Settings │ │ ├── setembedcolor.ts │ │ └── userconf.ts ├── events │ ├── channelCreate.ts │ ├── channelDelete.ts │ ├── channelUpdate.ts │ ├── commandUnknown.ts │ ├── core │ │ ├── coreGuildCreate.ts │ │ ├── coreGuildDelete.ts │ │ ├── coreMessage.ts │ │ ├── coreMessageDelete.ts │ │ └── coreMessageUpdate.ts │ ├── emojiCreate.ts │ ├── emojiDelete.ts │ ├── emojiUpdate.ts │ ├── error.ts │ ├── guildBanAdd.ts │ ├── guildBanRemove.ts │ ├── guildMemberAdd.ts │ ├── guildMemberRemove.ts │ ├── guildMemberUpdate.ts │ ├── interactionCreate.ts │ ├── interactions │ │ └── applicationCommands │ │ │ ├── animal.ts │ │ │ ├── assign.ts │ │ │ ├── avatar.ts │ │ │ ├── convert.ts │ │ │ ├── dftba.ts │ │ │ ├── roleinfo.ts │ │ │ ├── rps.ts │ │ │ ├── serverinfo.ts │ │ │ └── whois.ts │ ├── inviteCreate.ts │ ├── inviteDelete.ts │ ├── messageDelete.ts │ ├── messageDeleteBulk.ts │ ├── onceReady.ts │ ├── raw.ts │ ├── roleCreate.ts │ ├── roleDelete.ts │ └── roleUpdate.ts ├── extendables │ ├── Channel.ts │ ├── GuildMember.ts │ ├── Language.ts │ ├── Role.ts │ └── Schedule.ts ├── finalizers │ └── commandCooldown.ts ├── inhibitors │ ├── cooldown.ts │ ├── disabled.ts │ ├── hidden.ts │ ├── ignoredChannel.ts │ ├── ignoredRole.ts │ ├── missingBotPermissions.ts │ ├── nsfw.ts │ ├── permissions.ts │ ├── pingProtection.ts │ ├── requiredSettings.ts │ ├── runIn.ts │ └── slowmode.ts ├── languages │ └── en-US.ts ├── lib │ ├── SteveClient.ts │ ├── extensions │ │ └── SteveGuild.ts │ ├── schemas │ │ ├── Guild.ts │ │ └── User.ts │ ├── setup │ │ └── PermissionLevels.ts │ ├── structures │ │ ├── ModerationCases.ts │ │ ├── ModerationManager.ts │ │ ├── commands │ │ │ ├── ModerationCommand.ts │ │ │ ├── SteveCommand.ts │ │ │ └── UnitConversionCommand.ts │ │ └── events │ │ │ └── ApplicationCommand.ts │ ├── types │ │ ├── Augments.d.ts │ │ ├── Enums.d.ts │ │ ├── Languages.d.ts │ │ ├── Messages.d.ts │ │ └── settings │ │ │ ├── ClientSettings.ts │ │ │ ├── GuildSettings.ts │ │ │ └── UserSettings.ts │ └── util │ │ ├── HelpBuilder.ts │ │ ├── RockPaperScissors.ts │ │ ├── UserInfo.ts │ │ └── util.ts ├── monitors │ ├── commandHandler.ts │ ├── mentionSpam.ts │ ├── pinsAdd.ts │ └── wordBlacklist.ts ├── providers │ └── mongodb.ts ├── serializers │ ├── color.ts │ └── trustedrolesetting.ts └── tasks │ ├── reminder.ts │ ├── unban.ts │ ├── undeafen.ts │ └── unmute.ts ├── tests ├── MockClient.ts ├── lib │ ├── games.test.ts │ └── util.test.ts └── tsconfig.json ├── tsconfig.eslint.json └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root=true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | trim_trailing_whitespace = true 7 | 8 | [*.{js,ts}] 9 | charset = utf-8 10 | indent_style = tab 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | dist/ 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "bamboo", 4 | "rules": { 5 | "max-len": [ 6 | "warn", 7 | { 8 | "code": 140, 9 | "ignoreStrings": true, 10 | "ignoreTemplateLiterals": true, 11 | "ignoreRegExpLiterals": true 12 | } 13 | ], 14 | "@typescript-eslint/no-throw-literal": 0, 15 | "@typescript-eslint/restrict-plus-operands": 0, 16 | "@typescript-eslint/no-unnecessary-condition": 0, 17 | "@typescript-eslint/naming-convention": 0, 18 | "@typescript-eslint/camelcase": 0, 19 | "@typescript-eslint/no-unsafe-member-access": 0, 20 | "@typescript-eslint/no-unsafe-assignment": 0, 21 | "@typescript-eslint/no-unsafe-call": 0, 22 | "@typescript-eslint/no-unsafe-return": 0, 23 | "@typescript-eslint/prefer-for-of": 0, 24 | "@typescript-eslint/explicit-module-boundary-types": 0, 25 | "@typescript-eslint/ban-ts-comment": 0 26 | }, 27 | "env": { 28 | "es6": true, 29 | "node": true 30 | }, 31 | "overrides": [ 32 | { 33 | "files": ["src/commands/**/*.ts", "src/events/interactions/applicationCommands/**/*.ts"], 34 | "rules": { 35 | "@typescript-eslint/require-await": 0 36 | } 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | 3 | *.js text 4 | *.ts text 5 | *.json text 6 | 7 | *.png binary 8 | *.mp3 binary 9 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Hi! Welcome to Steve's contribution guide. 2 | 3 | **The issue tracker is *only* for bugs/feature requests.** If you have a question about how to use Steve, or about how he works, ask Jonathan#0412 or BoedJ#5476 on Discord. 4 | 5 | Feel free to fork this repo and submit a pull request! 6 | 7 | ### Running Steve Locally 8 | Follow the steps in the README, fam. 9 | 10 | ### Guidelines 11 | - This is a Steve repo, not a Klasa or Discord.js repo. If you have an idea for improving one of those libraries, submit a PR to one of those repositories. 12 | - Any contributions should follow our ESLint rules and not produce any linter errors, even if that means you need to disable a rule for a single line. 13 | - Contributions should ideally add features that would be useful for a large number of our users, but if you have some random wild idea, we won't stop you. 14 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [jwford] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: koalawranglers 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to reduce Steve suck 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | What exactly did you do to produce this bug? We can't fix it if we don't know how to make it happen. 15 | 16 | **Expected behavior** 17 | What *should* have happened? 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for Steve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What would you like Steve to do?** 11 | -------------------------------------------------------------------------------- /.github/PRIVACY_POLICY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | The use of this application ("Bot") in a server requires the collection of some specific user data ("Data"). The Data collected includes, but is not limited to Discord user ID values. Use of the Bot is considered an agreement to the terms of this Policy. 4 | 5 | ## Access to Data 6 | 7 | Access to Data is only permitted to Bot's developers, and only in the scope required for the development, testing, and implementation of features for Bot. Data is not sold, provided to, or shared with any third party, except where required by law or a Terms of Service agreement. You can view the data upon request from `@jwford`. 8 | 9 | ## Storage of Data 10 | 11 | Data is stored in a MongoDB database. The database is secured to prevent external access, however no guarantee is provided and the Bot owners assume no liability for the unintentional or malicious breach of Data. In the event of an unauthorised Data access, users will be notified through the Discord client application. 12 | 13 | ## User Rights 14 | 15 | At any time, you have the right to request to view the Data pertaining to your Discord account. You may submit a request through the [Discord Server](https://discord.com/jJwfPGZgAM). You have the right to request the removal of relevant Data. 16 | 17 | ## Underage Users 18 | 19 | The use of the Bot is not permitted for minors under the age of 13, or under the age of legal consent for their country. This is in compliance with the [Discord Terms of Service](https://discord.com/terms). No information will be knowingly stored from an underage user. If it is found out that a user is underage we will take all necessary action to delete the stored data. 20 | 21 | ## Questions 22 | 23 | If you have any questions or are concerned about what data might be being stored from your account contact `@jwford`. For more information check the [Discord Terms Of Service](https://discord.com/terms). 24 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | **Describe this PR and why it should be merged:** 2 | 3 | **Status** 4 | - [ ] This PR has been tested and complies with this project's ESLint rules 5 | - [ ] This PR adds/modifes a command 6 | 7 | **Semantic Versioning** 8 | - [ ] This PR features bug fixes, or only style changes/refactorings, or no code changes 9 | - [ ] This PR adds features for users 10 | - [ ] This PR switches this project to a new a new Discord API library/new Discord.js framework 11 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Lint 3 | on: [push, pull_request] 4 | jobs: 5 | lint: 6 | name: ESLint 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout repository 10 | uses: actions/checkout@v2 11 | 12 | - name: Install Node v12 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: 12 16 | 17 | - name: Install dependencies 18 | run: npm ci 19 | 20 | - name: Run ESLint 21 | run: npm test 22 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.detectIndentation": false, 3 | "editor.insertSpaces": false, 4 | "editor.tabSize": 4, 5 | "eslint.validate": ["typescript"], 6 | "typescript.tsdk": "node_modules\\typescript\\lib", 7 | "typescript.preferences.quoteStyle": "single" 8 | } 9 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | RUN npm run build 12 | 13 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /assets/images/animals/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/images/animals/cat.png -------------------------------------------------------------------------------- /assets/images/f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/images/f.png -------------------------------------------------------------------------------- /assets/images/github/steve-readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/images/github/steve-readme.png -------------------------------------------------------------------------------- /assets/images/help_embed_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/images/help_embed_thumbnail.png -------------------------------------------------------------------------------- /assets/images/john_screech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/images/john_screech.png -------------------------------------------------------------------------------- /assets/images/pfp/steve_cute_gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/images/pfp/steve_cute_gray.png -------------------------------------------------------------------------------- /assets/images/pfp/steve_cute_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/images/pfp/steve_cute_green.png -------------------------------------------------------------------------------- /assets/images/pfp/steve_face_darkblue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/images/pfp/steve_face_darkblue.png -------------------------------------------------------------------------------- /assets/images/pfp/steve_fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/images/pfp/steve_fire.png -------------------------------------------------------------------------------- /assets/images/pfp/steve_peace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/images/pfp/steve_peace.png -------------------------------------------------------------------------------- /assets/images/playlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/images/playlist.png -------------------------------------------------------------------------------- /assets/images/stats_embed_thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/images/stats_embed_thumbnail.png -------------------------------------------------------------------------------- /assets/images/steve_dab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/images/steve_dab.png -------------------------------------------------------------------------------- /assets/sounds/plau.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stevebot-project/steve/750727ff4e4232b78926c3c9c0fa451d00b49feb/assets/sounds/plau.mp3 -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-env', 5 | { 6 | targets: { 7 | node: 'current' 8 | } 9 | } 10 | ] 11 | ] 12 | }; 13 | -------------------------------------------------------------------------------- /config.example.ts: -------------------------------------------------------------------------------- 1 | /* Rembemer to rename this file to config.ts! */ 2 | import { KlasaClientOptions } from 'klasa'; 3 | 4 | export const TOKENS = { 5 | BOT_TOKEN: '', // put your bot's token here 6 | GENIUS: '' // get a genius api key and put it here (https://docs.genius.com/) 7 | }; 8 | 9 | export const CLIENT_ID = ''; // put your bot's client id here 10 | 11 | export const DB_CONNECTION_STRING = ''; // mongodb connection string goes here 12 | 13 | export const LAVALINK_ENABLE = true; 14 | export const LAVALINK_PORT = 8080; 15 | export const LAVALINK_HOST = ''; 16 | export const LAVALINK_PASSWORD = ''; 17 | 18 | export const NAME = 'Steve'; 19 | 20 | export const FEEDBACK_GUILD = ''; // guild snowflake 21 | export const FEEDBACK_CHANNEL = ''; // text channel snowflake 22 | export const SUPPORT_LINK = ''; // link to the bot's support server 23 | 24 | export const CLIENT_OPTIONS: KlasaClientOptions = { 25 | commandEditing: true, 26 | createPiecesFolders: false, 27 | disableMentions: 'everyone', 28 | noPrefixDM: true, 29 | prefix: 's;', 30 | regexPrefix: /Steve, /i, 31 | prefixCaseInsensitive: true, 32 | presence: { 33 | activity: { 34 | name: '', 35 | type: '' 36 | } 37 | }, 38 | providers: { 39 | default: 'mongodb' 40 | }, 41 | lavalink: { 42 | host: `${LAVALINK_HOST}:${LAVALINK_PORT}`, 43 | password: LAVALINK_PASSWORD, 44 | userID: CLIENT_ID, 45 | shardCount: 0 46 | }, 47 | readyMessage: client => `Logged in and ready to serve ${client.guilds.cache.size} guilds as ${client.user!.tag}.` 48 | }; 49 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/require-await */ 2 | import type { Config } from '@jest/types'; 3 | 4 | export default async (): Promise => ({ 5 | moduleDirectories: [ 6 | 'src', 7 | 'node_modules' 8 | ], 9 | testEnvironment: 'node', 10 | testMatch: ['/tests/**/*.test.ts'], 11 | testRunner: 'jest-circus/runner', 12 | transform: { 13 | '^.+\\.(ts|tsx)$': 'ts-jest' 14 | }, 15 | verbose: true 16 | }); 17 | -------------------------------------------------------------------------------- /src/Steve.ts: -------------------------------------------------------------------------------- 1 | import 'module-alias/register'; 2 | import { TOKENS, CLIENT_OPTIONS } from '@root/config'; 3 | 4 | import { SteveClient } from '@lib/SteveClient'; 5 | 6 | const bot = new SteveClient(CLIENT_OPTIONS); 7 | 8 | bot.login(TOKENS.BOT_TOKEN).catch(e => console.error(e)); 9 | -------------------------------------------------------------------------------- /src/arguments/date.ts: -------------------------------------------------------------------------------- 1 | import { Message } from 'discord.js'; 2 | import { Argument, Possible } from 'klasa'; 3 | 4 | export default class extends Argument { 5 | 6 | private dateRegex = /^(?\d{4})(\/|-)(?0?[1-9]|1[012])(\/|-)(?0?[1-9]|[12][0-9]|3[01])$/; 7 | 8 | public run(arg: string, possible: Possible, msg: Message) { 9 | const match = arg.match(this.dateRegex); 10 | 11 | if (match) { 12 | const { year, month, day } = match.groups!; 13 | 14 | const date = new Date(Number(year), Number(month) - 1, Number(day)); 15 | 16 | if (!isNaN(date.getTime())) return date; 17 | } 18 | 19 | throw msg.language.tget('resolverInvalidDate', possible.name); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/arguments/rolename.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 dirigeants. All rights reserved. MIT license. 2 | import { Argument, util, Possible, KlasaMessage } from 'klasa'; 3 | import { Role, Guild } from 'discord.js'; 4 | 5 | const ROLE_REGEXP = Argument.regex.role; 6 | 7 | function resolveRole(query: Role | string, guild: Guild): Role | null | undefined { 8 | if (query instanceof Role) return guild.roles.cache.has(query.id) ? query : null; 9 | if (typeof query === 'string' && ROLE_REGEXP.test(query)) return guild.roles.cache.get(ROLE_REGEXP.exec(query)![1]); 10 | return null; 11 | } 12 | 13 | export default class extends Argument { 14 | 15 | public async run(arg: string, possible: Possible, msg: KlasaMessage): Promise { 16 | if (!msg.guild) return this.store.get('role').run(arg, possible, msg); 17 | const resRole = resolveRole(arg, msg.guild); 18 | if (resRole) return resRole; 19 | 20 | const results = []; 21 | const reg = new RegExp(util.regExpEsc(arg), 'i'); 22 | for (const role of msg.guild.roles.cache.values()) { 23 | if (reg.test(role.name)) results.push(role); 24 | } 25 | 26 | let querySearch: Role[] = []; 27 | if (results.length > 0) { 28 | const regWord = new RegExp(`\\b${util.regExpEsc(arg)}\\b`, 'i'); 29 | const filtered = results.filter(role => regWord.test(role.name)); 30 | querySearch = filtered.length > 0 ? filtered : results; 31 | } else { 32 | querySearch = results; 33 | } 34 | 35 | switch (querySearch.length) { 36 | case 0: throw msg.guild!.language.tget('argumentRoleNameCouldNotFind', possible.name, arg); 37 | case 1: return querySearch[0]; 38 | default: 39 | if (querySearch[0].name.toLowerCase() === arg.toLowerCase()) return querySearch[0]; 40 | if (querySearch.length < 40) { 41 | for (const search of querySearch) { 42 | if (search.name.toLowerCase() === arg.toLowerCase()) return search; 43 | } 44 | } 45 | throw msg.guild!.language.tget('argumentRoleNameMultipleMatches', querySearch.map(role => role.name).join('`, `'), arg); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/arguments/timespan.ts: -------------------------------------------------------------------------------- 1 | import { Argument, Duration, util, Possible, KlasaMessage } from 'klasa'; 2 | 3 | export default class extends Argument { 4 | 5 | public run(arg: string, possible: Possible, msg: KlasaMessage): number { 6 | const duration = new Duration(arg); 7 | 8 | if (duration.offset > 0 && util.isNumber(duration.fromNow.getTime())) return duration.offset; 9 | throw msg.language.tget('argumentTimespanInvalid', arg); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/arguments/username.ts: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2019 dirigeants. All rights reserved. MIT license. 2 | import { Argument, KlasaMessage, Possible, util } from 'klasa'; 3 | import { Guild, GuildMember, User } from 'discord.js'; 4 | 5 | const USER_REGEXP = Argument.regex.userOrMember; 6 | 7 | async function resolveUser(query: GuildMember | User | string, guild: Guild) { 8 | if (guild.memberCount > guild.members.cache.size) await guild.members.fetch(); 9 | 10 | if (query instanceof GuildMember) return query.user; 11 | if (query instanceof User) return query; 12 | if (typeof query === 'string') { 13 | if (USER_REGEXP.test(query)) return guild.client.users.fetch(USER_REGEXP.exec(query)![1]).catch(() => null); 14 | if (/\w{1,32}#\d{4}/.test(query)) { 15 | const res = guild.members.cache.find(member => member.user.tag === query); 16 | return res ? res.user : null; 17 | } 18 | } 19 | return null; 20 | } 21 | 22 | module.exports = class extends Argument { 23 | 24 | public async run(arg: string, possible: Possible, msg: KlasaMessage): Promise { 25 | if (!msg.guild) return this.store.get('user').run(arg, possible, msg); 26 | const resUser = await resolveUser(arg, msg.guild); 27 | if (resUser) return resUser; 28 | 29 | const results = []; 30 | const reg = new RegExp(util.regExpEsc(arg), 'i'); 31 | for (const member of msg.guild.members.cache.values()) { 32 | if (reg.test(member.user.username)) results.push(member.user); 33 | } 34 | 35 | // eslint-disable-next-line @typescript-eslint/init-declarations 36 | let querySearch: User[]; 37 | if (results.length > 0) { 38 | const regWord = new RegExp(`\\b${util.regExpEsc(arg)}\\b`, 'i'); 39 | const filtered = results.filter(user => regWord.test(user.username)); 40 | querySearch = filtered.length > 0 ? filtered : results; 41 | } else { 42 | querySearch = results; 43 | } 44 | 45 | switch (querySearch.length) { 46 | case 0: throw msg.language.tget('argumentUsernameCannotFind', possible.name); 47 | case 1: return querySearch[0]; 48 | default: throw msg.language.tget('argumentUsernameMultiple', querySearch.map(user => user.tag).join('`, `')); 49 | } 50 | } 51 | 52 | }; 53 | -------------------------------------------------------------------------------- /src/commands/Bot Info/info.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { ApplyOptions } from '@skyra/decorators'; 3 | import { CommandOptions, KlasaMessage } from 'klasa'; 4 | 5 | @ApplyOptions({ 6 | aliases: ['details', 'what'], 7 | guarded: true, 8 | description: lang => lang.tget('commandInfoDescription') 9 | }) 10 | export default class extends SteveCommand { 11 | 12 | public async run(msg: KlasaMessage) { 13 | return msg.channel.send(msg.language.tget('commandInfo')); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/commands/Bot Info/ping.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { CommandOptions, KlasaMessage } from 'klasa'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | 5 | @ApplyOptions({ 6 | guarded: true, 7 | description: lang => lang.tget('commandPingDescription') 8 | }) 9 | export default class extends SteveCommand { 10 | 11 | public async run(msg: KlasaMessage) { 12 | const pingMsg = await msg.channel.send(msg.language.tget('commandPing')); 13 | return pingMsg.edit(msg.language.tget('commandPingPong', (pingMsg.editedTimestamp || pingMsg.createdTimestamp) - (msg.editedTimestamp || msg.createdTimestamp), Math.round(this.client.ws.ping))); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/commands/Fun/Animals/cat.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { ImageAssets } from '@lib/types/Enums'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { sendLoadingMessage } from '@utils/util'; 5 | import axios from 'axios'; 6 | import { MessageEmbed } from 'discord.js'; 7 | import { CommandOptions, KlasaMessage } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['kitty'], 11 | cooldown: 30, 12 | cooldownLevel: 'author', 13 | description: lang => lang.tget('commandCatDescription'), 14 | extendedHelp: lang => lang.tget('commandCatExtended'), 15 | requiredPermissions: ['EMBED_LINKS'] 16 | }) 17 | export default class extends SteveCommand { 18 | 19 | private catUrl = 'https://cataas.com'; 20 | 21 | public async run(msg: KlasaMessage) { 22 | const response = await sendLoadingMessage(msg); 23 | const embed = new MessageEmbed(); 24 | 25 | try { 26 | const { data } = await axios.get(`${this.catUrl}/cat?json=true`); 27 | 28 | embed.setImage(data.url 29 | ? `${this.catUrl}${data.url}` 30 | : ImageAssets.Cat); 31 | } catch { 32 | embed.setImage(ImageAssets.Cat); 33 | } 34 | 35 | return response.edit(undefined, embed); 36 | } 37 | 38 | } 39 | 40 | interface CatResponse { 41 | id: string; 42 | created_at: string; 43 | tags: string[]; 44 | url: string; 45 | } 46 | -------------------------------------------------------------------------------- /src/commands/Fun/Animals/dog.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { ImageAssets } from '@lib/types/Enums'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { sendLoadingMessage } from '@utils/util'; 5 | import axios from 'axios'; 6 | import { MessageEmbed } from 'discord.js'; 7 | import { CommandOptions, KlasaMessage } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['puppy'], 11 | cooldown: 30, 12 | cooldownLevel: 'author', 13 | description: lang => lang.tget('commandDogDescription'), 14 | extendedHelp: lang => lang.tget('commandDogExtended'), 15 | requiredPermissions: ['EMBED_LINKS'] 16 | }) 17 | export default class extends SteveCommand { 18 | 19 | private dogUrl = 'https://dog.ceo/api/breeds/image/random'; 20 | 21 | public async run(msg: KlasaMessage) { 22 | const response = await sendLoadingMessage(msg); 23 | const embed = new MessageEmbed(); 24 | 25 | try { 26 | const { data } = await axios.get(this.dogUrl); 27 | 28 | embed.setImage(data.message); 29 | } catch { 30 | embed.setImage(ImageAssets.Dog); 31 | } 32 | 33 | return response.edit(undefined, embed); 34 | } 35 | 36 | } 37 | 38 | interface DogResponse { 39 | message: string; 40 | status: string; 41 | } 42 | -------------------------------------------------------------------------------- /src/commands/Fun/Animals/fox.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { ImageAssets } from '@lib/types/Enums'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { sendLoadingMessage } from '@utils/util'; 5 | import axios from 'axios'; 6 | import { MessageEmbed } from 'discord.js'; 7 | import { CommandOptions, KlasaMessage } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | cooldown: 30, 11 | cooldownLevel: 'author', 12 | description: lang => lang.tget('commandFoxDescription'), 13 | extendedHelp: lang => lang.tget('commandFoxExtended'), 14 | requiredPermissions: ['ATTACH_FILES', 'EMBED_LINKS'] 15 | }) 16 | export default class extends SteveCommand { 17 | 18 | private foxUrl = 'https://randomfox.ca/floof'; 19 | 20 | public async run(msg: KlasaMessage) { 21 | const response = await sendLoadingMessage(msg); 22 | 23 | const { data } = await axios.get(this.foxUrl); 24 | 25 | const embed = new MessageEmbed() 26 | .setImage(data.image ?? ImageAssets.Fox); 27 | 28 | return response.edit(undefined, embed); 29 | } 30 | 31 | } 32 | 33 | interface FoxResponse { 34 | image: string; 35 | link: string; 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/Fun/Games/rockpaperscissors.ts: -------------------------------------------------------------------------------- 1 | import { CommandOptions, KlasaMessage } from 'klasa'; 2 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 3 | import { Message } from 'discord.js'; 4 | import { checkWinner, chooseRandomPlay, rpsPlay } from '@lib/util/RockPaperScissors'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | 7 | @ApplyOptions({ 8 | aliases: ['rps'], 9 | cooldown: 5, 10 | cooldownLevel: 'author', 11 | description: lang => lang.tget('commandRockPaperScissorsDescription'), 12 | extendedHelp: lang => lang.tget('commandRockPaperScissorsExtended'), 13 | usage: '' 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: KlasaMessage, [playerMove]: [rpsPlay]): Promise { 18 | const steveMove = chooseRandomPlay(); 19 | const winner = checkWinner(steveMove, playerMove); 20 | return msg.channel.send(msg.language.tget('commandRockPaperScissorsWinner', playerMove, steveMove, winner)); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/commands/Fun/RNG/8ball.ts: -------------------------------------------------------------------------------- 1 | import { CommandOptions, KlasaMessage } from 'klasa'; 2 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 3 | import { Message } from 'discord.js'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | import { pickRandom } from '@utils/util'; 6 | 7 | @ApplyOptions({ 8 | aliases: ['magic8ball'], 9 | cooldown: 5, 10 | cooldownLevel: 'author', 11 | description: lang => lang.tget('command_8BallDescription'), 12 | extendedHelp: lang => lang.tget('command_8BallExtended'), 13 | usage: '' 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: KlasaMessage): Promise { 18 | const responses = msg.language.tget('command_8BallResponses') as string[]; 19 | return msg.channel.send(pickRandom(responses)); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/commands/Fun/RNG/choose.ts: -------------------------------------------------------------------------------- 1 | import { CommandOptions, KlasaMessage } from 'klasa'; 2 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 3 | import { Message } from 'discord.js'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | import { pickRandom } from '@utils/util'; 6 | 7 | @ApplyOptions({ 8 | cooldown: 5, 9 | cooldownLevel: 'author', 10 | description: lang => lang.tget('commandChooseDescription'), 11 | extendedHelp: lang => lang.tget('commandChooseExtended'), 12 | usage: ' [...]' 13 | }) 14 | export default class extends SteveCommand { 15 | 16 | public async run(msg: KlasaMessage, choices: string[]): Promise { 17 | if (choices.length < 2) throw msg.language.tget('commandChooseTooFew'); 18 | return msg.channel.send(msg.language.tget('commandChooseResponse', pickRandom(choices))); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/commands/Fun/RNG/dftba.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { ApplyOptions } from '@skyra/decorators'; 3 | import { Message } from 'discord.js'; 4 | import { CommandOptions, KlasaMessage } from 'klasa'; 5 | 6 | @ApplyOptions({ 7 | cooldown: 30, 8 | cooldownLevel: 'author', 9 | description: lang => lang.tget('commandDftbaDescription'), 10 | extendedHelp: lang => lang.tget('commandDftbaExtended') 11 | }) 12 | export default class extends SteveCommand { 13 | 14 | public async run(msg: KlasaMessage): Promise { 15 | return msg.channel.send(msg.language.randomDftba); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/commands/Fun/RNG/rate.ts: -------------------------------------------------------------------------------- 1 | import { CommandOptions, KlasaMessage } from 'klasa'; 2 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 3 | import { Message } from 'discord.js'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | 6 | @ApplyOptions({ 7 | cooldown: 5, 8 | cooldownLevel: 'author', 9 | description: lang => lang.tget('commandRateDescription'), 10 | extendedHelp: lang => lang.tget('commandRateExtended'), 11 | usage: '' 12 | }) 13 | export default class extends SteveCommand { 14 | 15 | public async run(msg: KlasaMessage, [thing]: [string]): Promise { 16 | return msg.channel.send(msg.language.tget('commandRateResponse', thing, Math.floor((Math.random() * 5) + 1))); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/Fun/audino.ts: -------------------------------------------------------------------------------- 1 | import { CommandOptions, KlasaMessage } from 'klasa'; 2 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 3 | import { Message } from 'discord.js'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | 6 | @ApplyOptions({ 7 | cooldown: 60, 8 | cooldownLevel: 'channel', 9 | description: lang => lang.tget('commandAudinoDescription'), 10 | extendedHelp: lang => lang.tget('commandAudinoExtended') 11 | }) 12 | export default class extends SteveCommand { 13 | 14 | public async run(msg: KlasaMessage): Promise { 15 | return msg.channel.send(msg.language.tget('commandAudinoId'), 16 | { files: [{ attachment: './assets/images/john_screech.png', name: 'john_screech.png' }] }); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/Fun/f.ts: -------------------------------------------------------------------------------- 1 | import { CommandOptions, KlasaMessage } from 'klasa'; 2 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 3 | import { Message } from 'discord.js'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | 6 | @ApplyOptions({ 7 | cooldown: 60, 8 | cooldownLevel: 'channel', 9 | description: lang => lang.tget('commandFDescription'), 10 | extendedHelp: lang => lang.tget('commandFExtended') 11 | }) 12 | export default class extends SteveCommand { 13 | 14 | public async run(msg: KlasaMessage): Promise { 15 | return msg.channel.send(msg.language.tget('commandFId'), 16 | { files: [{ attachment: './assets/images/f.png', name: 'pay_respects.png' }] }); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/Fun/xkcd.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { ApplyOptions } from '@skyra/decorators'; 3 | import { formatDate, sendLoadingMessage } from '@utils/util'; 4 | import { MessageEmbed } from 'discord.js'; 5 | import { CommandOptions, KlasaMessage } from 'klasa'; 6 | import { oneLine } from 'common-tags'; 7 | import axios from 'axios'; 8 | 9 | interface XkcdComic { 10 | alt: string; 11 | day: string; 12 | img: string; 13 | link: string; 14 | month: string; 15 | news: string; 16 | num: number; 17 | safe_title: string; 18 | title: string; 19 | transcript: string; 20 | year: string; 21 | } 22 | 23 | @ApplyOptions({ 24 | cooldown: 15, 25 | cooldownLevel: 'author', 26 | description: lang => lang.tget('commandXkcdDescription'), 27 | extendedHelp: lang => lang.tget('commandXkcdExtended'), 28 | requiredPermissions: ['EMBED_LINKS'], 29 | usage: '[comicNumber:integer]' 30 | }) 31 | export default class extends SteveCommand { 32 | 33 | public async run(msg: KlasaMessage, [comicID]: [number]) { 34 | const response = await sendLoadingMessage(msg); 35 | 36 | const comic = comicID 37 | ? await this.getXkcdByNumber(comicID).catch(() => { throw msg.language.tget('commandXkcdInvalid'); }) 38 | : await this.getCurrentXkcd(); 39 | 40 | const embed = new MessageEmbed() 41 | .setColor(0x2242c7) 42 | .setDescription(comic.transcript || comic.alt) 43 | .setImage(comic.img) 44 | .setTimestamp() 45 | .setTitle(oneLine`${comic.safe_title} (#${comic.num}, 46 | ${formatDate(new Date(Number(comic.year), Number(comic.month) - 1, Number(comic.day)), 'YYYY MMMM Do')})`); 47 | 48 | return response.edit(undefined, embed); 49 | } 50 | 51 | private async getCurrentXkcd() { 52 | const res = await axios.get(`http://xkcd.com/info.0.json`); 53 | return res.data; 54 | } 55 | 56 | /** 57 | * 58 | * @param id The ID of the desired comic 59 | */ 60 | private async getXkcdByNumber(id: number) { 61 | const res = await axios.get(`http://xkcd.com/${id}/info.0.json`); 62 | return res.data; 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/commands/Member Info/avatar.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { UserSettings } from '@lib/types/settings/UserSettings'; 3 | import { ApplyOptions, CreateResolvers } from '@skyra/decorators'; 4 | import { Message, MessageEmbed, User } from 'discord.js'; 5 | import { CommandOptions, KlasaMessage } from 'klasa'; 6 | 7 | @ApplyOptions({ 8 | aliases: ['av'], 9 | description: lang => lang.tget('commandAvatarDescription'), 10 | requiredPermissions: ['EMBED_LINKS'], 11 | usage: '[user:username]' 12 | }) 13 | @CreateResolvers([ 14 | [ 15 | 'username', 16 | (str, possible, msg) => { 17 | const usernameArgument = msg.client.arguments.get('username'); 18 | 19 | return str 20 | ? usernameArgument.run(str, possible, msg) 21 | : usernameArgument.run(msg.author.tag, possible, msg); 22 | } 23 | ] 24 | ]) 25 | export default class extends SteveCommand { 26 | 27 | public async run(msg: KlasaMessage, [user]: [User]): Promise { 28 | if (!user) throw msg.language.tget('userNotFound'); 29 | const member = msg.guild ? await msg.guild.members.fetch(user.id) : null; 30 | 31 | try { 32 | const embed = new MessageEmbed() 33 | .setAuthor(user.tag) 34 | .setImage(user.displayAvatarURL({ dynamic: true })); 35 | 36 | const userColor = user.settings.get(UserSettings.EmbedColor); 37 | if (userColor) { 38 | embed.setColor(userColor); 39 | } else if (member) { 40 | embed.setColor(member.displayHexColor); 41 | } 42 | 43 | return msg.channel.send(embed); 44 | } catch (e) { 45 | return msg.channel.send(msg.language.tget('commandAvatarCannotDisplay', user.tag)); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/commands/Miscellaneous/lyrics.ts: -------------------------------------------------------------------------------- 1 | import * as Genius from 'genius-lyrics'; 2 | import { CommandOptions, KlasaMessage } from 'klasa'; 3 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 4 | import { Message, MessageEmbed } from 'discord.js'; 5 | import { TOKENS } from '@root/config'; 6 | import { ApplyOptions } from '@skyra/decorators'; 7 | import { sendLoadingMessage } from '@utils/util'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['genius'], 11 | description: lang => lang.tget('commandLyricsDescription'), 12 | extendedHelp: lang => lang.tget('commandLyricsExtended'), 13 | usage: '' 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async init(): Promise { 18 | if (!TOKENS.GENIUS) this.disable(); 19 | } 20 | 21 | public async run(msg: KlasaMessage, [song]: [string]): Promise { 22 | const response = await sendLoadingMessage(msg); 23 | const Client = new Genius.Client(TOKENS.GENIUS); 24 | const songs = await Client.songs.search(song); 25 | 26 | if (songs.length < 1) return response.edit('commandLyricsNoLyrics', { embed: null }); 27 | 28 | const embedData = msg.language.tget('commandLyricsEmbed'); 29 | 30 | const embed = new MessageEmbed() 31 | .setTitle(embedData.title); 32 | 33 | for (let i = 0; i < 5; i++) { 34 | if (!songs[i]) break; 35 | embed 36 | .addFields([ 37 | { name: songs[i].fullTitle, value: songs[i].url } 38 | ]); 39 | } 40 | 41 | return response.edit(undefined, embed); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/commands/Miscellaneous/shared.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { ApplyOptions } from '@skyra/decorators'; 3 | import { CommandOptions, KlasaMessage } from 'klasa'; 4 | 5 | @ApplyOptions({ 6 | description: lang => lang.tget('commandSharedDescription'), 7 | extendedHelp: lang => lang.tget('commandSharedExtended') 8 | }) 9 | export default class extends SteveCommand { 10 | 11 | public async run(msg: KlasaMessage) { 12 | const sharedGuilds = this.client.guilds.cache.filter(guild => guild.members.cache.has(msg.author.id)); 13 | 14 | return msg.channel.send(msg.language.tget('commandShared', sharedGuilds)); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/commands/Moderation/Channel Modding/lock.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | import { Message } from 'discord.js'; 6 | import { CommandOptions } from 'klasa'; 7 | 8 | @ApplyOptions({ 9 | description: lang => lang.tget('commandLockDescription'), 10 | extendedHelp: lang => lang.tget('commandLockExtended'), 11 | permissionLevel: PermissionsLevels.MODERATOR, 12 | requiredPermissions: ['MANAGE_CHANNELS'], 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | await msg.channel.updateOverwrite(msg.guild.id, { SEND_MESSAGES: false }, msg.author.tag); 19 | 20 | return msg.channel.send(msg.guild.language.tget('commandLockLocked')); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/commands/Moderation/Channel Modding/purge.ts: -------------------------------------------------------------------------------- 1 | import { CommandOptions } from 'klasa'; 2 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 3 | import { PermissionsLevels, Time } from '@lib/types/Enums'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | import { GuildMessage } from '@lib/types/Messages'; 6 | 7 | @ApplyOptions({ 8 | description: lang => lang.tget('commandPurgeDescription'), 9 | extendedHelp: lang => lang.tget('commandPurgeExtended'), 10 | permissionLevel: PermissionsLevels.MODERATOR, 11 | requiredPermissions: ['MANAGE_MESSAGES'], 12 | runIn: ['text'], 13 | usage: '' 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage, [number]: [number]): Promise { 18 | const msgCollection = await msg.channel.bulkDelete(number + 1, true); 19 | 20 | const res = await msg.channel.send(msg.guild.language.tget('commandPurgePurged', msgCollection.size - 1)); 21 | 22 | setTimeout(() => res.delete(), Time.SECOND * 10); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Moderation/Channel Modding/slowmode.ts: -------------------------------------------------------------------------------- 1 | import { CommandOptions } from 'klasa'; 2 | import { Message } from 'discord.js'; 3 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 4 | import { PermissionsLevels } from '@lib/types/Enums'; 5 | import { friendlyDuration } from '@utils/util'; 6 | import { ApplyOptions } from '@skyra/decorators'; 7 | import { GuildMessage } from '@lib/types/Messages'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['slow'], 11 | description: lang => lang.tget('commandSlowModeDescription'), 12 | extendedHelp: lang => lang.tget('commandSlowModeExtended'), 13 | permissionLevel: PermissionsLevels.MODERATOR, 14 | requiredPermissions: ['MANAGE_CHANNELS'], 15 | runIn: ['text'], 16 | usage: '' 17 | }) 18 | export default class extends SteveCommand { 19 | 20 | public async run(msg: GuildMessage, [duration]: [number | string]): Promise { 21 | /* divide by 1000 bc setRateLimitPerUser works with seconds */ 22 | if (msg.channel.isGuildTextChannel()) { 23 | await msg.channel.setRateLimitPerUser(typeof duration === 'number' ? duration / 1000 : 0, msg.author.tag); 24 | } 25 | 26 | return msg.channel.send(duration === 'reset' 27 | ? msg.guild.language.tget('commandSlowModeReset') 28 | : msg.guild.language.tget('commandSlowModeSet', friendlyDuration(duration as number))); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/commands/Moderation/Channel Modding/unlock.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | import { Message } from 'discord.js'; 6 | import { CommandOptions } from 'klasa'; 7 | 8 | @ApplyOptions({ 9 | description: lang => lang.tget('commandUnlockDescription'), 10 | extendedHelp: lang => lang.tget('commandUnlockExtended'), 11 | permissionLevel: PermissionsLevels.MODERATOR, 12 | requiredPermissions: ['MANAGE_CHANNELS'], 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | await msg.channel.updateOverwrite(msg.guild.id, { SEND_MESSAGES: true }, msg.author.tag); 19 | 20 | return msg.channel.send(msg.guild.language.tget('commandUnlockUnlocked')); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/commands/Moderation/Member Modding/ban.ts: -------------------------------------------------------------------------------- 1 | import { ModerationCommand, ModerationCommandOptions } from '@lib/structures/commands/ModerationCommand'; 2 | import { User, Guild, GuildMember, Message } from 'discord.js'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { GuildMessage } from '@lib/types/Messages'; 5 | 6 | @ApplyOptions({ 7 | description: lang => lang.tget('commandBanDescription'), 8 | duration: true, 9 | extendedHelp: lang => lang.tget('commandBanExtended'), 10 | requiredPermissions: ['BAN_MEMBERS'] 11 | }) 12 | export default class extends ModerationCommand { 13 | 14 | public async prehandle(target: User, guild: Guild): Promise { 15 | const member = await guild.members.fetch(target); 16 | if (!member) throw guild.language.tget('userNotInGuild', target.tag); 17 | return member; 18 | } 19 | 20 | public async handle(msg: GuildMessage, target: GuildMember, reason: string): Promise { 21 | try { 22 | await msg.guild.moderation.ban(target, reason); 23 | } catch (err) { 24 | this.client.console.error(err); 25 | throw msg.guild.language.tget('commandBanUnable', target.user.tag); 26 | } 27 | 28 | return target; 29 | } 30 | 31 | public async posthandle(msg: GuildMessage, target: GuildMember, reason: string, duration: number | undefined): Promise { 32 | const modTask = duration 33 | ? await this.client.schedule.createModerationTask('unban', duration, { targetID: target.id, guildID: msg.guild.id }) 34 | : null; 35 | 36 | const thisCase = await msg.guild.moderation.cases.createCase('ban', msg.author, target.user, reason, duration, modTask); 37 | 38 | return msg.channel.send(msg.guild.language.tget('commandBanSuccess', target.user.tag, thisCase)); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/commands/Moderation/Member Modding/deafen.ts: -------------------------------------------------------------------------------- 1 | import { ModerationCommand, ModerationCommandOptions } from '@lib/structures/commands/ModerationCommand'; 2 | import { User, GuildMember, Guild, Message } from 'discord.js'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { GuildMessage } from '@lib/types/Messages'; 5 | 6 | @ApplyOptions({ 7 | description: lang => lang.tget('commandDeafenDescription'), 8 | duration: true, 9 | extendedHelp: lang => lang.tget('commandDeafenExtended'), 10 | requiredPermissions: ['MANAGE_ROLES'], 11 | requiredSettings: ['roles.deafened'] 12 | }) 13 | export default class extends ModerationCommand { 14 | 15 | public async prehandle(target: User, guild: Guild): Promise { 16 | const member = await guild.members.fetch(target); 17 | if (!member) throw guild.language.tget('userNotInGuild', target.tag); 18 | return member; 19 | } 20 | 21 | public async handle(msg: GuildMessage, target: GuildMember, reason: string): Promise { 22 | try { 23 | await msg.guild.moderation.deafen(target, reason); 24 | } catch (err) { 25 | this.client.console.error(err); 26 | throw msg.guild.language.tget('commandDeafenUnable', target.user.tag); 27 | } 28 | 29 | return target; 30 | } 31 | 32 | public async posthandle(msg: GuildMessage, target: GuildMember, reason: string, duration: number | undefined): Promise { 33 | const modTask = duration 34 | ? await this.client.schedule.createModerationTask('undeafen', duration, { targetID: target.id, guildID: msg.guild.id }) 35 | : null; 36 | 37 | const thisCase = await msg.guild.moderation.cases.createCase('deafen', msg.author, target.user, reason, duration, modTask); 38 | 39 | return msg.channel.send(msg.guild.language.tget('commandDeafenSuccess', target.user.tag, thisCase)); 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/commands/Moderation/Member Modding/kick.ts: -------------------------------------------------------------------------------- 1 | import { ModerationCommand, ModerationCommandOptions } from '@lib/structures/commands/ModerationCommand'; 2 | import { User, Guild, GuildMember, Message } from 'discord.js'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { GuildMessage } from '@lib/types/Messages'; 5 | 6 | @ApplyOptions({ 7 | description: lang => lang.tget('commandKickDescription'), 8 | extendedHelp: lang => lang.tget('commandKickExtended'), 9 | requiredPermissions: ['KICK_MEMBERS'] 10 | }) 11 | export default class extends ModerationCommand { 12 | 13 | public async prehandle(target: User, guild: Guild): Promise { 14 | const member = await guild.members.fetch(target); 15 | if (!member) throw guild.language.tget('userNotInGuild', target.tag); 16 | return member; 17 | } 18 | 19 | public async handle(msg: GuildMessage, target: GuildMember, reason: string): Promise { 20 | try { 21 | await msg.guild.moderation.kick(target, reason); 22 | } catch (err) { 23 | this.client.console.error(err); 24 | throw msg.guild.language.tget('commandKickUnable', target.user.tag); 25 | } 26 | 27 | return target; 28 | } 29 | 30 | public async posthandle(msg: GuildMessage, target: GuildMember, reason: string, duration: number | undefined): Promise { 31 | const thisCase = await msg.guild.moderation.cases.createCase('kick', msg.author, target.user, reason, duration, null); 32 | 33 | return msg.channel.send(msg.guild.language.tget('commandKickSuccess', target.user.tag, thisCase)); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/Moderation/Member Modding/mute.ts: -------------------------------------------------------------------------------- 1 | import { ModerationCommand, ModerationCommandOptions } from '@lib/structures/commands/ModerationCommand'; 2 | import { User, GuildMember, Guild, Message } from 'discord.js'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { GuildMessage } from '@lib/types/Messages'; 5 | 6 | @ApplyOptions({ 7 | description: lang => lang.tget('commandMuteDescription'), 8 | duration: true, 9 | extendedHelp: lang => lang.tget('commandMuteExtended'), 10 | requiredPermissions: ['MANAGE_ROLES'], 11 | requiredSettings: ['roles.muted'] 12 | }) 13 | export default class extends ModerationCommand { 14 | 15 | public async prehandle(target: User, guild: Guild): Promise { 16 | const member = await guild.members.fetch(target); 17 | if (!member) throw guild.language.tget('userNotInGuild', target.tag); 18 | return member; 19 | } 20 | 21 | public async handle(msg: GuildMessage, target: GuildMember, reason: string): Promise { 22 | try { 23 | await msg.guild.moderation.mute(target, reason); 24 | } catch (err) { 25 | this.client.console.error(err); 26 | throw msg.guild.language.tget('commandMuteUnable', target.user.tag); 27 | } 28 | 29 | return target; 30 | } 31 | 32 | public async posthandle(msg: GuildMessage, target: GuildMember, reason: string, duration: number | undefined): Promise { 33 | const modTask = duration 34 | ? await this.client.schedule.createModerationTask('unmute', duration, { targetID: target.id, guildID: msg.guild.id }) 35 | : null; 36 | 37 | const thisCase = await msg.guild.moderation.cases.createCase('mute', msg.author, target.user, reason, duration, modTask); 38 | 39 | return msg.channel.send(msg.guild.language.tget('commandMuteSuccess', target.user.tag, thisCase)); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/commands/Moderation/Member Modding/nickname.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | import { Message, User } from 'discord.js'; 6 | import { CommandOptions } from 'klasa'; 7 | 8 | @ApplyOptions({ 9 | aliases: ['nick'], 10 | description: lang => lang.tget('commandNicknameDescription'), 11 | extendedHelp: lang => lang.tget('commandNicknameExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | requiredPermissions: ['MANAGE_NICKNAMES'], 14 | runIn: ['text'], 15 | usage: ' [nickname:string{,32}]' 16 | }) 17 | export default class extends SteveCommand { 18 | 19 | public async run(msg: GuildMessage, [user, nickname]: [User, string]): Promise { 20 | const member = await msg.guild.members.fetch(user); 21 | if (!member) return msg.channel.send(msg.guild.language.tget('userNotInGuild', user.tag)); 22 | 23 | try { 24 | await member.setNickname(nickname); 25 | 26 | return msg.channel.send(nickname 27 | ? msg.guild.language.tget('commandNicknameSet', user.tag) 28 | : msg.guild.language.tget('commandNicknameCleared', user.tag)); 29 | } catch (err) { 30 | return msg.channel.send(msg.guild.language.tget('commandNicknameUnableToSet', err, user.tag)); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/commands/Moderation/Member Modding/role.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | import { Message, Role, User } from 'discord.js'; 6 | import { CommandOptions } from 'klasa'; 7 | 8 | @ApplyOptions({ 9 | description: lang => lang.tget('commandRoleDescription'), 10 | extendedHelp: lang => lang.tget('commandRoleExtended'), 11 | permissionLevel: PermissionsLevels.MODERATOR, 12 | requiredPermissions: ['MANAGE_ROLES'], 13 | runIn: ['text'], 14 | usage: ' [...]' 15 | }) 16 | export default class extends SteveCommand { 17 | 18 | public async run(msg: GuildMessage, [user, ...roles]: [User, Role]): Promise { 19 | const member = await msg.guild.members.fetch(user); 20 | if (!member) return msg.channel.send(msg.guild.language.tget('userNotInGuild', user.tag)); 21 | 22 | const removed: string[] = []; 23 | const added: string[] = []; 24 | 25 | for (const role of roles) { 26 | if (member.roles.cache.has(role.id)) { 27 | await member.roles.remove(role); 28 | removed.push(role.name); 29 | } else { 30 | await member.roles.add(role); 31 | added.push(role.name); 32 | } 33 | } 34 | 35 | let output = ''; 36 | if (added.length) output += `${msg.guild.language.tget('commandRoleAdd', added.join(', '))}\n`; 37 | if (removed.length) output += `${msg.guild.language.tget('commandRoleRemove', removed.join(', '))}\n`; 38 | 39 | return msg.channel.send(output); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/commands/Moderation/Member Modding/unban.ts: -------------------------------------------------------------------------------- 1 | import { ModerationCommand, ModerationCommandOptions } from '@lib/structures/commands/ModerationCommand'; 2 | import { User, Message } from 'discord.js'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { GuildMessage } from '@lib/types/Messages'; 5 | 6 | @ApplyOptions({ 7 | description: lang => lang.tget('commandUnbanDescription'), 8 | extendedHelp: lang => lang.tget('commandUnbanExtended'), 9 | requiredPermissions: ['BAN_MEMBERS'], 10 | usage: ' [reason:string]' 11 | }) 12 | export default class extends ModerationCommand { 13 | 14 | public async prehandle(target: User): Promise { 15 | return target; 16 | } 17 | 18 | public async handle(msg: GuildMessage, target: User, reason: string): Promise { 19 | try { 20 | await msg.guild.moderation.unban(target, reason); 21 | } catch (err) { 22 | this.client.console.error(err); 23 | throw msg.guild.language.tget('commandUnbanUnable', target.tag); 24 | } 25 | 26 | return target; 27 | } 28 | 29 | public async posthandle(msg: GuildMessage, target: User, reason: string, duration: number | undefined): Promise { 30 | const thisCase = await msg.guild.moderation.cases.createCase('unban', msg.author, target, reason, duration, null); 31 | 32 | return msg.channel.send(msg.guild.language.tget('commandUnbanSuccess', target.tag, thisCase)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/commands/Moderation/Member Modding/undeafen.ts: -------------------------------------------------------------------------------- 1 | import { ModerationCommand, ModerationCommandOptions } from '@lib/structures/commands/ModerationCommand'; 2 | import { User, GuildMember, Guild, Message } from 'discord.js'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { GuildMessage } from '@lib/types/Messages'; 5 | 6 | @ApplyOptions({ 7 | description: lang => lang.tget('commandUndeafenDescription'), 8 | extendedHelp: lang => lang.tget('commandUndeafenExtended'), 9 | requiredPermissions: ['MANAGE_ROLES'], 10 | requiredSettings: ['roles.deafened'] 11 | }) 12 | export default class extends ModerationCommand { 13 | 14 | public async prehandle(target: User, guild: Guild): Promise { 15 | const member = await guild.members.fetch(target); 16 | if (!member) throw guild.language.tget('userNotInGuild', target.tag); 17 | return member; 18 | } 19 | 20 | public async handle(msg: GuildMessage, target: GuildMember, reason: string): Promise { 21 | try { 22 | await msg.guild.moderation.undeafen(target, reason); 23 | } catch (err) { 24 | this.client.console.error(err); 25 | throw msg.guild.language.tget('commandUndeafenUnable', target.user.tag); 26 | } 27 | 28 | return target; 29 | } 30 | 31 | public async posthandle(msg: GuildMessage, target: GuildMember, reason: string, duration: number | undefined): Promise { 32 | const thisCase = await msg.guild.moderation.cases.createCase('deafen', msg.author, target.user, reason, duration, null); 33 | 34 | return msg.channel.send(msg.guild.language.tget('commandUndeafenSuccess', target.user.tag, thisCase)); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/Moderation/Member Modding/unmute.ts: -------------------------------------------------------------------------------- 1 | import { ModerationCommand, ModerationCommandOptions } from '@lib/structures/commands/ModerationCommand'; 2 | import { User, GuildMember, Guild, Message } from 'discord.js'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { GuildMessage } from '@lib/types/Messages'; 5 | 6 | @ApplyOptions({ 7 | description: lang => lang.tget('commandUnmuteDescription'), 8 | extendedHelp: lang => lang.tget('commandUnmuteExtended'), 9 | requiredPermissions: ['MANAGE_ROLES'], 10 | requiredSettings: ['roles.muted'] 11 | }) 12 | export default class extends ModerationCommand { 13 | 14 | public async prehandle(target: User, guild: Guild): Promise { 15 | const member = await guild.members.fetch(target); 16 | if (!member) throw guild.language.tget('userNotInGuild', target.tag); 17 | return member; 18 | } 19 | 20 | public async handle(msg: GuildMessage, target: GuildMember, reason: string): Promise { 21 | try { 22 | await msg.guild.moderation.unmute(target, reason); 23 | } catch (err) { 24 | this.client.console.error(err); 25 | throw msg.guild.language.tget('commandUnmuteUnable', target.user.tag); 26 | } 27 | 28 | return target; 29 | } 30 | 31 | public async posthandle(msg: GuildMessage, target: GuildMember, reason: string, duration: number | undefined): Promise { 32 | const thisCase = await msg.guild.moderation.cases.createCase('mute', msg.author, target.user, reason, duration, null); 33 | 34 | return msg.channel.send(msg.guild.language.tget('commandUnmuteSuccess', target.user.tag, thisCase)); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/Moderation/Utilities/clearrole.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | import { Message, Role } from 'discord.js'; 6 | import { CommandOptions } from 'klasa'; 7 | 8 | @ApplyOptions({ 9 | aliases: ['crole'], 10 | description: lang => lang.tget('commandClearRoleDescription'), 11 | extendedHelp: lang => lang.tget('commandClearRoleExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'], 14 | requiredPermissions: ['MANAGE_ROLES'], 15 | usage: '' 16 | }) 17 | export default class extends SteveCommand { 18 | 19 | public async run(msg: GuildMessage, [role]: [Role]): Promise { 20 | const res = await msg.channel.send(msg.guild.language.tget('working')); 21 | await msg.guild.members.fetch(); 22 | const { size } = role.members; 23 | 24 | if (size < 1) return res.edit(msg.guild.language.tget('commandClearRoleRoleEmpty', role.name)); 25 | 26 | for (const [, member] of role.members) { 27 | if (member.roles.cache.has(role.id)) await member.roles.remove(role.id); 28 | } 29 | 30 | return res.edit(msg.guild.language.tget('commandClearRole', size, role.name)); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/commands/Moderation/Utilities/mentionable.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | import { Role } from 'discord.js'; 6 | import { CommandOptions } from 'klasa'; 7 | 8 | @ApplyOptions({ 9 | description: lang => lang.tget('commandMentionableDescription'), 10 | extendedHelp: lang => lang.tget('commandMentionableExtended'), 11 | permissionLevel: PermissionsLevels.MODERATOR, 12 | requiredPermissions: ['MANAGE_ROLES'], 13 | runIn: ['text'], 14 | usage: '' 15 | }) 16 | export default class extends SteveCommand { 17 | 18 | public async run(msg: GuildMessage, [role]: [Role]) { 19 | const currentlyMentionable = role.mentionable; 20 | 21 | await role.setMentionable(!currentlyMentionable); 22 | 23 | return msg.channel.send(msg.guild.language.tget('commandMentionable', role.name, !currentlyMentionable)); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/commands/Moderation/Utilities/permissions.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { CommandOptions } from 'klasa'; 3 | import { PermissionsLevels } from '@lib/types/Enums'; 4 | import { User, Message, Permissions, PermissionString, MessageEmbed } from 'discord.js'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { GuildMessage } from '@lib/types/Messages'; 7 | 8 | const PERM_FLAGS = Object.keys(Permissions.FLAGS) as PermissionString[]; 9 | 10 | @ApplyOptions({ 11 | aliases: ['perms'], 12 | description: lang => lang.tget('commandPermissionsDescription'), 13 | permissionLevel: PermissionsLevels.MODERATOR, 14 | requiredPermissions: ['EMBED_LINKS'], 15 | runIn: ['text'], 16 | usage: '' 17 | }) 18 | export default class extends SteveCommand { 19 | 20 | public async run(msg: GuildMessage, [user]: [User]): Promise { 21 | const member = await msg.guild.members.fetch(user); 22 | if (!member) throw msg.guild.language.tget('userNotInGuild', user.tag); 23 | 24 | const { permissions } = member; 25 | const permList: string[] = []; 26 | 27 | if (permissions.has(Permissions.FLAGS.ADMINISTRATOR)) { 28 | permList.push(msg.guild.language.tget('commandPermissionsHasAll', user.tag)); 29 | } else { 30 | for (const flag of PERM_FLAGS) { 31 | if (permissions.has(flag)) permList.push(`🔹 ${msg.guild.language.PERMISSIONS[flag]}`); 32 | } 33 | } 34 | 35 | const embed = new MessageEmbed() 36 | .setAuthor(user.tag, user.displayAvatarURL()) 37 | .setDescription(permList.join('\n')); 38 | 39 | return msg.channel.send(embed); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/commands/Productivity/pomodoro.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { ApplyOptions } from '@skyra/decorators'; 3 | import { Message } from 'discord.js'; 4 | import { CommandOptions, KlasaMessage } from 'klasa'; 5 | 6 | @ApplyOptions({ 7 | aliases: ['pomo', 'pom'], 8 | description: lang => lang.tget('commandPomodoroDescription'), 9 | extendedHelp: lang => lang.tget('commandPomodoroExtended') 10 | }) 11 | export default class extends SteveCommand { 12 | 13 | public async run(msg: KlasaMessage): Promise { 14 | return msg.channel.send(msg.language.tget('commandPomodoroUnderConstruction')); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/commands/Self-Assign/addassignablerole.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Role } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['addrank'], 11 | description: lang => lang.tget('commandAddAssignableRoleDescription'), 12 | extendedHelp: lang => lang.tget('commandAddAssignableRoleExtended'), 13 | permissionLevel: PermissionsLevels.MODERATOR, 14 | runIn: ['text'], 15 | usage: ' [...]' 16 | }) 17 | export default class extends SteveCommand { 18 | 19 | public async run(msg: GuildMessage, roles: Role[]) { 20 | const asssignableRoleSnowflakes = msg.guild.settings.get(GuildSettings.Roles.Assignable) as string[]; 21 | const addedRoleNames: string[] = []; 22 | 23 | for (const role of roles) { 24 | if (asssignableRoleSnowflakes.includes(role.id)) continue; 25 | 26 | await msg.guild.settings.update(GuildSettings.Roles.Assignable, role.id, msg.guild.id, { action: 'add' }); 27 | 28 | addedRoleNames.push(role.name); 29 | } 30 | 31 | return msg.channel.send(msg.guild.language.tget('commandAddAssignableRole', addedRoleNames)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/commands/Self-Assign/removeassignablerole.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Role } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['removerank', 'remrank'], 11 | description: lang => lang.tget('commandRemoveAssignableRoleDescription'), 12 | extendedHelp: lang => lang.tget('commandRemoveAssignableRoleExtended'), 13 | permissionLevel: PermissionsLevels.MODERATOR, 14 | runIn: ['text'], 15 | usage: ' [...]' 16 | }) 17 | export default class extends SteveCommand { 18 | 19 | public async run(msg: GuildMessage, roles: Role[]) { 20 | const asssignableRoleSnowflakes = msg.guild.settings.get(GuildSettings.Roles.Assignable) as string[]; 21 | const removedRoleNames: string[] = []; 22 | 23 | for (const role of roles) { 24 | if (!asssignableRoleSnowflakes.includes(role.id)) continue; 25 | 26 | await msg.guild.settings.update(GuildSettings.Roles.Assignable, role.id, msg.guild.id, { action: 'remove' }); 27 | 28 | removedRoleNames.push(role.name); 29 | } 30 | 31 | return msg.channel.send(msg.guild.language.tget('commandRemoveAssignableRole', removedRoleNames)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/commands/Server Info/serverinfo.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { CommandOptions } from 'klasa'; 3 | import { Message, MessageEmbed } from 'discord.js'; 4 | import { friendlyDuration, formatDate } from '@utils/util'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { GuildMessage } from '@lib/types/Messages'; 7 | 8 | @ApplyOptions({ 9 | aliases: ['serverstats'], 10 | description: lang => lang.tget('commandServerInfoDescription'), 11 | extendedHelp: lang => lang.tget('commandServerInfoExtended'), 12 | requiredPermissions: ['EMBED_LINKS'], 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const embedData = msg.guild.language.tget('commandServerInfoEmbed'); 19 | 20 | const embed = new MessageEmbed() 21 | .addFields([ 22 | { name: embedData.fieldTitles.totalMembers, value: msg.guild.memberCount, inline: true }, 23 | { name: embedData.fieldTitles.bots, value: msg.guild.members.cache.filter(m => m.user.bot).size, inline: true }, 24 | { name: embedData.fieldTitles.textChannels, value: msg.guild.channels.cache.filter(c => c.type === 'text').size, inline: true }, 25 | { name: embedData.fieldTitles.voiceChannels, value: msg.guild.channels.cache.filter(c => c.type === 'voice').size, inline: true }, 26 | { name: embedData.fieldTitles.roles, value: msg.guild.roles.cache.size, inline: true }, 27 | { name: embedData.fieldTitles.emojis, value: msg.guild.emojis.cache.size, inline: true } 28 | ]) 29 | .setAuthor(msg.guild.name, msg.guild.iconURL()!) 30 | .setFooter(embedData.footer(formatDate(msg.guild.createdTimestamp), friendlyDuration(Date.now() - msg.guild.createdTimestamp))) 31 | .setTimestamp(); 32 | 33 | return msg.channel.send(embed); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Ignored Channels/ignorechannel.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions, CreateResolvers } from '@skyra/decorators'; 6 | import { GuildChannel, TextChannel } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandIgnoreChannelDescription'), 11 | extendedHelp: lang => lang.tget('commandIgnoreChannelExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'], 14 | usage: '' 15 | }) 16 | @CreateResolvers([ 17 | [ 18 | 'channel', 19 | async (str, possible, msg) => { 20 | const channel = await msg.client.arguments.get('channel').run(str, possible, msg) as GuildChannel; 21 | if (channel.type !== 'text') throw msg.guild!.language.tget('commandIgnoreChannelTextChannelRequired'); 22 | 23 | const ignoredChannels = msg.guild!.settings.get(GuildSettings.IgnoredChannels) as string[]; 24 | if (ignoredChannels.includes(channel.id)) throw msg.guild!.language.tget('commandIgnoreChannelAlreadyIgnored', channel.id); 25 | 26 | return channel as TextChannel; 27 | } 28 | ] 29 | ]) 30 | export default class extends SteveCommand { 31 | 32 | public async run(msg: GuildMessage, [channel]: [TextChannel]) { 33 | await msg.guild.settings.update(GuildSettings.IgnoredChannels, channel.id, { action: 'add' }); 34 | 35 | return msg.channel.send(msg.guild.language.tget('commandIgnoreChannel', channel.id)); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Ignored Channels/showignoredchannels.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { GuildMessage } from '@lib/types/Messages'; 3 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | import { richDisplayList } from '@utils/util'; 6 | import { MessageEmbed } from 'discord.js'; 7 | import { CommandOptions, KlasaMessage } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandShowIgnoredChannelsDescription'), 11 | runIn: ['text'] 12 | }) 13 | export default class extends SteveCommand { 14 | 15 | public async run(msg: GuildMessage) { 16 | const ignoredChannels = msg.guild.settings.get(GuildSettings.IgnoredChannels) as string[]; 17 | const ignoredChannelNames: string[] = []; 18 | 19 | for (let i = 0; i < ignoredChannels.length; i++) { 20 | const channel = msg.guild.channels.cache.get(ignoredChannels[i]); 21 | if (!channel) continue; 22 | 23 | ignoredChannelNames.push(channel.name); 24 | } 25 | 26 | if (!ignoredChannelNames.length) throw msg.guild.language.tget('commandShowIgnoredChannelsNoChannels'); 27 | 28 | const display = richDisplayList(ignoredChannelNames, 30); 29 | 30 | const res = await msg.channel.send(new MessageEmbed() 31 | .setDescription('Loading...')); 32 | 33 | await display.run(res as KlasaMessage); 34 | return res; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Ignored Channels/unignorechannel.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions, CreateResolvers } from '@skyra/decorators'; 6 | import { GuildChannel, TextChannel } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandUnignoreChannelDescription'), 11 | extendedHelp: lang => lang.tget('commandUnignoreChannelExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'], 14 | usage: '' 15 | }) 16 | @CreateResolvers([ 17 | [ 18 | 'channel', 19 | async (str, possible, msg) => { 20 | const channel = await msg.client.arguments.get('channel').run(str, possible, msg) as GuildChannel; 21 | 22 | const ignoredChannels = msg.guild!.settings.get(GuildSettings.IgnoredChannels) as string[]; 23 | if (!ignoredChannels.includes(channel.id)) throw msg.guild!.language.tget('commandUnignoreChannelNotIgnored', channel.id); 24 | 25 | return channel as TextChannel; 26 | } 27 | ] 28 | ]) 29 | export default class extends SteveCommand { 30 | 31 | public async run(msg: GuildMessage, [channel]: [TextChannel]) { 32 | await msg.guild.settings.update(GuildSettings.IgnoredChannels, channel.id, { action: 'remove' }); 33 | 34 | return msg.channel.send(msg.guild.language.tget('commandUnignoreChannel', channel.id)); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Ignored Roles/ignorerole.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions, CreateResolvers } from '@skyra/decorators'; 6 | import { Role } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandIgnoreRoleDescription'), 11 | extendedHelp: lang => lang.tget('commandIgnoreRoleExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'], 14 | usage: '' 15 | }) 16 | @CreateResolvers([ 17 | [ 18 | 'rolename', 19 | async (str, possible, msg) => { 20 | const role = await msg.client.arguments.get('rolename').run(str, possible, msg); 21 | 22 | const ignoredRoles = msg.guild!.settings.get(GuildSettings.IgnoredRoles) as string[]; 23 | if (ignoredRoles.includes(role.id)) throw msg.guild!.language.tget('commandIgnoreRoleAlreadyIgnored', role.name); 24 | 25 | return role; 26 | } 27 | ] 28 | ]) 29 | export default class extends SteveCommand { 30 | 31 | public async run(msg: GuildMessage, [role]: [Role]) { 32 | await msg.guild.settings.update(GuildSettings.IgnoredRoles, role.id, msg.guild.id, { action: 'add' }); 33 | 34 | return msg.channel.send(msg.guild.language.tget('commandIgnoreRole', role.name)); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Ignored Roles/showignoredroles.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { GuildMessage } from '@lib/types/Messages'; 3 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | import { richDisplayList } from '@utils/util'; 6 | import { MessageEmbed } from 'discord.js'; 7 | import { CommandOptions, KlasaMessage } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandShowIgnoredRolesDescription'), 11 | runIn: ['text'] 12 | }) 13 | export default class extends SteveCommand { 14 | 15 | public async run(msg: GuildMessage) { 16 | const ignoredRoles = msg.guild.settings.get(GuildSettings.IgnoredRoles) as string[]; 17 | const ignoredRoleNames: string[] = []; 18 | 19 | for (let i = 0; i < ignoredRoles.length; i++) { 20 | const role = msg.guild.roles.cache.get(ignoredRoles[i]); 21 | if (!role) continue; 22 | 23 | ignoredRoleNames.push(role.name); 24 | } 25 | 26 | if (!ignoredRoleNames.length) throw msg.guild.language.tget('commandShowIgnoredRolesNoRoles'); 27 | 28 | const display = richDisplayList(ignoredRoleNames, 30); 29 | 30 | const res = await msg.channel.send(new MessageEmbed() 31 | .setDescription('Loading...')); 32 | 33 | await display.run(res as KlasaMessage); 34 | return res; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Ignored Roles/unignorerole.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions, CreateResolvers } from '@skyra/decorators'; 6 | import { Role } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandUnignoreRoleDescription'), 11 | extendedHelp: lang => lang.tget('commandUnignoreRoleExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'], 14 | usage: '' 15 | }) 16 | @CreateResolvers([ 17 | [ 18 | 'rolename', 19 | async (str, possible, msg) => { 20 | const role = await msg.client.arguments.get('rolename').run(str, possible, msg); 21 | 22 | const ignoredRoles = msg.guild!.settings.get(GuildSettings.IgnoredRoles) as string[]; 23 | if (!ignoredRoles.includes(role.id)) throw msg.guild!.language.tget('commandUnignoreRoleNotIgnored', role.name); 24 | 25 | return role; 26 | } 27 | ] 28 | ]) 29 | export default class extends SteveCommand { 30 | 31 | public async run(msg: GuildMessage, [role]: [Role]) { 32 | await msg.guild.settings.update(GuildSettings.IgnoredRoles, role.id, msg.guild.id, { action: 'remove' }); 33 | 34 | return msg.channel.send(msg.guild.language.tget('commandUnignoreRole', role.name)); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/togglechannelcreate.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tcc'], 11 | description: lang => lang.tget('commandToggleChannelCreateDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.ChannelCreate) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.ChannelCreate, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleChannelCreate', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/togglechanneldelete.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tcd'], 11 | description: lang => lang.tget('commandToggleChannelDeleteDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.ChannelDelete) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.ChannelDelete, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleChannelDelete', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/togglechannelupdate.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tcu'], 11 | description: lang => lang.tget('commandToggleChannelUpdateDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.ChannelUpdate) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.ChannelUpdate, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleChannelUpdate', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/toggleemojicreate.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tec'], 11 | description: lang => lang.tget('commandToggleEmojiCreateDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | 18 | public async run(msg: GuildMessage): Promise { 19 | const current = msg.guild.settings.get(GuildSettings.LogEvents.EmojiCreate) as boolean; 20 | 21 | await msg.guild.settings.update(GuildSettings.LogEvents.EmojiCreate, !current); 22 | 23 | return msg.channel.send(msg.guild.language.tget('commandToggleEmojiCreate', current)); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/toggleemojidelete.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['ted'], 11 | description: lang => lang.tget('commandToggleEmojiDeleteDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | 18 | public async run(msg: GuildMessage): Promise { 19 | const current = msg.guild.settings.get(GuildSettings.LogEvents.EmojiDelete) as boolean; 20 | 21 | await msg.guild.settings.update(GuildSettings.LogEvents.EmojiDelete, !current); 22 | 23 | return msg.channel.send(msg.guild.language.tget('commandToggleEmojiDelete', current)); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/toggleemojiupdate.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['teu'], 11 | description: lang => lang.tget('commandToggleEmojiUpdateDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.EmojiUpdate) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.EmojiUpdate, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleEmojiUpdate', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/toggleguildbanadd.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tgba'], 11 | description: lang => lang.tget('commandToggleGuildBanAddDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.GuildBanAdd) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.GuildBanAdd, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleGuildBanAdd', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/toggleguildbanremove.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tgbr'], 11 | description: lang => lang.tget('commandToggleGuildBanRemoveDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.GuildBanRemove) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.GuildBanRemove, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleGuildBanRemove', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/toggleguildmemberadd.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tgma'], 11 | description: lang => lang.tget('commandToggleGuildMemberAddDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.GuildMemberAdd) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.GuildMemberAdd, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleGuildMemberAdd', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/toggleguildmemberremove.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tgmr'], 11 | description: lang => lang.tget('commandToggleGuildMemberRemoveDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.GuildMemberRemove) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.GuildMemberRemove, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleGuildMemberRemove', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/toggleguildmemberupdate.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tgmu'], 11 | description: lang => lang.tget('commandToggleGuildMemberUpdateDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.GuildMemberUpdate) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.GuildMemberUpdate, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleGuildMemberUpdate', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/toggleinvitecreate.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tic'], 11 | description: lang => lang.tget('commandToggleInviteCreateDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.InviteCreate) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.InviteCreate, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleInviteCreate', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/toggleinvitedelete.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tid'], 11 | description: lang => lang.tget('commandToggleInviteDeleteDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.InviteDelete) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.InviteDelete, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleInviteDelete', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/togglemessagedelete.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tmd'], 11 | description: lang => lang.tget('commandToggleMessageDeleteDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.MessageDelete) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.MessageDelete, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleMessageDelete', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/togglemessagedeletebulk.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tmdb'], 11 | description: lang => lang.tget('commandToggleMessageDeleteBulkDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.MessageDeleteBulk) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.MessageDeleteBulk, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleMessageDeleteBulk', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/togglerolecreate.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['trc'], 11 | description: lang => lang.tget('commandToggleRoleCreateDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.RoleCreate) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.RoleCreate, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleRoleCreate', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/toggleroledelete.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['trd'], 11 | description: lang => lang.tget('commandToggleRoleDeleteDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.RoleDelete) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.RoleDelete, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleRoleDelete', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/Log Events/toggleroleupdate.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['tru'], 11 | description: lang => lang.tget('commandToggleRoleUpdateDescription'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage): Promise { 18 | const current = msg.guild.settings.get(GuildSettings.LogEvents.RoleUpdate) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.LogEvents.RoleUpdate, !current); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleRoleUpdate', current)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/managedisabledcommands.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions, CreateResolvers } from '@skyra/decorators'; 6 | import { richDisplayList } from '@utils/util'; 7 | import { MessageEmbed } from 'discord.js'; 8 | import { Command, CommandOptions, KlasaMessage } from 'klasa'; 9 | 10 | @ApplyOptions({ 11 | aliases: ['mdc'], 12 | description: lang => lang.tget('commandManageDisabledCommandsDescription'), 13 | extendedHelp: lang => lang.tget('commandManageDisabledCommandsExtended'), 14 | permissionLevel: PermissionsLevels.MODERATOR, 15 | runIn: ['text'], 16 | usage: '' 17 | }) 18 | @CreateResolvers([ 19 | [ 20 | 'command', 21 | (str, possible, msg) => !str || str === '' ? null : msg.client.arguments.get('command').run(str, possible, msg) 22 | ] 23 | ]) 24 | export default class extends SteveCommand { 25 | 26 | public async run(msg: GuildMessage, [cmd]: [Command | 'show']) { 27 | const disabledCommands = (msg.guild.settings.get(GuildSettings.DisabledCommands) as string[]) 28 | .filter(cmdName => this.store.has(cmdName)); 29 | 30 | if (cmd === 'show') { 31 | if (!disabledCommands.length) return msg.channel.send(msg.guild.language.tget('commandManageDisabledCommandsNoCommandsDisabled')); 32 | 33 | const res = await msg.channel.send(new MessageEmbed() 34 | .setDescription('Loading...')); 35 | 36 | const display = richDisplayList(disabledCommands, 30); 37 | 38 | await display.run(res as KlasaMessage); 39 | return res; 40 | } 41 | 42 | await msg.guild.settings.update(GuildSettings.DisabledCommands, cmd.name); 43 | 44 | const currentlyDisabled = disabledCommands.includes(cmd.name); 45 | return msg.channel.send(msg.guild.language.tget('commandManageDisabledCommands', cmd.name, currentlyDisabled)); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/commands/Server Settings/managewordblacklist.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandManageWordBlacklistDescription'), 11 | extendedHelp: lang => lang.tget('commandManageWordBlacklistExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'], 14 | usage: '' 15 | }) 16 | export default class extends SteveCommand { 17 | 18 | public async run(msg: GuildMessage, [word]: [string]): Promise { 19 | if (word === 'enable') { 20 | await msg.guild.settings.update(GuildSettings.WordBlacklist.Enabled, true); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandManageWordBlacklistEnabled')); 23 | } else if (word === 'disable') { 24 | await msg.guild.settings.update(GuildSettings.WordBlacklist.Enabled, false); 25 | 26 | return msg.channel.send(msg.guild.language.tget('commandManageWordBlacklistDisabled')); 27 | } else if (word === 'reset') { 28 | await msg.guild.settings.reset(GuildSettings.WordBlacklist.List); 29 | 30 | return msg.channel.send(msg.guild.language.tget('commandManageWordBlacklistReset')); 31 | } 32 | 33 | const removing = (msg.guild.settings.get(GuildSettings.WordBlacklist.List) as string[]).includes(word); 34 | 35 | await msg.guild.settings.update(GuildSettings.WordBlacklist.List, word); 36 | 37 | return msg.channel.send(msg.guild.language.tget('commandManageWordBlacklistUpdate', removing)); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/commands/Server Settings/setadministratorrole.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message, Role } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['setadminrole'], 11 | description: lang => lang.tget('commandSetAdministratorRoleDescription'), 12 | extendedHelp: lang => lang.tget('commandSetAdministratorRoleExtended'), 13 | permissionLevel: PermissionsLevels.ADMINISTRATOR, 14 | runIn: ['text'], 15 | usage: '' 16 | }) 17 | export default class extends SteveCommand { 18 | 19 | public async run(msg: GuildMessage, [role]: [Role]): Promise { 20 | await msg.guild.settings.update(GuildSettings.Roles.Administrator, role.id, msg.guild.id); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandSetAdministratorRoleSet', role.name)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/setbandeletedays.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandSetBanDeleteDaysDescription'), 11 | extendedHelp: lang => lang.tget('commandSetBanDeleteDaysExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'], 14 | usage: '' 15 | }) 16 | export default class extends SteveCommand { 17 | 18 | public async run(msg: GuildMessage, [days]: [number]): Promise { 19 | await msg.guild.settings.update(GuildSettings.Moderation.BanDeleteDays, days); 20 | 21 | return msg.channel.send(msg.guild.language.tget('commandSetBanDeleteDaysSet', days)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/Server Settings/setdeafenedrole.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message, Role } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandSetDeafenedRoleDescription'), 11 | extendedHelp: lang => lang.tget('commandSetDeafenedRoleExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'], 14 | usage: '' 15 | }) 16 | export default class extends SteveCommand { 17 | 18 | public async run(msg: GuildMessage, [role]: [Role]): Promise { 19 | await msg.guild.settings.update(GuildSettings.Roles.Deafened, role.id, msg.guild.id); 20 | 21 | return msg.channel.send(msg.guild.language.tget('commandSetDeafenedRoleSet', role.name)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/Server Settings/setmemberlog.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message, TextChannel } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandSetMemberLogDescription'), 11 | extendedHelp: lang => lang.tget('commandSetMemberLogExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'], 14 | usage: '' 15 | }) 16 | export default class extends SteveCommand { 17 | 18 | public async run(msg: GuildMessage, [channel]: [TextChannel]): Promise { 19 | await msg.guild.settings.update(GuildSettings.Channels.Memberlog, channel.id); 20 | 21 | return msg.channel.send(msg.guild.language.tget('commandSetMemberLogSet', channel.id)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/Server Settings/setmoderatorrole.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message, Role } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['setmodrole'], 11 | description: lang => lang.tget('commandSetModeratorRoleDescription'), 12 | extendedHelp: lang => lang.tget('commandSetModeratorRoleExtended'), 13 | permissionLevel: PermissionsLevels.MODERATOR, 14 | runIn: ['text'], 15 | usage: '' 16 | }) 17 | export default class extends SteveCommand { 18 | 19 | public async run(msg: GuildMessage, [role]: [Role]): Promise { 20 | await msg.guild.settings.update(GuildSettings.Roles.Moderator, role.id, msg.guild.id); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandSetModeratorRoleSet', role.name)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/setmutedrole.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message, Role } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandSetMutedRoleDescription'), 11 | extendedHelp: lang => lang.tget('commandSetMutedRoleExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'], 14 | usage: '' 15 | }) 16 | export default class extends SteveCommand { 17 | 18 | public async run(msg: GuildMessage, [role]: [Role]): Promise { 19 | await msg.guild.settings.update(GuildSettings.Roles.Muted, role.id, msg.guild.id); 20 | 21 | return msg.channel.send(msg.guild.language.tget('commandSetMutedRoleSet', role.name)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/Server Settings/setreminderchannel.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message, TextChannel } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandSetReminderChannelDescription'), 11 | extendedHelp: lang => lang.tget('commandSetReminderChannelExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'], 14 | usage: '' 15 | }) 16 | export default class extends SteveCommand { 17 | 18 | public async run(msg: GuildMessage, [channel]: [TextChannel]): Promise { 19 | await msg.guild.settings.update(GuildSettings.Channels.ReminderChannel, channel.id); 20 | 21 | return msg.channel.send(msg.guild.language.tget('commandSetReminderChannelSet', channel.id)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/Server Settings/setserverlog.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message, TextChannel } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandServerLogDescription'), 11 | extendedHelp: lang => lang.tget('commandServerLogExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'], 14 | usage: '' 15 | }) 16 | export default class extends SteveCommand { 17 | 18 | public async run(msg: GuildMessage, [channel]: [TextChannel]): Promise { 19 | await msg.guild.settings.update(GuildSettings.Channels.Serverlog, channel.id); 20 | 21 | return msg.channel.send(msg.guild.language.tget('commandServerLogSet', channel.id)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/Server Settings/settrustedrole.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message, Role } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandSetTrustedRoleDescription'), 11 | extendedHelp: lang => lang.tget('commandSetTrustedRoleExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'], 14 | usage: '' 15 | }) 16 | export default class extends SteveCommand { 17 | 18 | public async run(msg: GuildMessage, [role]: [Role]): Promise { 19 | await msg.guild.settings.update(GuildSettings.Roles.Trusted, role.id, msg.guild.id); 20 | 21 | return msg.channel.send(msg.guild.language.tget('commandSetTrustedRoleSet', role.name)); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/Server Settings/toggledeletepinmessages.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { CommandOptions } from 'klasa'; 7 | 8 | @ApplyOptions({ 9 | aliases: ['tdpm'], 10 | description: lang => lang.tget('commandToggleDeletePinMessagesDescription'), 11 | extendedHelp: lang => lang.tget('commandToggleDeletePinMessagesExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | runIn: ['text'] 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: GuildMessage) { 18 | const currentSetting = msg.guild.settings.get(GuildSettings.DeletePinMessages) as boolean; 19 | 20 | await msg.guild.settings.update(GuildSettings.DeletePinMessages, !currentSetting); 21 | 22 | return msg.channel.send(msg.guild.language.tget('commandToggleDeletePinMessages', currentSetting)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/Server Settings/toggletrustedrolerequirement.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildMessage } from '@lib/types/Messages'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | import { Message } from 'discord.js'; 7 | import { CommandOptions } from 'klasa'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandToggleTrustedRoleRequirementDescription'), 11 | extendedHelp: lang => lang.tget('commandToggleTrustedRoleRequirementExtended'), 12 | permissionLevel: PermissionsLevels.MODERATOR, 13 | requiredSettings: ['roles.trusted'], 14 | runIn: ['text'] 15 | }) 16 | export default class extends SteveCommand { 17 | 18 | public async run(msg: GuildMessage): Promise { 19 | const required: boolean = msg.guild.settings.get(GuildSettings.Roles.RequireTrustedRoleForSelfAssign); 20 | 21 | if (required) { 22 | await msg.guild.settings.update(GuildSettings.Roles.RequireTrustedRoleForSelfAssign, false); 23 | 24 | return msg.channel.send(msg.guild.language.tget('commandToggleTrustedRoleRequirementDisable')); 25 | } 26 | 27 | await msg.guild.settings.update(GuildSettings.Roles.RequireTrustedRoleForSelfAssign, true); 28 | 29 | return msg.channel.send(msg.guild.language.tget('commandToggleTrustedRoleRequirementEnable')); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/Space/spacepic.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { CommandOptions, KlasaMessage } from 'klasa'; 3 | import { TOKENS } from '@root/config'; 4 | import axios from 'axios'; 5 | import { MessageEmbed } from 'discord.js'; 6 | import { ApplyOptions } from '@skyra/decorators'; 7 | import { formatDate, sendLoadingMessage } from '@utils/util'; 8 | 9 | @ApplyOptions({ 10 | aliases: ['apod'], 11 | cooldown: 30, 12 | cooldownLevel: 'author', 13 | description: lang => lang.tget('commandSpacePicDescription'), 14 | extendedHelp: lang => lang.tget('commandSpacePicExtended'), 15 | requiredPermissions: ['EMBED_LINKS'], 16 | usage: '[date:date]' 17 | }) 18 | export default class extends SteveCommand { 19 | 20 | private baseUrl = `https://api.nasa.gov/planetary/apod?api_key=${TOKENS.NASA}`; 21 | 22 | public async init() { 23 | if (!TOKENS.NASA) this.disable(); 24 | } 25 | 26 | public async run(msg: KlasaMessage, [date]: [Date]) { 27 | const response = await sendLoadingMessage(msg); 28 | 29 | try { 30 | const res = await axios.get(`${this.baseUrl}${date ? `&date=${formatDate(date, 'YYYY-MM-DD')}` : ''}`); 31 | 32 | if (res.statusText === 'OK') { 33 | const { data } = res; 34 | 35 | const embed = new MessageEmbed() 36 | .setDescription(data.explanation) 37 | .setImage(data.hdurl) 38 | .setTitle(`${data.title} (${data.date})`); 39 | 40 | if (data.copyright) embed.setFooter(`Copyright ${data.copyright}`); 41 | 42 | return response.edit(undefined, embed); 43 | } 44 | } catch { 45 | return response.edit(msg.language.tget('commandSpacePicError'), { embed: null }); 46 | } 47 | } 48 | 49 | } 50 | 51 | interface ApodResponse { 52 | copyright?: string; 53 | date: string; 54 | explanation: string; 55 | hdurl: string; 56 | media_type: string; 57 | service_version: string; 58 | title: string; 59 | url: string; 60 | } 61 | -------------------------------------------------------------------------------- /src/commands/System/Developer Tools/load.ts: -------------------------------------------------------------------------------- 1 | import { CommandOptions, KlasaMessage, Stopwatch, Store, Piece } from 'klasa'; 2 | import { pathExists } from 'fs-nextra'; 3 | import { join } from 'path'; 4 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 5 | import { PermissionsLevels } from '@lib/types/Enums'; 6 | import { Constructor } from 'discord.js'; 7 | import { ApplyOptions } from '@skyra/decorators'; 8 | 9 | @ApplyOptions({ 10 | description: lang => lang.tget('commandLoadDescription'), 11 | permissionLevel: PermissionsLevels.OWNER 12 | }) 13 | export default class extends SteveCommand { 14 | 15 | public regExp = /\\\\?|\//g; 16 | 17 | public async run(msg: KlasaMessage, [core, store, path]: [string, Store>, string]) { 18 | const pathSplit = (path.endsWith('.js') ? path : `${path}.js`).split(this.regExp); 19 | const timer = new Stopwatch(); 20 | const piece = await (core ? this.tryEach(store, pathSplit) : store.load(store.userDirectory, pathSplit)); 21 | 22 | try { 23 | if (!piece) throw msg.language.tget('commandLoadFail'); 24 | await piece.init(); 25 | if (this.client.shard) { 26 | await this.client.shard.broadcastEval(` 27 | if (String(this.options.shards) !== '${this.client.options.shards}') { 28 | const piece = this.${piece.store}.load('${piece.directory}', ${JSON.stringify(pathSplit)}); 29 | if (piece) piece.init(); 30 | } 31 | `); 32 | } 33 | return msg.sendLocale('commandLoad', [timer.stop(), store.name, piece.name]); 34 | } catch (error) { 35 | timer.stop(); 36 | throw msg.language.tget('commandLoadError', store.name, piece ? piece.name : pathSplit.join('/'), error); 37 | } 38 | } 39 | 40 | private async tryEach(store: Store>, path: string[]) { 41 | // @ts-ignore 2341 42 | for (const dir of store.coreDirectories) if (await pathExists(join(dir, ...path))) return store.load(dir, path); 43 | return undefined; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/commands/System/Developer Tools/reboot.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { CommandOptions, KlasaMessage } from 'klasa'; 5 | 6 | @ApplyOptions({ 7 | description: lang => lang.tget('commandRebootDescription'), 8 | guarded: true, 9 | permissionLevel: PermissionsLevels.OWNER 10 | }) 11 | export default class extends SteveCommand { 12 | 13 | public async run(msg: KlasaMessage) { 14 | await msg.sendLocale('commandReboot').catch(err => this.client.emit('error', err)); 15 | await Promise.all(this.client.providers.map(provider => provider.shutdown())); 16 | process.exit(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/System/Developer Tools/transfer.ts: -------------------------------------------------------------------------------- 1 | import { CommandOptions, KlasaMessage, Piece } from 'klasa'; 2 | import * as fs from 'fs-nextra'; 3 | import { resolve, join } from 'path'; 4 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 5 | import { PermissionsLevels } from '@lib/types/Enums'; 6 | import { ApplyOptions } from '@skyra/decorators'; 7 | 8 | @ApplyOptions({ 9 | description: lang => lang.tget('commandTransferDescription'), 10 | guarded: true, 11 | permissionLevel: PermissionsLevels.OWNER, 12 | usage: '' 13 | }) 14 | export default class extends SteveCommand { 15 | 16 | public async run(msg: KlasaMessage, [piece]: [Piece]) { 17 | const file = join(...piece.file); 18 | const fileLocation = resolve(piece.directory, file); 19 | 20 | await fs.access(fileLocation).catch(() => { 21 | throw msg.language.tget('commandTransferError'); 22 | }); 23 | 24 | try { 25 | await fs.copy(fileLocation, join(piece.store.userDirectory, file)); 26 | piece.store.load(piece.store.userDirectory, piece.file); 27 | if (this.client.shard) { 28 | await this.client.shard.broadcastEval(` 29 | if (String(this.options.shards) !== '${this.client.options.shards}') this.${piece.store}.load(${piece.store.userDirectory}, ${JSON.stringify(piece.file)}); 30 | `); 31 | } 32 | return msg.sendLocale('commandTransferSuccess', [piece.type, piece.name]); 33 | } catch (err) { 34 | this.client.emit('error', err.stack); 35 | return msg.sendLocale('commandTransferFailed', [piece.type, piece.name]); 36 | } 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/commands/System/Developer Tools/unload.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { CommandOptions, KlasaMessage, Piece } from 'klasa'; 5 | 6 | @ApplyOptions({ 7 | aliases: ['u'], 8 | description: lang => lang.tget('commandUnloadDescription'), 9 | guarded: true, 10 | permissionLevel: PermissionsLevels.OWNER, 11 | usage: '' 12 | }) 13 | export default class extends SteveCommand { 14 | 15 | public async run(msg: KlasaMessage, [piece]: [Piece]) { 16 | if ((piece.type === 'event' && piece.name === 'message') || (piece.type === 'monitor' && piece.name === 'commandHandler')) { 17 | return msg.sendLocale('commandUnloadWarn'); 18 | } 19 | piece.unload(); 20 | if (this.client.shard) { 21 | await this.client.shard.broadcastEval(` 22 | if (String(this.options.shards) !== '${this.client.options.shards}') this.${piece.store}.get('${piece.name}').unload(); 23 | `); 24 | } 25 | return msg.sendLocale('commandUnload', [piece.type, piece.name]); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/commands/System/blacklist.ts: -------------------------------------------------------------------------------- 1 | 2 | import { CommandOptions, KlasaMessage } from 'klasa'; 3 | import { Guild, User } from 'discord.js'; 4 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 5 | import { PermissionsLevels } from '@lib/types/Enums'; 6 | import { ApplyOptions } from '@skyra/decorators'; 7 | 8 | @ApplyOptions({ 9 | description: lang => lang.tget('commandBlacklistDescription'), 10 | guarded: true, 11 | permissionLevel: PermissionsLevels.OWNER, 12 | usage: ' [...]' 13 | }) 14 | export default class extends SteveCommand { 15 | 16 | public terms = ['usersAdded', 'usersRemoved', 'guildsAdded', 'guildsRemoved']; 17 | 18 | public async run(msg: KlasaMessage, usersAndGuilds: Array) { 19 | const changes = [[], [], [], []]; 20 | const queries = [[], []]; 21 | 22 | for (const userOrGuild of new Set(usersAndGuilds)) { 23 | const type = userOrGuild instanceof User ? 'user' : 'guild'; 24 | if (this.client.settings!.get(`${type}Blacklist`).includes(userOrGuild.id || userOrGuild)) { 25 | // @ts-expect-error 2339 2345 26 | changes[this.terms.indexOf(`${type}sRemoved`)].push(userOrGuild.name || userOrGuild.username || userOrGuild); 27 | } else { 28 | // @ts-expect-error 2339 2345 29 | changes[this.terms.indexOf(`${type}sAdded`)].push(userOrGuild.name || userOrGuild.username || userOrGuild); 30 | } 31 | // @ts-expect-error 2345 32 | queries[Number(type === 'guild')].push(userOrGuild.id || userOrGuild); 33 | } 34 | 35 | const { errors } = await this.client.settings!.update([['userBlacklist', queries[0]], ['guildBlacklist', queries[1]]]); 36 | if (errors.length) throw String(errors[0]); 37 | 38 | return msg.sendLocale('commandBlacklistSuccess', changes); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/commands/System/disable.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { CommandOptions, KlasaMessage, Piece } from 'klasa'; 5 | 6 | @ApplyOptions({ 7 | description: lang => lang.tget('commandDisableDescription'), 8 | guarded: true, 9 | permissionLevel: PermissionsLevels.OWNER, 10 | usage: '' 11 | }) 12 | export default class extends SteveCommand { 13 | 14 | public async run(msg: KlasaMessage, [piece]: [Piece]) { 15 | if ((piece.type === 'event' && piece.name === 'coreMessage') || (piece.type === 'monitor' && piece.name === 'commandHandler')) { 16 | return msg.sendLocale('commandDisableWarn'); 17 | } 18 | piece.disable(); 19 | if (this.client.shard) { 20 | await this.client.shard.broadcastEval(` 21 | if (String(this.options.shards) !== '${this.client.options.shards}') this.${piece.store}.get('${piece.name}').disable(); 22 | `); 23 | } 24 | return msg.sendLocale('commandDisable', [piece.type, piece.name], { code: 'diff' }); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/commands/System/enable.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { CommandOptions, KlasaMessage, Piece } from 'klasa'; 5 | 6 | @ApplyOptions({ 7 | description: language => language.tget('commandEnableDescription'), 8 | guarded: true, 9 | permissionLevel: PermissionsLevels.OWNER, 10 | usage: '' 11 | }) 12 | export default class extends SteveCommand { 13 | 14 | public async run(msg: KlasaMessage, [piece]: [Piece]) { 15 | piece.enable(); 16 | if (this.client.shard) { 17 | await this.client.shard.broadcastEval(` 18 | if (String(this.options.shards) !== '${this.client.options.shards}') this.${piece.store}.get('${piece.name}').enable(); 19 | `); 20 | } 21 | return msg.sendLocale('commandEnable', [piece.type, piece.name], { code: 'diff' }); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/System/feedback.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { CommandOptions, KlasaMessage } from 'klasa'; 3 | import { Message, MessageEmbed, TextChannel } from 'discord.js'; 4 | import { FEEDBACK_GUILD, FEEDBACK_CHANNEL } from '@root/config'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | 7 | @ApplyOptions({ 8 | aliases: ['suggest', 'makeyourbotworkbetter'], 9 | cooldown: 60, 10 | cooldownLevel: 'author', 11 | description: lang => lang.tget('commandFeedbackDescription'), 12 | extendedHelp: lang => lang.tget('commandFeedbackExtended'), 13 | usage: '' 14 | }) 15 | export default class extends SteveCommand { 16 | 17 | public async run(msg: KlasaMessage, [feedback]: [string]): Promise { 18 | const feedbackGuild = this.client.guilds.cache.get(FEEDBACK_GUILD); 19 | if (!feedbackGuild) throw msg.language.tget('commandFeedbackNoGuild'); 20 | const feedbackChannel = feedbackGuild.channels.cache.get(FEEDBACK_CHANNEL) as TextChannel; 21 | if (!feedbackChannel) throw msg.language.tget('commandFeedbackNoChannel'); 22 | 23 | const embed = new MessageEmbed() 24 | .addFields( 25 | { name: 'Feedback', value: feedback } 26 | ) 27 | .setAuthor(msg.author.tag, msg.author.displayAvatarURL()) 28 | .setTimestamp(); 29 | 30 | return Promise.all([ 31 | msg.channel.send(msg.language.tget('commandFeedbackSent')), 32 | feedbackChannel.send(embed) 33 | ]); 34 | } 35 | 36 | public async init(): Promise { 37 | if (!FEEDBACK_GUILD || !FEEDBACK_CHANNEL) this.disable(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/commands/System/invite.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | import { CommandOptions, KlasaMessage } from 'klasa'; 5 | 6 | @ApplyOptions({ 7 | description: lang => lang.tget('commandInviteDescription'), 8 | guarded: true 9 | }) 10 | export default class extends SteveCommand { 11 | 12 | public async init() { 13 | if (this.client.application && !this.client.application.botPublic) this.permissionLevel = PermissionsLevels.OWNER; 14 | } 15 | 16 | public async run(msg: KlasaMessage) { 17 | return msg.sendLocale('commandInvite'); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/System/support.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { CommandOptions, KlasaMessage } from 'klasa'; 3 | import { SUPPORT_LINK } from '@root/config'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | 6 | @ApplyOptions({ 7 | description: lang => lang.tget('commandSupportDescription'), 8 | guarded: true 9 | }) 10 | export default class extends SteveCommand { 11 | 12 | public async init() { 13 | if (!SUPPORT_LINK) this.disable(); 14 | } 15 | 16 | public async run(msg: KlasaMessage) { 17 | return msg.channel.send(SUPPORT_LINK); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/Unit Conversions/lengthconvert.ts: -------------------------------------------------------------------------------- 1 | import { ApplyOptions, CreateResolvers } from '@skyra/decorators'; 2 | import { CommandOptions } from 'klasa'; 3 | import { UnitConversionCommand, lengthUnits } from '@lib/structures/commands/UnitConversionCommand'; 4 | @ApplyOptions({ 5 | aliases: ['length'], 6 | description: lang => lang.tget('commandLengthConvertDescription'), 7 | extendedHelp: lang => lang.tget('commandLengthConvertExtended') 8 | }) 9 | @CreateResolvers([ 10 | [ 11 | 'unit', 12 | (str, possible, msg) => { 13 | str = str.toLowerCase(); 14 | 15 | // @ts-expect-error 2345 16 | if (lengthUnits.includes(str)) return str; 17 | 18 | switch (str) { 19 | case 'millimeters': 20 | return 'mm'; 21 | case 'millimeter': 22 | return 'mm'; 23 | case 'centimeters': 24 | return 'cm'; 25 | case 'centimeter': 26 | return 'cm'; 27 | case 'meters': 28 | return 'm'; 29 | case 'meter': 30 | return 'm'; 31 | case 'inches': 32 | return 'in'; 33 | case 'inch': 34 | return 'in'; 35 | case 'feet': 36 | return 'ft'; 37 | case 'foot': 38 | return 'ft'; 39 | case 'miles': 40 | return 'mi'; 41 | case 'mile': 42 | return 'mi'; 43 | case 'kilometers': 44 | return 'km'; 45 | case 'kilometer': 46 | return 'km'; 47 | default: 48 | throw msg.language.tget('invalidUnit', str); 49 | } 50 | } 51 | ] 52 | ]) 53 | export default class extends UnitConversionCommand { } 54 | -------------------------------------------------------------------------------- /src/commands/Unit Conversions/massconvert.ts: -------------------------------------------------------------------------------- 1 | import { ApplyOptions, CreateResolvers } from '@skyra/decorators'; 2 | import { CommandOptions } from 'klasa'; 3 | import { UnitConversionCommand, massUnits } from '@lib/structures/commands/UnitConversionCommand'; 4 | 5 | @ApplyOptions({ 6 | aliases: ['mass'], 7 | description: lang => lang.tget('commandMassConvertDescription'), 8 | extendedHelp: lang => lang.tget('commandMassConvertExtended') 9 | }) 10 | @CreateResolvers([ 11 | [ 12 | 'unit', 13 | (str, possible, msg) => { 14 | str = str.toLowerCase(); 15 | 16 | // @ts-expect-error 2345 17 | if (massUnits.includes(str)) return str; 18 | 19 | switch (str) { 20 | case 'micrograms': 21 | return 'mcg'; 22 | case 'microgram': 23 | return 'mcg'; 24 | case 'milligrams': 25 | return 'mg'; 26 | case 'milligram': 27 | return 'mg'; 28 | case 'grams': 29 | return 'g'; 30 | case 'gram': 31 | return 'g'; 32 | case 'kilograms': 33 | return 'kg'; 34 | case 'kilogram': 35 | return 'kg'; 36 | case 'ounces': 37 | return 'oz'; 38 | case 'ounce': 39 | return 'oz'; 40 | case 'pounds': 41 | return 'lb'; 42 | case 'pound': 43 | return 'lb'; 44 | case 'metric tonnes': 45 | return 'mt'; 46 | case 'metric tonne': 47 | return 'mt'; 48 | case 'tons': 49 | return 't'; 50 | case 'ton': 51 | return 't'; 52 | default: 53 | throw msg.language.tget('invalidUnit', str); 54 | } 55 | } 56 | ] 57 | ]) 58 | export default class extends UnitConversionCommand { } 59 | -------------------------------------------------------------------------------- /src/commands/Unit Conversions/tempconvert.ts: -------------------------------------------------------------------------------- 1 | import { ApplyOptions, CreateResolvers } from '@skyra/decorators'; 2 | import { CommandOptions } from 'klasa'; 3 | import { UnitConversionCommand, temperatureUnits } from '@lib/structures/commands/UnitConversionCommand'; 4 | 5 | @ApplyOptions({ 6 | aliases: ['temp'], 7 | description: lang => lang.tget('commandTempConvertDescription'), 8 | extendedHelp: lang => lang.tget('commandTempConvertExtended') 9 | }) 10 | @CreateResolvers([ 11 | [ 12 | 'unit', 13 | (str, possible, msg) => { 14 | str = str.toUpperCase(); 15 | 16 | // @ts-expect-error 2345 17 | if (temperatureUnits.includes(str)) return str; 18 | if (str === 'CELSIUS' || str === 'FAHRENHEIT' || str === 'KELVIN' || str === 'RANKINE') return str[1]; 19 | 20 | throw msg.language.tget('invalidUnit', str); 21 | } 22 | ] 23 | ]) 24 | export default class extends UnitConversionCommand { } 25 | -------------------------------------------------------------------------------- /src/commands/User Settings/setembedcolor.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { UserSettings } from '@lib/types/settings/UserSettings'; 3 | import { ApplyOptions, CreateResolvers } from '@skyra/decorators'; 4 | import { Message } from 'discord.js'; 5 | import { CommandOptions, KlasaMessage } from 'klasa'; 6 | 7 | @ApplyOptions({ 8 | description: lang => lang.tget('commandSetEmbedColorDescription'), 9 | extendedHelp: lang => lang.tget('commandSetEmbedColorExtended'), 10 | usage: '' 11 | }) 12 | @CreateResolvers([ 13 | [ 14 | 'color', 15 | (str, possible, msg) => { 16 | if (/^#[0-9A-F]{6}$/i.test(str)) return str; 17 | throw msg.language.tget('resolverInvalidColor', str); 18 | } 19 | ] 20 | ]) 21 | export default class extends SteveCommand { 22 | 23 | public async run(msg: KlasaMessage, [color]: [string]): Promise { 24 | if (color === 'reset') { 25 | await msg.author.settings.reset(UserSettings.EmbedColor); 26 | } else if (color !== 'show') { 27 | await msg.author.settings.update(UserSettings.EmbedColor, color); 28 | } 29 | 30 | if (color === 'show') { 31 | return msg.author.settings.get(UserSettings.EmbedColor) === null 32 | ? msg.channel.send(msg.language.tget('commandSetEmbedColorShowNone')) 33 | : msg.channel.send(msg.language.tget('commandSetEmbedColorShow', msg.author.settings.get(UserSettings.EmbedColor))); 34 | } 35 | 36 | return color === 'reset' 37 | ? msg.channel.send(msg.language.tget('commandSetEmbedColorReset')) 38 | : msg.channel.send(msg.language.tget('commandSetEmbedColorSet', color)); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/events/channelCreate.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | import { DMChannel, GuildChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; 3 | import { LogColors } from '@lib/types/Enums'; 4 | import { getExecutor, floatPromise } from '@utils/util'; 5 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 6 | 7 | export default class extends Event { 8 | 9 | public run(channel: DMChannel | GuildChannel): void { 10 | if (channel instanceof DMChannel) return; 11 | 12 | if (channel.guild.settings.get(GuildSettings.LogEvents.ChannelCreate) as boolean) { 13 | const serverlog = channel.guild.channels.cache.get(channel.guild.settings.get(GuildSettings.Channels.Serverlog)); 14 | if (serverlog && serverlog.isGuildTextChannel()) floatPromise(this, this.handleLog(channel, serverlog)); 15 | } 16 | } 17 | 18 | private async handleLog(channel: GuildChannel, serverlog: TextChannel): Promise { 19 | const executor = await getExecutor(channel.guild, 'CHANNEL_CREATE'); 20 | 21 | const embedData = channel.guild.language.tget('eventChannelCreateEmbed'); 22 | 23 | const embed = new MessageEmbed() 24 | .setAuthor(executor.tag, executor.displayAvatarURL()) 25 | .setColor(LogColors.PURPLE) 26 | .setFooter(embedData.footer(channel.id)) 27 | .setTimestamp() 28 | .setTitle(embedData.title(channel.type, channel.name)); 29 | 30 | return serverlog.send(embed); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/events/channelDelete.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | import { DMChannel, GuildChannel, Message, MessageEmbed, TextChannel } from 'discord.js'; 3 | import { LogColors } from '@lib/types/Enums'; 4 | import { getExecutor, floatPromise } from '@utils/util'; 5 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 6 | 7 | export default class extends Event { 8 | 9 | public run(channel: DMChannel | GuildChannel): void { 10 | if (channel instanceof DMChannel) return; 11 | 12 | if (channel.guild.settings.get(GuildSettings.LogEvents.ChannelDelete) as boolean) { 13 | const serverlog = channel.guild.channels.cache.get(channel.guild.settings.get(GuildSettings.Channels.Serverlog)); 14 | if (serverlog && serverlog.isGuildTextChannel()) floatPromise(this, this.handleLog(channel, serverlog)); 15 | } 16 | } 17 | 18 | private async handleLog(channel: GuildChannel, serverlog: TextChannel): Promise { 19 | const executor = await getExecutor(channel.guild, 'CHANNEL_DELETE'); 20 | 21 | const embedData = channel.guild.language.tget('eventChannelDeleteEmbed'); 22 | 23 | const embed = new MessageEmbed() 24 | .setAuthor(executor.tag, executor.displayAvatarURL()) 25 | .setColor(LogColors.PURPLE) 26 | .setFooter(embedData.footer(channel.id)) 27 | .setTimestamp() 28 | .setTitle(embedData.title(channel.type, channel.name)); 29 | 30 | return serverlog.send(embed); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/events/channelUpdate.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Event } from 'klasa'; 3 | import { DMChannel, TextChannel, Message, Channel, GuildChannel, MessageEmbed } from 'discord.js'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { floatPromise, getExecutor } from '@utils/util'; 6 | import { LogColors } from '@lib/types/Enums'; 7 | 8 | export default class extends Event { 9 | 10 | public run(oldChannel: Channel, newChannel: Channel): void { 11 | if (newChannel instanceof DMChannel) return; 12 | 13 | if ((newChannel as GuildChannel).guild.settings.get(GuildSettings.LogEvents.ChannelUpdate) as boolean) { 14 | // eslint-disable-next-line max-len 15 | const serverlog = (newChannel as GuildChannel).guild.channels.cache.get((newChannel as GuildChannel).guild.settings.get(GuildSettings.Channels.Serverlog)); 16 | if (serverlog && serverlog.isGuildTextChannel()) { 17 | if ((oldChannel as GuildChannel).name !== (newChannel as GuildChannel).name) { 18 | floatPromise(this, this.logChannelNameChange(oldChannel as GuildChannel, newChannel as GuildChannel, serverlog)); 19 | } 20 | } 21 | } 22 | } 23 | 24 | private async logChannelNameChange(oldChannel: GuildChannel, newChannel: GuildChannel, serverlog: TextChannel): Promise { 25 | const executor = await getExecutor(newChannel.guild, 'CHANNEL_UPDATE'); 26 | 27 | const embedData = newChannel.guild.language.tget('eventChannelUpdateNameChangeEmbed'); 28 | 29 | const embed = new MessageEmbed() 30 | .setAuthor(executor.tag, executor.displayAvatarURL()) 31 | .setColor(LogColors.PURPLE) 32 | .setFooter(embedData.footer(newChannel.id)) 33 | .setTimestamp() 34 | .setTitle(embedData.title(oldChannel.name, newChannel.name, newChannel.type)); 35 | 36 | return serverlog.send(embed); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/events/core/coreGuildCreate.ts: -------------------------------------------------------------------------------- 1 | import { ClientSettings } from '@lib/types/settings/ClientSettings'; 2 | import { ApplyOptions } from '@skyra/decorators'; 3 | import { floatPromise } from '@utils/util'; 4 | import { Guild } from 'discord.js'; 5 | import { Event, EventOptions } from 'klasa'; 6 | 7 | @ApplyOptions({ 8 | event: 'guildCreate' 9 | }) 10 | export default class extends Event { 11 | 12 | public run(guild: Guild) { 13 | if (!guild.available) return; 14 | if (this.client.settings!.get(ClientSettings.GuildBlacklist).includes(guild.id)) { 15 | floatPromise(this, guild.leave()); 16 | this.client.emit('warn', `Blacklisted guild detected: ${guild.name} [${guild.id}]`); 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/events/core/coreGuildDelete.ts: -------------------------------------------------------------------------------- 1 | import { ApplyOptions } from '@skyra/decorators'; 2 | import { Guild } from 'discord.js'; 3 | import { Event, EventOptions } from 'klasa'; 4 | 5 | @ApplyOptions({ 6 | event: 'guildDelete' 7 | }) 8 | export default class extends Event { 9 | 10 | public run(guild: Guild) { 11 | if (this.client.ready && guild.available && !this.client.options.preserveSettings) guild.settings.destroy().catch(() => null); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/events/core/coreMessage.ts: -------------------------------------------------------------------------------- 1 | import { ApplyOptions } from '@skyra/decorators'; 2 | import { floatPromise } from '@utils/util'; 3 | import { Event, EventOptions, KlasaMessage } from 'klasa'; 4 | 5 | @ApplyOptions({ 6 | event: 'message' 7 | }) 8 | export default class extends Event { 9 | 10 | public run(msg: KlasaMessage) { 11 | if (this.client.ready) floatPromise(this, this.client.monitors.run(msg)); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/events/core/coreMessageDelete.ts: -------------------------------------------------------------------------------- 1 | import { ApplyOptions } from '@skyra/decorators'; 2 | import { floatPromise } from '@utils/util'; 3 | import { Event, EventOptions, KlasaMessage } from 'klasa'; 4 | 5 | @ApplyOptions({ 6 | event: 'messageDelete' 7 | }) 8 | export default class extends Event { 9 | 10 | public run(msg: KlasaMessage) { 11 | if (msg.command && msg.command.deletable) { 12 | for (const res of msg.responses) { 13 | floatPromise(this, res.delete()); 14 | } 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/events/core/coreMessageUpdate.ts: -------------------------------------------------------------------------------- 1 | import { Event, EventOptions, KlasaMessage } from 'klasa'; 2 | import { Message } from 'discord.js'; 3 | import { floatPromise } from '@utils/util'; 4 | import { ApplyOptions } from '@skyra/decorators'; 5 | 6 | @ApplyOptions({ 7 | event: 'messageUpdate' 8 | }) 9 | export default class extends Event { 10 | 11 | public run(old: Message, msg: Message) { 12 | if (this.client.ready && !old.partial && old.content !== msg.content) { 13 | floatPromise(this, this.client.monitors.run(msg as KlasaMessage)); 14 | } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/events/emojiCreate.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | import { Message, MessageEmbed, TextChannel, GuildEmoji } from 'discord.js'; 3 | import { LogColors } from '@lib/types/Enums'; 4 | import { getExecutor, floatPromise } from '@utils/util'; 5 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 6 | 7 | export default class extends Event { 8 | 9 | public run(emoji: GuildEmoji): void { 10 | if (emoji.guild.settings.get(GuildSettings.LogEvents.EmojiCreate) as boolean) { 11 | const serverlog = emoji.guild.channels.cache.get(emoji.guild.settings.get(GuildSettings.Channels.Serverlog)); 12 | if (serverlog && serverlog.isGuildTextChannel()) floatPromise(this, this.handleLog(emoji, serverlog)); 13 | } 14 | } 15 | 16 | private async handleLog(emoji: GuildEmoji, serverlog: TextChannel): Promise { 17 | const executor = await getExecutor(emoji.guild, 'EMOJI_CREATE'); 18 | 19 | const embedData = emoji.guild.language.tget('eventEmojiCreateEmbed'); 20 | 21 | const embed = new MessageEmbed() 22 | .setAuthor(executor.tag, executor.displayAvatarURL()) 23 | .setColor(LogColors.PINK) 24 | .setFooter(embedData.footer(emoji.id)) 25 | .setTimestamp() 26 | .setTitle(embedData.title(emoji.name)); 27 | 28 | return serverlog.send(embed); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/events/emojiDelete.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | import { Message, MessageEmbed, TextChannel, GuildEmoji } from 'discord.js'; 3 | import { LogColors } from '@lib/types/Enums'; 4 | import { getExecutor, floatPromise } from '@utils/util'; 5 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 6 | 7 | export default class extends Event { 8 | 9 | public run(emoji: GuildEmoji): void { 10 | if (emoji.guild.settings.get(GuildSettings.LogEvents.EmojiDelete) as boolean) { 11 | const serverlog = emoji.guild.channels.cache.get(emoji.guild.settings.get(GuildSettings.Channels.Serverlog)) as TextChannel; 12 | if (serverlog && serverlog.isGuildTextChannel()) floatPromise(this, this.handleLog(emoji, serverlog)); 13 | } 14 | } 15 | 16 | private async handleLog(emoji: GuildEmoji, serverlog: TextChannel): Promise { 17 | const executor = await getExecutor(emoji.guild, 'EMOJI_DELETE'); 18 | 19 | const embedData = emoji.guild.language.tget('eventEmojiDeleteEmbed'); 20 | 21 | const embed = new MessageEmbed() 22 | .setAuthor(executor.tag, executor.displayAvatarURL()) 23 | .setColor(LogColors.PINK) 24 | .setFooter(embedData.footer(emoji.id)) 25 | .setTimestamp() 26 | .setTitle(embedData.title(emoji.name)); 27 | 28 | return serverlog.send(embed); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/events/emojiUpdate.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | import { Message, MessageEmbed, TextChannel, GuildEmoji } from 'discord.js'; 3 | import { LogColors } from '@lib/types/Enums'; 4 | import { getExecutor, floatPromise } from '@utils/util'; 5 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 6 | 7 | export default class extends Event { 8 | 9 | public run(oldEmoji: GuildEmoji, newEmoji: GuildEmoji): void { 10 | if (newEmoji.guild.settings.get(GuildSettings.LogEvents.EmojiUpdate) as boolean) { 11 | const serverlog = newEmoji.guild.channels.cache.get(newEmoji.guild.settings.get(GuildSettings.Channels.Serverlog)); 12 | 13 | if (serverlog && serverlog.isGuildTextChannel()) { 14 | if (oldEmoji.name !== newEmoji.name) floatPromise(this, this.logEmojiNameChange(oldEmoji, newEmoji, serverlog)); 15 | } 16 | } 17 | } 18 | 19 | private async logEmojiNameChange(oldEmoji: GuildEmoji, newEmoji: GuildEmoji, serverlog: TextChannel): Promise { 20 | const executor = await getExecutor(newEmoji.guild, 'EMOJI_UPDATE'); 21 | 22 | const embedData = newEmoji.guild.language.tget('eventEmojiUpdateNameChangeEmbedx'); 23 | 24 | const embed = new MessageEmbed() 25 | .setAuthor(executor.tag, executor.displayAvatarURL()) 26 | .setColor(LogColors.PINK) 27 | .setFooter(embedData.footer(newEmoji.id)) 28 | .setTimestamp() 29 | .setTitle(embedData.title(oldEmoji.name, newEmoji.name, newEmoji.animated)); 30 | 31 | return serverlog.send(embed); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/events/error.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | 3 | export default class extends Event { 4 | 5 | public run(err: Error) { 6 | this.client.console.error(err); 7 | } 8 | 9 | // eslint-disable-next-line @typescript-eslint/require-await 10 | public async init() { 11 | if (!this.client.options.consoleEvents.error) this.disable(); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/events/guildBanAdd.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | import { Guild, User, TextChannel, Message, MessageEmbed } from 'discord.js'; 3 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 4 | import { floatPromise, getExecutor } from '@utils/util'; 5 | import { LogColors } from '@lib/types/Enums'; 6 | 7 | export default class extends Event { 8 | 9 | public run(guild: Guild, user: User): void { 10 | if (guild.settings.get(GuildSettings.LogEvents.GuildBanAdd) as boolean) { 11 | const memberlog = guild.channels.cache.get(guild.settings.get(GuildSettings.Channels.Memberlog)); 12 | if (memberlog && memberlog.isGuildTextChannel()) floatPromise(this, this.handleLog(guild, user, memberlog)); 13 | } 14 | } 15 | 16 | private async handleLog(guild: Guild, user: User, memberlog: TextChannel): Promise { 17 | const executor = await getExecutor(guild, 'MEMBER_BAN_ADD'); 18 | 19 | const embedData = guild.language.tget('eventGuildBanAddEmbed'); 20 | 21 | const embed = new MessageEmbed() 22 | .setAuthor(user.tag, user.displayAvatarURL()) 23 | .setColor(LogColors.RED) 24 | .setFooter(embedData.footer(user.id)) 25 | .setTimestamp() 26 | .setTitle(embedData.title(executor.tag)); 27 | 28 | return memberlog.send(embed); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/events/guildBanRemove.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | import { Guild, User, TextChannel, Message, MessageEmbed } from 'discord.js'; 3 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 4 | import { floatPromise, getExecutor } from '@utils/util'; 5 | import { LogColors } from '@lib/types/Enums'; 6 | 7 | export default class extends Event { 8 | 9 | public run(guild: Guild, user: User): void { 10 | if (guild.settings.get(GuildSettings.LogEvents.GuildBanRemove) as boolean) { 11 | const memberlog = guild.channels.cache.get(guild.settings.get(GuildSettings.Channels.Memberlog)); 12 | if (memberlog && memberlog.isGuildTextChannel()) floatPromise(this, this.handleLog(guild, user, memberlog)); 13 | } 14 | } 15 | 16 | private async handleLog(guild: Guild, user: User, memberlog: TextChannel): Promise { 17 | const executor = await getExecutor(guild, 'MEMBER_BAN_REMOVE'); 18 | 19 | const embedData = guild.language.tget('eventGuildBanRemoveEmbed'); 20 | 21 | const embed = new MessageEmbed() 22 | .setAuthor(user.tag, user.displayAvatarURL()) 23 | .setColor(LogColors.RED) 24 | .setFooter(embedData.footer(user.id)) 25 | .setTimestamp() 26 | .setTitle(embedData.title(executor.tag)); 27 | 28 | return memberlog.send(embed); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/events/guildMemberAdd.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | import { GuildMember, Message, MessageEmbed, TextChannel } from 'discord.js'; 3 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 4 | import { friendlyDuration, getExecutor, floatPromise } from '@utils/util'; 5 | import { LogColors } from '@lib/types/Enums'; 6 | import { TrustedRoleSetting } from './guildMemberUpdate'; 7 | 8 | export default class extends Event { 9 | 10 | public run(member: GuildMember): void { 11 | if (member.guild.settings.get(GuildSettings.LogEvents.GuildMemberAdd) as boolean) { 12 | const memberlog = member.guild.channels.cache.get(member.guild.settings.get(GuildSettings.Channels.Memberlog)); 13 | if (memberlog && memberlog.isGuildTextChannel()) floatPromise(this, this.handleLog(member, memberlog)); 14 | } 15 | 16 | this.handleTrustedRole(member); 17 | } 18 | 19 | private async handleLog(member: GuildMember, memberlog: TextChannel): Promise { 20 | const accountCreatedTime = friendlyDuration(Date.now() - member.user.createdTimestamp); 21 | 22 | const embedData = member.guild.language.tget('eventGuildMemberAddEmbed'); 23 | 24 | const embed = new MessageEmbed() 25 | .addFields( 26 | { 27 | name: embedData.fieldTitles.human, 28 | value: embedData.fieldValues.accountAge(accountCreatedTime) 29 | } 30 | ) 31 | .setAuthor(member.user.tag, member.user.displayAvatarURL()) 32 | .setColor(LogColors.TURQUOISE) 33 | .setFooter(embedData.footer(member.id)) 34 | .setTimestamp(); 35 | 36 | if (member.user.bot) { 37 | const executor = await getExecutor(member.guild, 'BOT_ADD'); 38 | embed.fields[0].name = embedData.fieldTitles.bot(executor.tag); 39 | } 40 | 41 | return memberlog.send(embed); 42 | } 43 | 44 | private handleTrustedRole(member: GuildMember): void { 45 | const { guild } = member; 46 | const trustedRole = guild.roles.cache.get(guild.settings.get(GuildSettings.Roles.Trusted)); 47 | const trustedRoleSetting: TrustedRoleSetting = guild.settings.get(GuildSettings.Roles.GiveTrustedRoleOn); 48 | 49 | if (trustedRole && trustedRoleSetting === 'join') floatPromise(this, member.roles.add(trustedRole)); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/events/guildMemberRemove.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | import { GuildMember, Message, TextChannel, MessageEmbed } from 'discord.js'; 3 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 4 | import { LogColors } from '@lib/types/Enums'; 5 | import { friendlyDuration, floatPromise } from '@utils/util'; 6 | 7 | export default class extends Event { 8 | 9 | public run(member: GuildMember): void { 10 | if (member.guild.settings.get(GuildSettings.LogEvents.GuildMemberRemove) as boolean) { 11 | const memberlog = member.guild.channels.cache.get(member.guild.settings.get(GuildSettings.Channels.Memberlog)); 12 | if (memberlog && memberlog.isGuildTextChannel()) floatPromise(this, this.handleLog(member, memberlog)); 13 | } 14 | } 15 | 16 | private async handleLog(member: GuildMember, memberlog: TextChannel): Promise { 17 | if (!member.joinedTimestamp) member = await member.guild.members.fetch(member.user); 18 | 19 | let memberRoles = member.roles.cache.filter(r => r.id !== member.guild.id).map(r => r.name).join(', '); 20 | memberRoles = memberRoles.length > 0 ? memberRoles : member.guild.language.tget('none'); 21 | 22 | const embedData = member.guild.language.tget('eventGuildMemberRemoveEmbed'); 23 | 24 | const embed = new MessageEmbed() 25 | .addFields( 26 | { 27 | name: embedData.fieldTitles.joinDate(member.user.bot), 28 | value: embedData.fieldValues.joinDate(friendlyDuration(Date.now() - member.joinedTimestamp!)), 29 | inline: true 30 | }, 31 | { name: embedData.fieldTitles.roles, value: memberRoles, inline: true } 32 | ) 33 | .setAuthor(member.user.tag, member.user.displayAvatarURL()) 34 | .setColor(LogColors.TURQUOISE) 35 | .setFooter(embedData.footer(member.id)) 36 | .setTimestamp(); 37 | 38 | return memberlog.send(embed); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/events/interactionCreate.ts: -------------------------------------------------------------------------------- 1 | /* If you're self-hosting Steve, you'll have to register these slash commands yourself! There's a tool to do so at 2 | * https://github.com/tuataria/slash, although you might have to modify it to fit your specific needs. 3 | */ 4 | import { Event } from 'klasa'; 5 | import { ApplicationCommands } from '@lib/types/Enums'; 6 | import { inspect } from 'util'; 7 | import { APIApplicationCommandInteraction } from 'discord-api-types/payloads/v8'; 8 | 9 | export default class extends Event { 10 | 11 | public run(interaction: APIApplicationCommandInteraction) { 12 | switch (interaction.data.name) { 13 | case 'animal': 14 | this.client.emit(ApplicationCommands.Animal, interaction); 15 | break; 16 | case 'assign': 17 | this.client.emit(ApplicationCommands.Assign, interaction); 18 | break; 19 | case 'avatar': 20 | this.client.emit(ApplicationCommands.Avatar, interaction); 21 | break; 22 | case 'convert': 23 | this.client.emit(ApplicationCommands.Convert, interaction); 24 | break; 25 | case 'dftba': 26 | this.client.emit(ApplicationCommands.Dftba, interaction); 27 | break; 28 | case 'roleinfo': 29 | this.client.emit(ApplicationCommands.RoleInfo, interaction); 30 | break; 31 | case 'rps': 32 | this.client.emit(ApplicationCommands.Rps, interaction); 33 | break; 34 | case 'serverinfo': 35 | this.client.emit(ApplicationCommands.ServerInfo, interaction); 36 | break; 37 | case 'whois': 38 | this.client.emit(ApplicationCommands.Whois, interaction); 39 | break; 40 | default: 41 | this.client.console.log(inspect(interaction, { depth: 4 })); 42 | } 43 | } 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /src/events/interactions/applicationCommands/avatar.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationCommand } from '@lib/structures/events/ApplicationCommand'; 2 | import { APIApplicationCommandInteraction, APIApplicationCommandInteractionDataOptionWithValues, APIInteractionApplicationCommandCallbackData } from 'discord-api-types/payloads/v8'; 3 | import { MessageEmbed } from 'discord.js'; 4 | 5 | export default class extends ApplicationCommand { 6 | 7 | public async handle(interaction: APIApplicationCommandInteraction): Promise { 8 | const user = await this.client.users.fetch(interaction.data.resolved!.users![( 9 | interaction.data.options![0] as APIApplicationCommandInteractionDataOptionWithValues).value as string].id); 10 | 11 | const embed = new MessageEmbed() 12 | .setImage(user.displayAvatarURL({ dynamic: true })) 13 | .toJSON(); 14 | 15 | return { embeds: [embed] }; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/events/interactions/applicationCommands/convert.ts: -------------------------------------------------------------------------------- 1 | import { ConversionUnit } from '@lib/structures/commands/UnitConversionCommand'; 2 | import { ApplicationCommand } from '@lib/structures/events/ApplicationCommand'; 3 | import convert = require('convert-units'); 4 | import { APIApplicationCommandInteraction, APIInteractionApplicationCommandCallbackData, ApplicationCommandInteractionDataOptionSubCommand } from 'discord-api-types/payloads/v8'; 5 | import { MessageEmbed } from 'discord.js'; 6 | 7 | export default class extends ApplicationCommand { 8 | 9 | public async handle(interaction: APIApplicationCommandInteraction): Promise { 10 | const amount = (interaction.data.options![0] as ApplicationCommandInteractionDataOptionSubCommand).options![0].value as number; 11 | 12 | const firstUnit = (interaction.data.options![0] as ApplicationCommandInteractionDataOptionSubCommand) 13 | .options![1].value as ConversionUnit; 14 | 15 | const secondUnit = (interaction.data.options![0] as ApplicationCommandInteractionDataOptionSubCommand) 16 | .options![2].value as ConversionUnit; 17 | 18 | // eslint-disable-next-line newline-per-chained-call 19 | const convertedValue = Number(convert(amount).from(firstUnit).to(secondUnit).toFixed(2)); 20 | 21 | const embed = new MessageEmbed() 22 | .addFields( 23 | { name: convert().describe(firstUnit).plural, value: amount, inline: true }, 24 | { name: convert().describe(secondUnit).plural, value: convertedValue, inline: true } 25 | ) 26 | .setColor(0x71adcf) 27 | .toJSON(); 28 | 29 | return { embeds: [embed] }; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/events/interactions/applicationCommands/dftba.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationCommand } from '@lib/structures/events/ApplicationCommand'; 2 | import { APIInteractionApplicationCommandCallbackData } from 'discord-api-types/payloads/v8'; 3 | 4 | export default class extends ApplicationCommand { 5 | 6 | public async handle(): Promise { 7 | return { content: this.client.languages.default.randomDftba }; 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/events/interactions/applicationCommands/rps.ts: -------------------------------------------------------------------------------- 1 | import { checkWinner, chooseRandomPlay, rpsPlay } from '@lib/util/RockPaperScissors'; 2 | import { ApplicationCommand } from '@lib/structures/events/ApplicationCommand'; 3 | import { APIApplicationCommandInteraction, APIApplicationCommandInteractionDataOptionWithValues, APIInteractionApplicationCommandCallbackData } from 'discord-api-types/payloads/v8'; 4 | 5 | export default class extends ApplicationCommand { 6 | 7 | public async handle(interaction: APIApplicationCommandInteraction): Promise { 8 | const playerPlay = (interaction.data.options![0] as APIApplicationCommandInteractionDataOptionWithValues).value as rpsPlay; 9 | const stevePlay = chooseRandomPlay(); 10 | 11 | return { content: this.client.languages.default.tget('commandRockPaperScissorsWinner', playerPlay, stevePlay, checkWinner(stevePlay, playerPlay)) }; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/events/interactions/applicationCommands/serverinfo.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationCommand, ApplicationCommandOptions } from '@lib/structures/events/ApplicationCommand'; 2 | import { ApplyOptions } from '@skyra/decorators'; 3 | import { formatDate, friendlyDuration } from '@utils/util'; 4 | import { APIApplicationCommandGuildInteraction, APIInteractionApplicationCommandCallbackData } from 'discord-api-types/payloads/v8'; 5 | import { MessageEmbed } from 'discord.js'; 6 | 7 | @ApplyOptions({ 8 | guildOnly: true 9 | }) 10 | export default class extends ApplicationCommand { 11 | 12 | public async handle(interaction: APIApplicationCommandGuildInteraction): Promise { 13 | const guild = this.client.guilds.cache.get(interaction.guild_id)!; 14 | 15 | const embedData = guild.language.tget('commandServerInfoEmbed'); 16 | 17 | const embed = new MessageEmbed() 18 | .addFields([ 19 | { name: embedData.fieldTitles.totalMembers, value: guild.memberCount, inline: true }, 20 | { name: embedData.fieldTitles.bots, value: guild.members.cache.filter(m => m.user.bot).size, inline: true }, 21 | { name: embedData.fieldTitles.textChannels, value: guild.channels.cache.filter(c => c.type === 'text').size, inline: true }, 22 | { name: embedData.fieldTitles.voiceChannels, value: guild.channels.cache.filter(c => c.type === 'voice').size, inline: true }, 23 | { name: embedData.fieldTitles.roles, value: guild.roles.cache.size, inline: true }, 24 | { name: embedData.fieldTitles.emojis, value: guild.emojis.cache.size, inline: true } 25 | ]) 26 | .setAuthor(guild.name, guild.iconURL()!) 27 | .setFooter(embedData.footer(formatDate(guild.createdTimestamp), friendlyDuration(Date.now() - guild.createdTimestamp))) 28 | .setTimestamp() 29 | .toJSON(); 30 | 31 | return { embeds: [embed] }; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/events/inviteCreate.ts: -------------------------------------------------------------------------------- 1 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 2 | import { floatPromise } from '@utils/util'; 3 | import { Invite, Message, MessageEmbed, NewsChannel, TextChannel } from 'discord.js'; 4 | import { Event } from 'klasa'; 5 | import { LogColors } from '../lib/types/Enums'; 6 | 7 | export default class extends Event { 8 | 9 | public run(invite: Invite): void { 10 | if (!invite.guild) return; 11 | 12 | if (invite.guild.settings.get(GuildSettings.LogEvents.InviteCreate) as boolean) { 13 | const serverlog = invite.guild.channels.cache.get(invite.guild.settings.get(GuildSettings.Channels.Serverlog)); 14 | if (serverlog && serverlog.isGuildTextChannel()) floatPromise(this, this.handleLog(invite, serverlog)); 15 | } 16 | } 17 | 18 | private handleLog(invite: Invite, serverlog: TextChannel | NewsChannel): Promise { 19 | const embedData = invite.guild!.language.tget('eventInviteCreateEmbed'); 20 | 21 | const embed = new MessageEmbed() 22 | .setColor(LogColors.BLUE) 23 | .setFooter(embedData.footer(invite.code)) 24 | .setTimestamp() 25 | .setTitle(embedData.title(invite.channel.name)); 26 | 27 | if (invite.inviter) embed.setAuthor(invite.inviter.tag, invite.inviter.displayAvatarURL()); 28 | 29 | return serverlog.send(embed); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/events/inviteDelete.ts: -------------------------------------------------------------------------------- 1 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 2 | import { floatPromise } from '@utils/util'; 3 | import { Invite, Message, MessageEmbed, NewsChannel, TextChannel } from 'discord.js'; 4 | import { Event } from 'klasa'; 5 | import { LogColors } from '../lib/types/Enums'; 6 | 7 | export default class extends Event { 8 | 9 | public run(invite: Invite): void { 10 | if (!invite.guild) return; 11 | 12 | if (invite.guild.settings.get(GuildSettings.LogEvents.InviteDelete) as boolean) { 13 | const serverlog = invite.guild.channels.cache.get(invite.guild.settings.get(GuildSettings.Channels.Serverlog)); 14 | if (serverlog && serverlog.isGuildTextChannel()) floatPromise(this, this.handleLog(invite, serverlog)); 15 | } 16 | } 17 | 18 | private handleLog(invite: Invite, serverlog: TextChannel | NewsChannel): Promise { 19 | const embedData = invite.guild!.language.tget('eventInviteDeleteEmbed'); 20 | 21 | const embed = new MessageEmbed() 22 | .setColor(LogColors.BLUE) 23 | .setFooter(embedData.footer(invite.code)) 24 | .setTimestamp() 25 | .setTitle(embedData.title(invite.channel.name)); 26 | 27 | if (invite.inviter) embed.setAuthor(invite.inviter.tag, invite.inviter.displayAvatarURL()); 28 | 29 | return serverlog.send(embed); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/events/messageDelete.ts: -------------------------------------------------------------------------------- 1 | import { Event, KlasaMessage } from 'klasa'; 2 | import { MessageEmbed, DMChannel, TextChannel, Message } from 'discord.js'; 3 | import { floatPromise, friendlyDuration } from '@utils/util'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { LogColors } from '@lib/types/Enums'; 6 | 7 | export default class extends Event { 8 | 9 | public run(msg: KlasaMessage): void { 10 | if (msg.type === 'PINS_ADD' || msg.channel instanceof DMChannel) return; 11 | 12 | if (msg.guild!.settings.get(GuildSettings.LogEvents.MessageDelete) as boolean) { 13 | const serverlog = msg.guild!.channels.cache.get(msg.guild!.settings.get(GuildSettings.Channels.Serverlog)); 14 | if (serverlog && serverlog.isGuildTextChannel()) floatPromise(this, this.handleLog(msg, serverlog)); 15 | } 16 | } 17 | 18 | private async handleLog(msg: KlasaMessage, serverlog: TextChannel): Promise { 19 | if (msg.channel instanceof DMChannel) return; // way to just delcare that msg.channel is a TextChannel instead of a redundant if? 20 | 21 | const msgContent = msg.content.length < 1024 && msg.content.length > 0 22 | ? msg.content 23 | : msg.guild!.language.tget('eventMessageDeleteUnableToDisplay'); 24 | 25 | const parent = msg.channel.parent 26 | ? msg.channel.parent.name 27 | : msg.guild!.language.tget('noParentCategory'); 28 | 29 | const msgSentTime = friendlyDuration(Date.now() - msg.createdTimestamp); 30 | 31 | const embedData = msg.guild!.language.tget('eventMessageDeleteEmbed'); 32 | 33 | const embed = new MessageEmbed() 34 | .addFields( 35 | { name: embedData.fieldTitles.channel(msg.channel.name, parent), value: msgContent } 36 | ) 37 | .setAuthor(msg.author.tag, msg.author.displayAvatarURL()) 38 | .setColor(LogColors.REDORANGE) 39 | .setFooter(embedData.footer(msg.id, msgSentTime)) 40 | .setTimestamp(); 41 | 42 | return serverlog.send(embed); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/events/messageDeleteBulk.ts: -------------------------------------------------------------------------------- 1 | import { Collection, Message, DMChannel, TextChannel, MessageEmbed } from 'discord.js'; 2 | import { Event } from 'klasa'; 3 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 4 | import { floatPromise, getExecutor } from '@utils/util'; 5 | import { LogColors } from '@lib/types/Enums'; 6 | 7 | export default class extends Event { 8 | 9 | public run(msgs: MessageCollection): void { 10 | if (msgs.first()!.channel instanceof DMChannel) return; 11 | 12 | if (msgs.first()!.guild!.settings.get(GuildSettings.LogEvents.MessageDeleteBulk) as boolean) { 13 | const serverlog = msgs.first()!.guild!.channels.cache.get(msgs.first()!.guild!.settings.get(GuildSettings.Channels.Serverlog)); 14 | if (serverlog && serverlog.isGuildTextChannel()) floatPromise(this, this.handleLog(msgs, serverlog)); 15 | } 16 | } 17 | 18 | private async handleLog(msgs: MessageCollection, serverlog: TextChannel): Promise { 19 | if (msgs.first()!.channel instanceof DMChannel) return; 20 | 21 | const guild = msgs.first()!.guild!; 22 | const channel = msgs.first()!.channel as TextChannel; 23 | 24 | const parent = channel.parent ? channel.parent.name : guild.language.tget('noParentCategory'); 25 | const executor = await getExecutor(guild, 'MESSAGE_BULK_DELETE'); 26 | 27 | const embedData = guild.language.tget('eventMessageDeleteBulkEmbed'); 28 | 29 | const embed = new MessageEmbed() 30 | .setAuthor(executor.tag, executor.displayAvatarURL()) 31 | .setColor(LogColors.REDORANGE) 32 | .setFooter(embedData.footer(channel.id)) 33 | .setTimestamp() 34 | .setTitle(embedData.title(msgs.size, channel.name, parent)); 35 | 36 | return serverlog.send(embed); 37 | } 38 | 39 | } 40 | 41 | type MessageCollection = Collection; 42 | -------------------------------------------------------------------------------- /src/events/onceReady.ts: -------------------------------------------------------------------------------- 1 | import { Event, EventOptions, util } from 'klasa'; 2 | import { Team } from 'discord.js'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | let retries = 0; 5 | 6 | @ApplyOptions({ 7 | event: 'ready', 8 | once: true 9 | }) 10 | export default class extends Event { 11 | 12 | public async run(): Promise { 13 | try { 14 | await this.client.fetchApplication(); 15 | } catch (err) { 16 | if (++retries === 3) return process.exit(); 17 | this.client.emit('warning', `Unable to fetchApplication at this time, waiting 5 seconds and retrying. Retries left: ${retries - 3}`); 18 | await util.sleep(5000); 19 | return this.run(); 20 | } 21 | 22 | if (!this.client.options.owners.length) { 23 | if (this.client.application.owner instanceof Team) { 24 | this.client.options.owners.push(...this.client.application.owner.members.keys()); 25 | } else { 26 | this.client.options.owners.push(this.client.application.owner!.id); 27 | } 28 | } 29 | 30 | this.client.mentionPrefix = new RegExp(`^<@!?${this.client.user!.id}>`); 31 | 32 | this.client.settings = this.client.gateways.clientStorage.get(this.client.user!.id, true); 33 | // Added for consistency with other datastores, Client#clients does not exist 34 | // @ts-expect-error 2341 2345 35 | this.client.gateways.clientStorage.cache.set(this.client.user!.id, this.client); 36 | await this.client.gateways.sync(); 37 | 38 | // Init all the pieces 39 | await Promise.all(this.client.pieceStores.filter(store => !['providers', 'extendables'].includes(store.name)).map(store => store.init())); 40 | // @ts-expect-error 2341 41 | util.initClean(this.client); 42 | this.client.ready = true; 43 | 44 | // Init the schedule 45 | await this.client.schedule.init(); 46 | 47 | if (this.client.options.readyMessage !== null) { 48 | this.client.emit('log', util.isFunction(this.client.options.readyMessage) ? this.client.options.readyMessage(this.client) : this.client.options.readyMessage); 49 | } 50 | 51 | return this.client.emit('klasaReady'); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/events/raw.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | import { CustomEvents } from '@lib/types/Enums'; 3 | 4 | export default class extends Event { 5 | 6 | public run(packet: RawEventPacket) { 7 | if (packet.t === 'INTERACTION_CREATE') this.client.emit(CustomEvents.InteractionCreate, packet.d); 8 | } 9 | 10 | } 11 | 12 | interface RawEventPacket { 13 | t: string | null; 14 | s: number | null; 15 | op: number; 16 | d: any; 17 | } 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/events/roleCreate.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | import { Message, MessageEmbed, TextChannel, Role } from 'discord.js'; 3 | import { LogColors } from '@lib/types/Enums'; 4 | import { getExecutor, floatPromise } from '@utils/util'; 5 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 6 | 7 | export default class extends Event { 8 | 9 | public run(role: Role): void { 10 | if (role.guild.settings.get(GuildSettings.LogEvents.RoleCreate) as boolean) { 11 | const serverlog = role.guild.channels.cache.get(role.guild.settings.get(GuildSettings.Channels.Serverlog)); 12 | if (serverlog && serverlog.isGuildTextChannel()) floatPromise(this, this.handleLog(role, serverlog)); 13 | } 14 | } 15 | 16 | private async handleLog(role: Role, serverlog: TextChannel): Promise { 17 | const executor = await getExecutor(role.guild, 'ROLE_CREATE'); 18 | 19 | const embedData = role.guild.language.tget('eventRoleCreateEmbed'); 20 | 21 | const embed = new MessageEmbed() 22 | .setAuthor(executor.tag, executor.displayAvatarURL()) 23 | .setColor(LogColors.YELLOW) 24 | .setFooter(embedData.footer(role.id)) 25 | .setTimestamp() 26 | .setTitle(embedData.title(role.name)); 27 | 28 | return serverlog.send(embed); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/events/roleDelete.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | import { Message, MessageEmbed, TextChannel, Role } from 'discord.js'; 3 | import { LogColors } from '@lib/types/Enums'; 4 | import { getExecutor, floatPromise } from '@utils/util'; 5 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 6 | import { RoleAlias } from '../commands/Role Aliases/rolealias'; 7 | export default class extends Event { 8 | 9 | public run(role: Role): void { 10 | if (role.guild.settings.get(GuildSettings.LogEvents.RoleDelete) as boolean) { 11 | const serverlog = role.guild.channels.cache.get(role.guild.settings.get(GuildSettings.Channels.Serverlog)); 12 | if (serverlog && serverlog.isGuildTextChannel()) floatPromise(this, this.handleLog(role, serverlog)); 13 | } 14 | 15 | const aliases: RoleAlias[] = role.guild.settings.get(GuildSettings.RoleAliases); 16 | if (aliases.some(a => a.role === role.id)) floatPromise(this, this.handleAliases(aliases, role)); 17 | 18 | if (role.isAssignable) floatPromise(this, this.handleSelfAssign(role)); 19 | } 20 | 21 | private async handleAliases(aliases: RoleAlias[], role: Role) { 22 | const matches = aliases.filter(a => a.role === role.id); 23 | 24 | for (const match of matches) { 25 | await role.guild.settings.update(GuildSettings.RoleAliases, match, { action: 'remove' }); 26 | } 27 | } 28 | 29 | private async handleLog(role: Role, serverlog: TextChannel): Promise { 30 | const executor = await getExecutor(role.guild, 'ROLE_DELETE'); 31 | 32 | const embedData = role.guild.language.tget('eventRoleDeleteEmbed'); 33 | 34 | const embed = new MessageEmbed() 35 | .setAuthor(executor.tag, executor.displayAvatarURL()) 36 | .setColor(LogColors.YELLOW) 37 | .setFooter(embedData.footer(role.id)) 38 | .setTimestamp() 39 | .setTitle(embedData.title(role.name)); 40 | 41 | return serverlog.send(embed); 42 | } 43 | 44 | private async handleSelfAssign(role: Role) { 45 | await role.guild.settings.update(GuildSettings.Roles.Assignable, role.id, role.guild.id); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/events/roleUpdate.ts: -------------------------------------------------------------------------------- 1 | import { Event } from 'klasa'; 2 | import { Role, Message, TextChannel, MessageEmbed } from 'discord.js'; 3 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 4 | import { floatPromise, getExecutor } from '@utils/util'; 5 | import { LogColors } from '@lib/types/Enums'; 6 | 7 | export default class extends Event { 8 | 9 | public run(oldRole: Role, newRole: Role): void { 10 | if (newRole.guild.settings.get(GuildSettings.LogEvents.RoleUpdate) as boolean) { 11 | const serverlog = newRole.guild.channels.cache.get(newRole.guild.settings.get(GuildSettings.Channels.Serverlog)) as TextChannel; 12 | 13 | if (serverlog && serverlog.isGuildTextChannel()) { 14 | if (oldRole.name !== newRole.name) floatPromise(this, this.logRoleNameChange(oldRole, newRole, serverlog)); 15 | } 16 | } 17 | } 18 | 19 | private async logRoleNameChange(oldRole: Role, newRole: Role, serverlog: TextChannel): Promise { 20 | const executor = await getExecutor(newRole.guild, 'ROLE_UPDATE'); 21 | 22 | const embedData = newRole.guild.language.tget('eventRoleUpdateNameChangeEmbed'); 23 | 24 | const embed = new MessageEmbed() 25 | .setAuthor(executor.tag, executor.displayAvatarURL()) 26 | .setColor(LogColors.YELLOW) 27 | .setFooter(embedData.footer(newRole.id)) 28 | .setTimestamp() 29 | .setTitle(embedData.title(oldRole.name, newRole.name)); 30 | 31 | return serverlog.send(embed); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/extendables/Channel.ts: -------------------------------------------------------------------------------- 1 | import { Extendable, ExtendableOptions } from 'klasa'; 2 | import { Channel, TextChannel } from 'discord.js'; 3 | import { ApplyOptions } from '@skyra/decorators'; 4 | 5 | 6 | @ApplyOptions({ 7 | appliesTo: [Channel] 8 | }) 9 | export default class extends Extendable { 10 | 11 | public isGuildTextChannel(this: Channel): this is TextChannel { 12 | return this instanceof TextChannel; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/extendables/GuildMember.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Extendable, ExtendableOptions } from 'klasa'; 3 | import { GuildMember } from 'discord.js'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | 7 | @ApplyOptions({ 8 | appliesTo: [GuildMember] 9 | }) 10 | export default class extends Extendable { 11 | 12 | // @ts-expect-error 2784 13 | public get canUseSelfAssign(this: GuildMember) { 14 | let canAssign = true; 15 | 16 | const trustedRoleID = this.guild.settings.get(GuildSettings.Roles.Trusted); 17 | const trustedRoleRequirement = this.guild.settings.get(GuildSettings.Roles.RequireTrustedRoleForSelfAssign) as boolean; 18 | 19 | if (trustedRoleID && trustedRoleRequirement && !this.roles.cache.has(trustedRoleID)) canAssign = false; 20 | 21 | return canAssign; 22 | } 23 | 24 | // @ts-expect-error 2784 25 | public get isAdmin(this: GuildMember) { 26 | return this.roles.cache.has(this.guild.settings.get(GuildSettings.Roles.Administrator)); 27 | } 28 | 29 | // @ts-expect-error 2784 30 | public get isMod(this: GuildMember) { 31 | return this.roles.cache.has(this.guild.settings.get(GuildSettings.Roles.Moderator)); 32 | } 33 | 34 | // @ts-expect-error 2784 35 | public get isStaff(this: GuildMember) { 36 | return (this.isAdmin || this.isMod); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/extendables/Language.ts: -------------------------------------------------------------------------------- 1 | import { ComplexLanguageKeys, SimpleLanguageKeys } from '@lib/types/Augments'; 2 | import { ApplyOptions } from '@skyra/decorators'; 3 | import { Extendable, ExtendableOptions, Language, LanguageKeys } from 'klasa'; 4 | 5 | @ApplyOptions({ 6 | appliesTo: [Language] 7 | }) 8 | export default class extends Extendable { 9 | 10 | public tget(term: T): LanguageKeys[T]; 11 | 12 | public tget(term: T, ...args: Parameters): ReturnType 13 | 14 | public tget(this: Language, key: string, ...args: readonly unknown[]): unknown { 15 | return this.get(key, ...args); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/extendables/Role.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Extendable, ExtendableOptions } from 'klasa'; 3 | import { Role } from 'discord.js'; 4 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | 7 | @ApplyOptions({ 8 | appliesTo: [Role] 9 | }) 10 | export default class extends Extendable { 11 | 12 | // @ts-expect-error 2784 13 | public get isAssignable(this: Role): boolean { 14 | const assignables = this.guild.settings.get(GuildSettings.Roles.Assignable) as string[]; 15 | return assignables.includes(this.id); 16 | } 17 | 18 | // @ts-expect-error 2784 19 | public get isRestrictd(this: Role): boolean { 20 | const restricted = this.guild.settings.get(GuildSettings.Roles.Restricted) as string[]; 21 | return restricted.includes(this.id); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/extendables/Schedule.ts: -------------------------------------------------------------------------------- 1 | import { ApplyOptions } from '@skyra/decorators'; 2 | import { Extendable, ExtendableOptions, Schedule, ScheduledTask } from 'klasa'; 3 | 4 | @ApplyOptions({ 5 | appliesTo: [Schedule] 6 | }) 7 | export default class extends Extendable { 8 | 9 | // eslint-disable-next-line max-len 10 | public createModerationTask(this: Schedule, taskName: ModerationTask, duration: number, taskData: ModerationTaskData): Promise { 11 | return this.create(taskName, Date.now() + duration, { 12 | catchUp: true, 13 | data: { target: taskData.targetID, guild: taskData.guildID } 14 | }); 15 | } 16 | 17 | public createReminder(this: Schedule, duration: number, userID: string, content: string, channelID: string): Promise { 18 | return this.create('reminder', Date.now() + duration, { 19 | catchUp: true, 20 | data: { userID, content, channelID } 21 | }); 22 | } 23 | 24 | public getUserReminders(this: Schedule, userID: string): Reminder[] { 25 | const filter = (task: ScheduledTask) => { 26 | const id = task.data.userID ?? task.data.user; 27 | return task.taskName === 'reminder' && id === userID; 28 | }; 29 | return this.tasks.filter(filter); 30 | } 31 | 32 | } 33 | 34 | export type ModerationTask = 'unmute' | 'undeafen' | 'unban'; 35 | 36 | export interface ModerationTaskData { 37 | targetID: string; 38 | guildID: string; 39 | } 40 | 41 | export interface ReminderData { 42 | userID: string; 43 | content: string; 44 | channelID: string; 45 | } 46 | 47 | export interface OldReminderData { 48 | user: string; 49 | content: string; 50 | channel: string; 51 | } 52 | 53 | export interface Reminder extends ScheduledTask { 54 | data: ReminderData | OldReminderData; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/finalizers/commandCooldown.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { Finalizer, FinalizerStore, KlasaMessage, RateLimitManager } from 'klasa'; 3 | 4 | export default class extends Finalizer { 5 | 6 | public constructor(store: FinalizerStore, file: string[], directory: string) { 7 | super(store, file, directory); 8 | // @ts-ignore 2339 9 | this.cooldowns = new WeakMap(); 10 | } 11 | 12 | public run(msg: KlasaMessage, cmd: SteveCommand) { 13 | if (cmd.cooldown <= 0 || this.client.owners.has(msg.author)) return; 14 | 15 | try { 16 | this.getCooldown(msg, cmd).drip(); 17 | } catch (err) { 18 | this.client.emit('error', `${msg.author.username}[${msg.author.id}] has exceeded the RateLimit for ${msg.command}`); 19 | } 20 | } 21 | 22 | public getCooldown(msg: KlasaMessage, cmd: SteveCommand) { 23 | // @ts-ignore 2339 24 | let cooldownManager = this.cooldowns.get(cmd); 25 | if (!cooldownManager) { 26 | cooldownManager = new RateLimitManager(cmd.bucket, cmd.cooldown * 1000); 27 | // @ts-ignore 2339 28 | this.cooldowns.set(cmd, cooldownManager); 29 | } 30 | return cooldownManager.acquire(msg.guild ? msg[cmd.cooldownLevel]!.id : msg.author.id); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/inhibitors/cooldown.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { ApplyOptions } from '@skyra/decorators'; 3 | import { Inhibitor, InhibitorOptions, KlasaMessage } from 'klasa'; 4 | 5 | @ApplyOptions({ 6 | spamProtection: true 7 | }) 8 | export default class extends Inhibitor { 9 | 10 | public run(msg: KlasaMessage, cmd: SteveCommand) { 11 | if (this.client.owners.has(msg.author) || cmd.cooldown <= 0) return; 12 | 13 | // eslint-disable-next-line @typescript-eslint/init-declarations 14 | let existing; 15 | 16 | try { 17 | // @ts-expect-error 2339 18 | existing = this.client.finalizers.get('commandCooldown').getCooldown(msg, cmd); 19 | } catch (err) { 20 | return; 21 | } 22 | 23 | if (existing && existing.limited) throw msg.language.tget('inhibitorCooldown', Math.ceil(existing.remainingTime / 1000), cmd.cooldownLevel !== 'author'); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/inhibitors/disabled.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 3 | import { Inhibitor, KlasaMessage } from 'klasa'; 4 | 5 | module.exports = class extends Inhibitor { 6 | 7 | public run(msg: KlasaMessage, cmd: SteveCommand) { 8 | if (!cmd.enabled) throw msg.language.tget('inhibitorDisabledGlobal'); 9 | if (msg.guild && msg.guildSettings.get(GuildSettings.DisabledCommands).includes(cmd.name)) throw msg.language.tget('inhibitorDisabledGuild'); 10 | } 11 | 12 | }; 13 | -------------------------------------------------------------------------------- /src/inhibitors/hidden.ts: -------------------------------------------------------------------------------- 1 | import { GuildMessage } from '@lib/types/Messages'; 2 | import { Command, Inhibitor } from 'klasa'; 3 | 4 | export default class extends Inhibitor { 5 | 6 | public run(msg: GuildMessage, cmd: Command) { 7 | return cmd.hidden && msg.command !== cmd && !this.client.owners.has(msg.author); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/inhibitors/ignoredChannel.ts: -------------------------------------------------------------------------------- 1 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 2 | import { Inhibitor, KlasaMessage } from 'klasa'; 3 | 4 | export default class extends Inhibitor { 5 | 6 | public run(msg: KlasaMessage) { 7 | if (!msg.guild) return; 8 | 9 | const ignoredChannels = msg.guild.settings.get(GuildSettings.IgnoredChannels) as string[]; 10 | if (ignoredChannels.includes(msg.channel.id) && !msg.member!.isStaff) return true; 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/inhibitors/ignoredRole.ts: -------------------------------------------------------------------------------- 1 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 2 | import { ApplyOptions } from '@skyra/decorators'; 3 | import { Inhibitor, InhibitorOptions, KlasaMessage } from 'klasa'; 4 | 5 | @ApplyOptions({ 6 | spamProtection: true 7 | }) 8 | export default class extends Inhibitor { 9 | 10 | public run(msg: KlasaMessage) { 11 | if (!msg.guild) return; 12 | 13 | const ignoredRoles = msg.guild.settings.get(GuildSettings.IgnoredRoles) as string[]; 14 | 15 | for (const [id] of msg.member!.roles.cache) { 16 | if (ignoredRoles.includes(id) && !msg.member!.isStaff) return true; 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/inhibitors/missingBotPermissions.ts: -------------------------------------------------------------------------------- 1 | import { GuildMessage } from '@lib/types/Messages'; 2 | import { BitField, Permissions, PermissionString } from 'discord.js'; 3 | import { Command, Inhibitor, InhibitorStore, util } from 'klasa'; 4 | 5 | export default class extends Inhibitor { 6 | 7 | public friendlyPerms: Record; 8 | public impliedPermissions: Readonly>; 9 | 10 | public constructor(store: InhibitorStore, file: string[], directory: string) { 11 | super(store, file, directory); 12 | this.impliedPermissions = new Permissions(515136).freeze(); 13 | // VIEW_CHANNEL, SEND_MESSAGES, SEND_TTS_MESSAGES, EMBED_LINKS, ATTACH_FILES, 14 | // READ_MESSAGE_HISTORY, MENTION_EVERYONE, USE_EXTERNAL_EMOJIS, ADD_REACTIONS 15 | 16 | this.friendlyPerms = Object.keys(Permissions.FLAGS).reduce((obj, key) => { 17 | // @ts-ignore 7053 18 | obj[key] = util.toTitleCase(key.split('_').join(' ')); 19 | return obj; 20 | }, {}); 21 | } 22 | 23 | public run(msg: GuildMessage, cmd: Command) { 24 | const missing = msg.channel.type === 'text' 25 | ? msg.channel.permissionsFor(this.client.user!)!.missing(cmd.requiredPermissions, false) 26 | : this.impliedPermissions.missing(cmd.requiredPermissions, false); 27 | 28 | if (missing.length) throw msg.language.tget('inhibitorMissingBotPerms', missing.map(key => this.friendlyPerms[key]).join(', ')); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/inhibitors/nsfw.ts: -------------------------------------------------------------------------------- 1 | import { GuildMessage } from '@lib/types/Messages'; 2 | import { Command, Inhibitor } from 'klasa'; 3 | 4 | export default class extends Inhibitor { 5 | 6 | public run(msg: GuildMessage, cmd: Command) { 7 | if (cmd.nsfw && !msg.channel.nsfw) throw msg.language.tget('inhibitorNsfw'); 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/inhibitors/permissions.ts: -------------------------------------------------------------------------------- 1 | import { GuildMessage } from '@lib/types/Messages'; 2 | import { Command, Inhibitor } from 'klasa'; 3 | 4 | export default class extends Inhibitor { 5 | 6 | public async run(msg: GuildMessage, cmd: Command) { 7 | const { broke, permission } = await this.client.permissionLevels.run(msg, cmd.permissionLevel); 8 | if (!permission) throw broke ? msg.language.tget('inhibitorPermissions', cmd.name) : true; 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/inhibitors/pingProtection.ts: -------------------------------------------------------------------------------- 1 | import { ApplyOptions } from '@skyra/decorators'; 2 | import { Inhibitor, InhibitorOptions, KlasaMessage } from 'klasa'; 3 | 4 | @ApplyOptions({ 5 | spamProtection: true 6 | }) 7 | export default class extends Inhibitor { 8 | 9 | public run(msg: KlasaMessage): void { 10 | if (msg.channel.type === 'dm') return; 11 | 12 | if (msg.content.match(/<@&\d*>/)) { 13 | throw msg.language.tget('inhibitorPingProtectionEveryone'); 14 | } 15 | 16 | if (msg.content.match(/@everyone/)) { 17 | throw msg.language.tget('inhibitorPingprotectionEveryone'); 18 | } 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/inhibitors/requiredSettings.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { Inhibitor, KlasaMessage } from 'klasa'; 3 | 4 | export default class extends Inhibitor { 5 | 6 | public run(msg: KlasaMessage, cmd: SteveCommand) { 7 | if (!cmd.requiredSettings.length || msg.channel.type !== 'text') return; 8 | // eslint-disable-next-line eqeqeq, no-eq-null 9 | const requiredSettings = cmd.requiredSettings.filter(setting => msg.guild!.settings.get(setting) == null); 10 | if (requiredSettings.length) throw msg.language.tget('inhibitorRequiredSettings', requiredSettings); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/inhibitors/runIn.ts: -------------------------------------------------------------------------------- 1 | import { SteveCommand } from '@lib/structures/commands/SteveCommand'; 2 | import { Inhibitor, KlasaMessage } from 'klasa'; 3 | 4 | export default class extends Inhibitor { 5 | 6 | public run(msg: KlasaMessage, cmd: SteveCommand) { 7 | if (!cmd.runIn.length) throw msg.language.tget('inhibitorRunInNone', cmd.name); 8 | if (!cmd.runIn.includes(msg.channel.type)) throw msg.language.tget('inhibitorRunIn', cmd.runIn.join(', ')); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/inhibitors/slowmode.ts: -------------------------------------------------------------------------------- 1 | import { GuildMessage } from '@lib/types/Messages'; 2 | import { ApplyOptions } from '@skyra/decorators'; 3 | import { Inhibitor, InhibitorOptions, InhibitorStore, RateLimitManager } from 'klasa'; 4 | 5 | @ApplyOptions({ 6 | spamProtection: true 7 | }) 8 | export default class extends Inhibitor { 9 | 10 | public aggressive: boolean; 11 | public slowmode: RateLimitManager; 12 | 13 | public constructor(store: InhibitorStore, file: string[], directory: string) { 14 | super(store, file, directory); 15 | 16 | this.aggressive = this.client.options.slowmodeAggressive; 17 | this.slowmode = new RateLimitManager(1, this.client.options.slowmode); 18 | 19 | if (!this.client.options.slowmode) this.disable(); 20 | } 21 | 22 | public run(msg: GuildMessage) { 23 | if (this.client.owners.has(msg.author)) return; 24 | 25 | const rateLimit = this.slowmode.acquire(msg.author.id); 26 | 27 | try { 28 | rateLimit.drip(); 29 | } catch (err) { 30 | if (this.aggressive) rateLimit.resetTime(); 31 | throw true; 32 | } 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/lib/SteveClient.ts: -------------------------------------------------------------------------------- 1 | import { KlasaClient, Colors, KlasaClientOptions, PermissionLevels } from 'klasa'; 2 | import permissionLevels from '@lib/setup/PermissionLevels'; 3 | import { Node as Lavalink } from 'lavalink'; 4 | import { LAVALINK_ENABLE } from '@root/config'; 5 | import { Guild } from 'discord.js'; 6 | 7 | import '@lib/schemas/Guild'; 8 | import '@lib/schemas/User'; 9 | import '@lib/extensions/SteveGuild'; 10 | 11 | 12 | export class SteveClient extends KlasaClient { 13 | 14 | public lavalink: Lavalink | null; 15 | 16 | 17 | public constructor(options: KlasaClientOptions = {}) { 18 | super(options); 19 | 20 | this.lavalink = LAVALINK_ENABLE 21 | ? new Lavalink({ 22 | send: (guildID: string, packet: Record) => { 23 | const guild: Guild | undefined = this.guilds.cache.get(guildID); 24 | if (guild) this.ws.shards.get(guild.shardID)!.send(packet); 25 | else throw new Error('Attempted to send a packet on the wrong shard!'); 26 | }, 27 | ...this.options.lavalink 28 | }) 29 | : null; 30 | 31 | this.permissionLevels = permissionLevels as PermissionLevels; 32 | 33 | if (this.lavalink !== null) { 34 | this.lavalink.once('open', () => this.console.verbose(`${new Colors({ text: 'magenta' }).format('[LAVALINK]')} Connected.`)); 35 | } 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/extensions/SteveGuild.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-invalid-this */ 2 | import { Structures } from 'discord.js'; 3 | import { ModerationManager } from '@lib/structures/ModerationManager'; 4 | 5 | export class SteveGuild extends Structures.get('Guild') { 6 | 7 | public readonly moderation: ModerationManager = new ModerationManager(this); 8 | 9 | } 10 | 11 | Structures.extend('Guild', () => SteveGuild); 12 | -------------------------------------------------------------------------------- /src/lib/schemas/User.ts: -------------------------------------------------------------------------------- 1 | import { Client } from 'klasa'; 2 | 3 | export default Client.defaultUserSchema 4 | .add('embedColor', 'Color'); 5 | -------------------------------------------------------------------------------- /src/lib/setup/PermissionLevels.ts: -------------------------------------------------------------------------------- 1 | import { Client, PermissionLevels } from 'klasa'; 2 | import { PermissionsLevels } from '@lib/types/Enums'; 3 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 4 | import { Permissions } from 'discord.js'; 5 | 6 | export default Client.defaultPermissionLevels = new PermissionLevels() 7 | .add(PermissionsLevels.EVERYONE, () => true) 8 | .add(PermissionsLevels.TRUSTED, msg => msg.member 9 | ? msg.guild!.settings.get(GuildSettings.Roles.Trusted) 10 | ? msg.member.roles.cache.has(msg.guild!.settings.get(GuildSettings.Roles.Trusted)) 11 | : false 12 | : false, { fetch: true }) 13 | .add(PermissionsLevels.MODERATOR, msg => msg.member 14 | ? msg.guild!.settings.get(GuildSettings.Roles.Moderator) 15 | ? msg.member.roles.cache.has(msg.guild!.settings.get(GuildSettings.Roles.Moderator)) 16 | : msg.member.permissions.has(Permissions.FLAGS.BAN_MEMBERS) 17 | : false, { fetch: true }) 18 | .add(PermissionsLevels.ADMINISTRATOR, msg => msg.member 19 | ? msg.guild!.settings.get(GuildSettings.Roles.Administrator) 20 | ? msg.member.roles.cache.has(msg.guild!.settings.get(GuildSettings.Roles.Administrator)) 21 | : msg.member.permissions.has(Permissions.FLAGS.MANAGE_GUILD) 22 | // eslint-disable-next-line quote-props 23 | : false, { break: true, fetch: true }) 24 | .add(PermissionsLevels.OWNER, ({ author, client }) => client.owners.has(author)); 25 | -------------------------------------------------------------------------------- /src/lib/structures/commands/SteveCommand.ts: -------------------------------------------------------------------------------- 1 | import { Command, KlasaMessage, util, CommandStore, CommandOptions } from 'klasa'; 2 | 3 | export abstract class SteveCommand extends Command { 4 | 5 | protected constructor(store: CommandStore, file: string[], directory: string, options: CommandOptions) { 6 | super(store, file, directory, util.mergeDefault({ usageDelim: '|' }, options)); 7 | } 8 | 9 | /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ 10 | public run(msg: KlasaMessage, _params: any[]): any { return msg; } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/structures/commands/UnitConversionCommand.ts: -------------------------------------------------------------------------------- 1 | import { MessageEmbed } from 'discord.js'; 2 | import { CommandOptions, CommandStore, KlasaMessage, util } from 'klasa'; 3 | import { SteveCommand } from './SteveCommand'; 4 | import convert = require('convert-units'); 5 | 6 | export const lengthUnits = ['mm', 'cm', 'm', 'in', 'ft-us', 'ft', 'mi', 'km'] as const; 7 | export const massUnits = ['mcg', 'mg', 'g', 'kg', 'oz', 'lb', 'mt', 't'] as const; 8 | export const temperatureUnits = ['C', 'F', 'K', 'R'] as const; 9 | 10 | type LengthUnitTuple = typeof lengthUnits; 11 | type MassUnitTuple = typeof massUnits; 12 | type TemperatureUnitTuple = typeof temperatureUnits; 13 | 14 | type LengthUnit = LengthUnitTuple[number]; 15 | type MassUnit = MassUnitTuple[number]; 16 | type TemperatureUnit = TemperatureUnitTuple[number]; 17 | 18 | export type ConversionUnit = LengthUnit | MassUnit | TemperatureUnit; 19 | 20 | export abstract class UnitConversionCommand extends SteveCommand { 21 | 22 | public lengthUnits = lengthUnits; 23 | public massUnits = massUnits; 24 | public temperatureUnits = temperatureUnits; 25 | 26 | protected constructor(store: CommandStore, file: string[], directory: string, options: CommandOptions) { 27 | super(store, file, directory, util.mergeDefault({ usage: ' ' }, options)); 28 | } 29 | 30 | public async run(msg: KlasaMessage, [num, firstUnit, secondUnit]: [number, ConversionUnit, ConversionUnit]) { 31 | // eslint-disable-next-line newline-per-chained-call 32 | const convertedValue = Number(convert(num).from(firstUnit).to(secondUnit).toFixed(2)); 33 | 34 | const embed = new MessageEmbed() 35 | .addFields( 36 | { name: convert().describe(firstUnit).plural, value: num, inline: true }, 37 | { name: convert().describe(secondUnit).plural, value: convertedValue, inline: true } 38 | ) 39 | .setColor(0x71adcf); 40 | 41 | return msg.channel.send(embed); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/types/Augments.d.ts: -------------------------------------------------------------------------------- 1 | import { BaseNodeOptions, Node as Lavalink } from 'lavalink'; 2 | import { ModerationManager } from '@lib/structures/ModerationManager'; 3 | import { LanguageKeys } from 'klasa'; 4 | import { ModerationTask, ModerationTaskData, Reminder } from '../../extendables/Schedule'; 5 | import { TextChannel } from 'discord.js'; 6 | import { PermissionStrings } from '@skyra/decorators'; 7 | 8 | declare module 'discord.js' { 9 | interface Channel { 10 | isGuildTextChannel(): this is TextChannel; 11 | } 12 | interface Guild { 13 | readonly moderation: ModerationManager; 14 | } 15 | 16 | interface GuildMember { 17 | canUseSelfAssign: boolean; 18 | isAdmin: boolean; 19 | isMod: boolean; 20 | isStaff: boolean; 21 | } 22 | 23 | interface Role { 24 | isAssignable: boolean; 25 | isRestricted: boolean; 26 | } 27 | } 28 | 29 | declare module 'klasa' { 30 | interface KlasaClient { 31 | emit(eventName: string, ...args: any[]): boolean; 32 | lavalink: Lavalink | null; 33 | } 34 | 35 | interface KlasaClientOptions { 36 | lavalink?: BaseNodeOptions; 37 | } 38 | 39 | interface Language { 40 | caseActions: any; 41 | dftba: string[]; 42 | PERMISSIONS: PermissionStrings; 43 | randomDftba: string; 44 | randomLoadingMessage: string; 45 | tget(term: T): LanguageKeys[T]; 46 | tget(term: T, ...args: Parameters): ReturnType; 47 | } 48 | 49 | interface Schedule { 50 | createModerationTask(taskName: ModerationTask, duration: number, taskData: ModerationTaskData): Promise; 51 | createReminder(duration: number, userID: string, content: string, channelID: string): Promise; 52 | getUserReminders(userID: string): Reminder[]; 53 | } 54 | } 55 | 56 | interface Fn { 57 | (...args: readonly any[]): unknown; 58 | } 59 | 60 | export type SimpleLanguageKeys = { 61 | [K in keyof LanguageKeys]: LanguageKeys[K] extends Fn ? never : K; 62 | }[keyof LanguageKeys]; 63 | 64 | export type ComplexLanguageKeys = { 65 | [K in keyof LanguageKeys]: LanguageKeys[K] extends Fn ? K : never; 66 | }[keyof LanguageKeys]; 67 | -------------------------------------------------------------------------------- /src/lib/types/Enums.d.ts: -------------------------------------------------------------------------------- 1 | export const enum ApplicationCommands { 2 | Animal = 'animal', 3 | Assign = 'assign', 4 | Avatar = 'avatar', 5 | Convert = 'convert', 6 | Dftba = 'dftba', 7 | RoleInfo = 'roleinfo', 8 | Rps = 'rps', 9 | ServerInfo = 'serverinfo', 10 | Whois = 'whois' 11 | } 12 | 13 | export const enum CustomEvents { 14 | InteractionCreate = 'interactionCreate' 15 | } 16 | 17 | export const enum Emojis { 18 | LOADING = '', 19 | MINUS = '<:minus:693881833007611996>', 20 | PLUS = '<:plus:693881818675675136>', 21 | REDX = '<:red_x:688365693485187072>' 22 | } 23 | 24 | export const enum ImageAssets { 25 | AlarmClock = 'https://stevebot.xyz/steveassets/alarmclock.png', 26 | Cat = 'https://stevebot.xyz/steveassets/animals/cat.png', 27 | DiscordLogo = 'https://discord.com/assets/2c21aeda16de354ba5334551a883b481.png', 28 | Dog = 'https://stevebot.xyz/steveassets/animals/dog.png', 29 | Fox = 'https://stevebot.xyz/steveassets/animals/fox.png', 30 | NodeJs = 'https://stevebot.xyz/steveassets/nodejs.png' 31 | } 32 | 33 | export const enum LogColors { 34 | BLUE = 0x3a34eb, 35 | PINK = 0xfc0ce8, 36 | PURPLE = 0xb942f4, 37 | RED = 0xe83535, 38 | REDORANGE = 0xf49e42, 39 | TURQUOISE = 0x61e3f9, 40 | YELLOW = 0xffd944 41 | } 42 | 43 | export const enum PermissionsLevels { 44 | EVERYONE = 0, 45 | TRUSTED = 1, 46 | MODERATOR = 7, 47 | ADMINISTRATOR = 8, 48 | OWNER = 10 49 | } 50 | 51 | export const enum Time { 52 | MILLISECOND = 1, 53 | SECOND = 1 * 1000, 54 | MINUTE = 1 * 1000 * 60, 55 | HOUR = 1 * 1000 * 60 * 60, 56 | DAY = 1 * 1000 * 60 * 60 * 24 57 | } 58 | -------------------------------------------------------------------------------- /src/lib/types/Messages.d.ts: -------------------------------------------------------------------------------- 1 | import { KlasaMessage } from 'klasa'; 2 | import { Guild, GuildMember, NewsChannel, TextChannel } from 'discord.js'; 3 | 4 | export interface GuildMessage extends KlasaMessage { 5 | channel: TextChannel | NewsChannel; 6 | readonly guild: Guild; 7 | readonly member: GuildMember; 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/types/settings/ClientSettings.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-namespace */ 2 | export namespace ClientSettings { 3 | 4 | export const GuildBlacklist = 'guildBlacklist'; 5 | export const Schedules = 'schedules'; 6 | export const UserBlacklist = 'userBlacklist'; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /src/lib/types/settings/UserSettings.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-namespace */ 2 | export namespace UserSettings { 3 | 4 | export const EmbedColor = 'embedColor'; 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/lib/util/HelpBuilder.ts: -------------------------------------------------------------------------------- 1 | export class HelpBuilder { 2 | 3 | public examples: string | null = null; 4 | public explainedUsage: string | null = null; 5 | public reminder: string | null = null; 6 | 7 | public setExamples(text: string): HelpBuilder { 8 | this.examples = text; 9 | return this; 10 | } 11 | 12 | public setExplainedUsage(text: string): HelpBuilder { 13 | this.explainedUsage = text; 14 | return this; 15 | } 16 | 17 | public setReminder(text: string): HelpBuilder { 18 | this.reminder = text; 19 | return this; 20 | } 21 | 22 | public display(name: string, options: HelpBuilderDisplayOptions, multiline = false): string { 23 | const { extendedHelp, explainedUsage = [], examples = [], reminder } = options; 24 | const output: string[] = []; 25 | 26 | // Extended help 27 | if (extendedHelp) { 28 | output.push(HelpBuilder.resolveMultilineString(extendedHelp, multiline), ''); 29 | } 30 | 31 | // Examples 32 | if (examples.length) { 33 | output.push(this.examples!, ...examples.map(example => `→ Steve, ${name}${example ? ` *${example}*` : ''}`), ''); 34 | } else { 35 | output.push(this.examples!, `→ Steve, ${name}`, ''); 36 | } 37 | 38 | // Explained usage 39 | if (explainedUsage.length) { 40 | output.push(this.explainedUsage!, ...explainedUsage.map(([arg, desc]) => `→ **${arg}**: ${desc}`), ''); 41 | } 42 | 43 | // Reminder 44 | if (reminder) { 45 | output.push(this.reminder!, HelpBuilder.resolveMultilineString(reminder, multiline)); 46 | } 47 | 48 | return output.join('\n'); 49 | } 50 | 51 | public static resolveMultilineString(str: string | string[], multiline: boolean): string { 52 | return Array.isArray(str) 53 | ? HelpBuilder.resolveMultilineString(str.join(multiline ? '\n' : ' '), multiline) 54 | : str.split('\n').map(line => line.trim()).join(multiline ? '\n' : ' '); 55 | } 56 | 57 | } 58 | 59 | interface HelpBuilderDisplayOptions { 60 | extendedHelp?: string[] | string; 61 | explainedUsage?: Array<[string, string]>; 62 | examples?: string[]; 63 | reminder?: string[] | string; 64 | } 65 | -------------------------------------------------------------------------------- /src/lib/util/RockPaperScissors.ts: -------------------------------------------------------------------------------- 1 | const plays = ['rock', 'paper', 'scissors'] as const; 2 | 3 | /** 4 | * 5 | * @param stevePlay rock, paper, or scissors 6 | * @param playerPlay rock, paper, or scissors 7 | * @returns 0 for a draw, -1 for a bot win, 1 for a player win 8 | */ 9 | export function checkWinner(stevePlay: rpsPlay, playerPlay: rpsPlay) { 10 | const stevePlayNum = plays.indexOf(stevePlay); 11 | const playerPlayNum = plays.indexOf(playerPlay); 12 | 13 | if (stevePlayNum === playerPlayNum) return 0; 14 | 15 | const adjacent = Math.abs(stevePlayNum - playerPlayNum) === 1; 16 | 17 | if (stevePlayNum > playerPlayNum) { 18 | return adjacent 19 | ? -1 20 | : 1; 21 | } 22 | 23 | return adjacent 24 | ? 1 25 | : -1; 26 | } 27 | 28 | export function chooseRandomPlay() { 29 | return plays[Math.floor(Math.random() * plays.length)]; 30 | } 31 | 32 | export type rpsPlay = typeof plays[number]; 33 | -------------------------------------------------------------------------------- /src/lib/util/UserInfo.ts: -------------------------------------------------------------------------------- 1 | import { Time } from '@lib/types/Enums'; 2 | import { Guild } from 'discord.js'; 3 | import { formatDate, friendlyDuration } from './util'; 4 | 5 | export function getJoinDateString(guild: Guild, timestamp: number): string { 6 | const timeSinceJoin = Date.now() - timestamp; 7 | const joinDate = formatDate(timestamp); 8 | 9 | if (timeSinceJoin > Time.DAY && timeSinceJoin < Time.HOUR * 31) { 10 | return guild.language.tget('commandWhoIsJoinedGuildHours', Math.floor(timeSinceJoin / Time.HOUR), joinDate); 11 | } 12 | 13 | return guild.language.tget('commandWhoIsDate', friendlyDuration(timeSinceJoin), joinDate); 14 | } 15 | 16 | export function userAccountCreated(guild: Guild, timestamp: number) { 17 | return guild.language.tget('commandWhoIsDate', friendlyDuration(Date.now() - timestamp), formatDate(timestamp)); 18 | } 19 | -------------------------------------------------------------------------------- /src/monitors/mentionSpam.ts: -------------------------------------------------------------------------------- 1 | import { Monitor, MonitorOptions, KlasaMessage } from 'klasa'; 2 | import { TextChannel } from 'discord.js'; 3 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 4 | import { floatPromise } from '@utils/util'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | 7 | @ApplyOptions({ 8 | ignoreEdits: false, 9 | ignoreOthers: false 10 | }) 11 | export default class extends Monitor { 12 | 13 | public async run(msg: KlasaMessage): Promise { 14 | if (msg.channel instanceof TextChannel) { 15 | const maxMentions = msg.guild!.settings.get(GuildSettings.MaxMentions); 16 | if (msg.mentions.users.size > maxMentions) { 17 | const spamMsg = await msg.delete(); 18 | 19 | floatPromise(this, spamMsg.reply(msg.guild!.language.tget('monitorMentionSpamMax', maxMentions))); 20 | } 21 | } 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/monitors/pinsAdd.ts: -------------------------------------------------------------------------------- 1 | import { Monitor, MonitorOptions, KlasaMessage } from 'klasa'; 2 | import { ApplyOptions } from '@skyra/decorators'; 3 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 4 | 5 | @ApplyOptions({ 6 | allowedTypes: ['PINS_ADD'], 7 | ignoreOthers: false 8 | }) 9 | export default class extends Monitor { 10 | 11 | public async run(msg: KlasaMessage) { 12 | if (msg.guild) { 13 | const deletePinMessages = msg.guild!.settings.get(GuildSettings.DeletePinMessages) as boolean; 14 | if (deletePinMessages) return msg.delete(); 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/monitors/wordBlacklist.ts: -------------------------------------------------------------------------------- 1 | import { Monitor, MonitorOptions, KlasaMessage } from 'klasa'; 2 | import { Message } from 'discord.js'; 3 | import { GuildSettings } from '@lib/types/settings/GuildSettings'; 4 | import { floatPromise } from '@utils/util'; 5 | import { ApplyOptions } from '@skyra/decorators'; 6 | 7 | @ApplyOptions({ 8 | ignoreEdits: false, 9 | ignoreOthers: false 10 | }) 11 | export default class extends Monitor { 12 | 13 | public async run(msg: KlasaMessage): Promise { 14 | if (!msg.guild || !msg.guild.settings.get(GuildSettings.WordBlacklist.Enabled)) return; 15 | 16 | const blacklist = msg.guild.settings.get(GuildSettings.WordBlacklist.List) as string[]; 17 | const filtered = await this.filter(msg.content, blacklist); 18 | 19 | if (filtered) { 20 | return msg.delete().then(() => { 21 | floatPromise(this, msg.channel.send(msg.guild!.language.tget('monitorWordBlacklistFiltered'))); 22 | }); 23 | } 24 | } 25 | 26 | /* TODO: make this not suck to read */ 27 | private filter(string: string, list: string[]): Promise { 28 | return new Promise((resolve, reject) => { 29 | if (typeof string !== 'string') reject(new Error('word blacklist filter function "string" param should be a string')); 30 | string = string.replace(/[.,/#!$%^&*;:{}=\-_`~()@+=?"\u206a]/g, ''); 31 | 32 | for (let i = 0; i < list.length; i++) { 33 | let regex = ''; 34 | 35 | for (let j = 0; j < list[i].length; j++) { 36 | regex = j < 1 ? `${regex}${list[i][j]} *` : j > 1 ? `${regex} *${list[i][j]}` : regex + list[i][j]; 37 | } 38 | const finishedRegex = new RegExp(regex, 'i'); 39 | 40 | const match = string.match(finishedRegex); 41 | if (match === null) continue; 42 | 43 | if ((match.index !== 0 && string[match.index! - 1] !== ' ') 44 | || (match.index! + match[0].length !== string.length && string[match.index! + match[0].length] !== ' ')) resolve(false); 45 | 46 | resolve(true); 47 | } 48 | }); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/serializers/color.ts: -------------------------------------------------------------------------------- 1 | import { Language, SchemaPiece, Serializer } from 'klasa'; 2 | 3 | export default class extends Serializer { 4 | 5 | // eslint-disable-next-line @typescript-eslint/require-await 6 | public async deserialize(data: string, piece: SchemaPiece, lang: Language): Promise { 7 | if (/^#[0-9A-F]{6}$/i.test(data)) return data; 8 | throw lang.tget('serializerColorInvalidHex', data); 9 | } 10 | 11 | public serialize(value: string): string { 12 | return value; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/serializers/trustedrolesetting.ts: -------------------------------------------------------------------------------- 1 | import { Language, SchemaPiece, Serializer } from 'klasa'; 2 | export default class extends Serializer { 3 | 4 | // eslint-disable-next-line @typescript-eslint/require-await 5 | public async deserialize(data: string, piece: SchemaPiece, lang: Language): Promise { 6 | if (data === 'none' || data === 'join' || data === 'role') return data; 7 | throw lang.tget('serializerTrustedRoleSettingInvalidSetting', data); 8 | } 9 | 10 | public serialize(value: string): string { 11 | return value; 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/tasks/reminder.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Task } from 'klasa'; 3 | import { Message } from 'discord.js'; 4 | import { OldReminderData, ReminderData } from '../extendables/Schedule'; 5 | 6 | export default class extends Task { 7 | 8 | public async run(data: ReminderData | OldReminderData): Promise { 9 | // @ts-expect-error 2339 10 | const _channel = this.client.channels.cache.get(data.channelID ?? data.channel); 11 | // @ts-expect-error 2339 12 | const _user = await this.client.users.fetch(data.userID ?? data.user); 13 | 14 | if (_channel && _channel.isText()) return _channel.send(`${_user}, here's the reminder you asked for: **${data.content}**`); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/tasks/unban.ts: -------------------------------------------------------------------------------- 1 | import { Task } from 'klasa'; 2 | import { ModerationTaskData } from '../extendables/Schedule'; 3 | 4 | export default class extends Task { 5 | 6 | public async run({ targetID, guildID }: ModerationTaskData): Promise { 7 | const guild = this.client.guilds.cache.get(guildID); 8 | const target = await this.client.users.fetch(targetID); 9 | 10 | if (guild && target) { 11 | await guild.moderation.unban(target, guild.language.tget('moderationNoReason')); 12 | } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/tasks/undeafen.ts: -------------------------------------------------------------------------------- 1 | import { Task } from 'klasa'; 2 | import { ModerationTaskData } from '../extendables/Schedule'; 3 | 4 | export default class extends Task { 5 | 6 | public async run({ targetID, guildID }: ModerationTaskData): Promise { 7 | const guild = this.client.guilds.cache.get(guildID); 8 | const targetUser = await this.client.users.fetch(targetID); 9 | 10 | if (guild && targetUser) { 11 | const member = await guild.members.fetch(targetUser); 12 | 13 | if (member && guild.moderation.deafenedRole && member.roles.cache.has(guild.moderation.deafenedRole.id)) { 14 | await guild.moderation.undeafen(member, guild.language.tget('moderationNoReason')); 15 | } 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/tasks/unmute.ts: -------------------------------------------------------------------------------- 1 | import { Task } from 'klasa'; 2 | import { ModerationTaskData } from '../extendables/Schedule'; 3 | 4 | export default class extends Task { 5 | 6 | public async run({ targetID, guildID }: ModerationTaskData): Promise { 7 | const guild = this.client.guilds.cache.get(guildID); 8 | const targetUser = await this.client.users.fetch(targetID); 9 | 10 | if (guild && targetUser) { 11 | const member = await guild.members.fetch(targetUser); 12 | 13 | if (member && guild.moderation.mutedRole && member.roles.cache.has(guild.moderation.mutedRole.id)) { 14 | await guild.moderation.unmute(member, guild.language.tget('moderationNoReason')); 15 | } 16 | } 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /tests/MockClient.ts: -------------------------------------------------------------------------------- 1 | import { Client } from 'klasa'; 2 | 3 | export const client = new Client(); 4 | -------------------------------------------------------------------------------- /tests/lib/games.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import { checkWinner } from '../../src/lib/util/RockPaperScissors'; 3 | 4 | describe('RockPaperScissors', () => { 5 | describe('checkWinner', () => { 6 | test('GIVEN draw THEN return 0', () => { 7 | const expectedResult = 0; 8 | 9 | expect(checkWinner('rock', 'rock')).toBe(expectedResult); 10 | expect(checkWinner('paper', 'paper')).toBe(expectedResult); 11 | expect(checkWinner('scissors', 'scissors')).toBe(expectedResult); 12 | }); 13 | 14 | test('GIVEN bot win THEN return -1', () => { 15 | const expectedResult = -1; 16 | 17 | expect(checkWinner('rock', 'scissors')).toBe(expectedResult); 18 | expect(checkWinner('paper', 'rock')).toBe(expectedResult); 19 | expect(checkWinner('scissors', 'paper')).toBe(expectedResult); 20 | }); 21 | 22 | test('GIVEN player win THEN return 1', () => { 23 | const expectedResult = 1; 24 | 25 | expect(checkWinner('rock', 'paper')).toBe(expectedResult); 26 | expect(checkWinner('paper', 'scissors')).toBe(expectedResult); 27 | expect(checkWinner('scissors', 'rock')).toBe(expectedResult); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/lib/util.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | 3 | import { formatDate, friendlyDuration } from '../../src/lib/util/util'; 4 | import { Time } from '../../src/lib/types/Enums'; 5 | 6 | describe('Util', () => { 7 | 8 | describe('formatDate', () => { 9 | const mockDate = new Date(2017, 0, 25); // mock date for consistent result 10 | const mockTimestamp = mockDate.getTime(); 11 | 12 | test('GIVEN only date or timestamp THEN return default format', () => { 13 | const expectedResult = '2017 Jan 25th'; 14 | 15 | expect(formatDate(mockDate)).toBe(expectedResult); 16 | expect(formatDate(mockTimestamp)).toBe(expectedResult); 17 | }); 18 | 19 | test('GIVEN date and format THEN return proper format', () => { 20 | const testFormat = 'MMMM DD YY'; 21 | const expectedResult = 'January 25 17'; 22 | 23 | expect(formatDate(mockDate, testFormat)).toBe(expectedResult); 24 | expect(formatDate(mockTimestamp, testFormat)).toBe(expectedResult); 25 | }); 26 | }); 27 | 28 | describe('friendlyDuration', () => { 29 | test('GIVEN number THEN return correctly formatted readable string', () => { 30 | expect(friendlyDuration(Time.SECOND)).toBe('1 second'); 31 | expect(friendlyDuration(Time.MINUTE * 2)).toBe('2 minutes'); 32 | expect(friendlyDuration(Time.HOUR * 3)).toBe('3 hours'); 33 | expect(friendlyDuration(Time.DAY * 4)).toBe('4 days'); 34 | }); 35 | }); 36 | 37 | }); 38 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "paths": { 6 | "@lib/*": ["../src/lib/*"] 7 | } 8 | }, 9 | "include": [".", "../src/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": [ 4 | "src", 5 | "tests", 6 | "jest.config.ts", 7 | "babel.config.js" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint-config-bamboo", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "rootDir": ".", 6 | "baseUrl": ".", 7 | "experimentalDecorators": true, 8 | "paths": { 9 | "@utils/*": ["src/lib/util/*"], 10 | "@lib/*": ["src/lib/*"], 11 | "@root/*": ["*"] 12 | }, 13 | "target": "ES2019", 14 | "types": [ 15 | "node", 16 | "discord.js", 17 | "klasa", 18 | "jest" 19 | ], 20 | }, 21 | "include": ["."], 22 | "exclude": ["./config.example.ts"] 23 | } 24 | --------------------------------------------------------------------------------