├── .dockerignore ├── .eslintignore ├── .eslintrc.json ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── ESLint.yml │ ├── edge.yml │ └── prod.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── .vscode └── settings.json ├── Dockerfile ├── LICENSE ├── README.md ├── assets └── shortlinks.json ├── docker-compose.yml ├── docker └── prometheus │ ├── Dockerfile │ └── prometheus.yml ├── locales ├── en_US.json ├── fr_FR.json └── pt_BR.json ├── ormconfig.js ├── package.json ├── prettierrc.js ├── renovate.json ├── scripts ├── add-license.js ├── migrate.js ├── run-docker.sh ├── shortlinks.js ├── update-punishments.js └── util │ ├── getCaseType.js │ └── getRepositories.js ├── src ├── @types │ ├── eris.d.ts │ ├── global.d.ts │ ├── ioredis-lock.d.ts │ ├── json.d.ts │ ├── locale.d.ts │ └── reflect-metadata.d.ts ├── api │ └── API.ts ├── automod │ ├── Blacklist.ts │ ├── Dehoisting.ts │ ├── Invites.ts │ ├── Mentions.ts │ ├── Raid.ts │ ├── Shortlinks.ts │ └── Spam.ts ├── commands │ ├── core │ │ ├── HelpCommand.ts │ │ ├── InviteCommand.ts │ │ ├── LocaleCommand.ts │ │ ├── PingCommand.ts │ │ ├── ShardInfoCommand.ts │ │ ├── SourceCommand.ts │ │ ├── StatisticsCommand.ts │ │ └── UptimeCommand.ts │ ├── moderation │ │ ├── BanCommand.ts │ │ ├── CaseCommand.ts │ │ ├── KickCommand.ts │ │ ├── MuteCommand.ts │ │ ├── PardonCommand.ts │ │ ├── PurgeCommand.ts │ │ ├── ReasonCommand.ts │ │ ├── SoftbanCommand.ts │ │ ├── TimeoutsCommand.ts │ │ ├── UnbanCommand.ts │ │ ├── UnmuteCommand.ts │ │ ├── WarnCommand.ts │ │ ├── WarningsCommand.ts │ │ └── voice │ │ │ ├── VoiceDeafenCommand.ts │ │ │ ├── VoiceKickCommand.ts │ │ │ ├── VoiceMuteCommand.ts │ │ │ ├── VoiceUndeafenCommand.ts │ │ │ └── VoiceUnmuteCommand.ts │ ├── owner │ │ ├── BlacklistCommand.ts │ │ ├── EvalCommand.ts │ │ └── WhitelistCommand.ts │ └── settings │ │ ├── Automod.ts │ │ ├── Logging.ts │ │ ├── ModLog.ts │ │ ├── MutedRole.ts │ │ ├── Prefix.ts │ │ ├── Punishments.ts │ │ └── Settings.ts ├── components │ ├── Config.ts │ ├── Database.ts │ ├── Discord.ts │ ├── Prometheus.ts │ ├── Redis.ts │ ├── Sentry.ts │ └── timeouts │ │ ├── Timeouts.ts │ │ └── types.ts ├── container.ts ├── controllers │ ├── AutomodController.ts │ ├── BlacklistController.ts │ ├── CasesController.ts │ ├── GuildSettingsController.ts │ ├── LoggingController.ts │ ├── PunishmentsController.ts │ ├── UserSettingsController.ts │ └── WarningsController.ts ├── entities │ ├── AutomodEntity.ts │ ├── BlacklistEntity.ts │ ├── CaseEntity.ts │ ├── GuildEntity.ts │ ├── LoggingEntity.ts │ ├── PunishmentsEntity.ts │ ├── UserEntity.ts │ └── WarningsEntity.ts ├── listeners │ ├── GuildBansListener.ts │ ├── GuildListener.ts │ ├── GuildMemberListener.ts │ ├── MessageListener.ts │ ├── UserListener.ts │ ├── VoiceStateListener.ts │ └── VoidListener.ts ├── main.ts ├── migrations │ ├── 1617170164138-initialization.ts │ ├── 1617402812079-firstMigration.ts │ ├── 1618173354506-fixPrimaryColumnInWarnings.ts │ ├── 1618173954276-addIdPropToWarnings.ts │ ├── 1618174668865-snakeCaseColumnNames.ts │ ├── 1621720227973-addNewLogTypes.ts │ ├── 1621895533962-fixCaseTimeType.ts │ ├── 1622346188448-addAttachmentColumn.ts │ ├── 1625456992070-fixPunishmentIndex.ts │ ├── 1625457655665-addDaysColumn.ts │ └── 1625605609322-addWhitelistChannelsAutomod.ts ├── services │ ├── AutomodService.ts │ ├── BotlistService.ts │ ├── CommandService.ts │ ├── ListenerService.ts │ ├── LocalizationService.ts │ └── PunishmentService.ts ├── singletons │ ├── Http.ts │ └── Logger.ts ├── structures │ ├── Automod.ts │ ├── Command.ts │ ├── CommandMessage.ts │ ├── EmbedBuilder.ts │ ├── Locale.ts │ ├── Subcommand.ts │ ├── decorators │ │ ├── Subcommand.ts │ │ └── Subscribe.ts │ └── index.ts └── util │ ├── Constants.ts │ ├── ErisPatch.ts │ ├── Permissions.ts │ ├── Stopwatch.ts │ └── index.ts ├── tsconfig.json └── yarn.lock /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .github/ 3 | .vscode/ 4 | data/ 5 | dist/ 6 | .husky/ 7 | 8 | .travis.yml 9 | **.md 10 | !README.md 11 | docker-compose.yml 12 | renovate.json 13 | *.lock 14 | *.log 15 | application.yml 16 | config.yml 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["prettier", "@augu/eslint-config/ts.js"], 3 | "plugins": ["prettier"], 4 | "rules": { 5 | "@typescript-eslint/indent": "off", 6 | "quote-props": ["error", "consistent-as-needed"], 7 | "quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at augu@voided.pw. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Nino 2 | We gladly accept contributions and we truly thank whoever supports this project! 3 | 4 | ## Code of Conduct 5 | Check out our [Code of Conduct](https://github.com/auguwu/Nino/blob/master/CONTRIBUTING.md). 6 | 7 | By contributing, we expect you to uphold this conduct. 8 | 9 | ## Ways to Contribute 10 | ### Bug Reporting 11 | Before submitting an issue make sure the issue hasn't been reported yet. 12 | 13 | Please consider these following guidelines when contributing: 14 | * Be clear and concise with the title, it will help others link their issues and solutions to yours. 15 | * Specify the ways to reproduce the bug, so our development team and contributors can reproduce by themselves. 16 | * Show the output, using logs and screenshots, to help others understand your issue. 17 | * For any issue regarding memory leaks / high cpu usage please send a [diagnosis](https://nodejs.org/en/docs/guides/simple-profiling/) of your performance 18 | * For any uncertainty of how to reproduce the issue, specify that as well! Describe your actions before the bug occured. 19 | 20 | ### Submitting Enhancements 21 | We appreciate new ideas and improvements and we want Nino to reach its full potential! 22 | If you have any enhancement you want done in Nino please submit an issue! 23 | 24 | Before submitting the issue, consider the following guidelines: 25 | * Be clear and concise with the title, it will help others link their issues and solutions to yours. 26 | * In the enhancement's description, please add a way to implement the idea. 27 | It doesn't have to be in code but rather an idea to make sure it's up to standards 28 | * Add examples of how the feature can be interacted with, it will make the general concept much more easier to grasp. 29 | 30 | ### Pull Requests 31 | Before submitting a PR please make sure there is no other similar PR submitted. 32 | 33 | Do not submit a PR that adds any enhancements before the enhancement issue was approved, or else it will be ignored. 34 | 35 | If your PR regards any issues please link the issue to the PR. 36 | 37 | Make sure the PR succeeds all tests (linting and unit testing). 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '[Bug Report] ' 5 | labels: bug 6 | assignees: 'auguwu' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Discord (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Version [e.g. 22] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '[Feature Request] ' 5 | labels: enhancement 6 | assignees: auguwu 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/ESLint.yml: -------------------------------------------------------------------------------- 1 | name: ESLint Workflow 2 | on: 3 | push: 4 | branches: 5 | - 'feature/**' 6 | - 'issue/gh-**' 7 | - master 8 | - edge 9 | 10 | paths-ignore: 11 | - '.github/**' 12 | - '.husky/**' 13 | - '.vscode/**' 14 | - 'assets/**' 15 | - 'locales/**' 16 | - 'docker/**' 17 | - '.dockerignore' 18 | - '.eslintignore' 19 | - '.gitignore' 20 | - '**.md' 21 | - 'LICENSE' 22 | - 'renovate.json' 23 | 24 | pull_request: 25 | branches: 26 | - 'feature/**' 27 | - 'issue/gh-**' 28 | - master 29 | - edge 30 | 31 | paths-ignore: 32 | - '.github/**' 33 | - '.husky/**' 34 | - '.vscode/**' 35 | - 'assets/**' 36 | - 'locales/**' 37 | - 'docker/**' 38 | - '.dockerignore' 39 | - '.eslintignore' 40 | - '.gitignore' 41 | - '**.md' 42 | - 'LICENSE' 43 | - 'renovate.json' 44 | jobs: 45 | lint: 46 | runs-on: ubuntu-latest 47 | steps: 48 | - name: 'Checkout repository' 49 | uses: actions/checkout@v2 50 | 51 | - name: Use Node.js v16 52 | uses: actions/setup-node@v2 53 | with: 54 | node-version: 16.x 55 | 56 | - name: Installs all global packages 57 | run: npm install -g eslint typescript 58 | 59 | - name: Installs local packages 60 | run: yarn 61 | 62 | - name: Lints the repository and checks for linting errors 63 | run: eslint src --ext .ts 64 | 65 | - name: Compiles the project to check for any build errors 66 | run: tsc --noEmit 67 | -------------------------------------------------------------------------------- /.github/workflows/edge.yml: -------------------------------------------------------------------------------- 1 | # Worklow to update Nino Edge, development bot for testing the edge branch 2 | 3 | name: Updating Beta instance 4 | on: 5 | workflow_dispatch: 6 | push: 7 | branches: 8 | - edge 9 | 10 | paths-ignore: 11 | - '.github/**' 12 | - '.husky/**' 13 | - '.vscode/**' 14 | - 'assets/**' 15 | - 'locales/**' 16 | - 'docker/**' 17 | - '.dockerignore' 18 | - '.eslintignore' 19 | - '.gitignore' 20 | - '**.md' 21 | - 'LICENSE' 22 | - 'renovate.json' 23 | 24 | jobs: 25 | build-container: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checks out the repository 29 | uses: actions/checkout@v2 30 | 31 | - name: Login to the registry 32 | run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.floofy.dev -u august --password-stdin 33 | 34 | - name: Build the container 35 | run: docker build --no-cache . -t registry.floofy.dev/noelware/nino:edge-${{github.sha}} 36 | 37 | - name: Push to the registry 38 | run: docker push registry.floofy.dev/noelware/nino:edge-${{github.sha}} 39 | 40 | deploy: 41 | needs: build-container 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Login to Kubernetes 45 | run: | 46 | mkdir ~/.kube 47 | echo "${{ secrets.KUBE_CONFIG }}" > ~/.kube/config 48 | 49 | - name: Set tag 50 | run: kubectl set image deployment/nino-edge nino-edge=registry.floofy.dev/noelware/nino:edge-${{github.sha}} 51 | 52 | - name: Deploy to the bot 53 | run: kubectl rollout status deployment/nino-edge 54 | -------------------------------------------------------------------------------- /.github/workflows/prod.yml: -------------------------------------------------------------------------------- 1 | # Worklow to update Nino, production bot you see today! 2 | 3 | name: Update Production instance 4 | on: 5 | workflow_dispatch: 6 | push: 7 | branches: 8 | - master 9 | 10 | paths-ignore: 11 | - '.github/**' 12 | - '.husky/**' 13 | - '.vscode/**' 14 | - 'assets/**' 15 | - 'locales/**' 16 | - 'docker/**' 17 | - '.dockerignore' 18 | - '.eslintignore' 19 | - '.gitignore' 20 | - '**.md' 21 | - 'LICENSE' 22 | - 'renovate.json' 23 | 24 | jobs: 25 | build-container: 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checks out the repository 29 | uses: actions/checkout@v2 30 | 31 | - name: Login to the registry 32 | run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login registry.floofy.dev -u august --password-stdin 33 | 34 | - name: Build the container 35 | run: docker build --no-cache . -t registry.floofy.dev/noelware/nino:${{github.sha}} 36 | 37 | - name: Push to the registry 38 | run: docker push registry.floofy.dev/noelware/nino:${{github.sha}} 39 | 40 | deploy: 41 | needs: build-container 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Login to Kubernetes 45 | run: | 46 | mkdir ~/.kube 47 | echo "${{ secrets.KUBE_CONFIG }}" > ~/.kube/config 48 | 49 | - name: Set tag 50 | run: kubectl set image deployment/nino-prod nino-prod=registry.floofy.dev/noelware/nino:${{github.sha}} 51 | 52 | - name: Deploy to the bot 53 | run: kubectl rollout status deployment/nino-prod 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | node_modules/ 3 | old_locales/ 4 | .husky/_/ 5 | build/ 6 | 7 | # Jest 8 | coverage/ 9 | 10 | # Files 11 | application.yml 12 | config.yml 13 | *.log 14 | .env 15 | 16 | # Redis dumps, I run redis-server in the working directory :3 17 | *.rdb 18 | launch.json 19 | 20 | # Ignore user-config 21 | .vscode/ 22 | .idea/ 23 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | echo 'nino - ❓ lint ~ ❓ project - checking eslint for errors' 5 | eslint src --ext .ts # `--fix` would normally be here but it should only print and not fix 6 | 7 | echo 'nino - ✔ lint ~ ❓ project - compiling project for errors' 8 | yarn tsc --noEmit 9 | 10 | echo 'nino - ✔ lint ~ ✔ project - we are done here' 11 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .github 2 | .husky 3 | .idea 4 | assets 5 | build 6 | 7 | .dockerignore 8 | .env 9 | .eslintignore 10 | .gitignore 11 | LICENSE 12 | package-*.json 13 | package.json 14 | README.md 15 | renovate.json 16 | tsconfig.json 17 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules\\typescript\\lib" 3 | } 4 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | 3 | LABEL MAINTAINER="Nino " 4 | RUN apk update && apk add git ca-certificates 5 | 6 | WORKDIR /opt/Nino 7 | COPY . . 8 | RUN apk add --no-cache git 9 | RUN npm i -g typescript eslint typeorm 10 | RUN yarn 11 | RUN yarn build:no-lint 12 | RUN yarn cache clean 13 | 14 | # Give it executable permissions 15 | RUN chmod +x ./scripts/run-docker.sh 16 | 17 | ENTRYPOINT [ "sh", "./scripts/run-docker.sh" ] 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2021 Nino 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | services: 3 | bot: 4 | container_name: nino 5 | restart: always 6 | build: . 7 | depends_on: 8 | - postgresql 9 | - prometheus 10 | - timeouts 11 | - redis 12 | networks: 13 | - nino 14 | volumes: 15 | - ./config.yml:/opt/Nino/config.yml:ro # Read-only 16 | 17 | redis: 18 | container_name: redis 19 | restart: always 20 | image: redis:latest 21 | ports: 22 | - 6379:6379 23 | networks: 24 | - nino 25 | volumes: 26 | - redis:/data 27 | 28 | postgresql: 29 | container_name: postgres 30 | restart: always 31 | image: postgres:latest 32 | ports: 33 | - 5432:5432 34 | networks: 35 | - nino 36 | volumes: 37 | - postgres:/var/lib/postgresql/data 38 | environment: 39 | POSTGRES_USER: ${DATABASE_USERNAME} 40 | POSTGRES_PASSWORD: ${DATABASE_PASSWORD} 41 | POSTGRES_DB: ${DATABASE_NAME} 42 | 43 | prometheus: 44 | container_name: prometheus 45 | build: ./docker/prometheus 46 | restart: always 47 | networks: 48 | - default 49 | 50 | timeouts: 51 | container_name: timeouts 52 | restart: always 53 | image: docker.pkg.github.com/ninodiscord/timeouts/timeouts:latest 54 | ports: 55 | - 4025:4025 56 | networks: 57 | - nino 58 | environment: 59 | AUTH: ${TIMEOUTS_AUTH} 60 | 61 | volumes: 62 | redis: 63 | postgres: 64 | 65 | networks: 66 | nino: 67 | internal: true 68 | -------------------------------------------------------------------------------- /docker/prometheus/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM prom/prometheus:latest 2 | 3 | COPY prometheus.yml /etc/prometheus 4 | -------------------------------------------------------------------------------- /docker/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | scrape_timeout: 15s 4 | scrape_configs: 5 | - job_name: 'Nino' 6 | scrape_interval: 15s 7 | metrics_path: /metrics 8 | static_configs: 9 | - targets: ['prometheus:17290'] 10 | -------------------------------------------------------------------------------- /ormconfig.js: -------------------------------------------------------------------------------- 1 | const { parse } = require('@augu/dotenv'); 2 | const { join } = require('path'); 3 | 4 | const config = parse({ 5 | populate: false, 6 | file: join(__dirname, '.env'), 7 | 8 | schema: { 9 | DATABASE_USERNAME: 'string', 10 | DATABASE_PASSWORD: 'string', 11 | DATABASE_NAME: 'string', 12 | DATABASE_HOST: 'string', 13 | DATABASE_PORT: 'int', 14 | NODE_ENV: { 15 | type: 'string', 16 | default: ['development', 'production'], 17 | }, 18 | }, 19 | }); 20 | 21 | module.exports = { 22 | migrations: ['./build/migrations/*.js'], 23 | username: config.DATABASE_USERNAME, 24 | password: config.DATABASE_PASSWORD, 25 | entities: ['./build/entities/*.js'], 26 | database: config.DATABASE_NAME, 27 | logging: false, // enable this when the deprecated message is gone 28 | type: 'postgres', 29 | host: config.DATABASE_HOST, 30 | port: config.DATABASE_PORT, 31 | 32 | cli: { 33 | migrationsDir: 'src/migrations', 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nino", 3 | "description": "🔨 Advanced and cute moderation discord bot as an entry of Discord's Hack Week", 4 | "version": "1.4.3", 5 | "private": true, 6 | "homepage": "https://nino.floofy.dev", 7 | "license": "MIT", 8 | "repository": "https://github.com/NinoDiscord/Nino", 9 | "main": "build/main.js", 10 | "author": "August ", 11 | "prettier": "@augu/prettier-config", 12 | "maintainers": [ 13 | { 14 | "email": "cutie@floofy.dev", 15 | "name": "August", 16 | "url": "https://floofy.dev" 17 | } 18 | ], 19 | "engines": { 20 | "node": ">=14" 21 | }, 22 | "scripts": { 23 | "clean:node_modules": "rm -rf node_modules/**/node_modules && rm -rf node_modules/@types/**/node_modules && rm -rf node_modules/@augu/**/node_modules", 24 | "clean:win:tar": "cp node_modules/@augu/collections/build/index.js.* node_modules/@augu/collections/build/index.js && rm node_modules/@augu/collections/build/index.js.*", 25 | "husky:install": "husky install && rm .husky/.gitignore", 26 | "build:no-lint": "eslint src --ext .ts && rm -rf build && tsc", 27 | "migrations": "yarn build && typeorm migration:run", 28 | "shortlinks": "node scripts/shortlinks.js", 29 | "licenses": "node scripts/add-license.js", 30 | "prepare": "yarn clean:node_modules", 31 | "build": "yarn lint && yarn format && rm -rf build && tsc", 32 | "format": "prettier --write --parser typescript --config ./prettierrc.js src/**/*.ts", 33 | "start": "cd build && node main.js", 34 | "lint": "eslint src --ext .ts --fix", 35 | "dev": "cd src && nodemon --exec \"ts-node --project ../tsconfig.json --files\" main.ts" 36 | }, 37 | "dependencies": { 38 | "@augu/collections": "1.0.12", 39 | "@augu/dotenv": "1.3.0", 40 | "@augu/lilith": "5.0.4", 41 | "@augu/orchid": "3.1.1", 42 | "@augu/utils": "1.5.3", 43 | "@sentry/node": "6.10.0", 44 | "eris": "github:abalabahaha/eris#dev", 45 | "fastify": "3.20.1", 46 | "fastify-no-icon": "4.0.0", 47 | "ioredis": "4.27.6", 48 | "js-yaml": "4.1.0", 49 | "luxon": "2.0.1", 50 | "ms": "2.1.3", 51 | "pg": "8.7.1", 52 | "prom-client": "13.1.0", 53 | "reflect-metadata": "0.1.13", 54 | "slash-commands": "1.5.0", 55 | "source-map-support": "0.5.19", 56 | "tslog": "3.2.0", 57 | "typeorm": "0.2.31", 58 | "ws": "8.0.0" 59 | }, 60 | "devDependencies": { 61 | "@augu/eslint-config": "2.2.0", 62 | "@augu/prettier-config": "1.0.2", 63 | "@augu/tsconfig": "1.1.1", 64 | "@types/ioredis": "4.26.6", 65 | "@types/js-yaml": "4.0.2", 66 | "@types/luxon": "1.27.1", 67 | "@types/ms": "0.7.31", 68 | "@types/node": "15.3.1", 69 | "@types/ws": "7.4.7", 70 | "@typescript-eslint/eslint-plugin": "4.29.0", 71 | "@typescript-eslint/parser": "4.29.0", 72 | "discord-api-types": "0.22.0", 73 | "eslint": "7.32.0", 74 | "eslint-config-prettier": "8.3.0", 75 | "eslint-plugin-prettier": "3.4.0", 76 | "husky": "7.0.1", 77 | "nodemon": "2.0.12", 78 | "prettier": "2.3.2", 79 | "ts-node": "10.1.0", 80 | "typescript": "4.3.5" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /prettierrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2021 Noel 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | module.exports = { 24 | semi: true, 25 | tabWidth: 2, 26 | singleQuote: true, 27 | endOfLine: 'lf', 28 | printWidth: 120, 29 | trailingComma: 'es5', 30 | bracketSpacing: true, 31 | jsxBracketSameLine: false, 32 | }; 33 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "automerge": true, 4 | "baseBranches": ["edge"], 5 | "ignoreDeps": ["typeorm"] 6 | } 7 | -------------------------------------------------------------------------------- /scripts/add-license.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | const { readdir } = require('@augu/utils'); 24 | const path = require('path'); 25 | const fs = require('fs'); 26 | 27 | const LICENSE = `/** 28 | * Copyright (c) 2019-${new Date().getFullYear()} Nino 29 | * 30 | * Permission is hereby granted, free of charge, to any person obtaining a copy 31 | * of this software and associated documentation files (the "Software"), to deal 32 | * in the Software without restriction, including without limitation the rights 33 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 34 | * copies of the Software, and to permit persons to whom the Software is 35 | * furnished to do so, subject to the following conditions: 36 | * 37 | * The above copyright notice and this permission notice shall be included in all 38 | * copies or substantial portions of the Software. 39 | * 40 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 41 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 42 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 43 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 44 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 45 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 46 | * SOFTWARE. 47 | */ 48 | 49 | `; 50 | 51 | const main = async () => { 52 | console.log('adding licenses at the top of files...'); 53 | const files = await Promise.all([ 54 | readdir(path.join(__dirname, '..', 'src')), 55 | readdir(path.join(__dirname, '..', 'scripts')), 56 | ]).then((arr) => arr.flat()); 57 | 58 | for (const file of files) { 59 | console.log(`Adding license to ${file}...`); 60 | const content = fs.readFileSync(file, 'utf8'); 61 | const raw = content.includes('* Copyright (c)') 62 | ? content.split('\n').slice(22).join('\n') 63 | : content; 64 | 65 | await fs.promises.writeFile(file, LICENSE + raw); 66 | 67 | console.log(`Added license to ${file} :D`); 68 | } 69 | }; 70 | 71 | main() 72 | .then(() => process.exit(0)) 73 | .catch((error) => { 74 | console.error(error); 75 | process.exit(1); 76 | }); 77 | -------------------------------------------------------------------------------- /scripts/run-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo 'Running migrations' 4 | typeorm migration:run 5 | 6 | echo 'Migrations and schemas should be synced.' 7 | npm start 8 | -------------------------------------------------------------------------------- /scripts/shortlinks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | const { LoggerWithoutCallSite } = require('tslog'); 24 | const { calculateHRTime } = require('@augu/utils'); 25 | const { existsSync } = require('fs'); 26 | const { HttpClient } = require('@augu/orchid'); 27 | const { version } = require('../package.json'); 28 | const { join } = require('path'); 29 | const fs = require('fs/promises'); 30 | 31 | const http = new HttpClient({ 32 | userAgent: `Nino/DiscordBot (https://github.com/NinoDiscord/Nino, v${version})`, 33 | }); 34 | 35 | const logger = new LoggerWithoutCallSite({ 36 | displayFunctionName: true, 37 | exposeErrorCodeFrame: true, 38 | displayInstanceName: true, 39 | displayFilePath: false, 40 | dateTimePattern: '[ day-month-year / hour:minute:second ]', 41 | instanceName: 'script: shortlinks', 42 | name: 'scripts', 43 | }); 44 | 45 | const otherUrls = [ 46 | 'shorte.st', 47 | 'adf.ly', 48 | 'bc.vc', 49 | 'soo.gd', 50 | 'ouo.io', 51 | 'zzb.bz', 52 | 'adfoc.us', 53 | 'goo.gl', 54 | 'grabify.link', 55 | 'shorturl.at', 56 | 'tinyurl.com', 57 | 'tinyurl.one', 58 | 'rotf.lol', 59 | 'bit.do', 60 | 'is.gd', 61 | 'owl.ly', 62 | 'buff.ly', 63 | 'mcaf.ee', 64 | 'su.pr', 65 | 'bfy.tw', 66 | 'owo.vc', 67 | 'tiny.cc', 68 | 'rb.gy', 69 | 'bl.ink', 70 | 'v.gd', 71 | 'vurl.com', 72 | 'turl.ca', 73 | 'shrunken.com', 74 | 'p.asia', 75 | 'g.asia', 76 | '3.ly', 77 | '0.gp', 78 | '2.ly', 79 | '4.gp', 80 | '4.ly', 81 | '6.ly', 82 | '7.ly', 83 | '8.ly', 84 | '9.ly', 85 | '2.gp', 86 | '6.gp', 87 | '5.gp', 88 | 'ur3.us', 89 | 'kek.gg', 90 | 'waa.ai', 91 | 'steamcommunity.ru', 92 | 'steanconmunity.ru', 93 | ]; 94 | 95 | const startTime = process.hrtime(); 96 | (async () => { 97 | logger.info( 98 | 'Now reading list from https://raw.githubusercontent.com/sambokai/ShortURL-Services-List/master/shorturl-services-list.csv...' 99 | ); 100 | 101 | const requestTime = process.hrtime(); 102 | const res = await http.get( 103 | 'https://raw.githubusercontent.com/sambokai/ShortURL-Services-List/master/shorturl-services-list.csv' 104 | ); 105 | 106 | const requestEnd = calculateHRTime(requestTime); 107 | logger.debug( 108 | `It took ~${requestEnd}ms to get a "${res.statusCode}" response.` 109 | ); 110 | 111 | const data = res.body().split(/\n\r?/); 112 | data.shift(); 113 | 114 | const shortlinks = [ 115 | ...new Set( 116 | [].concat( 117 | data.map((s) => s.slice(0, s.length - 1)), 118 | otherUrls 119 | ) 120 | ), 121 | ].filter((s) => s !== ''); 122 | if (!existsSync(join(__dirname, '..', 'assets'))) 123 | await fs.mkdir(join(__dirname, '..', 'assets')); 124 | 125 | await fs.writeFile( 126 | join(__dirname, '..', 'assets', 'shortlinks.json'), 127 | `${JSON.stringify(shortlinks, null, '\t')}\n` 128 | ); 129 | logger.info( 130 | `It took about ~${calculateHRTime(startTime)}ms to retrieve ${ 131 | shortlinks.length 132 | } short-links.` 133 | ); 134 | process.exit(0); 135 | })(); 136 | -------------------------------------------------------------------------------- /scripts/update-punishments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | const { createConnection } = require('typeorm'); 24 | const { 25 | default: PunishmentsEntity, 26 | } = require('../build/entities/PunishmentsEntity'); 27 | 28 | async function main() { 29 | console.log( 30 | `[scripts:update-punishments | ${new Date().toLocaleTimeString()}] Updating punishments...` 31 | ); 32 | const connection = await createConnection(); 33 | 34 | // Retrieve all punishments 35 | const repository = connection.getRepository(PunishmentsEntity); 36 | const punishments = await repository.find({}); 37 | const traversedGuilds = []; 38 | let i = 0; 39 | 40 | await repository.delete({}); 41 | 42 | // Update punishments 43 | console.log( 44 | `[scripts:update-punishments | ${new Date().toLocaleTimeString()}] Found ${ 45 | punishments.length 46 | } punishments to update` 47 | ); 48 | for (const punishment of punishments) { 49 | console.log( 50 | `idx: ${i} | guild entry: ${ 51 | traversedGuilds.find((c) => c.guild === punishment.guildID) !== 52 | undefined 53 | }` 54 | ); 55 | 56 | if ( 57 | traversedGuilds.find((c) => c.guild === punishment.guildID) !== undefined 58 | ) { 59 | const f = traversedGuilds.find((c) => c.guild === punishment.guildID); 60 | const id = traversedGuilds.findIndex( 61 | (c) => c.guild === punishment.guildID 62 | ); 63 | const entity = new PunishmentsEntity(); 64 | 65 | entity.warnings = punishment.warnings; 66 | entity.guildID = punishment.guildID; 67 | entity.index = f.index += 1; 68 | entity.soft = punishment.soft; 69 | entity.time = punishment.time; 70 | entity.type = punishment.type; 71 | entity.days = punishment.days; 72 | 73 | traversedGuilds[id] = { 74 | guild: punishment.guildID, 75 | index: (f.index += 1), 76 | }; 77 | await repository.save(entity); 78 | } else { 79 | const entity = new PunishmentsEntity(); 80 | entity.warnings = punishment.warnings; 81 | entity.guildID = punishment.guildID; 82 | entity.index = 1; 83 | entity.soft = punishment.soft; 84 | entity.time = punishment.time; 85 | entity.type = punishment.type; 86 | entity.days = punishment.days; 87 | 88 | traversedGuilds.push({ guild: punishment.guildID, index: 1 }); 89 | await repository.save(entity); 90 | } 91 | } 92 | } 93 | 94 | main(); 95 | -------------------------------------------------------------------------------- /scripts/util/getCaseType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | const { PunishmentType } = require('../../build/entities/PunishmentsEntity'); 24 | 25 | /** 26 | * Determines the type from v0.x to v1.x 27 | * @param {'warning remove' | 'warning add' | 'unmute' | 'kick' | 'mute' | 'ban'} type The type to serialize 28 | * @returns {string} The punishment type 29 | */ 30 | module.exports = (type) => { 31 | switch (type) { 32 | case 'warning remove': 33 | return PunishmentType.WarningRemoved; 34 | 35 | case 'warning add': 36 | return PunishmentType.WarningAdded; 37 | 38 | case 'unmute': 39 | return PunishmentType.Unmute; 40 | 41 | case 'kick': 42 | return PunishmentType.Kick; 43 | 44 | case 'mute': 45 | return PunishmentType.Mute; 46 | 47 | case 'ban': 48 | return PunishmentType.Ban; 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /scripts/util/getRepositories.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | const { 24 | default: PunishmentsEntity, 25 | } = require('../../build/entities/PunishmentsEntity'); 26 | const { 27 | default: AutomodEntity, 28 | } = require('../../build/entities/AutomodEntity'); 29 | const { default: CaseEntity } = require('../../build/entities/CaseEntity'); 30 | const { default: GuildEntity } = require('../../build/entities/GuildEntity'); 31 | const { 32 | default: WarningsEntity, 33 | } = require('../../build/entities/WarningsEntity'); 34 | const { 35 | default: LoggingEntity, 36 | } = require('../../build/entities/LoggingEntity'); 37 | const { default: UserEntity } = require('../../build/entities/UserEntity'); 38 | 39 | /** 40 | * Returns the repositories from the responding `connection`. 41 | * @param {import('typeorm').Connection} connection The connection established 42 | */ 43 | module.exports = (connection) => ({ 44 | punishments: connection.getRepository(PunishmentsEntity), 45 | warnings: connection.getRepository(WarningsEntity), 46 | logging: connection.getRepository(LoggingEntity), 47 | automod: connection.getRepository(AutomodEntity), 48 | guilds: connection.getRepository(GuildEntity), 49 | cases: connection.getRepository(CaseEntity), 50 | users: connection.getRepository(UserEntity), 51 | }); 52 | -------------------------------------------------------------------------------- /src/@types/eris.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import Eris from 'eris'; 24 | 25 | declare module 'eris' { 26 | interface Collection extends Map { 27 | get(key: string | number): T | undefined; 28 | get(key: string | number): V; 29 | 30 | values(): IterableIterator; 31 | values(): IterableIterator; 32 | 33 | filter(func: (i: T) => boolean): T[]; 34 | filter(func: (i: V) => boolean): V[]; 35 | } 36 | 37 | interface Guild { 38 | channels: Collection; 39 | } 40 | 41 | interface User { 42 | tag: string; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/@types/global.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /* eslint-disable camelcase */ 24 | 25 | import { Container } from '@augu/lilith'; 26 | import { APIUser } from 'discord-api-types'; 27 | import { crypto } from '../api/encryption'; 28 | 29 | declare global { 30 | /** The current container running */ 31 | var app: Container; 32 | 33 | namespace NodeJS { 34 | interface Global { 35 | /** The current container running */ 36 | app: Container; 37 | } 38 | } 39 | 40 | interface APITokenResult extends Omit { 41 | expiryDate: number; 42 | data: APIUser; 43 | id: string; 44 | } 45 | 46 | interface RedisInfo { 47 | total_connections_received: number; 48 | total_commands_processed: number; 49 | instantaneous_ops_per_sec: number; 50 | total_net_input_bytes: number; 51 | total_net_output_bytes: number; 52 | instantaneous_input_kbps: number; 53 | instantaneous_output_kbps: number; 54 | rejected_connections: number; 55 | sync_full: number; 56 | sync_partial_ok: number; 57 | sync_partial_err: number; 58 | expired_keys: number; 59 | expired_stale_perc: number; 60 | expired_time_cap_reached_count: number; 61 | evicted_keys: number; 62 | keyspace_hits: number; 63 | keyspace_misses: number; 64 | pubsub_channels: number; 65 | pubsub_patterns: number; 66 | latest_fork_usec: number; 67 | migrate_cached_sockets: number; 68 | slave_expires_tracked_keys: number; 69 | active_defrag_hits: number; 70 | active_defrag_misses: number; 71 | active_defrag_key_hits: number; 72 | active_defrag_key_misses: number; 73 | } 74 | 75 | interface RedisServerInfo { 76 | redis_version: string; 77 | redis_git_sha1: string; 78 | redis_git_dirty: string; 79 | redis_build_id: string; 80 | redis_mode: string; 81 | os: string; 82 | arch_bits: string; 83 | multiplexing_api: string; 84 | atomicvar_api: string; 85 | gcc_version: string; 86 | process_id: string; 87 | process_supervised: string; 88 | run_id: string; 89 | tcp_port: string; 90 | server_time_usec: string; 91 | uptime_in_seconds: string; 92 | uptime_in_days: string; 93 | hz: string; 94 | configured_hz: string; 95 | lru_clock: string; 96 | executable: string; 97 | config_file: string; 98 | io_threads_active: string; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/@types/ioredis-lock.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /** */ 24 | declare module 'ioredis-lock' { 25 | import * as ioredis from 'ioredis'; 26 | 27 | interface RedisLockOptions { 28 | timeout?: number; 29 | retries?: number; 30 | delay?: number; 31 | } 32 | 33 | /** 34 | * Creates and returns a new Lock instance, configured for use with the supplied redis client, as well as options, if provided. 35 | * The options object may contain following three keys, as outlined at the start of the documentation: timeout, retries and delay. 36 | */ 37 | export function createLock(client: ioredis.Redis | ioredis.Cluster, options?: RedisLockOptions): Lock; 38 | 39 | /** 40 | * Returns an array of currently active/acquired locks. 41 | */ 42 | export function getAcquiredLocks(): Promise; 43 | 44 | /** 45 | * The constructor for a LockAcquisitionError. Thrown or returned when a lock 46 | * could not be acquired. 47 | */ 48 | export class LockAcquisitionError extends Error { 49 | constructor(message: string); 50 | } 51 | 52 | /** 53 | * The constructor for a LockReleaseError. Thrown or returned when a lock 54 | * could not be released. 55 | */ 56 | export class LockReleaseError extends Error { 57 | constructor(message: string); 58 | } 59 | 60 | /** 61 | * The constructor for a LockExtendError. Thrown or returned when a lock 62 | * could not be extended. 63 | */ 64 | export class LockExtendError extends Error { 65 | constructor(message: string); 66 | } 67 | 68 | /** 69 | * The constructor for a Lock object. Accepts both a redis client, as well as 70 | * an options object with the following properties: timeout, retries and delay. 71 | * Any options not supplied are subject to the current defaults. 72 | */ 73 | export class Lock { 74 | public readonly config: RedisLockOptions; 75 | 76 | /** 77 | * Attempts to acquire a lock, given a key, and an optional callback function. 78 | * If the initial lock fails, additional attempts will be made for the 79 | * configured number of retries, and padded by the delay. The callback is 80 | * invoked with an error on failure, and returns a promise if no callback is 81 | * supplied. If invoked in the context of a promise, it may throw a 82 | * LockAcquisitionError. 83 | * 84 | * @param key The redis key to use for the lock 85 | */ 86 | public acquire(key: string): Promise; 87 | 88 | /** 89 | * Attempts to extend the lock 90 | * @param expire in `timeout` seconds 91 | */ 92 | public extend(expire: number): Promise; 93 | 94 | /** 95 | * Attempts to release the lock, and accepts an optional callback function. 96 | * The callback is invoked with an error on failure, and returns a promise 97 | * if no callback is supplied. If invoked in the context of a promise, it may 98 | * throw a LockReleaseError. 99 | */ 100 | public release(): Promise; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/@types/json.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /** */ 24 | interface JSON { 25 | parse(content: string, reviver?: (this: any, key: string, value: any) => any): T; 26 | } 27 | -------------------------------------------------------------------------------- /src/@types/reflect-metadata.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | /** */ 24 | declare namespace Reflect { 25 | /** 26 | * Gets the metadata value for the provided metadata key on the target object or its prototype chain. 27 | * @param metadataKey A key used to store and retrieve metadata. 28 | * @param target The target object on which the metadata is defined. 29 | * @returns The metadata value for the metadata key if found; otherwise, `undefined`. 30 | * @example 31 | * 32 | * class Example { 33 | * } 34 | * 35 | * // constructor 36 | * result = Reflect.getMetadata("custom:annotation", Example); 37 | * 38 | */ 39 | function getMetadata(metadataKey: any, target: any): T; 40 | 41 | /** 42 | * Gets the metadata value for the provided metadata key on the target object or its prototype chain. 43 | * @param metadataKey A key used to store and retrieve metadata. 44 | * @param target The target object on which the metadata is defined. 45 | * @param propertyKey The property key for the target. 46 | * @returns The metadata value for the metadata key if found; otherwise, `undefined`. 47 | */ 48 | function getMetadata(metadataKey: any, target: any, propertyKey: string | symbol): T; 49 | } 50 | -------------------------------------------------------------------------------- /src/automod/Blacklist.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type { Message, TextChannel } from 'eris'; 24 | import LocalizationService from '../services/LocalizationService'; 25 | import PunishmentService from '../services/PunishmentService'; 26 | import PermissionUtil from '../util/Permissions'; 27 | import { Automod } from '../structures'; 28 | import { Inject } from '@augu/lilith'; 29 | import Database from '../components/Database'; 30 | import Discord from '../components/Discord'; 31 | 32 | export default class BlacklistAutomod implements Automod { 33 | public name: string = 'blacklists'; 34 | 35 | @Inject 36 | private readonly locales!: LocalizationService; 37 | 38 | @Inject 39 | private readonly punishments!: PunishmentService; 40 | 41 | @Inject 42 | private readonly database!: Database; 43 | 44 | @Inject 45 | private readonly discord!: Discord; 46 | 47 | async onMessage(msg: Message) { 48 | const settings = await this.database.automod.get(msg.guildID); 49 | if (settings === undefined || settings.blacklist === false) return false; 50 | 51 | if (!msg || msg === null) return false; 52 | 53 | const nino = msg.channel.guild.members.get(this.discord.client.user.id)!; 54 | 55 | if ( 56 | (msg.member !== null && !PermissionUtil.isMemberAbove(nino, msg.member)) || 57 | !msg.channel.permissionsOf(this.discord.client.user.id).has('manageMessages') || 58 | msg.author.bot || 59 | msg.channel.permissionsOf(msg.author.id).has('banMembers') 60 | ) 61 | return false; 62 | 63 | const content = msg.content.toLowerCase().split(' '); 64 | for (const word of settings.blacklistWords) { 65 | if (content.filter((c) => c === word.toLowerCase()).length > 0) { 66 | const language = this.locales.get(msg.guildID, msg.author.id); 67 | 68 | await msg.channel.createMessage(language.translate('automod.blacklist')); 69 | await msg.delete(); 70 | await this.punishments.createWarning(msg.member, '[Automod] User said a word that is blacklisted here (☍﹏⁰)。'); 71 | 72 | return true; 73 | } 74 | } 75 | 76 | return false; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/automod/Dehoisting.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type { Member } from 'eris'; 24 | import { Automod } from '../structures'; 25 | import { Inject } from '@augu/lilith'; 26 | import Database from '../components/Database'; 27 | import Discord from '../components/Discord'; 28 | 29 | export default class Dehoisting implements Automod { 30 | public name: string = 'dehoisting'; 31 | 32 | @Inject 33 | private database!: Database; 34 | 35 | @Inject 36 | private discord!: Discord; 37 | 38 | async onMemberNickUpdate(member: Member) { 39 | const settings = await this.database.automod.get(member.guild.id); 40 | if (settings !== undefined && settings.dehoist === false) return false; 41 | 42 | if (member.nick === null) return false; 43 | 44 | if (!member.guild.members.get(this.discord.client.user.id)?.permissions.has('manageNicknames')) return false; 45 | 46 | if (member.nick[0] < '0') { 47 | const origin = member.nick; 48 | await member.edit( 49 | { nick: 'hoister' }, 50 | `[Automod] Member ${member.username}#${member.discriminator} has updated their nick to "${origin}" and dehoisting is enabled.` 51 | ); 52 | 53 | return true; 54 | } 55 | 56 | return false; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/automod/Mentions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type { Message, TextChannel } from 'eris'; 24 | import LocalizationService from '../services/LocalizationService'; 25 | import PunishmentService from '../services/PunishmentService'; 26 | import PermissionUtil from '../util/Permissions'; 27 | import { Automod } from '../structures'; 28 | import { Inject } from '@augu/lilith'; 29 | import Database from '../components/Database'; 30 | import Discord from '../components/Discord'; 31 | 32 | export default class Mentions implements Automod { 33 | public name: string = 'mentions'; 34 | 35 | @Inject 36 | private readonly locales!: LocalizationService; 37 | 38 | @Inject 39 | private readonly punishments!: PunishmentService; 40 | 41 | @Inject 42 | private readonly database!: Database; 43 | 44 | @Inject 45 | private readonly discord!: Discord; 46 | 47 | async onMessage(msg: Message) { 48 | const settings = await this.database.automod.get(msg.guildID); 49 | if (settings === undefined || settings.invites === false) return false; 50 | 51 | if (!msg || msg === null) return false; 52 | 53 | const nino = msg.channel.guild.members.get(this.discord.client.user.id)!; 54 | 55 | if ( 56 | (msg.member !== null && !PermissionUtil.isMemberAbove(nino, msg.member)) || 57 | !msg.channel.permissionsOf(this.discord.client.user.id).has('manageMessages') || 58 | msg.author.bot || 59 | msg.channel.permissionsOf(msg.author.id).has('banMembers') 60 | ) 61 | return false; 62 | 63 | if (msg.mentions.length >= 4) { 64 | const language = this.locales.get(msg.guildID, msg.author.id); 65 | 66 | await msg.channel.createMessage(language.translate('automod.mentions')); 67 | await msg.delete(); 68 | await this.punishments.createWarning( 69 | msg.member, 70 | `[Automod] Mentioned 4 or more people in ${msg.channel.mention}` 71 | ); 72 | 73 | return true; 74 | } 75 | 76 | return false; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/automod/Shortlinks.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type { Message, TextChannel } from 'eris'; 24 | import LocalizationService from '../services/LocalizationService'; 25 | import PunishmentService from '../services/PunishmentService'; 26 | import * as Constants from '../util/Constants'; 27 | import PermissionUtil from '../util/Permissions'; 28 | import { Automod } from '../structures'; 29 | import { Inject } from '@augu/lilith'; 30 | import Database from '../components/Database'; 31 | import Discord from '../components/Discord'; 32 | 33 | // tfw ice the adorable fluff does all the regex for u :woeme: 34 | const LINK_REGEX = /(https?:\/\/)?(\w*\.\w*)/gi; 35 | 36 | export default class Shortlinks implements Automod { 37 | public name: string = 'shortlinks'; 38 | 39 | @Inject 40 | private readonly punishments!: PunishmentService; 41 | 42 | @Inject 43 | private readonly locales!: LocalizationService; 44 | 45 | @Inject 46 | private readonly database!: Database; 47 | 48 | @Inject 49 | private readonly discord!: Discord; 50 | 51 | async onMessage(msg: Message) { 52 | if (!msg || msg === null) return false; 53 | 54 | const nino = msg.channel.guild.members.get(this.discord.client.user.id)!; 55 | 56 | if ( 57 | (msg.member !== null && !PermissionUtil.isMemberAbove(nino, msg.member)) || 58 | !msg.channel.permissionsOf(this.discord.client.user.id).has('manageMessages') || 59 | msg.author.bot || 60 | msg.channel.permissionsOf(msg.author.id).has('banMembers') 61 | ) 62 | return false; 63 | 64 | const settings = await this.database.automod.get(msg.author.id); 65 | if (settings !== undefined && settings.shortLinks === false) return false; 66 | 67 | const matches = msg.content.match(LINK_REGEX); 68 | if (matches === null) return false; 69 | 70 | // Why not use .find/.filter? 71 | // Because, it should traverse *all* matches, just not one match. 72 | // 73 | // So for an example: 74 | // "haha scam thing!!! floofy.dev bit.ly/jnksdjklsdsj" 75 | // 76 | // In the string, if `.find`/`.filter` was the solution here, 77 | // it'll only match "owo.com" and not "bit.ly" 78 | for (let i = 0; i < matches.length; i++) { 79 | if (Constants.SHORT_LINKS.includes(matches[i])) { 80 | const language = this.locales.get(msg.guildID, msg.author.id); 81 | 82 | await msg.delete(); 83 | await msg.channel.createMessage(language.translate('automod.shortlinks')); 84 | await this.punishments.createWarning( 85 | msg.member, 86 | `[Automod] Sending a blacklisted URL in ${msg.channel.mention}` 87 | ); 88 | 89 | return true; 90 | } 91 | } 92 | 93 | return false; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/automod/Spam.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type { Message, TextChannel } from 'eris'; 24 | import LocalizationService from '../services/LocalizationService'; 25 | import PunishmentService from '../services/PunishmentService'; 26 | import { Collection } from '@augu/collections'; 27 | import PermissionUtil from '../util/Permissions'; 28 | import { Automod } from '../structures'; 29 | import { Inject } from '@augu/lilith'; 30 | import Database from '../components/Database'; 31 | import Discord from '../components/Discord'; 32 | 33 | export default class Mentions implements Automod { 34 | private cache: Collection> = new Collection(); 35 | public name: string = 'mentions'; 36 | 37 | @Inject 38 | private readonly punishments!: PunishmentService; 39 | 40 | @Inject 41 | private readonly database!: Database; 42 | 43 | @Inject 44 | private readonly locales!: LocalizationService; 45 | 46 | @Inject 47 | private readonly discord!: Discord; 48 | 49 | async onMessage(msg: Message) { 50 | const settings = await this.database.automod.get(msg.guildID); 51 | if (settings === undefined || settings.invites === false) return false; 52 | 53 | if (!msg || msg === null) return false; 54 | 55 | const nino = msg.channel.guild.members.get(this.discord.client.user.id)!; 56 | 57 | if ( 58 | (msg.member !== null && !PermissionUtil.isMemberAbove(nino, msg.member)) || 59 | !msg.channel.permissionsOf(this.discord.client.user.id).has('manageMessages') || 60 | msg.author.bot || 61 | msg.channel.permissionsOf(msg.author.id).has('banMembers') 62 | ) 63 | return false; 64 | 65 | const queue = this.get(msg.guildID, msg.author.id); 66 | queue.push(msg.timestamp); 67 | 68 | if (queue.length >= 5) { 69 | const old = queue.shift()!; 70 | if (msg.editedTimestamp && msg.editedTimestamp > msg.timestamp) return false; 71 | 72 | if (msg.timestamp - old <= 3000) { 73 | const language = this.locales.get(msg.guildID, msg.author.id); 74 | this.clear(msg.guildID, msg.author.id); 75 | 76 | await msg.channel.createMessage(language.translate('automod.spam')); 77 | await this.punishments.createWarning(msg.member, `[Automod] Spamming in ${msg.channel.mention} o(╥﹏╥)o`); 78 | return true; 79 | } 80 | } 81 | 82 | this.clean(msg.guildID); 83 | return false; 84 | } 85 | 86 | private clean(guildID: string) { 87 | const now = Date.now(); 88 | const buckets = this.cache.get(guildID); 89 | 90 | // Let's just not do anything if there is no spam cache for this guild 91 | if (buckets === undefined) return; 92 | 93 | const ids = buckets.filterKeys((val) => now - val[val.length - 1] >= 5000); 94 | for (const id of ids) this.cache.delete(id); 95 | } 96 | 97 | private get(guildID: string, userID: string) { 98 | if (!this.cache.has(guildID)) this.cache.set(guildID, new Collection()); 99 | 100 | if (!this.cache.get(guildID)!.has(userID)) this.cache.get(guildID)!.set(userID, []); 101 | 102 | return this.cache.get(guildID)!.get(userID)!; 103 | } 104 | 105 | private clear(guildID: string, userID: string) { 106 | this.cache.get(guildID)!.delete(userID); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/commands/core/InviteCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Command, CommandMessage } from '../../structures'; 24 | import { Inject } from '@augu/lilith'; 25 | import Discord from '../../components/Discord'; 26 | 27 | export default class InviteCommand extends Command { 28 | @Inject 29 | private discord!: Discord; 30 | 31 | constructor() { 32 | super({ 33 | description: 'descriptions.invite', 34 | aliases: ['inviteme', 'inv'], 35 | cooldown: 2, 36 | name: 'invite', 37 | }); 38 | } 39 | 40 | run(msg: CommandMessage) { 41 | return msg.translate('commands.invite', [ 42 | msg.author.tag, 43 | `https://discord.com/oauth2/authorize?client_id=${this.discord.client.user.id}&scope=bot`, 44 | 'https://discord.com/oauth2/authorize?client_id=613907896622907425&scope=bot', 45 | 'https://discord.gg/ATmjFH9kMH', 46 | ]); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/commands/core/PingCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Command, CommandMessage } from '../../structures'; 24 | import { calculateHRTime } from '@augu/utils'; 25 | import { Inject } from '@augu/lilith'; 26 | import Database from '../../components/Database'; 27 | import Discord from '../../components/Discord'; 28 | import Redis from '../../components/Redis'; 29 | 30 | export default class PingCommand extends Command { 31 | @Inject 32 | private discord!: Discord; 33 | 34 | constructor() { 35 | super({ 36 | description: 'descriptions.ping', 37 | aliases: ['pong', 'latency', 'lat'], 38 | cooldown: 1, 39 | name: 'ping', 40 | }); 41 | } 42 | 43 | async run(msg: CommandMessage) { 44 | const startedAt = process.hrtime(); 45 | const message = await msg.reply('What did you want me to respond to?'); 46 | const ping = calculateHRTime(startedAt); 47 | const node = process.env.REGION ?? 'unknown'; 48 | 49 | const deleteMsg = process.hrtime(); 50 | await message.delete(); 51 | 52 | const shard = this.discord.client.shards.get(msg.guild.shard.id)!; 53 | const redis = await app.get('redis').getStatistics(); 54 | const postgres = await app.get('database').getStatistics(); 55 | 56 | return msg.reply( 57 | [ 58 | `:satellite_orbital: Running under node **${node}**`, 59 | '', 60 | `> **Message Delete**: ${calculateHRTime(deleteMsg).toFixed()}ms`, 61 | `> **Message Send**: ${ping.toFixed()}ms`, 62 | `> **PostgreSQL**: ${postgres.ping}`, 63 | `> **Shard #${shard.id}**: ${shard.latency}ms`, 64 | `> **Redis**: ${redis.ping}`, 65 | ].join('\n') 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/commands/core/ShardInfoCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Command, CommandMessage, EmbedBuilder } from '../../structures'; 24 | import { firstUpper } from '@augu/utils'; 25 | import type { Shard } from 'eris'; 26 | import { Inject } from '@augu/lilith'; 27 | import Discord from '../../components/Discord'; 28 | 29 | type ShardStatus = Shard['status']; 30 | 31 | const hearts: { [P in ShardStatus]: string } = { 32 | disconnected: ':heart:', 33 | handshaking: ':yellow_heart:', 34 | connecting: ':yellow_heart:', 35 | identifying: ':blue_heart:', 36 | resuming: ':blue_heart:', 37 | ready: ':green_heart:', 38 | }; 39 | 40 | interface ShardInfo { 41 | current: boolean; 42 | status: ShardStatus; 43 | guilds: number; 44 | users: number; 45 | heart: string; 46 | id: number; 47 | } 48 | 49 | export default class ShardInfoCommand extends Command { 50 | @Inject 51 | private discord!: Discord; 52 | 53 | constructor() { 54 | super({ 55 | description: 'descriptions.shardinfo', 56 | aliases: ['shard', 'shards'], 57 | cooldown: 6, 58 | name: 'shardinfo', 59 | }); 60 | } 61 | 62 | run(msg: CommandMessage) { 63 | const shards = this.discord.client.shards.map((shard) => ({ 64 | current: msg.guild.shard.id === shard.id, 65 | status: shard.status, 66 | guilds: this.discord.client.guilds.filter((guild) => guild.shard.id === shard.id).length, 67 | users: this.discord.client.guilds 68 | .filter((guild) => guild.shard.id === shard.id) 69 | .reduce((a, b) => a + b.memberCount, 0), 70 | heart: hearts[shard.status], 71 | id: shard.id, 72 | })); 73 | 74 | const embed = EmbedBuilder.create().addFields( 75 | shards.map((shard) => ({ 76 | name: `❯ Shard #${shard.id}`, 77 | value: [ 78 | `${shard.heart} **${firstUpper(shard.status)}**${shard.current ? ' (current)' : ''}`, 79 | '', 80 | `• **Guilds**: ${shard.guilds}`, 81 | `• **Users**: ${shard.users}`, 82 | ].join('\n'), 83 | inline: true, 84 | })) 85 | ); 86 | 87 | return msg.reply(embed); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/commands/core/SourceCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Command, CommandMessage } from '../../structures'; 24 | 25 | export default class SourceCommand extends Command { 26 | constructor() { 27 | super({ 28 | description: 'descriptions.source', 29 | aliases: ['git', 'github', 'sauce', 'oss'], 30 | name: 'source', 31 | }); 32 | } 33 | 34 | run(msg: CommandMessage) { 35 | return msg.reply(':eyes: https://github.com/NinoDiscord/Nino'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/core/UptimeCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Command, CommandMessage } from '../../structures'; 24 | import { humanize } from '@augu/utils'; 25 | 26 | export default class UptimeCommand extends Command { 27 | constructor() { 28 | super({ 29 | description: 'descriptions.uptime', 30 | cooldown: 3, 31 | aliases: ['up', 'upfor', 'online'], 32 | name: 'uptime', 33 | }); 34 | } 35 | 36 | run(msg: CommandMessage) { 37 | return msg.reply(humanize(Math.floor(process.uptime() * 1000), true)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/commands/moderation/CaseCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Command, CommandMessage, EmbedBuilder } from '../../structures'; 24 | import PunishmentService from '../../services/PunishmentService'; 25 | import { Categories } from '../../util/Constants'; 26 | import { Inject } from '@augu/lilith'; 27 | import Database from '../../components/Database'; 28 | import Discord from '../../components/Discord'; 29 | import ms from 'ms'; 30 | 31 | export default class CaseCommand extends Command { 32 | @Inject 33 | private punishments!: PunishmentService; 34 | 35 | @Inject 36 | private database!: Database; 37 | 38 | @Inject 39 | private discord!: Discord; 40 | 41 | constructor() { 42 | super({ 43 | userPermissions: 'banMembers', 44 | description: 'descriptions.case', 45 | examples: ['case', 'case 3'], 46 | category: Categories.Moderation, 47 | aliases: ['lookup'], 48 | usage: '[caseID]', 49 | name: 'case', 50 | }); 51 | } 52 | 53 | async run(msg: CommandMessage, args: string[]) { 54 | if (args.length < 1) return msg.reply('No bot or user was found.'); 55 | 56 | const caseID = args[0]; 57 | if (isNaN(Number(caseID))) return msg.reply(`Case \`${caseID}\` was not a number.`); 58 | 59 | const caseModel = await this.database.cases.repository.findOne({ 60 | guildID: msg.guild.id, 61 | index: Number(caseID), 62 | }); 63 | 64 | if (caseModel === undefined) return msg.reply(`Case #**${caseID}** was not found.`); 65 | 66 | const moderator = this.discord.client.users.get(caseModel.moderatorID) ?? { 67 | username: 'Unknown User', 68 | discriminator: '0000', 69 | }; 70 | 71 | const victim = this.discord.client.users.get(caseModel.victimID) ?? { 72 | discriminator: '0000', 73 | username: 'Unknown User', 74 | }; 75 | 76 | const embed = EmbedBuilder.create() 77 | .setAuthor( 78 | `[ Case #${caseModel.index} | ${victim.username}#${victim.discriminator} (${caseModel.victimID})]`, 79 | undefined, 80 | (victim as any).dynamicAvatarURL?.('png', 1024) 81 | ) // dynamicAvatarURL might not exist since partials 82 | .setDescription([ 83 | `${ 84 | caseModel.reason 85 | ? `**${caseModel.reason}**` 86 | : `*Unknown, use \`${msg.settings.prefixes[0]}reason ${caseModel.index} \` to set a reason*` 87 | }`, 88 | '', 89 | caseModel.messageID !== null || msg.settings.modlogChannelID !== null 90 | ? `[**\`[Jump Here]\`**](https://discord.com/channels/${msg.guild.id}/${msg.settings.modlogChannelID}/${caseModel.messageID})` 91 | : '', 92 | ]) 93 | .addField( 94 | '• Moderator', 95 | `${moderator.username}#${moderator.discriminator} (${(moderator as any).id ?? '(unknown)'})`, 96 | true 97 | ) 98 | .addField('• Type', caseModel.type, true); 99 | 100 | if (caseModel.time !== null) embed.addField('• Time', ms(Number(caseModel.time!), { long: true }), true); 101 | 102 | return msg.reply(embed); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/commands/moderation/UnbanCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Command, CommandMessage } from '../../structures'; 24 | import { PunishmentType } from '../../entities/PunishmentsEntity'; 25 | import PunishmentService from '../../services/PunishmentService'; 26 | import { Categories } from '../../util/Constants'; 27 | import { Inject } from '@augu/lilith'; 28 | import Redis from '../../components/Redis'; 29 | 30 | export default class UnbanCommand extends Command { 31 | @Inject 32 | private punishments!: PunishmentService; 33 | 34 | @Inject 35 | private redis!: Redis; 36 | 37 | constructor() { 38 | super({ 39 | userPermissions: 'banMembers', 40 | botPermissions: 'banMembers', 41 | description: 'descriptions.ban', 42 | category: Categories.Moderation, 43 | examples: ['unban @Nino', 'unban @Nino some reason!'], 44 | aliases: ['unbent', 'unbean'], 45 | usage: ' [reason]', 46 | name: 'unban', 47 | }); 48 | } 49 | 50 | async run(msg: CommandMessage, [userID, ...reason]: [string, ...string[]]) { 51 | if (!userID) return msg.reply('No bot or user was specified.'); 52 | 53 | try { 54 | await this.punishments.apply({ 55 | moderator: msg.author, 56 | publish: true, 57 | reason: reason.join(' ') || 'No description was provided.', 58 | member: { id: userID, guild: msg.guild }, 59 | type: PunishmentType.Unban, 60 | }); 61 | 62 | const timeouts = await this.redis.getTimeouts(msg.guild.id); 63 | const available = timeouts.filter( 64 | (pkt) => pkt.type !== 'unban' && pkt.user !== userID && pkt.guild === msg.guild.id 65 | ); 66 | 67 | await this.redis.client.hmset('nino:timeouts', [msg.guild.id, available]); 68 | return msg.reply('User or bot has been unbanned successfully.'); 69 | } catch (ex) { 70 | return msg.reply( 71 | [ 72 | 'Uh-oh! An internal error has occured while running this.', 73 | 'Contact the developers in discord.gg/ATmjFH9kMH under <#824071651486335036>:', 74 | '', 75 | '```js', 76 | ex.stack ?? '<... no stacktrace? ...>', 77 | '```', 78 | ].join('\n') 79 | ); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/commands/owner/WhitelistCommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Command, CommandMessage } from '../../structures'; 24 | import { Categories } from '../../util/Constants'; 25 | import { Inject } from '@augu/lilith'; 26 | import Database from '../../components/Database'; 27 | import Config from '../../components/Config'; 28 | 29 | export default class WhitelistCommand extends Command { 30 | @Inject 31 | private database!: Database; 32 | 33 | @Inject 34 | private config!: Config; 35 | 36 | constructor() { 37 | super({ 38 | description: 'Whitelists a user or guild from Nino', 39 | category: Categories.Owner, 40 | ownerOnly: true, 41 | hidden: true, 42 | aliases: ['wl'], 43 | usage: '["guild" | "user"] [id] [...reason]', 44 | name: 'whitelist', 45 | }); 46 | } 47 | 48 | async run(msg: CommandMessage, [type, id]: ['guild' | 'user', string]) { 49 | if (!['guild', 'user'].includes(type)) 50 | return msg.reply('Missing the type to blacklist. Available options: `user` and `guild`.'); 51 | 52 | if (type === 'guild') { 53 | const entry = await this.database.blacklists.get(id); 54 | if (entry === undefined) return msg.reply(`Guild **${id}** is already whitelisted`); 55 | 56 | await this.database.blacklists.delete(id); 57 | return msg.reply(`:thumbsup: Whitelisted guild **${id}**.`); 58 | } 59 | 60 | if (type === 'user') { 61 | const owners = this.config.getProperty('owners') ?? []; 62 | if (owners.includes(id)) return msg.reply('Cannot whitelist a owner'); 63 | 64 | const entry = await this.database.blacklists.get(id); 65 | if (entry === undefined) return msg.reply(`User **${id}** is already whitelisted`); 66 | 67 | await this.database.blacklists.delete(id); 68 | return msg.reply(`:thumbsup: Whitelisted user ${id}.`); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/commands/settings/ModLog.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Command, CommandMessage, Subcommand } from '../../structures'; 24 | import type { TextChannel } from 'eris'; 25 | import { Categories } from '../../util/Constants'; 26 | import { Inject } from '@augu/lilith'; 27 | import Database from '../../components/Database'; 28 | import Discord from '../../components/Discord'; 29 | 30 | export default class ModLogCommand extends Command { 31 | @Inject 32 | private database!: Database; 33 | 34 | @Inject 35 | private discord!: Discord; 36 | 37 | constructor() { 38 | super({ 39 | userPermissions: ['manageGuild'], 40 | description: 'descriptions.modlog', 41 | category: Categories.Settings, 42 | examples: ['modlog', 'modlog reset', 'modlog <#236987412589632587>'], 43 | aliases: ['set-modlog'], 44 | usage: '[channel]', 45 | name: 'modlog', 46 | }); 47 | } 48 | 49 | async run(msg: CommandMessage, [channel]: [string]) { 50 | if (!channel) { 51 | const chan = 52 | msg.settings.modlogChannelID !== null 53 | ? await this.discord.getChannel(msg.settings.modlogChannelID!, msg.guild) 54 | : null; 55 | return msg.reply(chan === null ? 'No mod-log has been set.' : `Mod Logs are set in **#${chan.name}**`); 56 | } 57 | 58 | const chan = await this.discord.getChannel(channel, msg.guild); 59 | if (chan === null) return msg.reply(`Channel "${channel}" doesn't exist.`); 60 | 61 | if (chan.type !== 0) return msg.reply(`Channel #${chan.name} was not a text channel`); 62 | 63 | const perms = chan.permissionsOf(this.discord.client.user.id); 64 | if (!perms.has('sendMessages') || !perms.has('readMessages') || !perms.has('embedLinks')) 65 | return msg.reply( 66 | `I am missing the following permissions: **Send Messages**, **Read Messages**, and **Embed Links** in #${chan.name}.` 67 | ); 68 | 69 | await this.database.guilds.update(msg.guild.id, { 70 | modlogChannelID: chan.id, 71 | }); 72 | 73 | return msg.reply(`Mod Logs are now set in #${chan.name}!`); 74 | } 75 | 76 | @Subcommand() 77 | async reset(msg: CommandMessage) { 78 | if (!msg.settings.modlogChannelID) return msg.reply('No mod logs channel has been set.'); 79 | 80 | await this.database.guilds.update(msg.guild.id, { 81 | modlogChannelID: undefined, 82 | }); 83 | 84 | return msg.reply('Resetted the mod log successfully.'); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/commands/settings/MutedRole.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Command, Subcommand, CommandMessage } from '../../structures'; 24 | import { Categories } from '../../util/Constants'; 25 | import { Inject } from '@augu/lilith'; 26 | import Database from '../../components/Database'; 27 | import Discord from '../../components/Discord'; 28 | 29 | export default class MutedRoleCommand extends Command { 30 | @Inject 31 | private database!: Database; 32 | 33 | @Inject 34 | private discord!: Discord; 35 | 36 | constructor() { 37 | super({ 38 | userPermissions: 'manageGuild', 39 | description: 'descriptions.muted_role', 40 | category: Categories.Settings, 41 | examples: [ 42 | 'muterole | View the current Muted role in this server', 43 | 'muterole reset | Resets the Muted role in this server', 44 | 'muterole 3621587485965325 | Sets the current mute role', 45 | ], 46 | aliases: ['mutedrole', 'mute-role'], 47 | name: 'muterole', 48 | }); 49 | } 50 | 51 | async run(msg: CommandMessage, [roleID]: [string]) { 52 | if (!roleID) 53 | return msg.settings.mutedRoleID !== null 54 | ? msg.reply(`The muted role in this guild is <@&${msg.settings.mutedRoleID}>`) 55 | : msg.reply('No muted role is set in this guild.'); 56 | 57 | const role = await this.discord.getRole(roleID, msg.guild); 58 | if (role === null) return msg.reply(`\`${roleID}\` was not a role.`); 59 | 60 | await this.database.guilds.update(msg.guild.id, { mutedRoleID: role.id }); 61 | return msg.reply(`The Muted role is now set to **${role.name}**`); 62 | } 63 | 64 | @Subcommand() 65 | async reset(msg: CommandMessage) { 66 | await this.database.guilds.update(msg.guild.id, { mutedRoleID: undefined }); 67 | return msg.reply(':thumbsup: Muted role has been reset.'); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/Prometheus.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { createServer, IncomingMessage, ServerResponse } from 'http'; 24 | import { Component, Inject } from '@augu/lilith'; 25 | import { Logger } from 'tslog'; 26 | import Config from './Config'; 27 | import prom from 'prom-client'; 28 | 29 | @Component({ 30 | priority: 3, 31 | name: 'prometheus', 32 | }) 33 | export default class Prometheus { 34 | public commandsExecuted!: prom.Counter; 35 | public messagesSeen!: prom.Counter; 36 | public rawWSEvents!: prom.Counter; 37 | public guildCount!: prom.Gauge; 38 | #server!: ReturnType; // yes 39 | 40 | @Inject 41 | private logger!: Logger; 42 | 43 | @Inject 44 | private config!: Config; 45 | 46 | load() { 47 | const port = this.config.getProperty('prometheusPort'); 48 | if (port === undefined) { 49 | this.logger.warn( 50 | 'Prometheus will not be available! This is not recommended for private instances unless you want analytics.' 51 | ); 52 | return Promise.resolve(); 53 | } 54 | 55 | prom.collectDefaultMetrics(); 56 | this.commandsExecuted = new prom.Counter({ 57 | labelNames: ['command'], 58 | name: 'nino_commands_executed', 59 | help: 'How many commands Nino has executed successfully', 60 | }); 61 | 62 | this.messagesSeen = new prom.Counter({ 63 | name: 'nino_messages_seen', 64 | help: 'How many messages Nino has seen throughout the process lifespan', 65 | }); 66 | 67 | this.rawWSEvents = new prom.Counter({ 68 | labelNames: ['event'], 69 | name: 'nino_discord_websocket_events', 70 | help: 'Received WebSocket events from Discord and what they were', 71 | }); 72 | 73 | this.guildCount = new prom.Gauge({ 74 | name: 'nino_guild_count', 75 | help: 'Number of guilds Nino is in', 76 | }); 77 | 78 | this.#server = createServer(this.onRequest.bind(this)); 79 | this.#server.once('listening', () => this.logger.info(`Prometheus: Listening at http://localhost:${port}`)); 80 | this.#server.on('error', (error) => this.logger.fatal(error)); 81 | this.#server.listen(port); 82 | } 83 | 84 | private async onRequest(req: IncomingMessage, res: ServerResponse) { 85 | if (req.url! === '/metrics') { 86 | res.writeHead(200, { 'Content-Type': prom.register.contentType }); 87 | res.write(await prom.register.metrics()); 88 | } else if (req.url! === '/favicon.ico') { 89 | res.writeHead(404, { 'Content-Type': 'application/json' }); 90 | res.write('{"fuck":"you uwu"}'); 91 | } else { 92 | res.writeHead(400, { 'Content-Type': 'application/json' }); 93 | res.write('{"uwu":"owo"}'); 94 | } 95 | 96 | res.end(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/components/Sentry.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { version, commitHash } from '../util/Constants'; 24 | import { Component, Inject } from '@augu/lilith'; 25 | import { hostname } from 'os'; 26 | import { Logger } from 'tslog'; 27 | import * as sentry from '@sentry/node'; 28 | import Config from './Config'; 29 | 30 | @Component({ 31 | priority: 5, 32 | name: 'sentry', 33 | }) 34 | export default class Sentry { 35 | @Inject 36 | private logger!: Logger; 37 | 38 | @Inject 39 | private config!: Config; 40 | 41 | load() { 42 | this.logger.info('Initializing Sentry...'); 43 | 44 | const dsn = this.config.getProperty('sentryDsn'); 45 | if (dsn === undefined) { 46 | this.logger.warn("Missing sentryDsn variable in config.yml! Don't worry, this is optional."); 47 | return; 48 | } 49 | 50 | sentry.init({ 51 | tracesSampleRate: 1.0, 52 | integrations: [new sentry.Integrations.Http({ tracing: true })], 53 | environment: this.config.getProperty('environment')!, 54 | serverName: hostname(), 55 | release: version, 56 | dsn, 57 | }); 58 | 59 | sentry.configureScope((scope) => 60 | scope.setTags({ 61 | 'nino.environment': this.config.getProperty('environment')!, 62 | 'nino.commitHash': commitHash, 63 | 'nino.version': version, 64 | 'system.user': require('os').userInfo().username, 65 | 'system.os': process.platform, 66 | }) 67 | ); 68 | 69 | this.logger.info('Sentry has been installed.'); 70 | } 71 | 72 | report(ex: Error) { 73 | sentry.captureException(ex); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/components/timeouts/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type { PunishmentType } from '../../entities/PunishmentsEntity'; 24 | 25 | /** 26 | * Current state of the WebSocket connection with the timeouts service 27 | */ 28 | export const enum SocketState { 29 | Connecting = 'connecting', 30 | Connected = 'connected', 31 | Unknown = 'unknown', 32 | Closed = 'closed', 33 | } 34 | 35 | /** 36 | * List of OPCodes to send or receive from 37 | */ 38 | export const enum OPCodes { 39 | // receive 40 | Ready, 41 | Apply, 42 | 43 | // send 44 | Request, 45 | Acknowledged, 46 | } 47 | 48 | /** 49 | * Represents a data packet that is sent out 50 | * @typeparam T - The data packet received 51 | * @typeparam OP - The OPCode that this data represents 52 | */ 53 | export interface DataPacket { 54 | op: OP; 55 | d: T; 56 | } 57 | 58 | export interface Timeout { 59 | moderator: string; 60 | expired: number; 61 | issued: number; 62 | reason: string | null; 63 | guild: string; 64 | user: string; 65 | type: PunishmentTimeoutType; 66 | } 67 | 68 | /** 69 | * Represents that the service is ready and probably has 70 | * incoming timeouts to take action on 71 | */ 72 | export type ReadyPacket = DataPacket; 73 | 74 | /** 75 | * Represents what a request to send to the service 76 | */ 77 | export type RequestPacket = DataPacket; 78 | 79 | /** 80 | * Represents the data payload when we acknowledged 81 | */ 82 | export type AcknowledgedPacket = DataPacket; 83 | 84 | export type ApplyPacket = DataPacket; 85 | export type PunishmentTimeoutType = Exclude< 86 | PunishmentType, 87 | | PunishmentType.Kick 88 | | PunishmentType.WarningAdded 89 | | PunishmentType.WarningRemoved 90 | | PunishmentType.Mute 91 | | PunishmentType.Ban 92 | | PunishmentType.VoiceMute 93 | | PunishmentType.VoiceDeafen 94 | >; 95 | -------------------------------------------------------------------------------- /src/container.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Container } from '@augu/lilith'; 24 | import { join } from 'path'; 25 | import logger from './singletons/Logger'; 26 | import http from './singletons/Http'; 27 | 28 | const app = new Container({ 29 | componentsDir: join(__dirname, 'components'), 30 | servicesDir: join(__dirname, 'services'), 31 | singletons: [http, logger], 32 | }); 33 | 34 | app.on('onBeforeChildInit', (cls, child) => logger.debug(`>> ${cls.name}->${child.constructor.name}: initializing...`)); 35 | app.on('onAfterChildInit', (cls, child) => logger.debug(`>> ✔ ${cls.name}->${child.constructor.name}: initialized`)); 36 | app.on('onBeforeInit', (cls) => logger.debug(`>> ${cls.name}: initializing...`)); 37 | app.on('onAfterInit', (cls) => logger.debug(`>> ✔ ${cls.name}: initialized`)); 38 | app.on('debug', (message) => logger.debug(`lilith: ${message}`)); 39 | 40 | app.on('initError', console.error); 41 | app.on('childInitError', console.error); 42 | 43 | (global as any).app = app; 44 | export default app; 45 | -------------------------------------------------------------------------------- /src/controllers/AutomodController.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; 24 | import type Database from '../components/Database'; 25 | import AutomodEntity from '../entities/AutomodEntity'; 26 | 27 | export default class AutomodController { 28 | constructor(private database: Database) {} 29 | 30 | private get repository() { 31 | return this.database.connection.getRepository(AutomodEntity); 32 | } 33 | 34 | get(guildID: string) { 35 | return this.repository.findOne({ guildID }); 36 | } 37 | 38 | create(guildID: string) { 39 | // all automod is disabled by default 40 | const entry = new AutomodEntity(); 41 | entry.blacklistWords = []; 42 | entry.guildID = guildID; 43 | 44 | return this.repository.save(entry); 45 | } 46 | 47 | delete(guildID: string) { 48 | return this.repository.delete({ guildID }); 49 | } 50 | 51 | update(guildID: string, values: QueryDeepPartialEntity) { 52 | return this.database.connection 53 | .createQueryBuilder() 54 | .update(AutomodEntity) 55 | .set(values) 56 | .where('guild_id = :id', { id: guildID }) 57 | .execute(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/controllers/BlacklistController.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import BlacklistEntity, { BlacklistType } from '../entities/BlacklistEntity'; 24 | import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; 25 | import type Database from '../components/Database'; 26 | 27 | interface CreateBlacklistOptions { 28 | reason?: string; 29 | issuer: string; 30 | type: BlacklistType; 31 | id: string; 32 | } 33 | 34 | export default class BlacklistController { 35 | constructor(private database: Database) {} 36 | 37 | private get repository() { 38 | return this.database.connection.getRepository(BlacklistEntity); 39 | } 40 | 41 | get(id: string) { 42 | return this.repository.findOne({ id }); 43 | } 44 | 45 | getByType(type: BlacklistType) { 46 | return this.repository.find({ type }); 47 | } 48 | 49 | create({ reason, issuer, type, id }: CreateBlacklistOptions) { 50 | const entry = new BlacklistEntity(); 51 | entry.issuer = issuer; 52 | entry.type = type; 53 | entry.id = id; 54 | 55 | if (reason !== undefined) entry.reason = reason; 56 | 57 | return this.repository.save(entry); 58 | } 59 | 60 | delete(id: string) { 61 | return this.repository.delete({ id }); 62 | } 63 | 64 | update(id: string, values: QueryDeepPartialEntity) { 65 | return this.database.connection 66 | .createQueryBuilder() 67 | .update(BlacklistEntity) 68 | .set(values) 69 | .where('id = :id', { id }) 70 | .execute(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/controllers/CasesController.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; 24 | import { PunishmentType } from '../entities/PunishmentsEntity'; 25 | import type Database from '../components/Database'; 26 | import CaseEntity from '../entities/CaseEntity'; 27 | 28 | interface CreateCaseOptions { 29 | attachments: string[]; 30 | moderatorID: string; 31 | victimID: string; 32 | guildID: string; 33 | reason?: string; 34 | soft?: boolean; 35 | time?: number; 36 | type: PunishmentType; 37 | } 38 | 39 | export default class CasesController { 40 | constructor(private database: Database) {} 41 | 42 | get repository() { 43 | return this.database.connection.getRepository(CaseEntity); 44 | } 45 | 46 | get(guildID: string, caseID: number) { 47 | return this.repository.findOne({ guildID, index: caseID }); 48 | } 49 | 50 | getAll(guildID: string) { 51 | return this.repository.find({ guildID }); 52 | } 53 | 54 | async create({ attachments, moderatorID, victimID, guildID, reason, soft, time, type }: CreateCaseOptions) { 55 | const cases = await this.getAll(guildID); 56 | const index = (cases[cases.length - 1]?.index ?? 0) + 1; 57 | 58 | const entry = new CaseEntity(); 59 | entry.attachments = attachments; 60 | entry.moderatorID = moderatorID; 61 | entry.victimID = victimID; 62 | entry.guildID = guildID; 63 | entry.index = index; 64 | entry.soft = soft === true; // if it's undefined, then it'll be false so no ternaries :crab: 65 | entry.type = type; 66 | 67 | if (reason !== undefined) entry.reason = reason; 68 | 69 | if (time !== undefined) entry.time = String(time); 70 | 71 | return this.repository.save(entry); 72 | } 73 | 74 | update(guildID: string, index: number, values: QueryDeepPartialEntity) { 75 | return this.database.connection 76 | .createQueryBuilder() 77 | .update(CaseEntity) 78 | .set(values) 79 | .where('guild_id = :id', { id: guildID }) 80 | .andWhere('index = :idx', { idx: index }) 81 | .execute(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/controllers/GuildSettingsController.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; 24 | import type Database from '../components/Database'; 25 | import GuildEntity from '../entities/GuildEntity'; 26 | import { Inject } from '@augu/lilith'; 27 | import Config from '../components/Config'; 28 | 29 | export default class GuildSettingsController { 30 | @Inject 31 | private readonly config!: Config; 32 | 33 | constructor(private database: Database) {} 34 | 35 | get repository() { 36 | return this.database.connection.getRepository(GuildEntity); 37 | } 38 | 39 | get(id: string, create?: true): Promise; 40 | get(id: string, create?: false): Promise; 41 | async get(id: string, create: boolean = true) { 42 | const settings = await this.repository.findOne({ guildID: id }); 43 | if (settings === undefined && create) return this.create(id); 44 | 45 | return settings; 46 | } 47 | 48 | async create(id: string) { 49 | const entry = new GuildEntity(); 50 | entry.prefixes = this.config.getProperty('prefixes') ?? []; 51 | entry.language = 'en_US'; 52 | entry.guildID = id; 53 | 54 | await this.repository.save(entry); 55 | 56 | try { 57 | await this.database.logging.create(id); 58 | await this.database.automod.create(id); 59 | } catch { 60 | // swallow for now 61 | } 62 | 63 | return entry; 64 | } 65 | 66 | delete(id: string) { 67 | return this.repository.delete({ guildID: id }); 68 | } 69 | 70 | update(guildID: string, values: QueryDeepPartialEntity) { 71 | return this.database.connection 72 | .createQueryBuilder() 73 | .update(GuildEntity) 74 | .set(values) 75 | .where('guild_id = :id', { id: guildID }) 76 | .execute(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/controllers/LoggingController.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; 24 | import LoggingEntity from '../entities/LoggingEntity'; 25 | import type Database from '../components/Database'; 26 | 27 | export default class LoggingController { 28 | constructor(private database: Database) {} 29 | 30 | private get repository() { 31 | return this.database.connection.getRepository(LoggingEntity); 32 | } 33 | 34 | async get(guildID: string) { 35 | const entry = await this.repository.findOne({ guildID }); 36 | if (entry === undefined) return this.create(guildID); 37 | 38 | return entry; 39 | } 40 | 41 | create(guildID: string) { 42 | const entry = new LoggingEntity(); 43 | entry.ignoreChannels = []; 44 | entry.ignoreUsers = []; 45 | entry.enabled = false; 46 | entry.events = []; 47 | entry.guildID = guildID; 48 | 49 | return this.repository.save(entry); 50 | } 51 | 52 | update(guildID: string, values: QueryDeepPartialEntity) { 53 | return this.database.connection 54 | .createQueryBuilder() 55 | .update(LoggingEntity) 56 | .set(values) 57 | .where('guild_id = :id', { id: guildID }) 58 | .execute(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/controllers/PunishmentsController.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import PunishmentEntity, { PunishmentType } from '../entities/PunishmentsEntity'; 24 | import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; 25 | import Database from '../components/Database'; 26 | 27 | interface CreatePunishmentOptions { 28 | warnings: number; 29 | guildID: string; 30 | soft?: boolean; 31 | time?: number; 32 | days?: number; 33 | type: PunishmentType; 34 | } 35 | 36 | export default class PunishmentsController { 37 | constructor(private database: Database) {} 38 | 39 | private get repository() { 40 | return this.database.connection.getRepository(PunishmentEntity); 41 | } 42 | 43 | async create({ warnings, guildID, soft, time, days, type }: CreatePunishmentOptions) { 44 | const all = await this.getAll(guildID); 45 | const entry = new PunishmentEntity(); 46 | entry.warnings = warnings; 47 | entry.guildID = guildID; 48 | entry.index = all.length + 1; // increment by 1 49 | entry.type = type; 50 | 51 | if (soft !== undefined && soft === true) entry.soft = true; 52 | 53 | if (time !== undefined) entry.time = time; 54 | 55 | if (days !== undefined) entry.days = days; 56 | 57 | return this.repository.save(entry); 58 | } 59 | 60 | getAll(guildID: string) { 61 | return this.repository.find({ guildID }); 62 | } 63 | 64 | get(guildID: string, index: number) { 65 | return this.repository.findOne({ guildID, index }); 66 | } 67 | 68 | update(guildID: string, values: QueryDeepPartialEntity) { 69 | return this.database.connection 70 | .createQueryBuilder() 71 | .update(PunishmentEntity) 72 | .set(values) 73 | .where('guild_id = :id', { id: guildID }) 74 | .execute(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/controllers/UserSettingsController.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; 24 | import type Database from '../components/Database'; 25 | import UserEntity from '../entities/UserEntity'; 26 | 27 | export default class UserSettingsController { 28 | constructor(private database: Database) {} 29 | 30 | get repository() { 31 | return this.database.connection.getRepository(UserEntity); 32 | } 33 | 34 | async get(id: string) { 35 | const settings = await this.repository.findOne({ id }); 36 | if (settings === undefined) { 37 | const entry = new UserEntity(); 38 | entry.prefixes = []; 39 | entry.language = 'en_US'; 40 | entry.id = id; 41 | 42 | try { 43 | await this.repository.save(entry); 44 | } catch { 45 | // swallow the error for now 46 | } 47 | 48 | return entry; 49 | } 50 | 51 | return settings; 52 | } 53 | 54 | update(userID: string, values: QueryDeepPartialEntity) { 55 | return this.database.connection 56 | .createQueryBuilder() 57 | .update(UserEntity) 58 | .set(values) 59 | .where('user_id = :id', { id: userID }) 60 | .execute(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/controllers/WarningsController.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; 24 | import WarningEntity from '../entities/WarningsEntity'; 25 | import type Database from '../components/Database'; 26 | 27 | interface CreateWarningOptions { 28 | userID: string; 29 | guildID: string; 30 | reason?: string; 31 | amount: number; 32 | } 33 | 34 | export default class WarningsController { 35 | constructor(private database: Database) {} 36 | 37 | private get repository() { 38 | return this.database.connection.getRepository(WarningEntity); 39 | } 40 | 41 | get(guildID: string, userID: string) { 42 | return this.repository.findOne({ guildID, userID }); 43 | } 44 | 45 | getAll(guildID: string, userID?: string) { 46 | const filter = userID !== undefined ? { guildID, userID } : { guildID }; 47 | return this.repository.find(filter); 48 | } 49 | 50 | create({ guildID, userID, reason, amount }: CreateWarningOptions) { 51 | if (amount < 0) throw new RangeError('amount index out of bounds'); 52 | 53 | const entry = new WarningEntity(); 54 | entry.guildID = guildID; 55 | entry.reason = reason; 56 | entry.amount = amount; 57 | entry.userID = userID; 58 | 59 | return this.repository.save(entry); 60 | } 61 | 62 | update(guildID: string, userID: string, values: QueryDeepPartialEntity) { 63 | return this.database.connection 64 | .createQueryBuilder() 65 | .update(WarningEntity) 66 | .set(values) 67 | .where('guild_id = :id', { id: guildID }) 68 | .andWhere('user_id = :id', { id: userID }) 69 | .execute(); 70 | } 71 | 72 | clean(guildID: string, userID: string) { 73 | return this.repository.delete({ guildID, userID }); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/entities/AutomodEntity.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Entity, Column, PrimaryColumn } from 'typeorm'; 24 | 25 | @Entity({ name: 'automod' }) 26 | export default class AutomodEntity { 27 | @Column({ 28 | array: true, 29 | type: 'text', 30 | name: 'whitelist_channels_during_raid', 31 | default: '{}', 32 | }) 33 | public whitelistChannelsDuringRaid!: string[]; 34 | 35 | @Column({ array: true, type: 'text', name: 'blacklist_words', default: '{}' }) 36 | public blacklistWords!: string[]; 37 | 38 | @Column({ default: false, name: 'short_links' }) 39 | public shortLinks!: boolean; 40 | 41 | @Column({ default: false }) 42 | public blacklist!: boolean; 43 | 44 | @Column({ default: false }) 45 | public mentions!: boolean; 46 | 47 | @Column({ default: false }) 48 | public invites!: boolean; 49 | 50 | @Column({ default: false }) 51 | public dehoist!: boolean; 52 | 53 | @PrimaryColumn({ name: 'guild_id' }) 54 | public guildID!: string; 55 | 56 | @Column({ default: false }) 57 | public spam!: boolean; 58 | 59 | @Column({ default: false }) 60 | public raid!: boolean; 61 | } 62 | -------------------------------------------------------------------------------- /src/entities/BlacklistEntity.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Entity, PrimaryColumn, Column } from 'typeorm'; 24 | 25 | export enum BlacklistType { 26 | Guild, 27 | User, 28 | } 29 | 30 | @Entity({ name: 'blacklists' }) 31 | export default class BlacklistEntity { 32 | @Column({ nullable: true }) 33 | public reason?: string; 34 | 35 | @Column() 36 | public issuer!: string; 37 | 38 | @Column({ type: 'enum', enum: BlacklistType }) 39 | public type!: BlacklistType; 40 | 41 | @PrimaryColumn() 42 | public id!: string; 43 | } 44 | -------------------------------------------------------------------------------- /src/entities/CaseEntity.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Entity, Column, PrimaryColumn } from 'typeorm'; 24 | import { PunishmentType } from './PunishmentsEntity'; 25 | 26 | @Entity({ name: 'cases' }) 27 | export default class CaseEntity { 28 | @Column({ default: '{}', array: true, type: 'text' }) 29 | public attachments!: string[]; 30 | 31 | @Column({ name: 'moderator_id' }) 32 | public moderatorID!: string; 33 | 34 | @Column({ name: 'message_id', nullable: true, default: undefined }) 35 | public messageID?: string; 36 | 37 | @Column({ name: 'victim_id' }) 38 | public victimID!: string; 39 | 40 | @PrimaryColumn({ name: 'guild_id' }) 41 | public guildID!: string; 42 | 43 | @Column({ nullable: true, default: undefined }) 44 | public reason?: string; 45 | 46 | @PrimaryColumn() 47 | public index!: number; 48 | 49 | @Column({ 50 | type: 'enum', 51 | enum: PunishmentType, 52 | }) 53 | public type!: PunishmentType; 54 | 55 | @Column({ default: false }) 56 | public soft!: boolean; 57 | 58 | @Column({ nullable: true, default: undefined, type: 'bigint' }) 59 | public time?: string; 60 | } 61 | -------------------------------------------------------------------------------- /src/entities/GuildEntity.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Entity, Column, PrimaryColumn } from 'typeorm'; 24 | 25 | @Entity({ name: 'guilds' }) 26 | export default class GuildEntity { 27 | @Column({ default: null, nullable: true, name: 'modlog_channel_id' }) 28 | public modlogChannelID?: string; 29 | 30 | @Column({ default: null, nullable: true, name: 'muted_role_id' }) 31 | public mutedRoleID?: string; 32 | 33 | @Column({ array: true, type: 'text' }) 34 | public prefixes!: string[]; 35 | 36 | @Column({ default: 'en_US' }) 37 | public language!: string; 38 | 39 | @PrimaryColumn({ name: 'guild_id' }) 40 | public guildID!: string; 41 | } 42 | -------------------------------------------------------------------------------- /src/entities/LoggingEntity.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Entity, Column, PrimaryColumn } from 'typeorm'; 24 | 25 | export enum LoggingEvents { 26 | VoiceMemberDeafened = 'voice_member_deafened', 27 | VoiceChannelSwitch = 'voice_channel_switch', 28 | VoiceMemberMuted = 'voice_member_muted', 29 | VoiceChannelLeft = 'voice_channel_left', 30 | VoiceChannelJoin = 'voice_channel_join', 31 | MessageDeleted = 'message_delete', 32 | MessageUpdated = 'message_update', 33 | } 34 | 35 | @Entity({ name: 'logging' }) 36 | export default class LoggingEntity { 37 | @Column({ default: '{}', array: true, type: 'text', name: 'ignore_channels' }) 38 | public ignoreChannels!: string[]; 39 | 40 | @Column({ default: '{}', array: true, type: 'text', name: 'ignore_users' }) 41 | public ignoreUsers!: string[]; 42 | 43 | @Column({ name: 'channel_id', nullable: true }) 44 | public channelID?: string; 45 | 46 | @Column({ default: false }) 47 | public enabled!: boolean; 48 | 49 | @Column({ type: 'enum', array: true, enum: LoggingEvents, default: '{}' }) 50 | public events!: LoggingEvents[]; 51 | 52 | @PrimaryColumn({ name: 'guild_id' }) 53 | public guildID!: string; 54 | } 55 | -------------------------------------------------------------------------------- /src/entities/PunishmentsEntity.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Entity, Column, PrimaryGeneratedColumn, PrimaryColumn } from 'typeorm'; 24 | 25 | export enum PunishmentType { 26 | WarningRemoved = 'warning.removed', 27 | VoiceUndeafen = 'voice.undeafen', 28 | WarningAdded = 'warning.added', 29 | VoiceUnmute = 'voice.unmute', 30 | VoiceDeafen = 'voice.deafen', 31 | VoiceMute = 'voice.mute', 32 | Unmute = 'unmute', 33 | Unban = 'unban', 34 | Kick = 'kick', 35 | Mute = 'mute', 36 | Ban = 'ban', 37 | } 38 | 39 | @Entity({ name: 'punishments' }) 40 | export default class PunishmentEntity { 41 | @Column({ default: 1 }) 42 | public warnings!: number; 43 | 44 | @PrimaryColumn({ name: 'guild_id' }) 45 | public guildID!: string; 46 | 47 | @PrimaryColumn() 48 | public index!: number; 49 | 50 | @Column({ default: false }) 51 | public soft!: boolean; 52 | 53 | @Column({ default: undefined, nullable: true }) 54 | public time?: number; 55 | 56 | @Column({ default: undefined, nullable: true }) 57 | public days?: number; 58 | 59 | @Column({ 60 | type: 'enum', 61 | enum: PunishmentType, 62 | }) 63 | public type!: PunishmentType; 64 | } 65 | -------------------------------------------------------------------------------- /src/entities/UserEntity.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Entity, Column, PrimaryColumn } from 'typeorm'; 24 | 25 | @Entity({ name: 'users' }) 26 | export default class UserEntity { 27 | @Column({ default: 'en_US' }) 28 | public language!: string; 29 | 30 | @Column({ array: true, type: 'text' }) 31 | public prefixes!: string[]; 32 | 33 | @PrimaryColumn({ name: 'user_id' }) 34 | public id!: string; 35 | } 36 | -------------------------------------------------------------------------------- /src/entities/WarningsEntity.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Entity, Column, PrimaryColumn, PrimaryGeneratedColumn } from 'typeorm'; 24 | 25 | @Entity({ name: 'warnings' }) 26 | export default class WarningsEntity { 27 | @PrimaryColumn({ name: 'guild_id' }) 28 | public guildID!: string; 29 | 30 | @Column({ default: undefined, nullable: true }) 31 | public reason?: string; 32 | 33 | @Column({ default: 0 }) 34 | public amount!: number; 35 | 36 | @Column({ name: 'user_id' }) 37 | public userID!: string; 38 | 39 | @PrimaryGeneratedColumn() 40 | public id!: number; 41 | } 42 | -------------------------------------------------------------------------------- /src/listeners/UserListener.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Inject, Subscribe } from '@augu/lilith'; 24 | import AutomodService from '../services/AutomodService'; 25 | import type { User } from 'eris'; 26 | import Database from '../components/Database'; 27 | import Discord from '../components/Discord'; 28 | 29 | export default class UserListener { 30 | @Inject 31 | private readonly database!: Database; 32 | 33 | @Inject 34 | private readonly discord!: Discord; 35 | 36 | @Inject 37 | private readonly automod!: AutomodService; 38 | 39 | @Subscribe('userUpdate', { emitter: 'discord' }) 40 | async onUserUpdate(user: User) { 41 | const mutualGuilds = this.discord.client.guilds.filter((guild) => guild.members.has(user.id)); 42 | 43 | for (const guild of mutualGuilds) { 44 | const automod = await this.database.automod.get(guild.id); 45 | if (!automod) continue; 46 | 47 | if (automod.dehoist === true) await this.automod.run('memberNick', guild.members.get(user.id)!); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import 'source-map-support/register'; 24 | import 'reflect-metadata'; 25 | 26 | (require('@augu/dotenv') as typeof import('@augu/dotenv')).parse({ 27 | populate: true, 28 | delimiter: ',', 29 | file: require('path').join(process.cwd(), '..', '.env'), 30 | schema: { 31 | NODE_ENV: { 32 | oneOf: ['production', 'development'], 33 | default: 'development', 34 | type: 'string', 35 | }, 36 | }, 37 | }); 38 | 39 | import { commitHash, version } from './util/Constants'; 40 | import Discord from './components/Discord'; 41 | import Sentry from './components/Sentry'; 42 | import logger from './singletons/Logger'; 43 | import app from './container'; 44 | import Api from './api/API'; 45 | import ts from 'typescript'; 46 | 47 | (async () => { 48 | logger.info(`Loading Nino v${version} (${commitHash ?? ''})`); 49 | logger.info(`-> TypeScript: ${ts.version}`); 50 | logger.info(`-> Node.js: ${process.version}`); 51 | if (process.env.REGION !== undefined) logger.info(`-> Region: ${process.env.REGION}`); 52 | 53 | try { 54 | // call patch before container load 55 | await import('./util/ErisPatch'); 56 | await app.load(); 57 | await app.addComponent(Api); 58 | } catch (ex) { 59 | logger.fatal('Unable to load container'); 60 | console.error(ex); 61 | process.exit(1); 62 | } 63 | 64 | logger.info('✔ Nino has started successfully'); 65 | process.on('SIGINT', () => { 66 | logger.warn('Received CTRL+C call!'); 67 | 68 | app.dispose(); 69 | process.exit(0); 70 | }); 71 | })(); 72 | 73 | const ReconnectCodes = [ 74 | 1001, // Going Away (re-connect now) 75 | 1006, // Connection reset by peer 76 | ]; 77 | 78 | const OtherPossibleReconnectCodes = [ 79 | 'WebSocket was closed before the connection was established', 80 | "Server didn't acknowledge previous heartbeat, possible lost connection", 81 | ]; 82 | 83 | process.on('unhandledRejection', (error) => { 84 | const sentry = app.$ref(Sentry); 85 | if (error !== null || error !== undefined) { 86 | logger.fatal('Received unhandled Promise rejection:', error); 87 | if (error instanceof Error) sentry?.report(error); 88 | } 89 | }); 90 | 91 | process.on('uncaughtException', async (error) => { 92 | const sentry = app.$ref(Sentry); 93 | 94 | if ((error as any).code !== undefined) { 95 | if (ReconnectCodes.includes((error as any).code) || OtherPossibleReconnectCodes.includes(error.message)) { 96 | logger.fatal('Disconnected due to peer to peer connection ended, restarting client...'); 97 | 98 | const discord = app.$ref(Discord); 99 | discord.client.disconnect({ reconnect: false }); 100 | await discord.client.connect(); 101 | } 102 | } else { 103 | sentry?.report(error); 104 | logger.fatal('Uncaught exception has occured\n', error); 105 | } 106 | }); 107 | -------------------------------------------------------------------------------- /src/migrations/1617402812079-firstMigration.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { MigrationInterface, QueryRunner } from 'typeorm'; 24 | 25 | export class firstMigration1617402812079 implements MigrationInterface { 26 | name = 'firstMigration1617402812079'; 27 | 28 | public async up(queryRunner: QueryRunner): Promise { 29 | await queryRunner.query('ALTER TABLE "automod" ADD "shortLinks" boolean NOT NULL DEFAULT false'); 30 | await queryRunner.query('ALTER TYPE "logging_events_enum" RENAME TO "logging_events_enum_old"'); 31 | await queryRunner.query( 32 | "CREATE TYPE \"logging_events_enum\" AS ENUM('voice_channel_switch', 'voice_channel_left', 'voice_channel_join', 'message_delete', 'message_update')" 33 | ); 34 | await queryRunner.query('ALTER TABLE "logging" ALTER COLUMN "events" DROP DEFAULT'); 35 | await queryRunner.query( 36 | 'ALTER TABLE "logging" ALTER COLUMN "events" TYPE "logging_events_enum"[] USING "events"::"text"::"logging_events_enum"[]' 37 | ); 38 | await queryRunner.query('ALTER TABLE "logging" ALTER COLUMN "events" SET DEFAULT \'{}\''); 39 | await queryRunner.query('DROP TYPE "logging_events_enum_old"'); 40 | } 41 | 42 | public async down(queryRunner: QueryRunner): Promise { 43 | await queryRunner.query( 44 | "CREATE TYPE \"logging_events_enum_old\" AS ENUM('voice_channel_switch', 'voice_channel_left', 'voice_channel_join', 'message_delete', 'message_update', 'settings_update')" 45 | ); 46 | await queryRunner.query('ALTER TABLE "logging" ALTER COLUMN "events" DROP DEFAULT'); 47 | await queryRunner.query( 48 | 'ALTER TABLE "logging" ALTER COLUMN "events" TYPE "logging_events_enum_old"[] USING "events"::"text"::"logging_events_enum_old"[]' 49 | ); 50 | await queryRunner.query('ALTER TABLE "logging" ALTER COLUMN "events" SET DEFAULT \'{}\''); 51 | await queryRunner.query('DROP TYPE "logging_events_enum"'); 52 | await queryRunner.query('ALTER TYPE "logging_events_enum_old" RENAME TO "logging_events_enum"'); 53 | await queryRunner.query('ALTER TABLE "automod" DROP COLUMN "shortLinks"'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/migrations/1618173354506-fixPrimaryColumnInWarnings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { MigrationInterface, QueryRunner } from 'typeorm'; 24 | 25 | export class fixPrimaryColumnInWarnings1618173354506 implements MigrationInterface { 26 | name = 'fixPrimaryColumnInWarnings1618173354506'; 27 | 28 | public async up(queryRunner: QueryRunner): Promise { 29 | await queryRunner.query('ALTER TABLE "warnings" DROP CONSTRAINT "PK_7a14eba00a6aaf0dc04f76aff02"'); 30 | await queryRunner.query( 31 | 'ALTER TABLE "warnings" ADD CONSTRAINT "PK_cb17dc8ac1439c8d9bfb89ea41a" PRIMARY KEY ("user_id", "guild_id")' 32 | ); 33 | await queryRunner.query('ALTER TABLE "warnings" DROP CONSTRAINT "PK_cb17dc8ac1439c8d9bfb89ea41a"'); 34 | await queryRunner.query( 35 | 'ALTER TABLE "warnings" ADD CONSTRAINT "PK_acfe1e5e5e9ba6b9b0fa3f591fa" PRIMARY KEY ("guild_id")' 36 | ); 37 | } 38 | 39 | public async down(queryRunner: QueryRunner): Promise { 40 | await queryRunner.query('ALTER TABLE "warnings" DROP CONSTRAINT "PK_acfe1e5e5e9ba6b9b0fa3f591fa"'); 41 | await queryRunner.query( 42 | 'ALTER TABLE "warnings" ADD CONSTRAINT "PK_cb17dc8ac1439c8d9bfb89ea41a" PRIMARY KEY ("guild_id", "user_id")' 43 | ); 44 | await queryRunner.query('ALTER TABLE "warnings" DROP CONSTRAINT "PK_cb17dc8ac1439c8d9bfb89ea41a"'); 45 | await queryRunner.query( 46 | 'ALTER TABLE "warnings" ADD CONSTRAINT "PK_7a14eba00a6aaf0dc04f76aff02" PRIMARY KEY ("user_id")' 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/migrations/1618173954276-addIdPropToWarnings.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { MigrationInterface, QueryRunner } from 'typeorm'; 24 | 25 | export class addIdPropToWarnings1618173954276 implements MigrationInterface { 26 | name = 'addIdPropToWarnings1618173954276'; 27 | 28 | public async up(queryRunner: QueryRunner): Promise { 29 | await queryRunner.query('ALTER TABLE "warnings" ADD "id" SERIAL NOT NULL'); 30 | await queryRunner.query('ALTER TABLE "warnings" DROP CONSTRAINT "PK_acfe1e5e5e9ba6b9b0fa3f591fa"'); 31 | await queryRunner.query( 32 | 'ALTER TABLE "warnings" ADD CONSTRAINT "PK_1a1c969d7e8d8aad2231021420f" PRIMARY KEY ("guild_id", "id")' 33 | ); 34 | } 35 | 36 | public async down(queryRunner: QueryRunner): Promise { 37 | await queryRunner.query('ALTER TABLE "warnings" DROP CONSTRAINT "PK_1a1c969d7e8d8aad2231021420f"'); 38 | await queryRunner.query( 39 | 'ALTER TABLE "warnings" ADD CONSTRAINT "PK_acfe1e5e5e9ba6b9b0fa3f591fa" PRIMARY KEY ("guild_id")' 40 | ); 41 | await queryRunner.query('ALTER TABLE "warnings" DROP COLUMN "id"'); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/migrations/1618174668865-snakeCaseColumnNames.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { MigrationInterface, QueryRunner } from 'typeorm'; 24 | 25 | export class snakeCaseColumnNames1618174668865 implements MigrationInterface { 26 | name = 'snakeCaseColumnNames1618174668865'; 27 | 28 | public async up(queryRunner: QueryRunner): Promise { 29 | await queryRunner.query('ALTER TABLE "automod" DROP COLUMN "blacklistWords"'); 30 | await queryRunner.query('ALTER TABLE "automod" DROP COLUMN "shortLinks"'); 31 | await queryRunner.query('ALTER TABLE "logging" DROP COLUMN "ignoreChannels"'); 32 | await queryRunner.query('ALTER TABLE "logging" DROP COLUMN "ignoreUsers"'); 33 | await queryRunner.query('ALTER TABLE "automod" ADD "blacklist_words" text array NOT NULL DEFAULT \'{}\''); 34 | await queryRunner.query('ALTER TABLE "automod" ADD "short_links" boolean NOT NULL DEFAULT false'); 35 | await queryRunner.query('ALTER TABLE "logging" ADD "ignore_channels" text array NOT NULL DEFAULT \'{}\''); 36 | await queryRunner.query('ALTER TABLE "logging" ADD "ignore_users" text array NOT NULL DEFAULT \'{}\''); 37 | } 38 | 39 | public async down(queryRunner: QueryRunner): Promise { 40 | await queryRunner.query('ALTER TABLE "logging" DROP COLUMN "ignore_users"'); 41 | await queryRunner.query('ALTER TABLE "logging" DROP COLUMN "ignore_channels"'); 42 | await queryRunner.query('ALTER TABLE "automod" DROP COLUMN "short_links"'); 43 | await queryRunner.query('ALTER TABLE "automod" DROP COLUMN "blacklist_words"'); 44 | await queryRunner.query('ALTER TABLE "logging" ADD "ignoreUsers" text array NOT NULL DEFAULT \'{}\''); 45 | await queryRunner.query('ALTER TABLE "logging" ADD "ignoreChannels" text array NOT NULL DEFAULT \'{}\''); 46 | await queryRunner.query('ALTER TABLE "automod" ADD "shortLinks" boolean NOT NULL DEFAULT false'); 47 | await queryRunner.query('ALTER TABLE "automod" ADD "blacklistWords" text array NOT NULL'); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/migrations/1621720227973-addNewLogTypes.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { MigrationInterface, QueryRunner } from 'typeorm'; 24 | import { LoggingEvents } from '../entities/LoggingEntity'; 25 | 26 | const NewTypes = ['voice_member_muted', 'voice_member_deafened']; 27 | 28 | export class addNewLogTypes1621720227973 implements MigrationInterface { 29 | name = 'addNewLogTypes1621720227973'; 30 | 31 | public async up(runner: QueryRunner) { 32 | await runner.query('ALTER TYPE "logging_events_enum" RENAME TO "logging_events_enum_old";'); 33 | await runner.query( 34 | `CREATE TYPE "logging_events_enum" AS ENUM(${Object.values(LoggingEvents) 35 | .map((value) => `'${value}'`) 36 | .join(', ')});` 37 | ); 38 | await runner.query('ALTER TABLE "logging" ALTER COLUMN "events" DROP DEFAULT;'); 39 | await runner.query( 40 | 'ALTER TABLE "logging" ALTER COLUMN "events" TYPE "logging_events_enum"[] USING "events"::"text"::"logging_events_enum"[];' 41 | ); 42 | await runner.query('ALTER TABLE "logging" ALTER COLUMN "events" SET DEFAULT \'{}\';'); 43 | await runner.query('DROP TYPE "logging_events_enum_old";'); 44 | } 45 | 46 | public async down(runner: QueryRunner) { 47 | await runner.query( 48 | `CREATE TYPE "logging_events_enum_old" AS ENUM(${Object.values(LoggingEvents) 49 | .filter((v) => !NewTypes.includes(v)) 50 | .map((value) => `'${value}'`) 51 | .join(', ')});` 52 | ); 53 | await runner.query('ALTER TABLE "logging" ALTER COLUMN "events" DROP DEFAULT;'); 54 | await runner.query( 55 | 'ALTER TABLE "logging" ALTER COLUMN "events" TYPE "logging_events_enum_old"[] USING "events"::"text"::"logging_events_enum_old"[];' 56 | ); 57 | await runner.query('DROP TYPE "logging_events_enum";'); 58 | await runner.query('ALTER TYPE "logging_events_enum_old" RENAME TO "logging_events_enum";'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/migrations/1621895533962-fixCaseTimeType.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { MigrationInterface, QueryRunner } from 'typeorm'; 24 | 25 | export class fixCaseTimeType1621895533962 implements MigrationInterface { 26 | name = 'fixCaseTimeType1621895533962'; 27 | 28 | public async up(queryRunner: QueryRunner): Promise { 29 | await queryRunner.query('ALTER TABLE "cases" DROP COLUMN "time"'); 30 | await queryRunner.query('ALTER TABLE "cases" ADD "time" bigint'); 31 | } 32 | 33 | public async down(queryRunner: QueryRunner): Promise { 34 | await queryRunner.query('ALTER TABLE "cases" DROP COLUMN "time"'); 35 | await queryRunner.query('ALTER TABLE "cases" ADD "time" integer'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/migrations/1622346188448-addAttachmentColumn.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { MigrationInterface, QueryRunner } from 'typeorm'; 24 | 25 | export class addAttachmentColumn1622346188448 implements MigrationInterface { 26 | name = 'addAttachmentColumn1622346188448'; 27 | 28 | public async up(queryRunner: QueryRunner): Promise { 29 | await queryRunner.query('ALTER TABLE "cases" ADD "attachments" text array NOT NULL DEFAULT \'{}\''); 30 | } 31 | 32 | public async down(queryRunner: QueryRunner): Promise { 33 | await queryRunner.query('ALTER TABLE "cases" DROP COLUMN "attachments"'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/migrations/1625456992070-fixPunishmentIndex.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { MigrationInterface, QueryRunner } from 'typeorm'; 24 | 25 | export class fixPunishmentIndex1625456992070 implements MigrationInterface { 26 | name = 'fixPunishmentIndex1625456992070'; 27 | 28 | public async up(queryRunner: QueryRunner): Promise { 29 | await queryRunner.query('ALTER TABLE "punishments" ALTER COLUMN "index" DROP DEFAULT'); 30 | await queryRunner.query('DROP SEQUENCE "punishments_index_seq"'); 31 | await queryRunner.query('ALTER TYPE "punishments_type_enum" RENAME TO "punishments_type_enum_old"'); 32 | await queryRunner.query( 33 | "CREATE TYPE \"punishments_type_enum\" AS ENUM('warning.removed', 'voice.undeafen', 'warning.added', 'voice.unmute', 'voice.deafen', 'voice.mute', 'unmute', 'unban', 'kick', 'mute', 'ban')" 34 | ); 35 | await queryRunner.query( 36 | 'ALTER TABLE "punishments" ALTER COLUMN "type" TYPE "punishments_type_enum" USING "type"::"text"::"punishments_type_enum"' 37 | ); 38 | await queryRunner.query('DROP TYPE "punishments_type_enum_old"'); 39 | await queryRunner.query('ALTER TYPE "cases_type_enum" RENAME TO "cases_type_enum_old"'); 40 | await queryRunner.query( 41 | "CREATE TYPE \"cases_type_enum\" AS ENUM('warning.removed', 'voice.undeafen', 'warning.added', 'voice.unmute', 'voice.deafen', 'voice.mute', 'unmute', 'unban', 'kick', 'mute', 'ban')" 42 | ); 43 | await queryRunner.query( 44 | 'ALTER TABLE "cases" ALTER COLUMN "type" TYPE "cases_type_enum" USING "type"::"text"::"cases_type_enum"' 45 | ); 46 | await queryRunner.query('DROP TYPE "cases_type_enum_old"'); 47 | } 48 | 49 | public async down(queryRunner: QueryRunner): Promise { 50 | await queryRunner.query( 51 | "CREATE TYPE \"cases_type_enum_old\" AS ENUM('warning.removed', 'voice.undeafen', 'warning.added', 'voice.unmute', 'voice.deafen', 'voice.kick', 'voice.mute', 'unmute', 'unban', 'kick', 'mute', 'ban')" 52 | ); 53 | await queryRunner.query( 54 | 'ALTER TABLE "cases" ALTER COLUMN "type" TYPE "cases_type_enum_old" USING "type"::"text"::"cases_type_enum_old"' 55 | ); 56 | await queryRunner.query('DROP TYPE "cases_type_enum"'); 57 | await queryRunner.query('ALTER TYPE "cases_type_enum_old" RENAME TO "cases_type_enum"'); 58 | await queryRunner.query( 59 | "CREATE TYPE \"punishments_type_enum_old\" AS ENUM('warning.removed', 'voice.undeafen', 'warning.added', 'voice.unmute', 'voice.deafen', 'voice.kick', 'voice.mute', 'unmute', 'unban', 'kick', 'mute', 'ban')" 60 | ); 61 | await queryRunner.query( 62 | 'ALTER TABLE "punishments" ALTER COLUMN "type" TYPE "punishments_type_enum_old" USING "type"::"text"::"punishments_type_enum_old"' 63 | ); 64 | await queryRunner.query('DROP TYPE "punishments_type_enum"'); 65 | await queryRunner.query('ALTER TYPE "punishments_type_enum_old" RENAME TO "punishments_type_enum"'); 66 | await queryRunner.query('CREATE SEQUENCE "punishments_index_seq" OWNED BY "punishments"."index"'); 67 | await queryRunner.query( 68 | 'ALTER TABLE "punishments" ALTER COLUMN "index" SET DEFAULT nextval(\'punishments_index_seq\')' 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/services/AutomodService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { ComponentOrServiceHooks, Inject, Service } from '@augu/lilith'; 24 | import type { Member, Message, TextChannel, User } from 'eris'; 25 | import type { Automod } from '../structures'; 26 | import { Collection } from '@augu/collections'; 27 | import { Logger } from 'tslog'; 28 | import { join } from 'path'; 29 | 30 | @Service({ 31 | priority: 1, 32 | children: join(process.cwd(), 'automod'), 33 | name: 'automod', 34 | }) 35 | export default class AutomodService extends Collection implements ComponentOrServiceHooks { 36 | @Inject 37 | private logger!: Logger; 38 | 39 | onChildLoad(automod: Automod) { 40 | this.logger.info(`✔ Loaded automod ${automod.name}!`); 41 | this.set(automod.name, automod); 42 | } 43 | 44 | run(type: 'userUpdate', user: User): Promise; 45 | run(type: 'memberNick', member: Member): Promise; 46 | run(type: 'memberJoin', member: Member): Promise; 47 | run(type: 'message', msg: Message): Promise; 48 | async run(type: string, ...args: any[]) { 49 | switch (type) { 50 | case 'userUpdate': { 51 | const automod = this.filter((am) => am.onUserUpdate !== undefined); 52 | for (const am of automod) { 53 | const res = await am.onUserUpdate!(args[0]); 54 | if (res === true) return true; 55 | } 56 | 57 | return false; 58 | } 59 | 60 | case 'memberNick': { 61 | const automod = this.filter((am) => am.onMemberNickUpdate !== undefined); 62 | for (const am of automod) { 63 | const res = await am.onMemberNickUpdate!(args[0]); 64 | if (res === true) return true; 65 | } 66 | 67 | return false; 68 | } 69 | 70 | case 'memberJoin': { 71 | const automod = this.filter((am) => am.onMemberJoin !== undefined); 72 | for (const am of automod) { 73 | const res = await am.onMemberJoin!(args[0]); 74 | if (res === true) return true; 75 | } 76 | 77 | return false; 78 | } 79 | 80 | case 'message': { 81 | const automod = this.filter((am) => am.onMessage !== undefined); 82 | for (const am of automod) { 83 | const res = await am.onMessage!(args[0]); 84 | if (res === true) return true; 85 | } 86 | 87 | return false; 88 | } 89 | 90 | default: 91 | return true; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/services/ListenerService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Service } from '@augu/lilith'; 24 | import { join } from 'path'; 25 | 26 | @Service({ 27 | priority: 0, 28 | children: join(process.cwd(), 'listeners'), 29 | name: 'listeners', 30 | }) 31 | // a noop service to register all listeners 32 | export default class ListenerService {} 33 | -------------------------------------------------------------------------------- /src/services/LocalizationService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Service, Inject } from '@augu/lilith'; 24 | import { Collection } from '@augu/collections'; 25 | import { readdir } from '@augu/utils'; 26 | import { Logger } from 'tslog'; 27 | import { join } from 'path'; 28 | import Locale from '../structures/Locale'; 29 | import Config from '../components/Config'; 30 | import { readFileSync } from 'fs'; 31 | 32 | @Service({ 33 | priority: 1, 34 | name: 'localization', 35 | }) 36 | export default class LocalizationService { 37 | public defaultLocale!: Locale; 38 | public locales: Collection = new Collection(); 39 | 40 | @Inject 41 | private readonly logger!: Logger; 42 | 43 | @Inject 44 | private readonly config!: Config; 45 | 46 | async load() { 47 | this.logger.info('Loading in localization files...'); 48 | 49 | const directory = join(process.cwd(), '..', 'locales'); 50 | const files = await readdir(directory); 51 | 52 | if (!files.length) { 53 | this.logger.fatal('Missing localization files, did you clone the wrong commit?'); 54 | process.exit(1); 55 | } 56 | 57 | for (let i = 0; i < files.length; i++) { 58 | const contents = readFileSync(files[i], 'utf-8'); 59 | const lang = JSON.parse>(contents); 60 | 61 | this.logger.info(`✔ Found language ${lang.meta.full} (${lang.meta.code}) by ${lang.meta.translator}`); 62 | this.locales.set(lang.meta.code, new Locale(lang as { meta: LocalizationMeta; strings: LocalizationStrings })); 63 | } 64 | 65 | const defaultLocale = this.config.getProperty('defaultLocale') ?? 'en_US'; 66 | this.logger.info(`Default localization language was set to ${defaultLocale}, applying...`); 67 | 68 | const locale = this.locales.find((locale) => locale.code === defaultLocale); 69 | if (locale === null) { 70 | this.logger.fatal(`Localization "${defaultLocale}" was not found, defaulting to en_US...`); 71 | this.defaultLocale = this.locales.get('en_US')!; 72 | 73 | this.logger.warn( 74 | `Due to locale "${defaultLocale}" not being found and want to translate, read up on our translating guide:` 75 | ); 76 | } else { 77 | this.logger.info(`Localization "${defaultLocale}" was found!`); 78 | this.defaultLocale = locale; 79 | } 80 | } 81 | 82 | /** 83 | * Gets the localization for the [CommandService], determined by the [guild] and [user]'s locale. 84 | * @param guild The guild's localization code 85 | * @param user The user's localization code 86 | */ 87 | get(guild: string, user: string) { 88 | // this shouldn't happen but you never know 89 | if (!this.locales.has(guild) || !this.locales.has(user)) return this.defaultLocale; 90 | 91 | // committing yanderedev over here 92 | if (user === this.defaultLocale.code && guild === this.defaultLocale.code) return this.defaultLocale; 93 | else if (user !== this.defaultLocale.code && guild === this.defaultLocale.code) return this.locales.get(user)!; 94 | else if (guild !== this.defaultLocale.code && user === this.defaultLocale.code) return this.locales.get(guild)!; 95 | else return this.defaultLocale; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/singletons/Http.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { HttpClient } from '@augu/orchid'; 24 | import { version } from '../util/Constants'; 25 | 26 | export default new HttpClient({ 27 | userAgent: `Nino (v${version}, https://github.com/NinoDiscord/Nino)`, 28 | }); 29 | -------------------------------------------------------------------------------- /src/singletons/Logger.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { hostname } from 'os'; 24 | import { Logger } from 'tslog'; 25 | 26 | const logger = new Logger({ 27 | displayFunctionName: false, 28 | exposeErrorCodeFrame: true, 29 | displayInstanceName: true, 30 | dateTimePattern: '[hour:minute:second @ day/month/year]', 31 | displayFilePath: 'hideNodeModulesOnly', 32 | displayTypes: false, 33 | instanceName: hostname(), 34 | minLevel: process.env.NODE_ENV === 'production' ? 'info' : 'silly', 35 | name: 'Nino', 36 | }); 37 | 38 | export default logger; 39 | -------------------------------------------------------------------------------- /src/structures/Automod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type { Member, Message, TextChannel, User } from 'eris'; 24 | 25 | /** 26 | * Interface to implement as a [Automod] action. 27 | */ 28 | export interface Automod { 29 | /** 30 | * Handles any member's nickname updates 31 | * @param member The member 32 | */ 33 | onMemberNickUpdate?(member: Member): Promise; 34 | 35 | /** 36 | * Handles any user updates 37 | */ 38 | onUserUpdate?(user: User): Promise; 39 | 40 | /** 41 | * Handles any members joining the guild 42 | * @param member The member 43 | */ 44 | onMemberJoin?(member: Member): Promise; 45 | 46 | /** 47 | * Handles any message updates or creation 48 | * @param message The message 49 | */ 50 | onMessage?(message: Message): Promise; 51 | 52 | /** 53 | * The name for this [Automod] class. 54 | */ 55 | name: string; 56 | } 57 | -------------------------------------------------------------------------------- /src/structures/Command.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { getSubcommandsIn } from './decorators/Subcommand'; 24 | import type CommandMessage from './CommandMessage'; 25 | import type { Constants } from 'eris'; 26 | import { Categories } from '../util/Constants'; 27 | import Subcommand from './Subcommand'; 28 | 29 | export type PermissionField = keyof Constants['Permissions']; 30 | interface CommandInfo { 31 | userPermissions?: PermissionField | PermissionField[]; 32 | botPermissions?: PermissionField | PermissionField[]; 33 | description?: string; 34 | ownerOnly?: boolean; 35 | examples?: string[]; 36 | category?: Categories; 37 | cooldown?: number; 38 | aliases?: string[]; 39 | hidden?: boolean; 40 | usage?: string; 41 | name: string; 42 | } 43 | 44 | export default abstract class NinoCommand { 45 | public userPermissions: PermissionField[]; 46 | public botPermissions: PermissionField[]; 47 | public description: ObjectKeysWithSeperator; 48 | public ownerOnly: boolean; 49 | public examples: string[]; 50 | public category: Categories; 51 | public cooldown: number; 52 | public aliases: string[]; 53 | public hidden: boolean; 54 | public usage: string; 55 | public name: string; 56 | 57 | constructor(info: CommandInfo) { 58 | this.userPermissions = 59 | typeof info.userPermissions === 'string' 60 | ? [info.userPermissions] 61 | : Array.isArray(info.userPermissions) 62 | ? info.userPermissions 63 | : []; 64 | 65 | this.botPermissions = 66 | typeof info.botPermissions === 'string' 67 | ? [info.botPermissions] 68 | : Array.isArray(info.botPermissions) 69 | ? info.botPermissions 70 | : []; 71 | 72 | this.description = 73 | (info.description as unknown as ObjectKeysWithSeperator) ?? 'descriptions.unknown'; 74 | this.ownerOnly = info.ownerOnly ?? false; 75 | this.examples = info.examples ?? []; 76 | this.category = info.category ?? Categories.Core; 77 | this.cooldown = info.cooldown ?? 5; 78 | this.aliases = info.aliases ?? []; 79 | this.hidden = info.hidden ?? false; 80 | this.usage = info.usage ?? ''; 81 | this.name = info.name; 82 | } 83 | 84 | get subcommands() { 85 | return getSubcommandsIn(this).map((sub) => new Subcommand(sub)); 86 | } 87 | 88 | get format() { 89 | const subcommands = this.subcommands.map((sub) => `[${sub.name} ${sub.usage.trim()}]`.trim()).join(' | '); 90 | return `${this.name}${this.usage !== '' ? ` ${this.usage.trim()}` : ''} ${subcommands}`; 91 | } 92 | 93 | abstract run(msg: CommandMessage, ...args: any[]): any; 94 | } 95 | -------------------------------------------------------------------------------- /src/structures/Locale.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { isObject } from '@augu/utils'; 24 | 25 | const NOT_FOUND_SYMBOL = Symbol.for('$nino::localization::not_found'); 26 | 27 | interface Localization { 28 | meta: LocalizationMeta; 29 | strings: LocalizationStrings; 30 | } 31 | 32 | const KEY_REGEX = /[$]\{([\w\.]+)\}/g; 33 | 34 | export default class Locale { 35 | public contributors: string[]; 36 | public translator: string; 37 | public aliases: string[]; 38 | public flag: string; 39 | public full: string; 40 | public code: string; 41 | 42 | private strings: LocalizationStrings; 43 | 44 | constructor({ meta, strings }: Localization) { 45 | this.contributors = meta.contributors; 46 | this.translator = meta.translator; 47 | this.strings = strings; 48 | this.aliases = meta.aliases; 49 | this.flag = meta.flag; 50 | this.full = meta.full; 51 | this.code = meta.code; 52 | } 53 | 54 | translate, R = KeyToPropType>( 55 | key: K, 56 | args?: { [x: string]: any } | any[] 57 | ): R extends string[] ? string : string { 58 | const nodes = key.split('.'); 59 | let value: any = this.strings; 60 | 61 | for (const node of nodes) { 62 | try { 63 | value = value[node]; 64 | } catch (ex) { 65 | if (ex.message.includes('of undefined')) value = NOT_FOUND_SYMBOL; 66 | 67 | break; 68 | } 69 | } 70 | 71 | if (value === undefined || value === NOT_FOUND_SYMBOL) throw new TypeError(`Node '${key}' doesn't exist...`); 72 | 73 | if (isObject(value)) throw new TypeError(`Node '${key}' is a object!`); 74 | 75 | if (Array.isArray(value)) { 76 | return value.map((val) => this.stringify(val, args)).join('\n') as unknown as any; 77 | } else { 78 | return this.stringify(value, args); 79 | } 80 | } 81 | 82 | private stringify(value: any, rawArgs?: { [x: string]: any } | (string | number)[]) { 83 | // If no arguments are provided, best to assume to return the string 84 | if (!rawArgs) return value; 85 | 86 | // Convert it to a string 87 | if (typeof value !== 'string') value = String(value); 88 | 89 | let _i = 0; 90 | if (Array.isArray(rawArgs)) { 91 | const matches = /%s|%d/g.exec(value); 92 | if (matches === null) return value; 93 | 94 | for (let i = 0; i < matches.length; i++) { 95 | const match = matches[i]; 96 | if (match === '%s') { 97 | _i++; 98 | return value.replace(/%s/g, () => String(rawArgs.shift())); 99 | } else if (match === '%d') { 100 | if (isNaN(Number(rawArgs[_i]))) throw new TypeError(`Value "${rawArgs[_i]}" was not a number (index: ${_i})`); 101 | 102 | _i++; 103 | return value.replace(/%d/g, () => String(rawArgs.shift())); 104 | } 105 | } 106 | } else { 107 | return (value as string).replace(KEY_REGEX, (_, key) => { 108 | const value = String(rawArgs[key]); 109 | return value === '' ? '?' : value || '?'; 110 | }); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/structures/Subcommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type CommandMessage from './CommandMessage'; 24 | import type { Constants } from 'eris'; 25 | import type Command from './Command'; 26 | 27 | export interface SubcommandInfo { 28 | run(this: Command, msg: CommandMessage): Promise; 29 | 30 | permissions?: keyof Constants['Permissions']; 31 | methodName: string; 32 | aliases?: string[]; 33 | usage: string; 34 | } 35 | 36 | export default class Subcommand { 37 | public permissions?: keyof Constants['Permissions']; 38 | public aliases: string[]; 39 | public usage: string; 40 | public name: string; 41 | public run: (this: Command, msg: CommandMessage) => Promise; 42 | 43 | constructor(info: SubcommandInfo) { 44 | this.permissions = info.permissions; 45 | this.aliases = info.aliases ?? []; 46 | this.usage = info.usage; 47 | this.name = info.methodName; 48 | this.run = info.run; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/structures/decorators/Subcommand.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import type { SubcommandInfo } from '../Subcommand'; 24 | import { MetadataKeys } from '../../util/Constants'; 25 | 26 | export const getSubcommandsIn = (target: any) => 27 | Reflect.getMetadata(MetadataKeys.Subcommand, target) ?? []; 28 | 29 | interface SubcommandDecoratorOptions { 30 | aliases?: string[]; 31 | permissions?: SubcommandInfo['permissions']; 32 | } 33 | 34 | export default function Subcommand( 35 | usage?: string, 36 | aliasesOrOptions?: SubcommandDecoratorOptions | string[] 37 | ): MethodDecorator { 38 | return (target, methodName, descriptor: TypedPropertyDescriptor) => { 39 | const subcommands = getSubcommandsIn(target); 40 | 41 | subcommands.push({ 42 | permissions: Array.isArray(aliasesOrOptions) ? undefined : aliasesOrOptions?.permissions, 43 | aliases: Array.isArray(aliasesOrOptions) ? aliasesOrOptions : aliasesOrOptions?.aliases, 44 | methodName: String(methodName), 45 | usage: usage ?? '', 46 | run: descriptor.value!, 47 | }); 48 | 49 | Reflect.defineMetadata(MetadataKeys.Subcommand, subcommands, target); 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /src/structures/decorators/Subscribe.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { MetadataKeys } from '../../util/Constants'; 24 | 25 | interface Subscription { 26 | run(...args: any[]): Promise; 27 | event: string; 28 | } 29 | 30 | export const getSubscriptionsIn = (target: any) => 31 | Reflect.getMetadata(MetadataKeys.Subscribe, target) ?? []; 32 | export default function Subscribe(event: string): MethodDecorator { 33 | return (target, _, descriptor: TypedPropertyDescriptor) => { 34 | const subscriptions = getSubscriptionsIn(target); 35 | subscriptions.push({ 36 | event, 37 | run: descriptor.value!, 38 | }); 39 | 40 | Reflect.defineMetadata(MetadataKeys.Subscribe, subscriptions, target); 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /src/structures/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | export { default as Subcommand } from './decorators/Subcommand'; 24 | export { default as Subscribe } from './decorators/Subscribe'; 25 | 26 | export { default as CommandMessage } from './CommandMessage'; 27 | export { default as EmbedBuilder } from './EmbedBuilder'; 28 | export { default as Command } from './Command'; 29 | export { Automod } from './Automod'; 30 | -------------------------------------------------------------------------------- /src/util/Constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { readFileSync } from 'fs'; 24 | import { execSync } from 'child_process'; 25 | import { join } from 'path'; 26 | 27 | /** 28 | * Returns the current version of Nino 29 | */ 30 | export const version: string = require('../../package.json').version; 31 | 32 | /** 33 | * Returns the commit hash of the bot. 34 | */ 35 | export const commitHash: string | null = (() => { 36 | try { 37 | const hash = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); 38 | return hash.slice(0, 8); 39 | } catch { 40 | return null; 41 | } 42 | })(); 43 | 44 | export const SHORT_LINKS = JSON.parse( 45 | readFileSync(join(process.cwd(), '..', 'assets', 'shortlinks.json'), 'utf8').split(/\n\r?/).join('\n') 46 | ); 47 | export const Color = 0xdaa2c6; 48 | 49 | export const USERNAME_DISCRIM_REGEX = /^(.+)#(\d{4})$/; 50 | export const DISCORD_INVITE_REGEX = 51 | /(http(s)?:\/\/(www.)?)?(discord.gg|discord.io|discord.me|discord.link|invite.gg)\/\w+/; 52 | export const USER_MENTION_REGEX = /^<@!?([0-9]+)>$/; 53 | export const CHANNEL_REGEX = /<#([0-9]+)>$/; 54 | export const QUOTE_REGEX = /['"]/; 55 | export const ROLE_REGEX = /^<@&([0-9]+)>$/; 56 | export const ID_REGEX = /^\d+$/; 57 | 58 | /** 59 | * List of categories available to commands 60 | */ 61 | export enum Categories { 62 | Moderation = 'moderation', 63 | ThreadMod = 'thread moderation', 64 | VoiceMod = 'voice moderation', 65 | Settings = 'settings', 66 | Owner = 'owner', 67 | Core = 'core', 68 | } 69 | 70 | /** 71 | * List of metadata keys for decorators 72 | */ 73 | export const enum MetadataKeys { 74 | Subcommand = '$nino::subcommands', 75 | Subscribe = '$nino::subscriptions', 76 | APIRoute = '$nino::api-route', 77 | } 78 | -------------------------------------------------------------------------------- /src/util/ErisPatch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Logger } from 'tslog'; 24 | import { User } from 'eris'; 25 | 26 | const logger = app.$ref(Logger); 27 | logger.info('monkeypatching eris...'); 28 | 29 | Object.defineProperty(User.prototype, 'tag', { 30 | get(this: User) { 31 | return `${this.username}#${this.discriminator}`; 32 | }, 33 | 34 | set: () => { 35 | throw new TypeError('cannot set user tags :('); 36 | }, 37 | }); 38 | 39 | logger.info('Monkey patched the following items:', ['User#tag'].join('\n')); 40 | -------------------------------------------------------------------------------- /src/util/Permissions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { Constants, Role, Member, Permission } from 'eris'; 24 | 25 | /** 26 | * Contains utility functions to help with permission checking and hierarchy. 27 | */ 28 | export default class Permissions { 29 | /** 30 | * Returns the highest role the member has, `undefined` if none was found. 31 | * @param member The member to check 32 | */ 33 | static getTopRole(member: Member) { 34 | // eris why 35 | if (member === undefined || member === null) return; 36 | 37 | // For some reason, `roles` will become undefined? So we have to check for that. 38 | // It could be a bug in Discord or `member` is undefined. 39 | if (member.roles === undefined) return; 40 | 41 | if (member.roles.length === 0) return; 42 | 43 | return member.roles 44 | .map((roleID) => member.guild.roles.get(roleID)) 45 | .filter((role) => role !== undefined) 46 | .sort((a, b) => b!.position - a!.position)[0]; 47 | } 48 | 49 | /** 50 | * Checks if role A is above role B in hierarchy (vice-versa) 51 | * @param a The role that should be higher 52 | * @param b The role that should be lower 53 | */ 54 | static isRoleAbove(a?: Role, b?: Role) { 55 | if (!a) return false; 56 | if (!b) return true; 57 | 58 | return a.position > b.position; 59 | } 60 | 61 | /** 62 | * Checks if member A is above member B in hierarchy (vice-versa) 63 | * @param a The member that should be higher 64 | * @param b The member that should be lower 65 | */ 66 | static isMemberAbove(a: Member, b: Member) { 67 | const topRoleA = this.getTopRole(a); 68 | const topRoleB = this.getTopRole(b); 69 | 70 | return this.isRoleAbove(topRoleA, topRoleB); 71 | } 72 | 73 | /** 74 | * Shows a string representation of all of the permissions 75 | * @param bits The permission bitfield 76 | */ 77 | static stringify(permission: bigint) { 78 | const permissions = new Permission(Number(permission), 0).json; 79 | const names: string[] = []; 80 | 81 | for (const key of Object.keys(Constants.Permissions)) { 82 | if (permissions.hasOwnProperty(key)) names.push(key); 83 | } 84 | 85 | return names.join(', '); 86 | } 87 | 88 | /** 89 | * Returns if the user's bitfield reaches the threshold of the [required] bitfield. 90 | * @param user The user permission bitfield 91 | * @param required The required permission bitfield 92 | */ 93 | static hasOverlap(user: number, required: number) { 94 | return (user & 8) !== 0 || (user & required) === required; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/util/Stopwatch.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { performance } from 'perf_hooks'; 24 | 25 | /** 26 | * Utility stopwatch for calculating duration on asynchronous execution 27 | */ 28 | export default class Stopwatch { 29 | private startTime?: number; 30 | private endTime?: number; 31 | 32 | private symbolOf(type: number) { 33 | if (type > 1000) return `${type.toFixed(1)}s`; 34 | if (type > 1) return `${type.toFixed(1)}ms`; 35 | return `${type.toFixed(1)}µs`; 36 | } 37 | 38 | restart() { 39 | this.startTime = performance.now(); 40 | this.endTime = undefined; 41 | } 42 | 43 | start() { 44 | if (this.startTime !== undefined) throw new SyntaxError('Stopwatch has already started'); 45 | 46 | this.startTime = performance.now(); 47 | } 48 | 49 | end() { 50 | if (!this.startTime) throw new TypeError('Stopwatch has not started'); 51 | 52 | this.endTime = performance.now(); 53 | return this.symbolOf(this.endTime - this.startTime); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/util/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2019-2021 Nino 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | */ 22 | 23 | import { QUOTE_REGEX } from './Constants'; 24 | 25 | /** 26 | * Iterator function to provide a tuple of `[index, item]` in a Array. 27 | * @param arr The array to run this iterator function 28 | * @example 29 | * ```ts 30 | * const arr = ['str', 'uwu', 'owo']; 31 | * for (const [index, item] of withIndex(arr)) { 32 | * console.log(`${index}: ${item}`); 33 | * // prints out: 34 | * // 0: str 35 | * // 1: uwu 36 | * // 2: owo 37 | * } 38 | * ``` 39 | */ 40 | export function* withIndex(arr: T): Generator<[index: number, item: T[any]]> { 41 | for (let i = 0; i < arr.length; i++) { 42 | yield [i, arr[i]]; 43 | } 44 | } 45 | 46 | export function formatSize(bytes: number) { 47 | const kilo = bytes / 1024; 48 | const mega = kilo / 1024; 49 | const giga = mega / 1024; 50 | 51 | if (kilo < 1024) return `${kilo.toFixed(1)}KB`; 52 | else if (kilo > 1024 && mega < 1024) return `${mega.toFixed(1)}MB`; 53 | else return `${giga.toFixed(1)}GB`; 54 | } 55 | 56 | // credit: Ice (https://github.com/IceeMC) 57 | export function getQuotedStrings(content: string) { 58 | const parsed: string[] = []; 59 | let curr = ''; 60 | let opened = false; 61 | for (let i = 0; i < content.length; i++) { 62 | const char = content[i]; 63 | if (char === ' ' && !opened) { 64 | opened = false; 65 | if (curr.length > 0) parsed.push(curr); 66 | 67 | curr = ''; 68 | } 69 | 70 | if (QUOTE_REGEX.test(char)) { 71 | if (opened) { 72 | opened = false; 73 | if (curr.length > 0) parsed.push(curr); 74 | 75 | curr = ''; 76 | continue; 77 | } 78 | 79 | opened = true; 80 | continue; 81 | } 82 | 83 | if (!opened && char === ' ') continue; 84 | curr += char; 85 | } 86 | 87 | if (curr.length > 0) parsed.push(curr); 88 | return parsed; 89 | } 90 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@augu/tsconfig", 3 | "compilerOptions": { 4 | "moduleResolution": "node", 5 | "typeRoots": ["./src/@types", "./node_modules/@types"], 6 | "rootDir": "./src", 7 | "types": ["node", "reflect-metadata"], 8 | "outDir": "./build", 9 | "skipLibCheck": true 10 | }, 11 | "exclude": ["node_modules"], 12 | "include": ["src/**/*.ts"] 13 | } 14 | --------------------------------------------------------------------------------